mcp-subagents-opencode 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +602 -0
  3. package/build/config/timeouts.d.ts +9 -0
  4. package/build/config/timeouts.d.ts.map +1 -0
  5. package/build/config/timeouts.js +18 -0
  6. package/build/config/timeouts.js.map +1 -0
  7. package/build/helpers.d.ts +6 -0
  8. package/build/helpers.d.ts.map +1 -0
  9. package/build/helpers.js +47 -0
  10. package/build/helpers.js.map +1 -0
  11. package/build/index.d.ts +3 -0
  12. package/build/index.d.ts.map +1 -0
  13. package/build/index.js +245 -0
  14. package/build/index.js.map +1 -0
  15. package/build/models.d.ts +32 -0
  16. package/build/models.d.ts.map +1 -0
  17. package/build/models.js +58 -0
  18. package/build/models.js.map +1 -0
  19. package/build/server/register-notifications.d.ts +3 -0
  20. package/build/server/register-notifications.d.ts.map +1 -0
  21. package/build/server/register-notifications.js +77 -0
  22. package/build/server/register-notifications.js.map +1 -0
  23. package/build/server/register-resources.d.ts +3 -0
  24. package/build/server/register-resources.d.ts.map +1 -0
  25. package/build/server/register-resources.js +210 -0
  26. package/build/server/register-resources.js.map +1 -0
  27. package/build/server/register-retry-execution.d.ts +2 -0
  28. package/build/server/register-retry-execution.d.ts.map +1 -0
  29. package/build/server/register-retry-execution.js +28 -0
  30. package/build/server/register-retry-execution.js.map +1 -0
  31. package/build/server/register-tasks.d.ts +3 -0
  32. package/build/server/register-tasks.d.ts.map +1 -0
  33. package/build/server/register-tasks.js +52 -0
  34. package/build/server/register-tasks.js.map +1 -0
  35. package/build/server/register-tools.d.ts +3 -0
  36. package/build/server/register-tools.d.ts.map +1 -0
  37. package/build/server/register-tools.js +32 -0
  38. package/build/server/register-tools.js.map +1 -0
  39. package/build/server/resource-helpers.d.ts +21 -0
  40. package/build/server/resource-helpers.d.ts.map +1 -0
  41. package/build/server/resource-helpers.js +84 -0
  42. package/build/server/resource-helpers.js.map +1 -0
  43. package/build/services/account-manager.d.ts +88 -0
  44. package/build/services/account-manager.d.ts.map +1 -0
  45. package/build/services/account-manager.js +239 -0
  46. package/build/services/account-manager.js.map +1 -0
  47. package/build/services/claude-code-runner.d.ts +15 -0
  48. package/build/services/claude-code-runner.d.ts.map +1 -0
  49. package/build/services/claude-code-runner.js +475 -0
  50. package/build/services/claude-code-runner.js.map +1 -0
  51. package/build/services/client-context.d.ts +31 -0
  52. package/build/services/client-context.d.ts.map +1 -0
  53. package/build/services/client-context.js +44 -0
  54. package/build/services/client-context.js.map +1 -0
  55. package/build/services/exhaustion-fallback.d.ts +27 -0
  56. package/build/services/exhaustion-fallback.d.ts.map +1 -0
  57. package/build/services/exhaustion-fallback.js +30 -0
  58. package/build/services/exhaustion-fallback.js.map +1 -0
  59. package/build/services/fallback-orchestrator.d.ts +16 -0
  60. package/build/services/fallback-orchestrator.d.ts.map +1 -0
  61. package/build/services/fallback-orchestrator.js +48 -0
  62. package/build/services/fallback-orchestrator.js.map +1 -0
  63. package/build/services/opencode-client.d.ts +40 -0
  64. package/build/services/opencode-client.d.ts.map +1 -0
  65. package/build/services/opencode-client.js +147 -0
  66. package/build/services/opencode-client.js.map +1 -0
  67. package/build/services/opencode-spawner.d.ts +56 -0
  68. package/build/services/opencode-spawner.d.ts.map +1 -0
  69. package/build/services/opencode-spawner.js +426 -0
  70. package/build/services/opencode-spawner.js.map +1 -0
  71. package/build/services/output-file.d.ts +24 -0
  72. package/build/services/output-file.d.ts.map +1 -0
  73. package/build/services/output-file.js +90 -0
  74. package/build/services/output-file.js.map +1 -0
  75. package/build/services/progress-registry.d.ts +12 -0
  76. package/build/services/progress-registry.d.ts.map +1 -0
  77. package/build/services/progress-registry.js +97 -0
  78. package/build/services/progress-registry.js.map +1 -0
  79. package/build/services/question-registry.d.ts +79 -0
  80. package/build/services/question-registry.d.ts.map +1 -0
  81. package/build/services/question-registry.js +249 -0
  82. package/build/services/question-registry.js.map +1 -0
  83. package/build/services/retry-queue.d.ts +41 -0
  84. package/build/services/retry-queue.d.ts.map +1 -0
  85. package/build/services/retry-queue.js +195 -0
  86. package/build/services/retry-queue.js.map +1 -0
  87. package/build/services/sdk-client-manager.d.ts +149 -0
  88. package/build/services/sdk-client-manager.d.ts.map +1 -0
  89. package/build/services/sdk-client-manager.js +632 -0
  90. package/build/services/sdk-client-manager.js.map +1 -0
  91. package/build/services/sdk-session-adapter.d.ts +203 -0
  92. package/build/services/sdk-session-adapter.d.ts.map +1 -0
  93. package/build/services/sdk-session-adapter.js +1088 -0
  94. package/build/services/sdk-session-adapter.js.map +1 -0
  95. package/build/services/sdk-spawner.d.ts +42 -0
  96. package/build/services/sdk-spawner.d.ts.map +1 -0
  97. package/build/services/sdk-spawner.js +488 -0
  98. package/build/services/sdk-spawner.js.map +1 -0
  99. package/build/services/session-hooks.d.ts +24 -0
  100. package/build/services/session-hooks.d.ts.map +1 -0
  101. package/build/services/session-hooks.js +130 -0
  102. package/build/services/session-hooks.js.map +1 -0
  103. package/build/services/session-snapshot.d.ts +19 -0
  104. package/build/services/session-snapshot.d.ts.map +1 -0
  105. package/build/services/session-snapshot.js +203 -0
  106. package/build/services/session-snapshot.js.map +1 -0
  107. package/build/services/subscription-registry.d.ts +12 -0
  108. package/build/services/subscription-registry.d.ts.map +1 -0
  109. package/build/services/subscription-registry.js +27 -0
  110. package/build/services/subscription-registry.js.map +1 -0
  111. package/build/services/task-manager.d.ts +150 -0
  112. package/build/services/task-manager.d.ts.map +1 -0
  113. package/build/services/task-manager.js +765 -0
  114. package/build/services/task-manager.js.map +1 -0
  115. package/build/services/task-persistence.d.ts +29 -0
  116. package/build/services/task-persistence.d.ts.map +1 -0
  117. package/build/services/task-persistence.js +159 -0
  118. package/build/services/task-persistence.js.map +1 -0
  119. package/build/services/task-status-mapper.d.ts +21 -0
  120. package/build/services/task-status-mapper.d.ts.map +1 -0
  121. package/build/services/task-status-mapper.js +171 -0
  122. package/build/services/task-status-mapper.js.map +1 -0
  123. package/build/templates/index.d.ts +22 -0
  124. package/build/templates/index.d.ts.map +1 -0
  125. package/build/templates/index.js +147 -0
  126. package/build/templates/index.js.map +1 -0
  127. package/build/templates/overlays/coder-csharp.mdx +58 -0
  128. package/build/templates/overlays/coder-go.mdx +53 -0
  129. package/build/templates/overlays/coder-java.mdx +54 -0
  130. package/build/templates/overlays/coder-kotlin.mdx +56 -0
  131. package/build/templates/overlays/coder-nextjs.mdx +65 -0
  132. package/build/templates/overlays/coder-python.mdx +53 -0
  133. package/build/templates/overlays/coder-react.mdx +55 -0
  134. package/build/templates/overlays/coder-ruby.mdx +59 -0
  135. package/build/templates/overlays/coder-rust.mdx +48 -0
  136. package/build/templates/overlays/coder-supabase.mdx +268 -0
  137. package/build/templates/overlays/coder-supastarter.mdx +313 -0
  138. package/build/templates/overlays/coder-swift.mdx +56 -0
  139. package/build/templates/overlays/coder-tauri.mdx +566 -0
  140. package/build/templates/overlays/coder-triggerdev.mdx +296 -0
  141. package/build/templates/overlays/coder-typescript.mdx +45 -0
  142. package/build/templates/overlays/coder-vue.mdx +62 -0
  143. package/build/templates/overlays/planner-architecture.mdx +78 -0
  144. package/build/templates/overlays/planner-bugfix.mdx +36 -0
  145. package/build/templates/overlays/planner-feature.mdx +38 -0
  146. package/build/templates/overlays/planner-migration.mdx +50 -0
  147. package/build/templates/overlays/planner-refactor.mdx +57 -0
  148. package/build/templates/overlays/researcher-library.mdx +59 -0
  149. package/build/templates/overlays/researcher-performance.mdx +68 -0
  150. package/build/templates/overlays/researcher-security.mdx +86 -0
  151. package/build/templates/overlays/tester-graphql.mdx +191 -0
  152. package/build/templates/overlays/tester-playwright.mdx +621 -0
  153. package/build/templates/overlays/tester-rest.mdx +101 -0
  154. package/build/templates/overlays/tester-suite.mdx +177 -0
  155. package/build/templates/super-coder.mdx +529 -0
  156. package/build/templates/super-planner.mdx +568 -0
  157. package/build/templates/super-researcher.mdx +406 -0
  158. package/build/templates/super-tester.mdx +243 -0
  159. package/build/tools/answer-question.d.ts +30 -0
  160. package/build/tools/answer-question.d.ts.map +1 -0
  161. package/build/tools/answer-question.js +108 -0
  162. package/build/tools/answer-question.js.map +1 -0
  163. package/build/tools/cancel-task.d.ts +44 -0
  164. package/build/tools/cancel-task.d.ts.map +1 -0
  165. package/build/tools/cancel-task.js +144 -0
  166. package/build/tools/cancel-task.js.map +1 -0
  167. package/build/tools/send-message.d.ts +39 -0
  168. package/build/tools/send-message.d.ts.map +1 -0
  169. package/build/tools/send-message.js +124 -0
  170. package/build/tools/send-message.js.map +1 -0
  171. package/build/tools/shared-spawn.d.ts +56 -0
  172. package/build/tools/shared-spawn.d.ts.map +1 -0
  173. package/build/tools/shared-spawn.js +114 -0
  174. package/build/tools/shared-spawn.js.map +1 -0
  175. package/build/tools/spawn-agent.d.ts +85 -0
  176. package/build/tools/spawn-agent.d.ts.map +1 -0
  177. package/build/tools/spawn-agent.js +133 -0
  178. package/build/tools/spawn-agent.js.map +1 -0
  179. package/build/tools/spawn-coder.d.ts +70 -0
  180. package/build/tools/spawn-coder.d.ts.map +1 -0
  181. package/build/tools/spawn-coder.js +71 -0
  182. package/build/tools/spawn-coder.js.map +1 -0
  183. package/build/tools/spawn-planner.d.ts +70 -0
  184. package/build/tools/spawn-planner.d.ts.map +1 -0
  185. package/build/tools/spawn-planner.js +71 -0
  186. package/build/tools/spawn-planner.js.map +1 -0
  187. package/build/tools/spawn-researcher.d.ts +70 -0
  188. package/build/tools/spawn-researcher.d.ts.map +1 -0
  189. package/build/tools/spawn-researcher.js +70 -0
  190. package/build/tools/spawn-researcher.js.map +1 -0
  191. package/build/tools/spawn-task.d.ts +74 -0
  192. package/build/tools/spawn-task.d.ts.map +1 -0
  193. package/build/tools/spawn-task.js +107 -0
  194. package/build/tools/spawn-task.js.map +1 -0
  195. package/build/tools/spawn-tester.d.ts +70 -0
  196. package/build/tools/spawn-tester.d.ts.map +1 -0
  197. package/build/tools/spawn-tester.js +69 -0
  198. package/build/tools/spawn-tester.js.map +1 -0
  199. package/build/types.d.ts +101 -0
  200. package/build/types.d.ts.map +1 -0
  201. package/build/types.js +28 -0
  202. package/build/types.js.map +1 -0
  203. package/build/utils/brief-validator.d.ts +30 -0
  204. package/build/utils/brief-validator.d.ts.map +1 -0
  205. package/build/utils/brief-validator.js +254 -0
  206. package/build/utils/brief-validator.js.map +1 -0
  207. package/build/utils/format.d.ts +34 -0
  208. package/build/utils/format.d.ts.map +1 -0
  209. package/build/utils/format.js +55 -0
  210. package/build/utils/format.js.map +1 -0
  211. package/build/utils/sanitize.d.ts +240 -0
  212. package/build/utils/sanitize.d.ts.map +1 -0
  213. package/build/utils/sanitize.js +89 -0
  214. package/build/utils/sanitize.js.map +1 -0
  215. package/build/utils/task-id-generator.d.ts +10 -0
  216. package/build/utils/task-id-generator.d.ts.map +1 -0
  217. package/build/utils/task-id-generator.js +22 -0
  218. package/build/utils/task-id-generator.js.map +1 -0
  219. package/package.json +62 -0
@@ -0,0 +1,566 @@
1
+ ## TAURI 2.x GUIDELINES
2
+
3
+ You are working in a **Tauri 2.x** desktop application codebase (Rust backend + web frontend). Apply these patterns with zero exceptions. This is Tauri v2 -- do NOT use Tauri v1 APIs.
4
+
5
+ ### Project Structure -- THE STANDARD LAYOUT
6
+ ```
7
+ my-app/
8
+ src/ # Frontend (React/Vue/Svelte/etc.)
9
+ src-tauri/
10
+ Cargo.toml # Rust deps -- tauri, serde, thiserror
11
+ tauri.conf.json # App config, bundle ID, plugins, security
12
+ capabilities/ # ACL capability files (JSON/TOML)
13
+ default.json # Auto-enabled permissions for windows
14
+ icons/ # App icons (output of `tauri icon`)
15
+ build.rs # Must call tauri_build::build()
16
+ src/
17
+ lib.rs # Main entry -- Builder, plugins, state, commands
18
+ main.rs # Desktop-only: just calls app_lib::run()
19
+ commands/ # Split commands into modules by domain
20
+ mod.rs
21
+ files.rs
22
+ settings.rs
23
+ state.rs # AppState structs + type aliases
24
+ errors.rs # Custom error types with thiserror + Serialize
25
+ ```
26
+
27
+ - **lib.rs is the real entry point** -- `main.rs` only calls `app_lib::run()`. This enables mobile support.
28
+ - **NEVER put all commands in lib.rs** for non-trivial apps. Use a `commands/` module.
29
+ - Cargo.toml must include `[lib]` section with `name = "app_lib"` and `crate-type = ["staticlib", "cdylib", "rlib"]` for mobile compatibility.
30
+
31
+ ### lib.rs Entry Point Pattern
32
+ ```rust
33
+ #[cfg_attr(mobile, tauri::mobile_entry_point)]
34
+ pub fn run() {
35
+ tauri::Builder::default()
36
+ .plugin(tauri_plugin_fs::init())
37
+ .plugin(tauri_plugin_shell::init())
38
+ .manage(std::sync::Mutex::new(AppState::default()))
39
+ .invoke_handler(tauri::generate_handler![
40
+ commands::files::read_project,
41
+ commands::files::save_project,
42
+ commands::settings::get_settings,
43
+ commands::settings::update_settings,
44
+ ])
45
+ .run(tauri::generate_context!())
46
+ .expect("error while running tauri application");
47
+ }
48
+ ```
49
+
50
+ ### Commands -- #[tauri::command]
51
+
52
+ **Basic command with arguments:**
53
+ ```rust
54
+ #[tauri::command]
55
+ fn greet(name: String) -> String {
56
+ format!("Hello, {}!", name)
57
+ }
58
+ ```
59
+
60
+ **Frontend invocation -- arguments are camelCase by default:**
61
+ ```typescript
62
+ import { invoke } from '@tauri-apps/api/core';
63
+ const result = await invoke<string>('greet', { name: 'World' });
64
+ ```
65
+
66
+ **Force snake_case arguments from frontend:**
67
+ ```rust
68
+ #[tauri::command(rename_all = "snake_case")]
69
+ fn process_data(file_path: String) -> Result<(), AppError> { ... }
70
+ ```
71
+
72
+ **Async commands -- MUST return Result:**
73
+ ```rust
74
+ #[tauri::command]
75
+ async fn fetch_data(url: String) -> Result<Vec<DataItem>, AppError> {
76
+ let response = reqwest::get(&url).await.map_err(AppError::Network)?;
77
+ let items = response.json().await.map_err(AppError::Parse)?;
78
+ Ok(items)
79
+ }
80
+ ```
81
+
82
+ **Accessing AppHandle, Window, WebviewWindow in commands:**
83
+ ```rust
84
+ #[tauri::command]
85
+ fn do_work(app: tauri::AppHandle, window: tauri::WebviewWindow) -> Result<(), AppError> {
86
+ let label = window.label();
87
+ // Use app for emit, state, etc.
88
+ Ok(())
89
+ }
90
+ ```
91
+
92
+ **Returning large binary data -- use ipc::Response to avoid JSON overhead:**
93
+ ```rust
94
+ use tauri::ipc::Response;
95
+
96
+ #[tauri::command]
97
+ fn read_binary(path: String) -> Result<Response, AppError> {
98
+ let data = std::fs::read(&path).map_err(AppError::Io)?;
99
+ Ok(Response::new(data))
100
+ }
101
+ ```
102
+
103
+ ### Error Handling -- THE MANDATORY PATTERN
104
+
105
+ Standard Rust errors do NOT implement `serde::Serialize`. You MUST create a custom error type.
106
+
107
+ ```rust
108
+ // src-tauri/src/errors.rs
109
+ #[derive(Debug, thiserror::Error)]
110
+ pub enum AppError {
111
+ #[error(transparent)]
112
+ Io(#[from] std::io::Error),
113
+
114
+ #[error("Database error: {0}")]
115
+ Database(String),
116
+
117
+ #[error("Not found: {0}")]
118
+ NotFound(String),
119
+
120
+ #[error("Validation failed: {0}")]
121
+ Validation(String),
122
+
123
+ #[error(transparent)]
124
+ Anyhow(#[from] anyhow::Error),
125
+ }
126
+
127
+ // REQUIRED: Manual Serialize impl -- derive won't work for error types
128
+ impl serde::Serialize for AppError {
129
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
130
+ where S: serde::ser::Serializer {
131
+ serializer.serialize_str(self.to_string().as_ref())
132
+ }
133
+ }
134
+ ```
135
+
136
+ - Add `thiserror = "2"` and `serde = { version = "1", features = ["derive"] }` to Cargo.toml.
137
+ - Use `#[from]` for automatic conversion with `?` operator.
138
+ - NEVER use `Result<T, String>` in production -- always use a typed error enum.
139
+
140
+ ### State Management -- Mutex Patterns
141
+
142
+ **Setup state:**
143
+ ```rust
144
+ use std::sync::Mutex;
145
+
146
+ #[derive(Default)]
147
+ struct AppState {
148
+ counter: u32,
149
+ config: Option<Config>,
150
+ }
151
+
152
+ // Type alias prevents State<'_, Mutex<AppState>> vs State<'_, AppState> runtime panics
153
+ type AppStateMutex = Mutex<AppState>;
154
+
155
+ // In lib.rs run():
156
+ .manage(Mutex::new(AppState::default()))
157
+ ```
158
+
159
+ **Access state in commands:**
160
+ ```rust
161
+ #[tauri::command]
162
+ fn increment(state: tauri::State<'_, AppStateMutex>) -> Result<u32, AppError> {
163
+ let mut s = state.lock().map_err(|_| AppError::Validation("poisoned lock".into()))?;
164
+ s.counter += 1;
165
+ Ok(s.counter)
166
+ }
167
+ ```
168
+
169
+ **Async commands with state -- use tokio Mutex if holding lock across .await:**
170
+ ```rust
171
+ use tauri::async_runtime::Mutex as AsyncMutex;
172
+
173
+ #[tauri::command]
174
+ async fn load_data(state: tauri::State<'_, AsyncMutex<AppState>>) -> Result<String, AppError> {
175
+ let mut s = state.lock().await;
176
+ s.config = Some(fetch_config().await?);
177
+ Ok("loaded".into())
178
+ }
179
+ ```
180
+
181
+ **Access state outside commands via AppHandle:**
182
+ ```rust
183
+ fn background_task(app: tauri::AppHandle) {
184
+ let state = app.state::<AppStateMutex>();
185
+ let mut s = state.lock().unwrap();
186
+ s.counter = 0;
187
+ }
188
+ ```
189
+
190
+ - You do NOT need `Arc` for Tauri managed state -- Tauri wraps it in `Arc` internally.
191
+ - Each state type must be unique. You cannot `.manage()` two values of the same type.
192
+ - Use `std::sync::Mutex` by default. Only use `tauri::async_runtime::Mutex` when you hold the lock across `.await` points.
193
+
194
+ ### Events -- Rust to Frontend Communication
195
+
196
+ **Emit global event from Rust:**
197
+ ```rust
198
+ use tauri::Emitter;
199
+
200
+ #[tauri::command]
201
+ fn start_sync(app: tauri::AppHandle) {
202
+ std::thread::spawn(move || {
203
+ for progress in 0..=100 {
204
+ app.emit("sync-progress", progress).unwrap();
205
+ std::thread::sleep(std::time::Duration::from_millis(50));
206
+ }
207
+ app.emit("sync-complete", ()).unwrap();
208
+ });
209
+ }
210
+ ```
211
+
212
+ **Listen in frontend:**
213
+ ```typescript
214
+ import { listen } from '@tauri-apps/api/event';
215
+
216
+ const unlisten = await listen<number>('sync-progress', (event) => {
217
+ console.log(`Progress: ${event.payload}%`);
218
+ });
219
+
220
+ // ALWAYS unlisten on cleanup (React useEffect return, Vue onUnmounted, etc.)
221
+ unlisten();
222
+ ```
223
+
224
+ **Emit to specific window:**
225
+ ```rust
226
+ use tauri::Emitter;
227
+ app.emit_to("settings", "config-changed", &new_config).unwrap();
228
+ ```
229
+
230
+ **Emit with filter:**
231
+ ```rust
232
+ use tauri::{Emitter, EventTarget};
233
+ app.emit_filter("file-opened", &path, |target| match target {
234
+ EventTarget::WebviewWindow { label } => label == "main" || label == "editor",
235
+ _ => false,
236
+ }).unwrap();
237
+ ```
238
+
239
+ ### Channels -- For Streaming / High-Throughput Data
240
+
241
+ Use channels instead of events when you need ordered, fast streaming (download progress, log tailing, etc.).
242
+
243
+ **Rust side:**
244
+ ```rust
245
+ use tauri::ipc::Channel;
246
+ use serde::Serialize;
247
+
248
+ #[derive(Clone, Serialize)]
249
+ #[serde(rename_all = "camelCase", tag = "event", content = "data")]
250
+ enum DownloadEvent {
251
+ #[serde(rename_all = "camelCase")]
252
+ Progress { download_id: usize, bytes_done: usize, total: usize },
253
+ #[serde(rename_all = "camelCase")]
254
+ Finished { download_id: usize },
255
+ }
256
+
257
+ #[tauri::command]
258
+ async fn download(url: String, on_event: Channel<DownloadEvent>) -> Result<(), AppError> {
259
+ // ... stream data ...
260
+ on_event.send(DownloadEvent::Progress { download_id: 1, bytes_done: 500, total: 1000 })?;
261
+ on_event.send(DownloadEvent::Finished { download_id: 1 })?;
262
+ Ok(())
263
+ }
264
+ ```
265
+
266
+ **Frontend side:**
267
+ ```typescript
268
+ import { invoke, Channel } from '@tauri-apps/api/core';
269
+
270
+ type DownloadEvent =
271
+ | { event: 'progress'; data: { downloadId: number; bytesDone: number; total: number } }
272
+ | { event: 'finished'; data: { downloadId: number } };
273
+
274
+ const onEvent = new Channel<DownloadEvent>();
275
+ onEvent.onmessage = (msg) => {
276
+ if (msg.event === 'progress') {
277
+ updateProgressBar(msg.data.bytesDone / msg.data.total);
278
+ }
279
+ };
280
+ await invoke('download', { url: 'https://example.com/file.bin', onEvent });
281
+ ```
282
+
283
+ - Channels are faster than events and guarantee message ordering.
284
+ - Events are for pub/sub (multi-consumer). Channels are for point-to-point streaming.
285
+
286
+ ### Capabilities & Permissions -- Tauri 2.x Security (Replaces v1 Allowlist)
287
+
288
+ Capabilities are JSON files in `src-tauri/capabilities/`. All files in this directory are auto-enabled.
289
+
290
+ **src-tauri/capabilities/default.json:**
291
+ ```json
292
+ {
293
+ "$schema": "../gen/schemas/desktop-schema.json",
294
+ "identifier": "default",
295
+ "description": "Default capability for the main window",
296
+ "windows": ["main"],
297
+ "permissions": [
298
+ "core:default",
299
+ "fs:default",
300
+ "fs:allow-read-text-file",
301
+ "fs:allow-write-text-file",
302
+ "shell:allow-open",
303
+ {
304
+ "identifier": "fs:scope",
305
+ "allow": [{ "path": "$APPDATA" }, { "path": "$APPDATA/**" }]
306
+ }
307
+ ]
308
+ }
309
+ ```
310
+
311
+ **Platform-specific capabilities:**
312
+ ```json
313
+ {
314
+ "identifier": "desktop-only",
315
+ "windows": ["main"],
316
+ "platforms": ["linux", "macOS", "windows"],
317
+ "permissions": ["global-shortcut:allow-register"]
318
+ }
319
+ ```
320
+
321
+ **Multi-window with different capabilities:**
322
+ ```json
323
+ {
324
+ "identifier": "settings-window",
325
+ "windows": ["settings"],
326
+ "permissions": ["core:default", "fs:allow-read-text-file"]
327
+ }
328
+ ```
329
+
330
+ - A window with NO matching capability has ZERO access to IPC. This is intentional.
331
+ - Permission format: `plugin-name:permission-name` (e.g., `fs:allow-read-text-file`).
332
+ - Core permissions are prefixed with `core:` (e.g., `core:window:allow-set-title`).
333
+ - Deny scopes take precedence over allow scopes.
334
+ - NEVER use `"windows": ["*"]` unless every window truly needs those permissions.
335
+
336
+ ### Plugin System
337
+
338
+ **Using official plugins:**
339
+ ```bash
340
+ # Install from npm + cargo in one step
341
+ npm run tauri add fs
342
+ npm run tauri add shell
343
+ npm run tauri add updater
344
+ ```
345
+
346
+ **Register in lib.rs:**
347
+ ```rust
348
+ tauri::Builder::default()
349
+ .plugin(tauri_plugin_fs::init())
350
+ .plugin(tauri_plugin_shell::init())
351
+ .plugin(tauri_plugin_updater::init())
352
+ ```
353
+
354
+ **Creating a custom plugin:**
355
+ ```rust
356
+ use tauri::plugin::{Builder as PluginBuilder, TauriPlugin};
357
+ use tauri::Runtime;
358
+
359
+ #[tauri::command]
360
+ fn my_plugin_command(state: tauri::State<'_, MyPluginState>) -> Result<String, AppError> {
361
+ Ok("plugin response".into())
362
+ }
363
+
364
+ pub fn init<R: Runtime>() -> TauriPlugin<R> {
365
+ PluginBuilder::new("my-plugin")
366
+ .setup(|app, _api| {
367
+ app.manage(MyPluginState::default());
368
+ Ok(())
369
+ })
370
+ .invoke_handler(tauri::generate_handler![my_plugin_command])
371
+ .on_navigation(|window, url| {
372
+ // Return false to block navigation
373
+ true
374
+ })
375
+ .on_event(|app, event| {
376
+ // Handle app lifecycle events
377
+ })
378
+ .build()
379
+ }
380
+ ```
381
+
382
+ **Frontend calls plugin commands with namespace prefix:**
383
+ ```typescript
384
+ await invoke('plugin:my-plugin|my_plugin_command');
385
+ ```
386
+
387
+ - Plugin commands require permissions in capabilities: `"my-plugin:allow-my-plugin-command"`.
388
+ - For inline plugins (defined in the app, not a crate), you must update `build.rs` to generate permissions.
389
+
390
+ ### Window Management
391
+
392
+ **Create windows from Rust:**
393
+ ```rust
394
+ use tauri::{WebviewWindowBuilder, WebviewUrl};
395
+
396
+ #[tauri::command]
397
+ fn open_settings(app: tauri::AppHandle) -> Result<(), AppError> {
398
+ let _window = WebviewWindowBuilder::new(&app, "settings", WebviewUrl::App("/settings".into()))
399
+ .title("Settings")
400
+ .inner_size(600.0, 400.0)
401
+ .build()
402
+ .map_err(|e| AppError::Validation(e.to_string()))?;
403
+ Ok(())
404
+ }
405
+ ```
406
+
407
+ **Create windows from frontend (requires capability):**
408
+ ```typescript
409
+ import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
410
+
411
+ const settings = new WebviewWindow('settings', {
412
+ url: '/settings',
413
+ width: 600,
414
+ height: 400,
415
+ title: 'Settings',
416
+ });
417
+
418
+ settings.once('tauri://created', () => { /* success */ });
419
+ settings.once('tauri://error', (e) => { /* handle error */ });
420
+ ```
421
+
422
+ - Requires `"core:webview:allow-create-webview-window"` permission.
423
+ - Inter-window communication: use `emit_to()` targeting window labels or global events.
424
+ - Use `WebviewWindow::getByLabel('label')` to get a handle to an existing window.
425
+
426
+ ### File System Plugin
427
+
428
+ **Frontend file operations:**
429
+ ```typescript
430
+ import { readTextFile, writeTextFile, BaseDirectory } from '@tauri-apps/plugin-fs';
431
+ import { appDataDir, join } from '@tauri-apps/api/path';
432
+
433
+ // Read from app data
434
+ const config = await readTextFile('config.json', { baseDir: BaseDirectory.AppData });
435
+
436
+ // Write to app data
437
+ await writeTextFile('config.json', JSON.stringify(settings), {
438
+ baseDir: BaseDirectory.AppData,
439
+ });
440
+
441
+ // Resolve absolute path
442
+ const dataDir = await appDataDir();
443
+ const filePath = await join(dataDir, 'projects', 'data.json');
444
+ ```
445
+
446
+ **Rust path resolution:**
447
+ ```rust
448
+ use tauri::Manager;
449
+
450
+ fn get_app_data(app: &tauri::AppHandle) -> Result<std::path::PathBuf, AppError> {
451
+ app.path().app_data_dir().map_err(|e| AppError::Io(e.into()))
452
+ }
453
+ ```
454
+
455
+ - Paths using this API cannot traverse parent directories (`../` is blocked).
456
+ - BaseDirectory variables for scopes: `$APPDATA`, `$APPCONFIG`, `$APPLOG`, `$HOME`, `$RESOURCE`, `$TEMP`.
457
+ - App directories must be created at runtime before use -- they do not exist by default.
458
+
459
+ ### Security -- CSP & IPC Safety
460
+
461
+ **tauri.conf.json CSP configuration:**
462
+ ```json
463
+ {
464
+ "app": {
465
+ "security": {
466
+ "csp": {
467
+ "default-src": "'self' customprotocol: asset:",
468
+ "connect-src": "ipc: http://ipc.localhost",
469
+ "img-src": "'self' asset: http://asset.localhost blob: data:",
470
+ "style-src": "'unsafe-inline' 'self'"
471
+ }
472
+ }
473
+ }
474
+ }
475
+ ```
476
+
477
+ - Tauri auto-injects nonces and hashes for bundled scripts/styles at compile time.
478
+ - NEVER use `'unsafe-eval'` unless using WASM (then use `'wasm-unsafe-eval'`).
479
+ - Avoid loading remote scripts. Any untrusted content is an attack vector.
480
+ - Sanitize ALL user input on the Rust side before processing.
481
+ - Defer business logic to Rust -- minimize logic in the frontend.
482
+ - Consider the Isolation pattern (`"isolation": { "pattern": "..." }`) for apps handling untrusted content.
483
+
484
+ ### Build & Distribution
485
+
486
+ **Development:**
487
+ ```bash
488
+ npm run tauri dev # Hot-reload development
489
+ npm run tauri build # Production build
490
+ npm run tauri icon # Generate icons from source image
491
+ ```
492
+
493
+ **tauri.conf.json essentials:**
494
+ ```json
495
+ {
496
+ "productName": "My App",
497
+ "mainBinaryName": "my-app",
498
+ "version": "1.0.0",
499
+ "identifier": "com.mycompany.myapp",
500
+ "build": {
501
+ "frontendDist": "../dist",
502
+ "devUrl": "http://localhost:5173",
503
+ "beforeBuildCommand": "npm run build",
504
+ "beforeDevCommand": "npm run dev"
505
+ }
506
+ }
507
+ ```
508
+
509
+ **Updater setup (tauri.conf.json):**
510
+ ```json
511
+ {
512
+ "plugins": {
513
+ "updater": {
514
+ "endpoints": ["https://releases.example.com/{{target}}/{{arch}}/{{current_version}}"],
515
+ "pubkey": "YOUR_PUBLIC_KEY_HERE"
516
+ }
517
+ }
518
+ }
519
+ ```
520
+
521
+ **GitHub Actions CI/CD:**
522
+ ```yaml
523
+ - uses: tauri-apps/tauri-action@v0
524
+ env:
525
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
526
+ TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
527
+ TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
528
+ with:
529
+ tagName: v__VERSION__
530
+ releaseName: 'v__VERSION__'
531
+ includeUpdaterJson: true
532
+ ```
533
+
534
+ - `TAURI_SIGNING_PRIVATE_KEY` and `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` must be set as environment variables (not .env files).
535
+ - Generate signing keys with `npx tauri signer generate`.
536
+ - macOS requires code signing + notarization for distribution. Set `APPLE_SIGNING_IDENTITY` env var.
537
+ - `mainBinaryName` is REQUIRED in Tauri 2.x -- the binary is no longer auto-renamed to match `productName`.
538
+
539
+ ### Common Gotchas -- AVOID THESE
540
+
541
+ 1. **Serde serialization for errors:** Standard error types don't implement `Serialize`. You MUST create a custom error type (see Error Handling section above). This is the #1 newcomer mistake.
542
+
543
+ 2. **State type mismatch:** `State<'_, AppState>` vs `State<'_, Mutex<AppState>>` gives a runtime panic, not a compile error. Always use a type alias for your wrapped state.
544
+
545
+ 3. **Async commands with borrowed data:** `&str` and `State<'_, T>` in async commands cause "future is not Send" errors. Solution: async commands MUST return `Result<T, E>`. For `&str`, use `String` instead.
546
+
547
+ 4. **Missing capabilities:** Commands silently fail (or return 400) if the capability file doesn't include the permission. Check `src-tauri/capabilities/` first when a command doesn't work.
548
+
549
+ 5. **Frontend argument casing:** Rust `snake_case` args become `camelCase` on the frontend by default. Use `#[tauri::command(rename_all = "snake_case")]` to keep snake_case.
550
+
551
+ 6. **v1 imports still in code:** `@tauri-apps/api/tauri` is now `@tauri-apps/api/core`. `@tauri-apps/api/fs` is now `@tauri-apps/plugin-fs`. There is no backward compatibility.
552
+
553
+ 7. **Events are not type-safe:** Events use JSON payloads with no type checking at the Rust/TS boundary. Prefer commands for typed request/response and channels for typed streaming.
554
+
555
+ 8. **Window without capability = zero IPC access.** If a webview or its window matches no capability, it has no access to the IPC layer at all. This is not a bug.
556
+
557
+ 9. **Mutex poisoning:** Use `.lock().map_err(...)` instead of `.lock().unwrap()` in commands. A panicked thread poisons the mutex, and `.unwrap()` will crash subsequent calls.
558
+
559
+ 10. **Plugin names cannot contain underscores.** Use hyphens (`my-plugin`, not `my_plugin`). Underscore names cause cryptic serde deserialization errors in capabilities.
560
+
561
+ ### Naming & Conventions
562
+ - `snake_case` for Rust functions, modules, variables
563
+ - `PascalCase` for Rust types, traits, enums
564
+ - `camelCase` for TypeScript functions, variables, and invoke argument names
565
+ - Commands registered with `generate_handler!` use their Rust function name as the invoke name
566
+ - Plugin commands are invoked as `plugin:plugin-name|command_name`