@kood/claude-code 0.7.2 → 0.7.3

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.
@@ -2,6 +2,14 @@
2
2
 
3
3
  > **Version 2.x** | Cross-platform Desktop & Mobile Application Framework
4
4
 
5
+ <instructions>
6
+ @references/ipc-guide.md
7
+ @references/security-guide.md
8
+ @references/state-guide.md
9
+ @references/plugins-guide.md
10
+ @references/configuration.md
11
+ </instructions>
12
+
5
13
  ---
6
14
 
7
15
  <context>
@@ -30,16 +38,13 @@
30
38
  | **v1 API** | `Window` 타입 | `WebviewWindow` 사용 |
31
39
  | **v1 API** | `get_window()` | `get_webview_window()` 사용 |
32
40
  | **v1 API** | allowlist 설정 | Capabilities/Permissions 사용 |
33
- | **v1 API** | `.validator()` | v2에서 제거됨 |
34
41
  | **보안** | Permission 없이 Command 노출 | Capabilities 필수 |
35
42
  | **보안** | CSP 비활성화 | 항상 CSP 설정 |
36
43
  | **보안** | `'unsafe-eval'` 무분별 사용 | WASM만 `'wasm-unsafe-eval'` 허용 |
37
- | **보안** | CDN에서 원격 스크립트 로드 | 공격 벡터, 로컬 번들 사용 |
38
44
  | **IPC** | Rust 함수 직접 FFI 호출 | `invoke()` 메시지 패싱 사용 |
39
45
  | **IPC** | async command에 &str 인자 | `String`으로 변환 필수 |
40
46
  | **상태** | `State<T>`에 Arc 래핑 | Tauri가 내부적으로 처리 |
41
- | **상태** | async에서 await 걸쳐 Mutex lock | 데드락 위험, 표준 Mutex 사용 |
42
- | **Plugin** | v1 built-in API 직접 사용 | v2 plugin으로 설치 필요 |
47
+ | **상태** | async에서 await 걸쳐 Mutex lock | 데드락 위험 |
43
48
 
44
49
  </forbidden>
45
50
 
@@ -83,19 +88,13 @@ npx tauri init
83
88
 
84
89
  ## Prerequisites
85
90
 
86
- **모든 플랫폼:**
87
- - Rust (rustup 설치)
88
- - Node.js
91
+ **모든 플랫폼:** Rust (rustup), Node.js
89
92
 
90
- **macOS:**
91
- - Xcode Command Line Tools
93
+ **macOS:** Xcode Command Line Tools
92
94
 
93
- **Windows:**
94
- - Microsoft C++ Build Tools (Desktop development with C++)
95
- - WebView2 Runtime (Windows 10 v1803+ 기본 포함)
95
+ **Windows:** Microsoft C++ Build Tools, WebView2 Runtime
96
96
 
97
- **Linux:**
98
- - webkit2gtk, build-essential, libssl-dev, librsvg2-dev
97
+ **Linux:** webkit2gtk, build-essential, libssl-dev, librsvg2-dev
99
98
 
100
99
  ## 초기 설정
101
100
 
@@ -124,353 +123,17 @@ npx tauri init
124
123
  }
125
124
  ```
126
125
 
127
- ```typescript
128
- // vite.config.ts
129
- import { defineConfig } from 'vite';
130
- import react from '@vitejs/plugin-react';
131
-
132
- const host = process.env.TAURI_DEV_HOST;
133
-
134
- export default defineConfig({
135
- plugins: [react()],
136
- clearScreen: false,
137
- server: {
138
- port: 5173,
139
- strictPort: true,
140
- host: host || false,
141
- hmr: host ? { protocol: 'ws', host, port: 1421 } : undefined,
142
- watch: { ignored: ['**/src-tauri/**'] },
143
- },
144
- envPrefix: ['VITE_', 'TAURI_ENV_*'],
145
- build: {
146
- target: process.env.TAURI_ENV_PLATFORM === 'windows' ? 'chrome105' : 'safari13',
147
- minify: !process.env.TAURI_ENV_DEBUG ? 'esbuild' : false,
148
- sourcemap: !!process.env.TAURI_ENV_DEBUG,
149
- },
150
- });
151
- ```
152
-
153
126
  </setup>
154
127
 
155
128
  ---
156
129
 
157
- ## IPC (Inter-Process Communication)
158
-
159
- ### Commands (프론트엔드 → Rust)
160
-
161
- ```rust
162
- // src-tauri/src/commands/mod.rs
163
- use serde::{Deserialize, Serialize};
164
- use tauri::State;
165
- use std::sync::Mutex;
166
-
167
- #[derive(Serialize, Deserialize)]
168
- pub struct User {
169
- pub id: u32,
170
- pub name: String,
171
- }
172
-
173
- // 기본 Command
174
- #[tauri::command]
175
- fn greet(name: String) -> String {
176
- format!("Hello, {}!", name)
177
- }
178
-
179
- // 비동기 Command + State
180
- #[tauri::command]
181
- async fn get_user(id: u32, db: State<'_, Mutex<Database>>) -> Result<User, String> {
182
- let db = db.lock().map_err(|e| e.to_string())?;
183
- db.find_user(id).ok_or("User not found".into())
184
- }
185
-
186
- // 에러 처리 (thiserror 권장)
187
- #[derive(Debug, thiserror::Error)]
188
- pub enum AppError {
189
- #[error("Not found: {0}")]
190
- NotFound(String),
191
- #[error("Internal error: {0}")]
192
- Internal(String),
193
- }
194
-
195
- impl serde::Serialize for AppError {
196
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
197
- where S: serde::Serializer {
198
- serializer.serialize_str(self.to_string().as_ref())
199
- }
200
- }
201
-
202
- #[tauri::command]
203
- async fn delete_user(id: u32) -> Result<(), AppError> {
204
- // ...
205
- Err(AppError::NotFound(format!("User {} not found", id)))
206
- }
207
- ```
208
-
209
- ```typescript
210
- // 프론트엔드 invoke
211
- import { invoke } from '@tauri-apps/api/core';
212
-
213
- // 기본 호출
214
- const greeting = await invoke<string>('greet', { name: 'World' });
215
-
216
- // 타입 안전 호출
217
- interface User { id: number; name: string; }
218
- const user = await invoke<User>('get_user', { id: 1 });
219
-
220
- // 에러 처리
221
- try {
222
- await invoke('delete_user', { id: 999 });
223
- } catch (error) {
224
- console.error('Command failed:', error);
225
- }
226
- ```
227
-
228
- ### Events (양방향 통신)
229
-
230
- ```rust
231
- // Rust → 프론트엔드 Event 발행
232
- use tauri::{AppHandle, Emitter};
233
-
234
- #[derive(Clone, Serialize)]
235
- struct DownloadProgress {
236
- url: String,
237
- progress: f64,
238
- }
239
-
240
- fn download_file(app: AppHandle, url: String) {
241
- // 모든 윈도우에 Global Event
242
- app.emit("download-progress", DownloadProgress {
243
- url, progress: 0.5,
244
- }).unwrap();
245
-
246
- // 특정 윈도우에만
247
- app.emit_to("main", "download-complete", ()).unwrap();
248
- }
249
- ```
250
-
251
- ```typescript
252
- // 프론트엔드 Event 리스닝
253
- import { listen, once } from '@tauri-apps/api/event';
254
-
255
- // 지속 리스닝
256
- const unlisten = await listen<{ url: string; progress: number }>(
257
- 'download-progress',
258
- (event) => console.log(`${event.payload.url}: ${event.payload.progress * 100}%`)
259
- );
260
-
261
- // 단발 리스닝
262
- await once('download-complete', () => console.log('Done!'));
263
-
264
- // 리스너 해제
265
- unlisten();
266
- ```
267
-
268
- ### Channels (고속 스트리밍)
269
-
270
- ```rust
271
- use tauri::ipc::Channel;
272
-
273
- #[tauri::command]
274
- fn stream_data(channel: Channel<String>) {
275
- for i in 0..100 {
276
- channel.send(format!("chunk {}", i)).unwrap();
277
- }
278
- }
279
- ```
280
-
281
- ---
282
-
283
- ## Security (보안)
284
-
285
- ### Capabilities
286
-
287
- ```json
288
- // src-tauri/capabilities/default.json
289
- {
290
- "identifier": "default",
291
- "description": "메인 윈도우 기본 권한",
292
- "windows": ["main"],
293
- "permissions": [
294
- "core:default",
295
- "core:window:allow-set-title",
296
- "core:window:allow-close",
297
- "shell:allow-open"
298
- ]
299
- }
300
- ```
301
-
302
- ```json
303
- // 플랫폼별 Capability
304
- {
305
- "identifier": "mobile-features",
306
- "description": "모바일 전용 권한",
307
- "windows": ["main"],
308
- "platforms": ["iOS", "android"],
309
- "permissions": [
310
- "nfc:default",
311
- "biometric:default"
312
- ]
313
- }
314
- ```
315
-
316
- ### Permission 패턴
317
-
318
- ```
319
- core:default # Core 기본 권한
320
- core:window:allow-<command> # Window 개별 허용
321
- core:event:allow-listen # Event 리스닝 허용
322
- <plugin>:default # Plugin 기본 권한
323
- <plugin>:allow-<command> # Plugin 개별 허용
324
- <plugin>:deny-<command> # Plugin 개별 거부
325
- ```
326
-
327
- ### CSP 설정
328
-
329
- ```json
330
- // tauri.conf.json > app > security > csp
331
- {
332
- "default-src": "'self' customprotocol: asset:",
333
- "connect-src": "ipc: http://ipc.localhost",
334
- "font-src": ["https://fonts.gstatic.com"],
335
- "img-src": "'self' asset: http://asset.localhost blob: data:",
336
- "style-src": "'unsafe-inline' 'self'"
337
- }
338
- ```
339
-
340
- ---
341
-
342
- ## State Management (상태 관리)
343
-
344
- ```rust
345
- use std::sync::Mutex;
346
- use tauri::{Builder, Manager, State};
347
-
348
- struct AppState {
349
- counter: u32,
350
- db_url: String,
351
- }
352
-
353
- #[tauri::command]
354
- async fn increment(state: State<'_, Mutex<AppState>>) -> Result<u32, String> {
355
- let mut s = state.lock().map_err(|e| e.to_string())?;
356
- s.counter += 1;
357
- Ok(s.counter)
358
- }
359
-
360
- fn main() {
361
- Builder::default()
362
- .setup(|app| {
363
- // 불변 상태
364
- app.manage(String::from("config-value"));
365
- // 가변 상태
366
- app.manage(Mutex::new(AppState { counter: 0, db_url: String::new() }));
367
- Ok(())
368
- })
369
- .invoke_handler(tauri::generate_handler![increment])
370
- .run(tauri::generate_context!())
371
- .unwrap();
372
- }
373
- ```
374
-
375
- **규칙:**
376
- - Arc 불필요 (State<T>가 내부 처리)
377
- - 가변 상태 → `Mutex<T>` (std::sync::Mutex 권장)
378
- - 비동기 코드에서 await 포인트 넘어 lock 유지 금지
379
- - 잘못된 타입 접근 → 런타임 패닉 (타입 별칭 권장)
380
-
381
- ---
382
-
383
- ## Plugins (v2)
384
-
385
- ### v1 Built-in → v2 Plugin 전환
386
-
387
- | 기능 | v2 Plugin 패키지 |
388
- |------|-----------------|
389
- | Clipboard | `@tauri-apps/plugin-clipboard-manager` |
390
- | Dialog | `@tauri-apps/plugin-dialog` |
391
- | File System | `@tauri-apps/plugin-fs` |
392
- | HTTP | `@tauri-apps/plugin-http` |
393
- | Shell | `@tauri-apps/plugin-shell` |
394
- | Process | `@tauri-apps/plugin-process` |
395
- | OS Info | `@tauri-apps/plugin-os` |
396
- | Store | `@tauri-apps/plugin-store` |
397
- | Notification | `@tauri-apps/plugin-notification` |
398
- | Global Shortcut | `@tauri-apps/plugin-global-shortcut` |
399
-
400
- ### Plugin 사용 패턴
401
-
402
- ```bash
403
- # 설치 (Rust + JS 바인딩)
404
- npm add @tauri-apps/plugin-shell
405
- cargo add tauri-plugin-shell -F tauri-plugin-shell/build
406
- ```
407
-
408
- ```rust
409
- // main.rs에 등록
410
- fn main() {
411
- tauri::Builder::default()
412
- .plugin(tauri_plugin_shell::init())
413
- .run(tauri::generate_context!())
414
- .unwrap();
415
- }
416
- ```
417
-
418
- ```json
419
- // capabilities에 Permission 추가
420
- {
421
- "permissions": ["shell:allow-open"]
422
- }
423
- ```
424
-
425
- ---
426
-
427
- ## Configuration
428
-
429
- ### 플랫폼별 설정
430
-
431
- ```
432
- src-tauri/
433
- ├── tauri.conf.json # 공통 설정
434
- ├── tauri.windows.conf.json # Windows 오버라이드
435
- ├── tauri.macos.conf.json # macOS 오버라이드
436
- ├── tauri.linux.conf.json # Linux 오버라이드
437
- ├── tauri.android.conf.json # Android 오버라이드
438
- └── tauri.ios.conf.json # iOS 오버라이드
439
- ```
440
-
441
- ### 핵심 설정 필드
442
-
443
- ```json
444
- {
445
- "productName": "My App",
446
- "version": "1.0.0",
447
- "identifier": "com.example.myapp",
448
- "build": {
449
- "beforeDevCommand": "npm run dev",
450
- "beforeBuildCommand": "npm run build",
451
- "devUrl": "http://localhost:5173",
452
- "frontendDist": "../dist"
453
- },
454
- "app": {
455
- "windows": [{ "title": "My App", "width": 1024, "height": 768 }],
456
- "security": { "csp": "..." }
457
- },
458
- "bundle": {
459
- "active": true,
460
- "targets": ["deb", "rpm", "appimage", "msi", "nsis", "dmg", "app"],
461
- "icon": ["icons/32x32.png", "icons/icon.icns", "icons/icon.ico"],
462
- "android": { "minSdkVersion": 24 },
463
- "macOS": { "minimumSystemVersion": "10.13" }
464
- }
465
- }
466
- ```
130
+ <quick_reference>
467
131
 
468
- ---
132
+ ## Quick Reference
469
133
 
470
- <quick_reference>
134
+ ### Command 기본 패턴
471
135
 
472
136
  ```rust
473
- // Command 기본 패턴
474
137
  #[tauri::command]
475
138
  async fn my_command(arg: String, state: State<'_, Mutex<AppState>>) -> Result<MyResponse, MyError> {
476
139
  let s = state.lock().map_err(|e| MyError::Lock(e.to_string()))?;
@@ -478,16 +141,26 @@ async fn my_command(arg: String, state: State<'_, Mutex<AppState>>) -> Result<My
478
141
  }
479
142
  ```
480
143
 
144
+ ### invoke 기본 패턴
145
+
481
146
  ```typescript
482
- // invoke 기본 패턴
147
+ import { invoke } from '@tauri-apps/api/core';
483
148
  const result = await invoke<ResponseType>('my_command', { arg: 'value' });
484
149
  ```
485
150
 
151
+ ### Capability 기본 패턴
152
+
486
153
  ```json
487
- // Capability 기본 패턴
488
154
  { "identifier": "main", "windows": ["main"], "permissions": ["core:default", "plugin:allow-command"] }
489
155
  ```
490
156
 
157
+ ### Event 리스닝
158
+
159
+ ```typescript
160
+ import { listen } from '@tauri-apps/api/event';
161
+ const unlisten = await listen<PayloadType>('event-name', (e) => console.log(e.payload));
162
+ ```
163
+
491
164
  </quick_reference>
492
165
 
493
166
  ---
@@ -505,6 +178,21 @@ const result = await invoke<ResponseType>('my_command', { arg: 'value' });
505
178
  - Window: `Window` → `WebviewWindow`, `get_window()` → `get_webview_window()`
506
179
  - Security: allowlist → Capabilities/Permissions 시스템
507
180
  - Plugins: 모든 시스템 API가 별도 plugin으로 분리
508
- - Events: source-based → target-based 필터링
509
181
 
510
182
  </version_info>
183
+
184
+ ---
185
+
186
+ <references_index>
187
+
188
+ ## References
189
+
190
+ | 문서 | 내용 |
191
+ |------|------|
192
+ | **@references/ipc-guide.md** | Commands, Events, Channels 상세 |
193
+ | **@references/security-guide.md** | Capabilities, CSP 상세 |
194
+ | **@references/state-guide.md** | State 패턴, Mutex 규칙 |
195
+ | **@references/plugins-guide.md** | v2 Plugin 목록, 사용법 |
196
+ | **@references/configuration.md** | 플랫폼별 설정 상세 |
197
+
198
+ </references_index>
@@ -0,0 +1,46 @@
1
+ # Configuration
2
+
3
+ > Tauri v2 설정 상세 가이드
4
+
5
+ ---
6
+
7
+ ## 플랫폼별 설정
8
+
9
+ ```
10
+ src-tauri/
11
+ ├── tauri.conf.json # 공통 설정
12
+ ├── tauri.windows.conf.json # Windows 오버라이드
13
+ ├── tauri.macos.conf.json # macOS 오버라이드
14
+ ├── tauri.linux.conf.json # Linux 오버라이드
15
+ ├── tauri.android.conf.json # Android 오버라이드
16
+ └── tauri.ios.conf.json # iOS 오버라이드
17
+ ```
18
+
19
+ ---
20
+
21
+ ## 핵심 설정 필드
22
+
23
+ ```json
24
+ {
25
+ "productName": "My App",
26
+ "version": "1.0.0",
27
+ "identifier": "com.example.myapp",
28
+ "build": {
29
+ "beforeDevCommand": "npm run dev",
30
+ "beforeBuildCommand": "npm run build",
31
+ "devUrl": "http://localhost:5173",
32
+ "frontendDist": "../dist"
33
+ },
34
+ "app": {
35
+ "windows": [{ "title": "My App", "width": 1024, "height": 768 }],
36
+ "security": { "csp": "..." }
37
+ },
38
+ "bundle": {
39
+ "active": true,
40
+ "targets": ["deb", "rpm", "appimage", "msi", "nsis", "dmg", "app"],
41
+ "icon": ["icons/32x32.png", "icons/icon.icns", "icons/icon.ico"],
42
+ "android": { "minSdkVersion": 24 },
43
+ "macOS": { "minimumSystemVersion": "10.13" }
44
+ }
45
+ }
46
+ ```
@@ -0,0 +1,131 @@
1
+ # IPC Guide
2
+
3
+ > Inter-Process Communication 상세 가이드
4
+
5
+ ---
6
+
7
+ ## Commands (프론트엔드 → Rust)
8
+
9
+ ```rust
10
+ // src-tauri/src/commands/mod.rs
11
+ use serde::{Deserialize, Serialize};
12
+ use tauri::State;
13
+ use std::sync::Mutex;
14
+
15
+ #[derive(Serialize, Deserialize)]
16
+ pub struct User {
17
+ pub id: u32,
18
+ pub name: String,
19
+ }
20
+
21
+ // 기본 Command
22
+ #[tauri::command]
23
+ fn greet(name: String) -> String {
24
+ format!("Hello, {}!", name)
25
+ }
26
+
27
+ // 비동기 Command + State
28
+ #[tauri::command]
29
+ async fn get_user(id: u32, db: State<'_, Mutex<Database>>) -> Result<User, String> {
30
+ let db = db.lock().map_err(|e| e.to_string())?;
31
+ db.find_user(id).ok_or("User not found".into())
32
+ }
33
+
34
+ // 에러 처리 (thiserror 권장)
35
+ #[derive(Debug, thiserror::Error)]
36
+ pub enum AppError {
37
+ #[error("Not found: {0}")]
38
+ NotFound(String),
39
+ #[error("Internal error: {0}")]
40
+ Internal(String),
41
+ }
42
+
43
+ impl serde::Serialize for AppError {
44
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
45
+ where S: serde::Serializer {
46
+ serializer.serialize_str(self.to_string().as_ref())
47
+ }
48
+ }
49
+
50
+ #[tauri::command]
51
+ async fn delete_user(id: u32) -> Result<(), AppError> {
52
+ // ...
53
+ Err(AppError::NotFound(format!("User {} not found", id)))
54
+ }
55
+ ```
56
+
57
+ ```typescript
58
+ // 프론트엔드 invoke
59
+ import { invoke } from '@tauri-apps/api/core';
60
+
61
+ // 기본 호출
62
+ const greeting = await invoke<string>('greet', { name: 'World' });
63
+
64
+ // 타입 안전 호출
65
+ interface User { id: number; name: string; }
66
+ const user = await invoke<User>('get_user', { id: 1 });
67
+
68
+ // 에러 처리
69
+ try {
70
+ await invoke('delete_user', { id: 999 });
71
+ } catch (error) {
72
+ console.error('Command failed:', error);
73
+ }
74
+ ```
75
+
76
+ ---
77
+
78
+ ## Events (양방향 통신)
79
+
80
+ ```rust
81
+ // Rust → 프론트엔드 Event 발행
82
+ use tauri::{AppHandle, Emitter};
83
+
84
+ #[derive(Clone, Serialize)]
85
+ struct DownloadProgress {
86
+ url: String,
87
+ progress: f64,
88
+ }
89
+
90
+ fn download_file(app: AppHandle, url: String) {
91
+ // 모든 윈도우에 Global Event
92
+ app.emit("download-progress", DownloadProgress {
93
+ url, progress: 0.5,
94
+ }).unwrap();
95
+
96
+ // 특정 윈도우에만
97
+ app.emit_to("main", "download-complete", ()).unwrap();
98
+ }
99
+ ```
100
+
101
+ ```typescript
102
+ // 프론트엔드 Event 리스닝
103
+ import { listen, once } from '@tauri-apps/api/event';
104
+
105
+ // 지속 리스닝
106
+ const unlisten = await listen<{ url: string; progress: number }>(
107
+ 'download-progress',
108
+ (event) => console.log(`${event.payload.url}: ${event.payload.progress * 100}%`)
109
+ );
110
+
111
+ // 단발 리스닝
112
+ await once('download-complete', () => console.log('Done!'));
113
+
114
+ // 리스너 해제
115
+ unlisten();
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Channels (고속 스트리밍)
121
+
122
+ ```rust
123
+ use tauri::ipc::Channel;
124
+
125
+ #[tauri::command]
126
+ fn stream_data(channel: Channel<String>) {
127
+ for i in 0..100 {
128
+ channel.send(format!("chunk {}", i)).unwrap();
129
+ }
130
+ }
131
+ ```
@@ -0,0 +1,47 @@
1
+ # Plugins Guide
2
+
3
+ > Tauri v2 Plugin 사용 가이드
4
+
5
+ ---
6
+
7
+ ## v1 Built-in → v2 Plugin 전환
8
+
9
+ | 기능 | v2 Plugin 패키지 |
10
+ |------|-----------------|
11
+ | Clipboard | `@tauri-apps/plugin-clipboard-manager` |
12
+ | Dialog | `@tauri-apps/plugin-dialog` |
13
+ | File System | `@tauri-apps/plugin-fs` |
14
+ | HTTP | `@tauri-apps/plugin-http` |
15
+ | Shell | `@tauri-apps/plugin-shell` |
16
+ | Process | `@tauri-apps/plugin-process` |
17
+ | OS Info | `@tauri-apps/plugin-os` |
18
+ | Store | `@tauri-apps/plugin-store` |
19
+ | Notification | `@tauri-apps/plugin-notification` |
20
+ | Global Shortcut | `@tauri-apps/plugin-global-shortcut` |
21
+
22
+ ---
23
+
24
+ ## Plugin 사용 패턴
25
+
26
+ ```bash
27
+ # 설치 (Rust + JS 바인딩)
28
+ npm add @tauri-apps/plugin-shell
29
+ cargo add tauri-plugin-shell -F tauri-plugin-shell/build
30
+ ```
31
+
32
+ ```rust
33
+ // main.rs에 등록
34
+ fn main() {
35
+ tauri::Builder::default()
36
+ .plugin(tauri_plugin_shell::init())
37
+ .run(tauri::generate_context!())
38
+ .unwrap();
39
+ }
40
+ ```
41
+
42
+ ```json
43
+ // capabilities에 Permission 추가
44
+ {
45
+ "permissions": ["shell:allow-open"]
46
+ }
47
+ ```