clawmate 1.4.0 → 1.4.2

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 (42) hide show
  1. package/index.js +441 -442
  2. package/main/ai-bridge.js +59 -59
  3. package/main/ai-connector.js +60 -60
  4. package/main/autostart.js +6 -6
  5. package/main/desktop-path.js +4 -4
  6. package/main/file-command-parser.js +46 -46
  7. package/main/file-ops.js +27 -27
  8. package/main/index.js +17 -17
  9. package/main/ipc-handlers.js +24 -24
  10. package/main/manifest.js +2 -2
  11. package/main/platform.js +16 -16
  12. package/main/smart-file-ops.js +64 -64
  13. package/main/store.js +1 -1
  14. package/main/telegram.js +137 -137
  15. package/main/tray.js +61 -61
  16. package/main/updater.js +13 -13
  17. package/openclaw.plugin.json +1 -1
  18. package/package.json +2 -2
  19. package/preload/preload.js +18 -18
  20. package/renderer/css/effects.css +6 -6
  21. package/renderer/css/pet.css +8 -8
  22. package/renderer/css/speech.css +5 -5
  23. package/renderer/first-run.html +14 -14
  24. package/renderer/index.html +4 -4
  25. package/renderer/js/ai-controller.js +91 -91
  26. package/renderer/js/app.js +24 -24
  27. package/renderer/js/browser-watcher.js +32 -32
  28. package/renderer/js/character.js +33 -33
  29. package/renderer/js/interactions.js +21 -21
  30. package/renderer/js/memory.js +60 -60
  31. package/renderer/js/metrics.js +141 -141
  32. package/renderer/js/mode-manager.js +13 -13
  33. package/renderer/js/pet-engine.js +236 -236
  34. package/renderer/js/speech.js +19 -19
  35. package/renderer/js/state-machine.js +23 -23
  36. package/renderer/js/time-aware.js +15 -15
  37. package/renderer/launcher.html +8 -8
  38. package/shared/constants.js +11 -11
  39. package/shared/messages.js +130 -130
  40. package/shared/personalities.js +44 -44
  41. package/skills/launch-pet/index.js +57 -47
  42. package/skills/launch-pet/skill.json +12 -23
@@ -18,7 +18,7 @@ const memoryStore = new Store('clawmate-memory', {
18
18
  });
19
19
 
20
20
  function registerIpcHandlers(getMainWindow, getAIBridge) {
21
- // 클릭 통과 제어
21
+ // Click-through control
22
22
  ipcMain.on('set-click-through', (event, ignore) => {
23
23
  const win = getMainWindow();
24
24
  if (win) {
@@ -26,14 +26,14 @@ function registerIpcHandlers(getMainWindow, getAIBridge) {
26
26
  }
27
27
  });
28
28
 
29
- // 파일 작업
29
+ // File operations
30
30
  ipcMain.handle('get-desktop-files', async () => getDesktopFiles());
31
31
  ipcMain.handle('move-file', async (_, fileName, newPos) => moveFile(fileName, newPos));
32
32
  ipcMain.handle('undo-file-move', async (_, moveId) => undoFileMove(moveId));
33
33
  ipcMain.handle('undo-all-moves', async () => undoAllMoves());
34
34
  ipcMain.handle('get-file-manifest', async () => getFileManifest());
35
35
 
36
- // 모드
36
+ // Mode
37
37
  ipcMain.handle('get-mode', () => store.get('mode'));
38
38
  ipcMain.handle('set-mode', (_, mode) => {
39
39
  store.set('mode', mode);
@@ -42,7 +42,7 @@ function registerIpcHandlers(getMainWindow, getAIBridge) {
42
42
  return mode;
43
43
  });
44
44
 
45
- // 설정
45
+ // Config
46
46
  ipcMain.handle('get-config', () => store.getAll());
47
47
  ipcMain.handle('set-config', (_, key, value) => {
48
48
  store.set(key, value);
@@ -51,20 +51,20 @@ function registerIpcHandlers(getMainWindow, getAIBridge) {
51
51
  return true;
52
52
  });
53
53
 
54
- // 메모리
54
+ // Memory
55
55
  ipcMain.handle('get-memory', () => memoryStore.getAll());
56
56
  ipcMain.handle('save-memory', (_, data) => {
57
57
  Object.entries(data).forEach(([key, value]) => memoryStore.set(key, value));
58
58
  return true;
59
59
  });
60
60
 
61
- // 화면 크기
61
+ // Screen size
62
62
  ipcMain.handle('get-screen-size', () => {
63
63
  const { width, height } = screen.getPrimaryDisplay().workAreaSize;
64
64
  return { width, height };
65
65
  });
66
66
 
67
- // 화면 캡처
67
+ // Screen capture
68
68
  ipcMain.handle('capture-screen', async () => {
69
69
  try {
70
70
  const primaryDisplay = screen.getPrimaryDisplay();
@@ -76,7 +76,7 @@ function registerIpcHandlers(getMainWindow, getAIBridge) {
76
76
  });
77
77
 
78
78
  if (sources.length > 0) {
79
- // NativeImage base64 JPEG 변환 (크기 최적화)
79
+ // Convert NativeImage to base64 JPEG (size optimized)
80
80
  const thumbnail = sources[0].thumbnail;
81
81
  const jpegBuffer = thumbnail.toJPEG(60);
82
82
  return {
@@ -93,9 +93,9 @@ function registerIpcHandlers(getMainWindow, getAIBridge) {
93
93
  }
94
94
  });
95
95
 
96
- // === AI 통신 ===
96
+ // === AI Communication ===
97
97
 
98
- // 사용자 이벤트를 AI Bridge 전달 (렌더러 main AI)
98
+ // Forward user events to AI Bridge (renderer -> main -> AI)
99
99
  ipcMain.on('report-to-ai', (_, event, data) => {
100
100
  const bridge = getAIBridge();
101
101
  if (bridge && bridge.isConnected()) {
@@ -125,24 +125,24 @@ function registerIpcHandlers(getMainWindow, getAIBridge) {
125
125
  bridge.reportIdleTime(data.idleSeconds);
126
126
  break;
127
127
  case 'browsing':
128
- // 브라우징 컨텍스트 (제목 + 커서 위치 + 화면 캡처) AI 코멘트 생성
128
+ // Browsing context (title + cursor position + screen capture) -> AI comment generation
129
129
  bridge.send('user_event', { event: 'browsing', ...data });
130
130
  break;
131
131
  default:
132
- // 없는 이벤트도 AI 전달 (확장성)
132
+ // Forward unknown events to AI as well (extensibility)
133
133
  bridge.send('user_event', { event, ...data });
134
134
  break;
135
135
  }
136
136
  }
137
137
  });
138
138
 
139
- // AI 연결 상태 확인
139
+ // Check AI connection status
140
140
  ipcMain.handle('is-ai-connected', () => {
141
141
  const bridge = getAIBridge();
142
142
  return bridge ? bridge.isConnected() : false;
143
143
  });
144
144
 
145
- // 메트릭 보고 (렌더러 main AI)
145
+ // Metrics reporting (renderer -> main -> AI)
146
146
  ipcMain.on('report-metrics', (_, summary) => {
147
147
  const bridge = getAIBridge();
148
148
  if (bridge && bridge.isConnected()) {
@@ -150,38 +150,38 @@ function registerIpcHandlers(getMainWindow, getAIBridge) {
150
150
  }
151
151
  });
152
152
 
153
- // 열린 윈도우 위치/크기 조회
153
+ // Get open window positions/sizes
154
154
  ipcMain.handle('get-window-positions', async () => {
155
155
  const { getWindowPositions } = require('./platform');
156
156
  return await getWindowPositions();
157
157
  });
158
158
 
159
- // 활성 윈도우 제목 조회 (브라우저 감시용)
159
+ // Get active window title (for browser monitoring)
160
160
  ipcMain.handle('get-active-window-title', async () => {
161
161
  const { getActiveWindowTitle } = require('./platform');
162
162
  return await getActiveWindowTitle();
163
163
  });
164
164
 
165
- // 커서 위치 조회 (화면 좌표)
165
+ // Get cursor position (screen coordinates)
166
166
  ipcMain.handle('get-cursor-position', () => {
167
167
  const point = screen.getCursorScreenPoint();
168
168
  return { x: point.x, y: point.y };
169
169
  });
170
170
 
171
- // === 스마트 파일 조작 IPC ===
171
+ // === Smart File Operation IPC ===
172
172
 
173
- // 파일 명령 파싱 (렌더러에서도 사용 가능)
173
+ // Parse file command (also available from renderer)
174
174
  ipcMain.handle('parse-file-command', (_, text) => {
175
175
  return parseMessage(text);
176
176
  });
177
177
 
178
- // 필터된 파일 목록 조회
178
+ // Get filtered file list
179
179
  ipcMain.handle('list-filtered-files', async (_, sourceDir, filter) => {
180
180
  return listFilteredFiles(sourceDir, filter);
181
181
  });
182
182
 
183
- // 스마트 파일 조작 실행
184
- // 렌더러에서 직접 실행할 사용 (텔레그램 경유가 아닌 경우)
183
+ // Execute smart file operation
184
+ // Used when executed directly from renderer (not via Telegram)
185
185
  ipcMain.handle('smart-file-op', async (_, command) => {
186
186
  const win = getMainWindow();
187
187
  const callbacks = {
@@ -230,12 +230,12 @@ function registerIpcHandlers(getMainWindow, getAIBridge) {
230
230
  return await executeSmartFileOp(command, callbacks);
231
231
  });
232
232
 
233
- // 스마트 이동 되돌리기 (단일)
233
+ // Undo smart move (single)
234
234
  ipcMain.handle('undo-smart-move', async (_, moveId) => {
235
235
  return undoSmartMove(moveId);
236
236
  });
237
237
 
238
- // 스마트 이동 전체 되돌리기
238
+ // Undo all smart moves
239
239
  ipcMain.handle('undo-all-smart-moves', async () => {
240
240
  return undoAllSmartMoves();
241
241
  });
package/main/manifest.js CHANGED
@@ -3,8 +3,8 @@ const path = require('path');
3
3
  const { app } = require('electron');
4
4
 
5
5
  /**
6
- * 파일 이동 이력 관리 (Undo 지원)
7
- * 모든 파일 이동을 기록하고, 복원 가능하게 관리
6
+ * File move history management (Undo support)
7
+ * Records all file moves and manages restoration
8
8
  */
9
9
  const MANIFEST_FILE = () => path.join(app.getPath('userData'), 'file-manifest.json');
10
10
 
package/main/platform.js CHANGED
@@ -47,9 +47,9 @@ function isLinux() {
47
47
  }
48
48
 
49
49
  /**
50
- * OS별 열린 윈도우의 위치/크기 정보를 가져옴
51
- * 반환 형식: [{ id, title, x, y, width, height }]
52
- * 실패 배열 반환 (안전한 폴백)
50
+ * Get position/size info of open windows per OS
51
+ * Return format: [{ id, title, x, y, width, height }]
52
+ * Returns empty array on failure (safe fallback)
53
53
  */
54
54
  async function getWindowPositions() {
55
55
  if (platform === 'win32') {
@@ -62,12 +62,12 @@ async function getWindowPositions() {
62
62
  }
63
63
 
64
64
  /**
65
- * Windows: PowerShell로 보이는 윈도우 목록 + 위치/크기 조회
66
- * Add-Type으로 Win32 API(GetWindowRect) 호출
65
+ * Windows: Get visible window list + position/size via PowerShell
66
+ * Calls Win32 API (GetWindowRect) via Add-Type
67
67
  */
68
68
  async function getWindowPositionsWindows() {
69
69
  try {
70
- // PowerShell에서 C# 인라인 컴파일로 Win32 API 접근
70
+ // Access Win32 API via inline C# compilation in PowerShell
71
71
  const psScript = `
72
72
  Add-Type @"
73
73
  using System;
@@ -101,7 +101,7 @@ public class WinInfo {
101
101
  [WinInfo]::GetWindows()
102
102
  `.trim();
103
103
 
104
- // PowerShell 스크립트를 임시 파일 없이 stdin으로 전달
104
+ // Pass PowerShell script via stdin without temp files
105
105
  const { stdout } = await execAsync(
106
106
  `powershell -NoProfile -Command -`,
107
107
  { input: psScript, timeout: 5000, encoding: 'utf-8' }
@@ -129,11 +129,11 @@ public class WinInfo {
129
129
  }
130
130
 
131
131
  /**
132
- * macOS: AppleScript로 보이는 윈도우 위치/크기 조회
132
+ * macOS: Get visible window positions/sizes via AppleScript
133
133
  */
134
134
  async function getWindowPositionsMac() {
135
135
  try {
136
- // AppleScript로 보이는 프로세스의 윈도우 정보 수집
136
+ // Collect window info of visible processes via AppleScript
137
137
  const script = `
138
138
  tell application "System Events"
139
139
  set output to ""
@@ -180,8 +180,8 @@ end tell
180
180
  }
181
181
 
182
182
  /**
183
- * Linux: wmctrl -l -G 로 윈도우 위치/크기 조회
184
- * wmctrl 미설치 배열 반환
183
+ * Linux: Get window positions/sizes via wmctrl -l -G
184
+ * Returns empty array if wmctrl is not installed
185
185
  */
186
186
  async function getWindowPositionsLinux() {
187
187
  try {
@@ -193,8 +193,8 @@ async function getWindowPositionsLinux() {
193
193
  const result = (stdout || '').trim();
194
194
  if (!result) return [];
195
195
 
196
- // wmctrl -l -G 출력 형식:
197
- // 0x02000003 0 0 0 1920 1080 hostname 데스크톱
196
+ // wmctrl -l -G output format:
197
+ // 0x02000003 0 0 0 1920 1080 hostname Desktop
198
198
  // ID desktop x y width height hostname title
199
199
  return result.split('\n').filter(Boolean).map((line, i) => {
200
200
  const parts = line.trim().split(/\s+/);
@@ -203,7 +203,7 @@ async function getWindowPositionsLinux() {
203
203
  const y = parseInt(parts[3], 10) || 0;
204
204
  const width = parseInt(parts[4], 10) || 0;
205
205
  const height = parseInt(parts[5], 10) || 0;
206
- // hostname 이후가 타이틀 (공백 포함 가능)
206
+ // Everything after hostname is the title (may contain spaces)
207
207
  const title = parts.slice(7).join(' ');
208
208
  if (width < 50 || height < 50) return null;
209
209
  return { id: `win_${i}`, title, x, y, width, height };
@@ -214,8 +214,8 @@ async function getWindowPositionsLinux() {
214
214
  }
215
215
 
216
216
  /**
217
- * 현재 포커스된 (최상위) 윈도우의 제목 반환
218
- * 브라우저 제목을 감지하여 펫이 참견할 있게
217
+ * Return title of currently focused (foreground) window
218
+ * Detects browser tab titles so the pet can comment on them
219
219
  */
220
220
  async function getActiveWindowTitle() {
221
221
  try {
@@ -1,15 +1,15 @@
1
1
  /**
2
- * 스마트 파일 조작 시스템
2
+ * Smart File Operation System
3
3
  *
4
- * 텔레그램 또는 AI 명령으로 파일을 "펫이 직접 나르는" 방식으로 이동.
5
- * 펫이 파일 위치로 점프 집어들기 대상 폴더로 이동 내려놓기 순서로
6
- * 애니메이션과 실제 파일시스템 이동을 동시에 수행.
4
+ * Moves files via Telegram or AI commands in a "pet carries them" fashion.
5
+ * Pet jumps to file location -> picks up -> moves to target folder -> drops off,
6
+ * performing animation and actual filesystem move simultaneously.
7
7
  *
8
- * 안전장치:
9
- * - .exe/.dll/.sys 등 위험 확장자 제외
10
- * - 100MB 이상 파일 제외
11
- * - 모든 이동을 manifest 기록 (undo 가능)
12
- * - 진행 중단 이미 이동된 파일은 manifest 기록되어 복원 가능
8
+ * Safety measures:
9
+ * - Excludes dangerous extensions like .exe/.dll/.sys
10
+ * - Excludes files over 100MB
11
+ * - Records all moves in manifest (undo supported)
12
+ * - Files already moved during interruption are recorded in manifest for restoration
13
13
  */
14
14
 
15
15
  const fs = require('fs');
@@ -18,46 +18,46 @@ const { getDesktopPath } = require('./desktop-path');
18
18
  const manifest = require('./manifest');
19
19
  const { AUTO_CATEGORIES } = require('./file-command-parser');
20
20
 
21
- // 안전장치 상수
21
+ // Safety constants
22
22
  const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB
23
23
  const EXCLUDED_EXTS = new Set([
24
24
  '.exe', '.dll', '.sys', '.lnk', '.ini', '.bat', '.cmd',
25
25
  '.ps1', '.msi', '.scr', '.com', '.pif', '.vbs', '.wsf',
26
26
  ]);
27
27
 
28
- // 파일 이동 딜레이 (ms) - 애니메이션에 시간을
28
+ // Delay between file moves (ms) - gives time for pet animation
29
29
  const PER_FILE_DELAY = 2500;
30
30
 
31
31
  /**
32
- * 파일이 이동 가능한지 검증
33
- * @param {string} filePath - 파일 전체 경로
32
+ * Validate whether file can be moved
33
+ * @param {string} filePath - Full file path
34
34
  * @returns {{ safe: boolean, reason?: string }}
35
35
  */
36
36
  function validateFile(filePath) {
37
37
  const ext = path.extname(filePath).toLowerCase();
38
38
  if (EXCLUDED_EXTS.has(ext)) {
39
- return { safe: false, reason: `보호된 파일 유형 (${ext})` };
39
+ return { safe: false, reason: `Protected file type (${ext})` };
40
40
  }
41
41
 
42
42
  try {
43
43
  const stat = fs.statSync(filePath);
44
44
  if (stat.size > MAX_FILE_SIZE) {
45
- return { safe: false, reason: `파일 크기 초과 (${Math.round(stat.size / 1024 / 1024)}MB > 100MB)` };
45
+ return { safe: false, reason: `File size exceeded (${Math.round(stat.size / 1024 / 1024)}MB > 100MB)` };
46
46
  }
47
47
  if (!stat.isFile()) {
48
- return { safe: false, reason: '파일이 아님' };
48
+ return { safe: false, reason: 'Not a file' };
49
49
  }
50
50
  } catch {
51
- return { safe: false, reason: '파일 접근 불가' };
51
+ return { safe: false, reason: 'File inaccessible' };
52
52
  }
53
53
 
54
54
  return { safe: true };
55
55
  }
56
56
 
57
57
  /**
58
- * 소스 디렉토리에서 필터 조건에 맞는 파일 목록 조회
59
- * @param {string} sourceDir - 소스 디렉토리 경로
60
- * @param {string} filter - 확장자 필터 (예: ".md", "*")
58
+ * List files matching filter criteria in source directory
59
+ * @param {string} sourceDir - Source directory path
60
+ * @param {string} filter - Extension filter (e.g., ".md", "*")
61
61
  * @returns {Array<{ name: string, path: string, ext: string, size: number }>}
62
62
  */
63
63
  function listFilteredFiles(sourceDir, filter) {
@@ -72,10 +72,10 @@ function listFilteredFiles(sourceDir, filter) {
72
72
  const filePath = path.join(sourceDir, entry.name);
73
73
  const ext = path.extname(entry.name).toLowerCase();
74
74
 
75
- // 확장자 필터 적용
75
+ // Apply extension filter
76
76
  if (filter !== '*' && ext !== filter.toLowerCase()) continue;
77
77
 
78
- // 안전 검증
78
+ // Safety validation
79
79
  const validation = validateFile(filePath);
80
80
  if (!validation.safe) continue;
81
81
 
@@ -99,16 +99,16 @@ function listFilteredFiles(sourceDir, filter) {
99
99
  }
100
100
 
101
101
  /**
102
- * 자동 분류 모드: 파일을 확장자별 폴더로 분류
103
- * @param {string} sourceDir - 소스 디렉토리
104
- * @returns {Map<string, Array>} 카테고리명 파일 목록
102
+ * Auto-categorize mode: sort files into folders by extension
103
+ * @param {string} sourceDir - Source directory
104
+ * @returns {Map<string, Array>} Category name -> file list
105
105
  */
106
106
  function categorizeFiles(sourceDir) {
107
107
  const files = listFilteredFiles(sourceDir, '*');
108
108
  const categories = new Map();
109
109
 
110
110
  for (const file of files) {
111
- const category = AUTO_CATEGORIES[file.ext] || '기타';
111
+ const category = AUTO_CATEGORIES[file.ext] || 'Other';
112
112
  if (!categories.has(category)) {
113
113
  categories.set(category, []);
114
114
  }
@@ -119,10 +119,10 @@ function categorizeFiles(sourceDir) {
119
119
  }
120
120
 
121
121
  /**
122
- * 대상 폴더 생성 (없으면)
123
- * @param {string} sourceDir - 소스 디렉토리 (대상 폴더의 부모)
124
- * @param {string} targetName - 대상 폴더 이름
125
- * @returns {string} 대상 폴더 전체 경로
122
+ * Create target folder (if it doesn't exist)
123
+ * @param {string} sourceDir - Source directory (parent of target folder)
124
+ * @param {string} targetName - Target folder name
125
+ * @returns {string} Full path of target folder
126
126
  */
127
127
  function ensureTargetDir(sourceDir, targetName) {
128
128
  const targetDir = path.join(sourceDir, targetName);
@@ -133,16 +133,16 @@ function ensureTargetDir(sourceDir, targetName) {
133
133
  }
134
134
 
135
135
  /**
136
- * 단일 파일 이동 실행 + manifest 기록
137
- * @param {string} filePath - 원본 파일 경로
138
- * @param {string} targetDir - 대상 디렉토리
136
+ * Execute single file move + record in manifest
137
+ * @param {string} filePath - Original file path
138
+ * @param {string} targetDir - Target directory
139
139
  * @returns {{ success: boolean, newPath?: string, error?: string, moveId?: string }}
140
140
  */
141
141
  function moveFileToTarget(filePath, targetDir) {
142
142
  const fileName = path.basename(filePath);
143
143
  let newPath = path.join(targetDir, fileName);
144
144
 
145
- // 동일 이름 파일이 있으면 넘버링
145
+ // Number files if same name exists
146
146
  if (fs.existsSync(newPath)) {
147
147
  const ext = path.extname(fileName);
148
148
  const base = path.basename(fileName, ext);
@@ -156,7 +156,7 @@ function moveFileToTarget(filePath, targetDir) {
156
156
  try {
157
157
  fs.renameSync(filePath, newPath);
158
158
 
159
- // manifest 기록 (undo 지원)
159
+ // Record in manifest (undo support)
160
160
  const entry = manifest.addEntry({
161
161
  fileName,
162
162
  originalPath: filePath,
@@ -172,39 +172,39 @@ function moveFileToTarget(filePath, targetDir) {
172
172
  }
173
173
 
174
174
  /**
175
- * 스마트 파일 이동 되돌리기 (단일)
176
- * @param {string} moveId - manifest 엔트리 ID
175
+ * Undo smart file move (single)
176
+ * @param {string} moveId - Manifest entry ID
177
177
  * @returns {{ success: boolean, error?: string }}
178
178
  */
179
179
  function undoSmartMove(moveId) {
180
180
  const entries = manifest.getAll();
181
181
  const entry = entries.find(e => e.id === moveId && e.action === 'smart_move');
182
182
  if (!entry) {
183
- return { success: false, error: '이동 기록을 찾을 수 없음' };
183
+ return { success: false, error: 'Move record not found' };
184
184
  }
185
185
  if (entry.restored) {
186
- return { success: false, error: '이미 복원된 항목' };
186
+ return { success: false, error: 'Already restored' };
187
187
  }
188
188
 
189
189
  try {
190
- // 위치에서 원래 위치로 되돌리기
190
+ // Restore from new location to original location
191
191
  if (fs.existsSync(entry.newPath)) {
192
- // 원래 위치에 같은 이름 파일이 있으면 충돌 방지
192
+ // Prevent conflict if same name file exists at original location
193
193
  if (fs.existsSync(entry.originalPath)) {
194
- return { success: false, error: '원래 위치에 동일 이름 파일이 존재' };
194
+ return { success: false, error: 'File with same name exists at original location' };
195
195
  }
196
196
  fs.renameSync(entry.newPath, entry.originalPath);
197
197
  manifest.markRestored(moveId);
198
198
  return { success: true };
199
199
  }
200
- return { success: false, error: '이동된 파일을 찾을 수 없음' };
200
+ return { success: false, error: 'Moved file not found' };
201
201
  } catch (err) {
202
202
  return { success: false, error: err.message };
203
203
  }
204
204
  }
205
205
 
206
206
  /**
207
- * 스마트 파일 이동 전체 되돌리기
207
+ * Undo all smart file moves
208
208
  * @returns {{ success: boolean, restoredCount: number, errors: string[] }}
209
209
  */
210
210
  function undoAllSmartMoves() {
@@ -213,7 +213,7 @@ function undoAllSmartMoves() {
213
213
  let restoredCount = 0;
214
214
  const errors = [];
215
215
 
216
- // 최신 이동부터 역순으로 복원
216
+ // Restore in reverse order starting from most recent
217
217
  for (const entry of smartMoves.reverse()) {
218
218
  const result = undoSmartMove(entry.id);
219
219
  if (result.success) {
@@ -227,33 +227,33 @@ function undoAllSmartMoves() {
227
227
  }
228
228
 
229
229
  /**
230
- * 스마트 파일 조작 실행 (전체 흐름)
230
+ * Execute smart file operation (full flow)
231
231
  *
232
- * 콜백 함수를 통해 애니메이션을 제어하면서 파일을 순차적으로 이동.
232
+ * Sequentially moves files while controlling pet animation via callbacks.
233
233
  *
234
- * @param {object} command - 파싱된 파일 명령
235
- * - source: 소스 디렉토리 경로
236
- * - filter: 확장자 필터 (예: ".md", "*")
237
- * - target: 대상 폴더 이름 또는 "auto"
238
- * - autoCategory: 자동 분류 여부
239
- * @param {object} callbacks - 애니메이션 콜백
240
- * - onStart(totalFiles): 작업 시작
241
- * - onPickUp(fileName, index): 파일 집어들
242
- * - onDrop(fileName, targetName, index): 파일 내려놓을
243
- * - onComplete(result): 작업 완료
244
- * - onError(error): 오류 발생
234
+ * @param {object} command - Parsed file command
235
+ * - source: Source directory path
236
+ * - filter: Extension filter (e.g., ".md", "*")
237
+ * - target: Target folder name or "auto"
238
+ * - autoCategory: Whether to auto-categorize
239
+ * @param {object} callbacks - Pet animation callbacks
240
+ * - onStart(totalFiles): When operation starts
241
+ * - onPickUp(fileName, index): When picking up file
242
+ * - onDrop(fileName, targetName, index): When dropping file
243
+ * - onComplete(result): When operation completes
244
+ * - onError(error): When error occurs
245
245
  * @returns {Promise<{ success: boolean, movedCount: number, errors: string[], moveIds: string[] }>}
246
246
  */
247
247
  async function executeSmartFileOp(command, callbacks = {}) {
248
248
  const { source, filter, target, autoCategory } = command;
249
249
 
250
250
  try {
251
- // 자동 분류 모드
251
+ // Auto-categorize mode
252
252
  if (autoCategory) {
253
253
  return await _executeAutoCategory(source, callbacks);
254
254
  }
255
255
 
256
- // 특정 대상 폴더로 이동
256
+ // Move to specific target folder
257
257
  return await _executeTargetMove(source, filter, target, callbacks);
258
258
  } catch (err) {
259
259
  if (callbacks.onError) callbacks.onError(err.message);
@@ -262,7 +262,7 @@ async function executeSmartFileOp(command, callbacks = {}) {
262
262
  }
263
263
 
264
264
  /**
265
- * 자동 분류 실행
265
+ * Execute auto-categorization
266
266
  */
267
267
  async function _executeAutoCategory(sourceDir, callbacks) {
268
268
  const categories = categorizeFiles(sourceDir);
@@ -286,8 +286,8 @@ async function _executeAutoCategory(sourceDir, callbacks) {
286
286
  let fileIndex = 0;
287
287
 
288
288
  for (const [category, files] of categories) {
289
- // "기타" 카테고리에 파일이 적으면 건너뜀
290
- if (category === '기타' && files.length <= 2) continue;
289
+ // Skip "Other" category if few files
290
+ if (category === 'Other' && files.length <= 2) continue;
291
291
 
292
292
  const targetDir = ensureTargetDir(sourceDir, category);
293
293
 
@@ -315,7 +315,7 @@ async function _executeAutoCategory(sourceDir, callbacks) {
315
315
  }
316
316
 
317
317
  /**
318
- * 특정 대상 폴더로 이동 실행
318
+ * Execute move to specific target folder
319
319
  */
320
320
  async function _executeTargetMove(sourceDir, filter, targetName, callbacks) {
321
321
  const files = listFilteredFiles(sourceDir, filter);
package/main/store.js CHANGED
@@ -15,7 +15,7 @@ class Store {
15
15
  const raw = fs.readFileSync(this.path, 'utf-8');
16
16
  this.data = { ...this.data, ...JSON.parse(raw) };
17
17
  } catch {
18
- // 파일 없으면 기본값 사용
18
+ // Use defaults if file doesn't exist
19
19
  }
20
20
  }
21
21