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
package/main/telegram.js CHANGED
@@ -1,36 +1,36 @@
1
1
  /**
2
- * 텔레그램 통합 모듈
2
+ * Telegram Bot Integration Module
3
3
  *
4
- * 텔레그램 메시지 ClawMate 양방향 통신.
5
- * - 텔레그램에서 메시지를 파싱하여 AI Bridge에 명령 전달
6
- * - 펫의 상태/말을 텔레그램으로 역전달
4
+ * Bidirectional communication between Telegram messages and ClawMate.
5
+ * - Parses incoming Telegram messages and forwards commands to AI Bridge
6
+ * - Relays pet state/speech back to Telegram
7
7
  *
8
- * 토큰 우선순위:
9
- * 1. 환경변수 CLAWMATE_TELEGRAM_TOKEN
10
- * 2. 설정 파일 (Store)
11
- * 3. 없으면 조용히 비활성화 (에러 없음)
8
+ * Bot token priority:
9
+ * 1. Environment variable CLAWMATE_TELEGRAM_TOKEN
10
+ * 2. Config file (Store)
11
+ * 3. If neither exists, silently disabled (no error)
12
12
  *
13
- * 의존성: node-telegram-bot-api (npm install node-telegram-bot-api)
13
+ * Dependency: node-telegram-bot-api (npm install node-telegram-bot-api)
14
14
  */
15
15
 
16
16
  const EventEmitter = require('events');
17
17
  const { parseMessage } = require('./file-command-parser');
18
18
  const { executeSmartFileOp } = require('./smart-file-ops');
19
19
 
20
- // 텔레그램 API 동적 로드 (미설치 조용히 무시)
20
+ // Dynamically load Telegram Bot API (silently ignored if not installed)
21
21
  let TelegramBotAPI = null;
22
22
  try {
23
23
  TelegramBotAPI = require('node-telegram-bot-api');
24
24
  } catch {
25
- // node-telegram-bot-api 미설치텔레그램 기능 비활성화
25
+ // node-telegram-bot-api not installed Telegram features disabled
26
26
  }
27
27
 
28
28
  class TelegramBot extends EventEmitter {
29
29
  /**
30
- * @param {object} bridge - AIBridge 인스턴스
31
- * @param {object} options - 추가 옵션
32
- * - token: 토큰 (환경변수보다 우선)
33
- * - allowedChatIds: 허용된 채팅 ID 목록 (보안)
30
+ * @param {object} bridge - AIBridge instance
31
+ * @param {object} options - Additional options
32
+ * - token: Bot token (takes priority over env variable)
33
+ * - allowedChatIds: List of allowed chat IDs (security)
34
34
  */
35
35
  constructor(bridge, options = {}) {
36
36
  super();
@@ -38,24 +38,24 @@ class TelegramBot extends EventEmitter {
38
38
  this.bot = null;
39
39
  this.active = false;
40
40
  this.allowedChatIds = options.allowedChatIds || null;
41
- this.activeChatIds = new Set(); // 활성 채팅 ID 추적
41
+ this.activeChatIds = new Set(); // Track active chat IDs
42
42
 
43
- // 진행 중인 파일 작업 추적
43
+ // Track in-progress file operations
44
44
  this._fileOpInProgress = false;
45
45
 
46
- // 토큰 결정
46
+ // Determine bot token
47
47
  const token = options.token
48
48
  || process.env.CLAWMATE_TELEGRAM_TOKEN
49
49
  || null;
50
50
 
51
51
  if (!token) {
52
- console.log('[Telegram] 토큰 없음텔레그램 기능 비활성화');
52
+ console.log('[Telegram] No bot tokenTelegram features disabled');
53
53
  return;
54
54
  }
55
55
 
56
56
  if (!TelegramBotAPI) {
57
- console.log('[Telegram] node-telegram-bot-api 미설치텔레그램 기능 비활성화');
58
- console.log('[Telegram] 설치: npm install node-telegram-bot-api');
57
+ console.log('[Telegram] node-telegram-bot-api not installed Telegram features disabled');
58
+ console.log('[Telegram] Install: npm install node-telegram-bot-api');
59
59
  return;
60
60
  }
61
61
 
@@ -63,46 +63,46 @@ class TelegramBot extends EventEmitter {
63
63
  }
64
64
 
65
65
  /**
66
- * 초기화 메시지 리스너 등록
66
+ * Initialize bot and register message listeners
67
67
  */
68
68
  _init(token) {
69
69
  try {
70
70
  this.bot = new TelegramBotAPI(token, { polling: true });
71
71
  this.active = true;
72
- console.log('[Telegram] 초기화 성공 메시지 대기 ');
72
+ console.log('[Telegram] Bot initializedwaiting for messages');
73
73
 
74
- // 메시지 수신 핸들러
74
+ // Message receive handler
75
75
  this.bot.on('message', (msg) => this._handleMessage(msg));
76
76
 
77
- // 에러 핸들러 (연결 끊김 등)
77
+ // Error handler (disconnection, etc.)
78
78
  this.bot.on('polling_error', (err) => {
79
- // 토큰 오류 치명적 에러가 아니면 조용히 재시도
79
+ // Silently retry unless fatal error like invalid token
80
80
  if (err.code === 'ETELEGRAM' && err.response?.statusCode === 401) {
81
- console.error('[Telegram] 토큰이 유효하지 않음 텔레그램 비활성화');
81
+ console.error('[Telegram] Invalid bot tokenTelegram disabled');
82
82
  this.stop();
83
83
  }
84
84
  });
85
85
 
86
- // AI Bridge에서 이벤트 수신 → 텔레그램으로 전달
86
+ // Receive pet events from AI Bridge and forward to Telegram
87
87
  this._setupBridgeListeners();
88
88
  } catch (err) {
89
- console.error('[Telegram] 초기화 실패:', err.message);
89
+ console.error('[Telegram] Bot initialization failed:', err.message);
90
90
  this.active = false;
91
91
  }
92
92
  }
93
93
 
94
94
  /**
95
- * AI Bridge 이벤트 리스너 설정 ( 텔레그램)
95
+ * Set up AI Bridge event listeners (pet -> Telegram)
96
96
  */
97
97
  _setupBridgeListeners() {
98
98
  if (!this.bridge) return;
99
99
 
100
- // 펫이 말할 텔레그램으로 전달
100
+ // Forward pet speech to Telegram
101
101
  this.bridge.on('speak', (payload) => {
102
102
  this._broadcastToChats(`[Claw] ${payload.text}`);
103
103
  });
104
104
 
105
- // AI 의사결정에 speech 있으면 전달
105
+ // Forward speech from AI decisions
106
106
  this.bridge.on('ai_decision', (payload) => {
107
107
  if (payload.speech) {
108
108
  this._broadcastToChats(`[Claw] ${payload.speech}`);
@@ -111,7 +111,7 @@ class TelegramBot extends EventEmitter {
111
111
  }
112
112
 
113
113
  /**
114
- * 텔레그램 메시지 처리
114
+ * Handle incoming Telegram message
115
115
  */
116
116
  async _handleMessage(msg) {
117
117
  if (!this.active) return;
@@ -119,29 +119,29 @@ class TelegramBot extends EventEmitter {
119
119
 
120
120
  const chatId = msg.chat.id;
121
121
 
122
- // 보안: 허용된 채팅 ID만 처리
122
+ // Security: only process allowed chat IDs
123
123
  if (this.allowedChatIds && !this.allowedChatIds.includes(chatId)) {
124
124
  return;
125
125
  }
126
126
 
127
- // 활성 채팅 ID 추적 (역전달용)
127
+ // Track active chat IDs (for broadcasting)
128
128
  this.activeChatIds.add(chatId);
129
129
 
130
130
  const text = msg.text.trim();
131
- console.log(`[Telegram] 수신 (${chatId}): ${text}`);
131
+ console.log(`[Telegram] Received (${chatId}): ${text}`);
132
132
 
133
- // 특수 명령 처리
133
+ // Handle special commands
134
134
  if (text === '/start') {
135
135
  await this.bot.sendMessage(chatId,
136
- 'ClawMate 연결됨! \n\n' +
137
- '사용 가능한 명령:\n' +
138
- '- 아무 메시지: 펫에게 말하기\n' +
139
- '- 행동 키워드: 점프해, 잠자, 춤춰, 걸어...\n' +
140
- '- 파일 정리: "바탕화면의 .md 파일을 docs 폴더에 넣어줘"\n' +
141
- '- 캐릭터 변경: "파란 고양이로 바꿔줘"\n' +
142
- '- /reset: 원래 캐릭터로 되돌리기\n' +
143
- '- /status: 상태 확인\n' +
144
- '- /undo: 마지막 파일 이동 되돌리기'
136
+ 'ClawMate connected! \n\n' +
137
+ 'Available commands:\n' +
138
+ '- Any message: Talk to the pet\n' +
139
+ '- Action keywords: jump, sleep, dance, walk...\n' +
140
+ '- File organization: "Move .md files on desktop to docs folder"\n' +
141
+ '- Character change: "Change to blue cat"\n' +
142
+ '- /reset: Reset to default character\n' +
143
+ '- /status: Check pet status\n' +
144
+ '- /undo: Undo last file move'
145
145
  );
146
146
  return;
147
147
  }
@@ -158,22 +158,22 @@ class TelegramBot extends EventEmitter {
158
158
 
159
159
  if (text === '/reset') {
160
160
  this._sendToBridge('reset_character', {});
161
- await this.bot.sendMessage(chatId, '원래 캐릭터로 되돌렸어!');
161
+ await this.bot.sendMessage(chatId, 'Reset to default character!');
162
162
  return;
163
163
  }
164
164
 
165
- // 메시지 파싱 처리
165
+ // Parse and process message
166
166
  const parsed = parseMessage(text);
167
167
  await this._executeCommand(chatId, parsed);
168
168
  }
169
169
 
170
170
  /**
171
- * 파싱된 명령 실행
171
+ * Execute parsed command
172
172
  */
173
173
  async _executeCommand(chatId, command) {
174
174
  switch (command.type) {
175
175
  case 'speak':
176
- // 일반 대화 말풍선에 표시
176
+ // Normal conversation -> display in pet speech bubble
177
177
  this._sendToBridge('speak', { text: command.text, style: 'normal' });
178
178
  this._sendToBridge('ai_decision', {
179
179
  speech: command.text,
@@ -182,50 +182,50 @@ class TelegramBot extends EventEmitter {
182
182
  break;
183
183
 
184
184
  case 'action':
185
- // 행동 명령 행동 변경
185
+ // Action command -> change pet behavior
186
186
  this._sendToBridge('action', { state: command.action });
187
- await this.bot.sendMessage(chatId, `펫이 "${command.action}" 행동을 합니다!`);
187
+ await this.bot.sendMessage(chatId, `Pet is performing "${command.action}"!`);
188
188
  break;
189
189
 
190
190
  case 'smart_file_op':
191
- // 파일 조작 명령
191
+ // File operation command
192
192
  await this._executeFileOp(chatId, command);
193
193
  break;
194
194
 
195
195
  case 'character_change':
196
- // 캐릭터 변경 명령 AI 생성 요청
196
+ // Character change command -> AI generation request
197
197
  await this._handleCharacterChange(chatId, command.concept);
198
198
  break;
199
199
 
200
200
  case 'mode_change':
201
- // 모드 변경 명령
201
+ // Mode change command
202
202
  this._sendToBridge('set_mode', { mode: command.mode });
203
- const modeNames = { pet: 'Pet (Clawby)', incarnation: 'Incarnation (Claw)', both: '둘 다' };
204
- await this.bot.sendMessage(chatId, `모드 변경: ${modeNames[command.mode] || command.mode}`);
203
+ const modeNames = { pet: 'Pet (Clawby)', incarnation: 'Incarnation (Claw)', both: 'Both' };
204
+ await this.bot.sendMessage(chatId, `Mode changed: ${modeNames[command.mode] || command.mode}`);
205
205
  break;
206
206
 
207
207
  case 'preset_character': {
208
- // 캐릭터 프리셋 선택
208
+ // Character preset selection
209
209
  const presets = {
210
- default: { name: '기본 Claw', colorMap: { primary: '#ff4f40', secondary: '#ff775f', dark: '#8B4513', eye: '#ffffff', pupil: '#111111', claw: '#ff4f40' } },
211
- blue: { name: '파란 Claw', colorMap: { primary: '#4488ff', secondary: '#6699ff', dark: '#223388', eye: '#ffffff', pupil: '#111111', claw: '#4488ff' } },
212
- green: { name: '초록 Claw', colorMap: { primary: '#44cc44', secondary: '#66dd66', dark: '#226622', eye: '#ffffff', pupil: '#111111', claw: '#44cc44' } },
213
- purple: { name: '보라 Claw', colorMap: { primary: '#8844cc', secondary: '#aa66dd', dark: '#442266', eye: '#ffffff', pupil: '#111111', claw: '#8844cc' } },
214
- gold: { name: '골드 Claw', colorMap: { primary: '#ffcc00', secondary: '#ffdd44', dark: '#886600', eye: '#ffffff', pupil: '#111111', claw: '#ffcc00' } },
215
- pink: { name: '핑크 Claw', colorMap: { primary: '#ff69b4', secondary: '#ff8cc4', dark: '#8B3060', eye: '#ffffff', pupil: '#111111', claw: '#ff69b4' } },
216
- cat: { name: '고양이', colorMap: { primary: '#ff9944', secondary: '#ffbb66', dark: '#663300', eye: '#88ff88', pupil: '#111111', claw: '#ff9944' } },
217
- robot: { name: '로봇', colorMap: { primary: '#888888', secondary: '#aaaaaa', dark: '#444444', eye: '#66aaff', pupil: '#0044aa', claw: '#66aaff' } },
218
- ghost: { name: '유령', colorMap: { primary: '#ccccff', secondary: '#eeeeff', dark: '#6666aa', eye: '#ff6666', pupil: '#cc0000', claw: '#ccccff' } },
219
- dragon: { name: '드래곤', colorMap: { primary: '#cc2222', secondary: '#ff4444', dark: '#661111', eye: '#ffaa00', pupil: '#111111', claw: '#ffaa00' } },
210
+ default: { name: 'Default Claw', colorMap: { primary: '#ff4f40', secondary: '#ff775f', dark: '#8B4513', eye: '#ffffff', pupil: '#111111', claw: '#ff4f40' } },
211
+ blue: { name: 'Blue Claw', colorMap: { primary: '#4488ff', secondary: '#6699ff', dark: '#223388', eye: '#ffffff', pupil: '#111111', claw: '#4488ff' } },
212
+ green: { name: 'Green Claw', colorMap: { primary: '#44cc44', secondary: '#66dd66', dark: '#226622', eye: '#ffffff', pupil: '#111111', claw: '#44cc44' } },
213
+ purple: { name: 'Purple Claw', colorMap: { primary: '#8844cc', secondary: '#aa66dd', dark: '#442266', eye: '#ffffff', pupil: '#111111', claw: '#8844cc' } },
214
+ gold: { name: 'Gold Claw', colorMap: { primary: '#ffcc00', secondary: '#ffdd44', dark: '#886600', eye: '#ffffff', pupil: '#111111', claw: '#ffcc00' } },
215
+ pink: { name: 'Pink Claw', colorMap: { primary: '#ff69b4', secondary: '#ff8cc4', dark: '#8B3060', eye: '#ffffff', pupil: '#111111', claw: '#ff69b4' } },
216
+ cat: { name: 'Cat', colorMap: { primary: '#ff9944', secondary: '#ffbb66', dark: '#663300', eye: '#88ff88', pupil: '#111111', claw: '#ff9944' } },
217
+ robot: { name: 'Robot', colorMap: { primary: '#888888', secondary: '#aaaaaa', dark: '#444444', eye: '#66aaff', pupil: '#0044aa', claw: '#66aaff' } },
218
+ ghost: { name: 'Ghost', colorMap: { primary: '#ccccff', secondary: '#eeeeff', dark: '#6666aa', eye: '#ff6666', pupil: '#cc0000', claw: '#ccccff' } },
219
+ dragon: { name: 'Dragon', colorMap: { primary: '#cc2222', secondary: '#ff4444', dark: '#661111', eye: '#ffaa00', pupil: '#111111', claw: '#ffaa00' } },
220
220
  };
221
221
  const preset = presets[command.preset];
222
222
  if (preset) {
223
223
  if (command.preset === 'default') {
224
224
  this._sendToBridge('reset_character', {});
225
225
  } else {
226
- this._sendToBridge('set_character', { colorMap: preset.colorMap, speech: `${preset.name}(으)로 변신!` });
226
+ this._sendToBridge('set_character', { colorMap: preset.colorMap, speech: `Transforming into ${preset.name}!` });
227
227
  }
228
- await this.bot.sendMessage(chatId, `캐릭터 변경: ${preset.name}`);
228
+ await this.bot.sendMessage(chatId, `Character changed: ${preset.name}`);
229
229
  }
230
230
  break;
231
231
  }
@@ -233,24 +233,24 @@ class TelegramBot extends EventEmitter {
233
233
  }
234
234
 
235
235
  /**
236
- * 캐릭터 변경 요청 처리
236
+ * Handle character change request
237
237
  *
238
- * 컨셉 텍스트를 AI 전달하여
239
- * 색상 + 프레임 데이터를 생성하고 펫에 적용.
238
+ * Passes concept text to AI to generate
239
+ * color + frame data and apply to pet.
240
240
  *
241
- * AI가 없으면 컨셉에서 색상만 추출하여 기본 변환.
241
+ * Falls back to extracting colors from concept if AI is unavailable.
242
242
  */
243
243
  async _handleCharacterChange(chatId, concept) {
244
- await this.bot.sendMessage(chatId, `"${concept}" 캐릭터 생성 중...`);
244
+ await this.bot.sendMessage(chatId, `Creating "${concept}" character...`);
245
245
 
246
- // AI Bridge를 통해 AI 캐릭터 생성 요청
246
+ // Request character generation from AI via AI Bridge
247
247
  this._sendToBridge('ai_decision', {
248
- speech: `${concept}(으)로 변신 준비 중...`,
248
+ speech: `Preparing to transform into ${concept}...`,
249
249
  emotion: 'curious',
250
250
  action: 'excited',
251
251
  });
252
252
 
253
- // user_event로 캐릭터 변경 요청 전달 (AI 생성)
253
+ // Forward character change request via user_event (AI generates it)
254
254
  if (this.bridge) {
255
255
  this.bridge.send('user_event', {
256
256
  event: 'character_request',
@@ -259,25 +259,25 @@ class TelegramBot extends EventEmitter {
259
259
  });
260
260
  }
261
261
 
262
- // 폴백: AI 응답이 3초 내에 없으면 키워드 기반 색상 변환
262
+ // Fallback: keyword-based color conversion if no AI response within 3 seconds
263
263
  this._characterFallbackTimer = setTimeout(() => {
264
264
  const colorMap = this._extractColorsFromConcept(concept);
265
265
  if (colorMap) {
266
266
  this._sendToBridge('set_character', {
267
267
  colorMap,
268
- speech: `${concept} 변신!`,
268
+ speech: `Transformed into ${concept}!`,
269
269
  });
270
- this.bot.sendMessage(chatId, `"${concept}" 캐릭터로 바꿨어! (색상 기반)`);
270
+ this.bot.sendMessage(chatId, `Changed to "${concept}" character! (color-based)`);
271
271
  }
272
272
  }, 3000);
273
273
 
274
- // AI가 캐릭터를 생성하면 타이머를 취소
274
+ // Cancel this timer when AI generates the character
275
275
  this._pendingCharacterChatId = chatId;
276
276
  }
277
277
 
278
278
  /**
279
- * AI가 캐릭터 생성을 완료했을 호출
280
- * 폴백 타이머를 취소하고 텔레그램에 알림
279
+ * Called when AI completes character generation
280
+ * Cancels fallback timer and notifies Telegram
281
281
  */
282
282
  onCharacterGenerated(concept) {
283
283
  if (this._characterFallbackTimer) {
@@ -286,52 +286,52 @@ class TelegramBot extends EventEmitter {
286
286
  }
287
287
  if (this._pendingCharacterChatId) {
288
288
  this.bot?.sendMessage(this._pendingCharacterChatId,
289
- `"${concept}" 캐릭터 생성 완료! AI가 만든 커스텀 캐릭터야!`);
289
+ `"${concept}" character created! Custom character generated by AI!`);
290
290
  this._pendingCharacterChatId = null;
291
291
  }
292
292
  }
293
293
 
294
294
  /**
295
- * 컨셉 텍스트에서 색상 추출 (AI 없을 폴백)
296
- * 키워드 매칭으로 색상 팔레트 결정
295
+ * Extract colors from concept text (fallback when AI unavailable)
296
+ * Determines color palette via keyword matching
297
297
  */
298
298
  _extractColorsFromConcept(concept) {
299
299
  const c = concept.toLowerCase();
300
300
 
301
- // 색상 키워드 팔레트 매핑
301
+ // Color keyword -> palette mapping
302
302
  const colorKeywords = {
303
- // 파란 계열
303
+ // Blue family
304
304
  '파란': { primary: '#4488ff', secondary: '#6699ff', dark: '#223388', claw: '#4488ff' },
305
305
  '파랑': { primary: '#4488ff', secondary: '#6699ff', dark: '#223388', claw: '#4488ff' },
306
306
  'blue': { primary: '#4488ff', secondary: '#6699ff', dark: '#223388', claw: '#4488ff' },
307
- // 초록 계열
307
+ // Green family
308
308
  '초록': { primary: '#44cc44', secondary: '#66dd66', dark: '#226622', claw: '#44cc44' },
309
309
  '녹색': { primary: '#44cc44', secondary: '#66dd66', dark: '#226622', claw: '#44cc44' },
310
310
  'green': { primary: '#44cc44', secondary: '#66dd66', dark: '#226622', claw: '#44cc44' },
311
- // 보라 계열
311
+ // Purple family
312
312
  '보라': { primary: '#8844cc', secondary: '#aa66dd', dark: '#442266', claw: '#8844cc' },
313
313
  'purple': { primary: '#8844cc', secondary: '#aa66dd', dark: '#442266', claw: '#8844cc' },
314
- // 노란 계열
314
+ // Yellow family
315
315
  '노란': { primary: '#ffcc00', secondary: '#ffdd44', dark: '#886600', claw: '#ffcc00' },
316
316
  '금색': { primary: '#ffd700', secondary: '#ffe44d', dark: '#8B7500', claw: '#ffd700' },
317
317
  'yellow': { primary: '#ffcc00', secondary: '#ffdd44', dark: '#886600', claw: '#ffcc00' },
318
318
  'gold': { primary: '#ffd700', secondary: '#ffe44d', dark: '#8B7500', claw: '#ffd700' },
319
- // 분홍 계열
319
+ // Pink family
320
320
  '분홍': { primary: '#ff69b4', secondary: '#ff8cc4', dark: '#8B3060', claw: '#ff69b4' },
321
321
  '핑크': { primary: '#ff69b4', secondary: '#ff8cc4', dark: '#8B3060', claw: '#ff69b4' },
322
322
  'pink': { primary: '#ff69b4', secondary: '#ff8cc4', dark: '#8B3060', claw: '#ff69b4' },
323
- // 하얀 계열
323
+ // White family
324
324
  '하얀': { primary: '#eeeeee', secondary: '#ffffff', dark: '#999999', claw: '#dddddd' },
325
325
  '흰': { primary: '#eeeeee', secondary: '#ffffff', dark: '#999999', claw: '#dddddd' },
326
326
  'white': { primary: '#eeeeee', secondary: '#ffffff', dark: '#999999', claw: '#dddddd' },
327
- // 검정 계열
327
+ // Black family
328
328
  '검정': { primary: '#333333', secondary: '#555555', dark: '#111111', claw: '#444444' },
329
329
  '까만': { primary: '#333333', secondary: '#555555', dark: '#111111', claw: '#444444' },
330
330
  'black': { primary: '#333333', secondary: '#555555', dark: '#111111', claw: '#444444' },
331
- // 주황 계열
331
+ // Orange family
332
332
  '주황': { primary: '#ff8800', secondary: '#ffaa33', dark: '#884400', claw: '#ff8800' },
333
333
  'orange': { primary: '#ff8800', secondary: '#ffaa33', dark: '#884400', claw: '#ff8800' },
334
- // 틸/민트 계열
334
+ // Teal/Mint family
335
335
  '민트': { primary: '#00BFA5', secondary: '#33D4BC', dark: '#006655', claw: '#00BFA5' },
336
336
  '틸': { primary: '#00BFA5', secondary: '#33D4BC', dark: '#006655', claw: '#00BFA5' },
337
337
  'teal': { primary: '#00BFA5', secondary: '#33D4BC', dark: '#006655', claw: '#00BFA5' },
@@ -347,7 +347,7 @@ class TelegramBot extends EventEmitter {
347
347
  }
348
348
  }
349
349
 
350
- // 생물 키워드 특징적 색상
350
+ // Creature keywords -> characteristic colors
351
351
  const creatureColors = {
352
352
  '고양이': { primary: '#ff9944', secondary: '#ffbb66', dark: '#663300', claw: '#ff9944' },
353
353
  'cat': { primary: '#ff9944', secondary: '#ffbb66', dark: '#663300', claw: '#ff9944' },
@@ -381,7 +381,7 @@ class TelegramBot extends EventEmitter {
381
381
  }
382
382
  }
383
383
 
384
- // 매칭 되면 랜덤 색상
384
+ // Random color if no match
385
385
  const hue = Math.floor(Math.random() * 360);
386
386
  return {
387
387
  primary: `hsl(${hue}, 70%, 55%)`,
@@ -394,11 +394,11 @@ class TelegramBot extends EventEmitter {
394
394
  }
395
395
 
396
396
  /**
397
- * 스마트 파일 조작 실행 + 애니메이션 + 텔레그램 피드백
397
+ * Execute smart file operation + pet animation + Telegram feedback
398
398
  */
399
399
  async _executeFileOp(chatId, command) {
400
400
  if (this._fileOpInProgress) {
401
- await this.bot.sendMessage(chatId, '이미 파일 작업이 진행 중이야! 잠시만 기다려줘.');
401
+ await this.bot.sendMessage(chatId, 'A file operation is already in progress! Please wait.');
402
402
  return;
403
403
  }
404
404
 
@@ -406,16 +406,16 @@ class TelegramBot extends EventEmitter {
406
406
 
407
407
  const callbacks = {
408
408
  onStart: (totalFiles) => {
409
- this.bot.sendMessage(chatId, `${totalFiles} 파일을 발견했어! 나르기 시작할게~`);
409
+ this.bot.sendMessage(chatId, `Found ${totalFiles} files! Starting to carry them~`);
410
410
  this._sendToBridge('ai_decision', {
411
411
  action: 'excited',
412
- speech: `${totalFiles} 파일 정리 시작!`,
412
+ speech: `Starting to organize ${totalFiles} files!`,
413
413
  emotion: 'happy',
414
414
  });
415
415
  },
416
416
 
417
417
  onPickUp: (fileName, index) => {
418
- // 펫이 파일을 집어드는 애니메이션
418
+ // Pet picks up file animation
419
419
  this._sendToBridge('smart_file_op', {
420
420
  phase: 'pick_up',
421
421
  fileName,
@@ -423,13 +423,13 @@ class TelegramBot extends EventEmitter {
423
423
  });
424
424
  this._sendToBridge('ai_decision', {
425
425
  action: 'carrying',
426
- speech: `${fileName} 집었다!`,
426
+ speech: `Picked up ${fileName}!`,
427
427
  emotion: 'focused',
428
428
  });
429
429
  },
430
430
 
431
431
  onDrop: (fileName, targetName, index) => {
432
- // 펫이 파일을 내려놓는 애니메이션
432
+ // Pet drops file animation
433
433
  this._sendToBridge('smart_file_op', {
434
434
  phase: 'drop',
435
435
  fileName,
@@ -438,7 +438,7 @@ class TelegramBot extends EventEmitter {
438
438
  });
439
439
  this._sendToBridge('ai_decision', {
440
440
  action: 'walking',
441
- speech: `${fileName} ${targetName}에 놓았다!`,
441
+ speech: `Placed ${fileName} in ${targetName}!`,
442
442
  emotion: 'happy',
443
443
  });
444
444
  },
@@ -448,11 +448,11 @@ class TelegramBot extends EventEmitter {
448
448
 
449
449
  let message;
450
450
  if (result.movedCount === 0) {
451
- message = '옮길 파일이 없었어!';
451
+ message = 'No files to move!';
452
452
  } else {
453
- message = `${result.movedCount} 파일 옮겼어!`;
453
+ message = `Moved ${result.movedCount} files!`;
454
454
  if (result.errors.length > 0) {
455
- message += `\n(${result.errors.length} 실패)`;
455
+ message += `\n(${result.errors.length} failed)`;
456
456
  }
457
457
  }
458
458
 
@@ -463,7 +463,7 @@ class TelegramBot extends EventEmitter {
463
463
  emotion: 'proud',
464
464
  });
465
465
 
466
- // smart_file_op 완료 이벤트
466
+ // smart_file_op completion event
467
467
  this._sendToBridge('smart_file_op', {
468
468
  phase: 'complete',
469
469
  movedCount: result.movedCount,
@@ -473,10 +473,10 @@ class TelegramBot extends EventEmitter {
473
473
 
474
474
  onError: (error) => {
475
475
  this._fileOpInProgress = false;
476
- this.bot.sendMessage(chatId, `파일 작업 오류 발생: ${error}`);
476
+ this.bot.sendMessage(chatId, `Error during file operation: ${error}`);
477
477
  this._sendToBridge('ai_decision', {
478
478
  action: 'scared',
479
- speech: '앗, 뭔가 잘못됐어...',
479
+ speech: 'Oops, something went wrong...',
480
480
  emotion: 'scared',
481
481
  });
482
482
  },
@@ -486,28 +486,28 @@ class TelegramBot extends EventEmitter {
486
486
  }
487
487
 
488
488
  /**
489
- * 상태 조회 텔레그램으로 전송
489
+ * Query pet status and send to Telegram
490
490
  */
491
491
  async _sendStatus(chatId) {
492
492
  if (!this.bridge) {
493
- await this.bot.sendMessage(chatId, 'AI Bridge에 연결되지 않았어.');
493
+ await this.bot.sendMessage(chatId, 'Not connected to AI Bridge.');
494
494
  return;
495
495
  }
496
496
 
497
497
  const state = this.bridge.petState;
498
498
  const statusText =
499
- `상태: ${state.state}\n` +
500
- `위치: (${state.position.x}, ${state.position.y})\n` +
501
- `모드: ${state.mode}\n` +
502
- `감정: ${state.emotion}\n` +
503
- `진화: ${state.evolutionStage}단계\n` +
504
- `AI 연결: ${this.bridge.isConnected() ? 'O' : 'X'}`;
499
+ `State: ${state.state}\n` +
500
+ `Position: (${state.position.x}, ${state.position.y})\n` +
501
+ `Mode: ${state.mode}\n` +
502
+ `Emotion: ${state.emotion}\n` +
503
+ `Evolution: Stage ${state.evolutionStage}\n` +
504
+ `AI Connected: ${this.bridge.isConnected() ? 'Yes' : 'No'}`;
505
505
 
506
506
  await this.bot.sendMessage(chatId, statusText);
507
507
  }
508
508
 
509
509
  /**
510
- * 마지막 파일 이동 되돌리기
510
+ * Undo last file move
511
511
  */
512
512
  async _undoLastMove(chatId) {
513
513
  try {
@@ -515,75 +515,75 @@ class TelegramBot extends EventEmitter {
515
515
  const result = undoAllSmartMoves();
516
516
 
517
517
  if (result.restoredCount === 0) {
518
- await this.bot.sendMessage(chatId, '되돌릴 파일 이동이 없어.');
518
+ await this.bot.sendMessage(chatId, 'No file moves to undo.');
519
519
  } else {
520
- let message = `${result.restoredCount} 파일을 원래 위치로 되돌렸어!`;
520
+ let message = `Restored ${result.restoredCount} files to original locations!`;
521
521
  if (result.errors.length > 0) {
522
- message += `\n(${result.errors.length} 복원 실패)`;
522
+ message += `\n(${result.errors.length} restore failures)`;
523
523
  }
524
524
  await this.bot.sendMessage(chatId, message);
525
525
  this._sendToBridge('ai_decision', {
526
526
  action: 'walking',
527
- speech: '파일들을 원래대로 돌려놨어!',
527
+ speech: 'Restored files to their original locations!',
528
528
  emotion: 'happy',
529
529
  });
530
530
  }
531
531
  } catch (err) {
532
- await this.bot.sendMessage(chatId, `되돌리기 실패: ${err.message}`);
532
+ await this.bot.sendMessage(chatId, `Undo failed: ${err.message}`);
533
533
  }
534
534
  }
535
535
 
536
536
  /**
537
- * AI Bridge에 명령 전달
537
+ * Forward command to AI Bridge
538
538
  */
539
539
  _sendToBridge(type, payload) {
540
540
  if (!this.bridge) return;
541
541
 
542
- // bridge _handleCommand 직접 호출 (내부 이벤트 방출)
543
- // telegram에서 명령임을 표시
542
+ // Directly invoke bridge's _handleCommand (emit internal event)
543
+ // Mark as command from Telegram
544
544
  payload._fromTelegram = true;
545
545
  this.bridge.emit(type, payload);
546
546
  }
547
547
 
548
548
  /**
549
- * 모든 활성 채팅에 메시지 브로드캐스트
549
+ * Broadcast message to all active chats
550
550
  */
551
551
  _broadcastToChats(text) {
552
552
  if (!this.bot || !this.active) return;
553
553
 
554
554
  for (const chatId of this.activeChatIds) {
555
555
  this.bot.sendMessage(chatId, text).catch(() => {
556
- // 전송 실패 해당 채팅 ID 제거
556
+ // Remove chat ID on send failure
557
557
  this.activeChatIds.delete(chatId);
558
558
  });
559
559
  }
560
560
  }
561
561
 
562
562
  /**
563
- * 특정 채팅에 메시지 전송
563
+ * Send message to specific chat
564
564
  */
565
565
  async sendMessage(chatId, text) {
566
566
  if (!this.bot || !this.active) return;
567
567
  try {
568
568
  await this.bot.sendMessage(chatId, text);
569
569
  } catch (err) {
570
- console.error('[Telegram] 메시지 전송 실패:', err.message);
570
+ console.error('[Telegram] Message send failed:', err.message);
571
571
  }
572
572
  }
573
573
 
574
574
  /**
575
- * 중지
575
+ * Stop bot
576
576
  */
577
577
  stop() {
578
578
  if (this.bot && this.active) {
579
579
  this.bot.stopPolling();
580
580
  this.active = false;
581
- console.log('[Telegram] 중지');
581
+ console.log('[Telegram] Bot stopped');
582
582
  }
583
583
  }
584
584
 
585
585
  /**
586
- * 활성 상태 확인
586
+ * Check if bot is active
587
587
  */
588
588
  isActive() {
589
589
  return this.active;