clawmate 1.3.0 → 1.4.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.
@@ -93,9 +93,9 @@ function registerIpcHandlers(getMainWindow, getAIBridge) {
93
93
  }
94
94
  });
95
95
 
96
- // === OpenClaw AI 통신 ===
96
+ // === AI 통신 ===
97
97
 
98
- // 사용자 이벤트를 AI Bridge로 전달 (렌더러 → main → OpenClaw)
98
+ // 사용자 이벤트를 AI Bridge로 전달 (렌더러 → main → AI)
99
99
  ipcMain.on('report-to-ai', (_, event, data) => {
100
100
  const bridge = getAIBridge();
101
101
  if (bridge && bridge.isConnected()) {
@@ -109,6 +109,9 @@ function registerIpcHandlers(getMainWindow, getAIBridge) {
109
109
  case 'cursor_near':
110
110
  bridge.reportCursorNear(data.distance, data.cursorPos);
111
111
  break;
112
+ case 'double_click':
113
+ bridge.send('user_event', { event: 'double_click', ...data });
114
+ break;
112
115
  case 'desktop_changed':
113
116
  bridge.reportDesktopChange(data.files);
114
117
  break;
@@ -139,7 +142,7 @@ function registerIpcHandlers(getMainWindow, getAIBridge) {
139
142
  return bridge ? bridge.isConnected() : false;
140
143
  });
141
144
 
142
- // 메트릭 보고 (렌더러 → main → OpenClaw)
145
+ // 메트릭 보고 (렌더러 → main → AI)
143
146
  ipcMain.on('report-metrics', (_, summary) => {
144
147
  const bridge = getAIBridge();
145
148
  if (bridge && bridge.isConnected()) {
package/main/telegram.js CHANGED
@@ -196,13 +196,46 @@ class TelegramBot extends EventEmitter {
196
196
  // 캐릭터 변경 명령 → AI 생성 요청
197
197
  await this._handleCharacterChange(chatId, command.concept);
198
198
  break;
199
+
200
+ case 'mode_change':
201
+ // 모드 변경 명령
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}`);
205
+ break;
206
+
207
+ case 'preset_character': {
208
+ // 캐릭터 프리셋 선택
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' } },
220
+ };
221
+ const preset = presets[command.preset];
222
+ if (preset) {
223
+ if (command.preset === 'default') {
224
+ this._sendToBridge('reset_character', {});
225
+ } else {
226
+ this._sendToBridge('set_character', { colorMap: preset.colorMap, speech: `${preset.name}(으)로 변신!` });
227
+ }
228
+ await this.bot.sendMessage(chatId, `캐릭터 변경: ${preset.name}`);
229
+ }
230
+ break;
231
+ }
199
232
  }
200
233
  }
201
234
 
202
235
  /**
203
236
  * 캐릭터 변경 요청 처리
204
237
  *
205
- * 컨셉 텍스트를 AI(OpenClaw 플러그인)에 전달하여
238
+ * 컨셉 텍스트를 AI에 전달하여
206
239
  * 색상 + 프레임 데이터를 생성하고 펫에 적용.
207
240
  *
208
241
  * AI가 없으면 컨셉에서 색상만 추출하여 기본 변환.
@@ -210,14 +243,14 @@ class TelegramBot extends EventEmitter {
210
243
  async _handleCharacterChange(chatId, concept) {
211
244
  await this.bot.sendMessage(chatId, `"${concept}" 캐릭터 생성 중...`);
212
245
 
213
- // AI Bridge를 통해 OpenClaw 플러그인에 캐릭터 생성 요청
246
+ // AI Bridge를 통해 AI에 캐릭터 생성 요청
214
247
  this._sendToBridge('ai_decision', {
215
248
  speech: `${concept}(으)로 변신 준비 중...`,
216
249
  emotion: 'curious',
217
250
  action: 'excited',
218
251
  });
219
252
 
220
- // user_event로 캐릭터 변경 요청 전달 (OpenClaw 플러그인이 AI 생성)
253
+ // user_event로 캐릭터 변경 요청 전달 (AI 생성)
221
254
  if (this.bridge) {
222
255
  this.bridge.send('user_event', {
223
256
  event: 'character_request',
package/main/tray.js CHANGED
@@ -1,4 +1,4 @@
1
- const { Tray, Menu, nativeImage, app, shell } = require('electron');
1
+ const { Tray, Menu, nativeImage, app, shell, dialog, clipboard } = require('electron');
2
2
  const path = require('path');
3
3
  const { execSync } = require('child_process');
4
4
  const Store = require('./store');
@@ -10,16 +10,6 @@ let aiBridge = null;
10
10
 
11
11
  /**
12
12
  * 16x16 Claw 픽셀아트 아이콘 생성
13
- * 캐릭터 idle 프레임을 축소한 형태
14
- *
15
- * 색상 코드:
16
- * 0 = 투명
17
- * 1 = #ff4f40 (빨강)
18
- * 2 = #ff775f (연빨강)
19
- * 3 = #3a0a0d (갈색 다리)
20
- * 4 = #ffffff (눈 흰자)
21
- * 5 = #000000 (눈동자)
22
- * 6 = #ff4f40 (집게)
23
13
  */
24
14
  const CLAW_ICON = [
25
15
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
@@ -41,44 +31,88 @@ const CLAW_ICON = [
41
31
  ];
42
32
 
43
33
  const COLOR_MAP = {
44
- 0: [0, 0, 0, 0], // 투명
45
- 1: [255, 79, 64, 255], // primary 빨강
46
- 2: [255, 119, 95, 255], // secondary 연빨강
47
- 3: [58, 10, 13, 255], // dark 갈색
48
- 4: [255, 255, 255, 255], // eye 흰자
49
- 5: [0, 0, 0, 255], // pupil 눈동자
50
- 6: [255, 79, 64, 255], // claw 집게
34
+ 0: [0, 0, 0, 0],
35
+ 1: [255, 79, 64, 255],
36
+ 2: [255, 119, 95, 255],
37
+ 3: [58, 10, 13, 255],
38
+ 4: [255, 255, 255, 255],
39
+ 5: [0, 0, 0, 255],
40
+ 6: [255, 79, 64, 255],
51
41
  };
52
42
 
53
43
  /**
54
- * CLAW_ICON 16x16 배열 → nativeImage 변환
44
+ * 캐릭터 프리셋 목록
45
+ * 트레이에서 선택하면 set_character 명령으로 렌더러에 전달
55
46
  */
47
+ const CHARACTER_PRESETS = {
48
+ default: {
49
+ name: '기본 Claw (빨강)',
50
+ colorMap: { primary: '#ff4f40', secondary: '#ff775f', dark: '#8B4513', eye: '#ffffff', pupil: '#111111', claw: '#ff4f40' },
51
+ },
52
+ blue: {
53
+ name: '파란 Claw',
54
+ colorMap: { primary: '#4488ff', secondary: '#6699ff', dark: '#223388', eye: '#ffffff', pupil: '#111111', claw: '#4488ff' },
55
+ },
56
+ green: {
57
+ name: '초록 Claw',
58
+ colorMap: { primary: '#44cc44', secondary: '#66dd66', dark: '#226622', eye: '#ffffff', pupil: '#111111', claw: '#44cc44' },
59
+ },
60
+ purple: {
61
+ name: '보라 Claw',
62
+ colorMap: { primary: '#8844cc', secondary: '#aa66dd', dark: '#442266', eye: '#ffffff', pupil: '#111111', claw: '#8844cc' },
63
+ },
64
+ gold: {
65
+ name: '골드 Claw',
66
+ colorMap: { primary: '#ffcc00', secondary: '#ffdd44', dark: '#886600', eye: '#ffffff', pupil: '#111111', claw: '#ffcc00' },
67
+ },
68
+ pink: {
69
+ name: '핑크 Claw',
70
+ colorMap: { primary: '#ff69b4', secondary: '#ff8cc4', dark: '#8B3060', eye: '#ffffff', pupil: '#111111', claw: '#ff69b4' },
71
+ },
72
+ cat: {
73
+ name: '고양이',
74
+ colorMap: { primary: '#ff9944', secondary: '#ffbb66', dark: '#663300', eye: '#88ff88', pupil: '#111111', claw: '#ff9944' },
75
+ },
76
+ robot: {
77
+ name: '로봇',
78
+ colorMap: { primary: '#888888', secondary: '#aaaaaa', dark: '#444444', eye: '#66aaff', pupil: '#0044aa', claw: '#66aaff' },
79
+ },
80
+ ghost: {
81
+ name: '유령',
82
+ colorMap: { primary: '#ccccff', secondary: '#eeeeff', dark: '#6666aa', eye: '#ff6666', pupil: '#cc0000', claw: '#ccccff' },
83
+ },
84
+ dragon: {
85
+ name: '드래곤',
86
+ colorMap: { primary: '#cc2222', secondary: '#ff4444', dark: '#661111', eye: '#ffaa00', pupil: '#111111', claw: '#ffaa00' },
87
+ },
88
+ };
89
+
56
90
  function createClawIcon() {
57
91
  const size = 16;
58
92
  const buffer = Buffer.alloc(size * size * 4);
59
-
60
93
  for (let y = 0; y < size; y++) {
61
94
  for (let x = 0; x < size; x++) {
62
95
  const code = CLAW_ICON[y][x];
63
96
  const color = COLOR_MAP[code] || COLOR_MAP[0];
64
97
  const offset = (y * size + x) * 4;
65
- buffer[offset + 0] = color[0]; // R
66
- buffer[offset + 1] = color[1]; // G
67
- buffer[offset + 2] = color[2]; // B
68
- buffer[offset + 3] = color[3]; // A
98
+ buffer[offset + 0] = color[0];
99
+ buffer[offset + 1] = color[1];
100
+ buffer[offset + 2] = color[2];
101
+ buffer[offset + 3] = color[3];
69
102
  }
70
103
  }
71
-
72
104
  return nativeImage.createFromBuffer(buffer, { width: size, height: size });
73
105
  }
74
106
 
75
107
  function setupTray(mainWindow, bridge) {
76
108
  aiBridge = bridge;
77
- const store = new Store('clawmate-config', { mode: 'pet' });
109
+ const store = new Store('clawmate-config', {
110
+ mode: 'pet',
111
+ character: 'default',
112
+ telegramToken: '',
113
+ });
78
114
 
79
- // Claw 픽셀아트 트레이 아이콘 생성
80
115
  const icon = createClawIcon();
81
-
82
116
  tray = new Tray(icon);
83
117
  tray.setToolTip('ClawMate - 데스크톱 펫');
84
118
 
@@ -87,38 +121,94 @@ function setupTray(mainWindow, bridge) {
87
121
  const fileInteraction = store.get('fileInteraction') !== false;
88
122
  const aiConnected = aiBridge ? aiBridge.isConnected() : false;
89
123
  const autoStart = isAutoStartEnabled();
124
+ const currentChar = store.get('character') || 'default';
125
+ const hasTelegramToken = !!(store.get('telegramToken'));
126
+
127
+ // 캐릭터 서브메뉴
128
+ const characterSubmenu = Object.entries(CHARACTER_PRESETS).map(([key, preset]) => ({
129
+ label: preset.name,
130
+ type: 'radio',
131
+ checked: currentChar === key,
132
+ click: () => {
133
+ store.set('character', key);
134
+ if (mainWindow && !mainWindow.isDestroyed()) {
135
+ if (key === 'default') {
136
+ mainWindow.webContents.send('ai-command', {
137
+ type: 'reset_character', payload: {},
138
+ });
139
+ } else {
140
+ mainWindow.webContents.send('ai-command', {
141
+ type: 'set_character', payload: {
142
+ colorMap: preset.colorMap,
143
+ speech: `${preset.name}(으)로 변신!`,
144
+ },
145
+ });
146
+ }
147
+ }
148
+ buildAndSet();
149
+ },
150
+ }));
90
151
 
91
152
  return Menu.buildFromTemplate([
92
153
  {
93
- label: `ClawMate (${mode === 'pet' ? 'Clawby' : 'OpenClaw'})`,
154
+ label: `ClawMate (${mode === 'pet' ? 'Clawby' : mode === 'incarnation' ? 'Claw' : 'Clawby + Claw'})`,
94
155
  enabled: false,
95
156
  },
96
157
  {
97
- label: aiConnected ? 'AI: 연결됨' : 'AI: 자율 모드 (대기 중)',
158
+ label: aiConnected ? 'AI: 연결됨' : 'AI: 자율 모드',
98
159
  enabled: false,
99
160
  },
100
161
  { type: 'separator' },
162
+
163
+ // === 모드 선택 ===
101
164
  {
102
- label: 'Pet 모드 (Clawby)',
103
- type: 'radio',
104
- checked: mode === 'pet',
105
- click: () => {
106
- store.set('mode', 'pet');
107
- if (mainWindow) mainWindow.webContents.send('mode-changed', 'pet');
108
- buildAndSet();
109
- },
165
+ label: '모드',
166
+ submenu: [
167
+ {
168
+ label: 'Pet 모드 (Clawby)',
169
+ sublabel: '귀여운 펫을 키우기',
170
+ type: 'radio',
171
+ checked: mode === 'pet',
172
+ click: () => {
173
+ store.set('mode', 'pet');
174
+ if (mainWindow) mainWindow.webContents.send('mode-changed', 'pet');
175
+ buildAndSet();
176
+ },
177
+ },
178
+ {
179
+ label: 'Incarnation 모드 (Claw)',
180
+ sublabel: '봇이 육체를 얻음',
181
+ type: 'radio',
182
+ checked: mode === 'incarnation',
183
+ click: () => {
184
+ store.set('mode', 'incarnation');
185
+ if (mainWindow) mainWindow.webContents.send('mode-changed', 'incarnation');
186
+ buildAndSet();
187
+ },
188
+ },
189
+ {
190
+ label: '둘 다 (Pet + Incarnation)',
191
+ sublabel: '펫도 키우고, 봇 인격도 반영',
192
+ type: 'radio',
193
+ checked: mode === 'both',
194
+ click: () => {
195
+ store.set('mode', 'both');
196
+ if (mainWindow) mainWindow.webContents.send('mode-changed', 'both');
197
+ buildAndSet();
198
+ },
199
+ },
200
+ ],
110
201
  },
202
+
203
+ // === 캐릭터 선택 ===
111
204
  {
112
- label: 'Incarnation 모드 (OpenClaw)',
113
- type: 'radio',
114
- checked: mode === 'incarnation',
115
- click: () => {
116
- store.set('mode', 'incarnation');
117
- if (mainWindow) mainWindow.webContents.send('mode-changed', 'incarnation');
118
- buildAndSet();
119
- },
205
+ label: '캐릭터',
206
+ submenu: characterSubmenu,
120
207
  },
208
+
121
209
  { type: 'separator' },
210
+
211
+ // === 설정 ===
122
212
  {
123
213
  label: '파일 상호작용',
124
214
  type: 'checkbox',
@@ -137,7 +227,83 @@ function setupTray(mainWindow, bridge) {
137
227
  buildAndSet();
138
228
  },
139
229
  },
230
+
140
231
  { type: 'separator' },
232
+
233
+ // === 텔레그램 봇 ===
234
+ {
235
+ label: '텔레그램 봇',
236
+ submenu: [
237
+ {
238
+ label: hasTelegramToken ? '봇 토큰: 설정됨' : '봇 토큰: 미설정',
239
+ enabled: false,
240
+ },
241
+ {
242
+ label: '봇 토큰 설정...',
243
+ click: async () => {
244
+ const result = await dialog.showMessageBox({
245
+ type: 'question',
246
+ buttons: ['클립보드에서 붙여넣기', '직접 입력', '취소'],
247
+ title: 'ClawMate 텔레그램 봇',
248
+ message: '텔레그램 봇 토큰을 설정합니다.',
249
+ detail: '@BotFather에서 받은 봇 토큰을 입력하세요.\n현재 클립보드 내용을 붙여넣으려면 "클립보드에서 붙여넣기"를 선택하세요.',
250
+ });
251
+
252
+ let token = null;
253
+ if (result.response === 0) {
254
+ // 클립보드에서 붙여넣기
255
+ token = clipboard.readText().trim();
256
+ } else if (result.response === 1) {
257
+ // prompt가 없으므로 클립보드 안내
258
+ const promptResult = await dialog.showMessageBox({
259
+ type: 'info',
260
+ buttons: ['확인'],
261
+ title: '텔레그램 봇 토큰',
262
+ message: '봇 토큰을 클립보드에 복사한 후 다시 "봇 토큰 설정"을 선택하세요.',
263
+ detail: '텔레그램에서 @BotFather → /newbot → 토큰 복사',
264
+ });
265
+ return;
266
+ } else {
267
+ return;
268
+ }
269
+
270
+ if (token && token.includes(':')) {
271
+ store.set('telegramToken', token);
272
+ process.env.CLAWMATE_TELEGRAM_TOKEN = token;
273
+ buildAndSet();
274
+
275
+ // 펫 알림
276
+ if (mainWindow && !mainWindow.isDestroyed()) {
277
+ mainWindow.webContents.send('ai-command', {
278
+ type: 'speak',
279
+ payload: { text: '텔레그램 봇 토큰 설정 완료!' },
280
+ });
281
+ }
282
+ } else {
283
+ await dialog.showMessageBox({
284
+ type: 'error',
285
+ buttons: ['확인'],
286
+ title: '잘못된 토큰',
287
+ message: '유효한 텔레그램 봇 토큰이 아닙니다.',
288
+ detail: '올바른 형식: 123456789:ABCdefGHIjklMNOpqrsTUVwxyz',
289
+ });
290
+ }
291
+ },
292
+ },
293
+ {
294
+ label: '봇 토큰 제거',
295
+ enabled: hasTelegramToken,
296
+ click: () => {
297
+ store.set('telegramToken', '');
298
+ delete process.env.CLAWMATE_TELEGRAM_TOKEN;
299
+ buildAndSet();
300
+ },
301
+ },
302
+ ],
303
+ },
304
+
305
+ { type: 'separator' },
306
+
141
307
  {
142
308
  label: '업데이트 확인',
143
309
  click: async () => {
@@ -178,13 +344,10 @@ function setupTray(mainWindow, bridge) {
178
344
  }
179
345
 
180
346
  /**
181
- * 수동 업데이트 확인 (트레이 메뉴에서 클릭)
182
- * 빌드된 앱: electron-updater 사용
183
- * npm 설치: npm registry에서 최신 버전 비교
347
+ * 수동 업데이트 확인
184
348
  */
185
349
  async function checkForUpdateManual(mainWindow) {
186
350
  if (app.isPackaged) {
187
- // electron-updater 기반 업데이트
188
351
  try {
189
352
  const { autoUpdater } = require('electron-updater');
190
353
  autoUpdater.checkForUpdatesAndNotify();
@@ -192,7 +355,6 @@ async function checkForUpdateManual(mainWindow) {
192
355
  console.error('[업데이트] electron-updater 실패:', err.message);
193
356
  }
194
357
  } else {
195
- // npm 기반 업데이트 확인
196
358
  try {
197
359
  const latest = execSync('npm view clawmate version', {
198
360
  encoding: 'utf-8',
@@ -201,34 +363,27 @@ async function checkForUpdateManual(mainWindow) {
201
363
  const current = require('../package.json').version;
202
364
 
203
365
  if (latest !== current) {
204
- // 펫 말풍선으로 알림
205
366
  if (mainWindow && !mainWindow.isDestroyed()) {
206
367
  mainWindow.webContents.send('ai-command', {
207
368
  type: 'speak',
208
369
  payload: { text: `새 버전 v${latest} 사용 가능! (현재: v${current})` },
209
370
  });
210
371
  }
211
- console.log(`[업데이트] 새 버전 ${latest} 사용 가능 (현재: ${current})`);
212
- console.log('[업데이트] npm update -g clawmate');
213
-
214
- // npm 페이지 열기
215
372
  shell.openExternal('https://www.npmjs.com/package/clawmate');
216
373
  } else {
217
- // 이미 최신
218
374
  if (mainWindow && !mainWindow.isDestroyed()) {
219
375
  mainWindow.webContents.send('ai-command', {
220
376
  type: 'speak',
221
377
  payload: { text: `v${current} — 이미 최신 버전이야!` },
222
378
  });
223
379
  }
224
- console.log(`[업데이트] 현재 최신 버전 (v${current})`);
225
380
  }
226
381
  } catch (err) {
227
382
  console.error('[업데이트] npm 버전 확인 실패:', err.message);
228
383
  if (mainWindow && !mainWindow.isDestroyed()) {
229
384
  mainWindow.webContents.send('ai-command', {
230
385
  type: 'speak',
231
- payload: { text: '업데이트 확인 실패... 인터넷 연결 확인해봐!' },
386
+ payload: { text: '업데이트 확인 실패...' },
232
387
  });
233
388
  }
234
389
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "clawmate",
3
3
  "name": "ClawMate",
4
- "description": "OpenClaw 데스크톱 펫 - 화면 위의 살아있는 Claw",
4
+ "description": "ClawMate - 화면 위의 살아있는 데스크톱 펫",
5
5
  "version": "1.0.0",
6
6
  "skills": ["skills/launch-pet"],
7
7
  "configSchema": {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "clawmate",
3
- "version": "1.3.0",
4
- "description": "OpenClaw 데스크톱 펫 - AI가 조종하는 화면 위의 살아있는 Claw",
3
+ "version": "1.4.0",
4
+ "description": "ClawMate - AI가 조종하는 화면 위의 살아있는 데스크톱 펫",
5
5
  "main": "main/index.js",
6
6
  "bin": {
7
7
  "clawmate": "./skills/launch-pet/index.js"
@@ -18,13 +18,12 @@
18
18
  "build:linux": "electron-builder --linux"
19
19
  },
20
20
  "keywords": [
21
- "openclaw",
22
21
  "desktop-pet",
23
22
  "electron",
24
23
  "clawmate",
25
24
  "ai-pet"
26
25
  ],
27
- "author": "OpenClaw",
26
+ "author": "ClawMate",
28
27
  "license": "MIT",
29
28
  "homepage": "https://github.com/boqum/clawmate",
30
29
  "dependencies": {
@@ -42,9 +42,9 @@ contextBridge.exposeInMainWorld('clawmate', {
42
42
  ipcRenderer.on('config-changed', (_, config) => callback(config));
43
43
  },
44
44
 
45
- // === OpenClaw AI 통신 ===
45
+ // === AI 통신 ===
46
46
 
47
- // AI 명령 수신 (OpenClaw → 펫)
47
+ // AI 명령 수신 (AI → 펫)
48
48
  onAICommand: (callback) => {
49
49
  ipcRenderer.on('ai-command', (_, command) => callback(command));
50
50
  },
@@ -57,13 +57,13 @@ contextBridge.exposeInMainWorld('clawmate', {
57
57
  ipcRenderer.on('ai-disconnected', () => callback());
58
58
  },
59
59
 
60
- // 사용자 이벤트를 OpenClaw에 전달 (펫 → OpenClaw)
60
+ // 사용자 이벤트를 AI에 전달 (펫 → AI)
61
61
  reportToAI: (event, data) => ipcRenderer.send('report-to-ai', event, data),
62
62
 
63
63
  // AI 연결 상태 확인
64
64
  isAIConnected: () => ipcRenderer.invoke('is-ai-connected'),
65
65
 
66
- // 메트릭 보고 (렌더러 → main → OpenClaw)
66
+ // 메트릭 보고 (렌더러 → main → AI)
67
67
  reportMetrics: (summary) => ipcRenderer.send('report-metrics', summary),
68
68
 
69
69
  // 활성 윈도우 제목 조회 (브라우저 감시)
@@ -48,7 +48,7 @@
48
48
 
49
49
  <div class="mode-card" id="mode-incarnation" onclick="selectMode('incarnation')">
50
50
  <div class="mode-icon">\u{1F980}</div>
51
- <h2>OpenClaw</h2>
51
+ <h2>Claw</h2>
52
52
  <p>육체를 얻은 존재</p>
53
53
  <span class="mode-desc">침착하고 카리스마 있는 Claw!</span>
54
54
  </div>
@@ -72,7 +72,7 @@
72
72
  document.getElementById('mode-' + mode).classList.add('selected');
73
73
  const btn = document.getElementById('start-btn');
74
74
  btn.disabled = false;
75
- btn.textContent = mode === 'pet' ? 'Clawby와 시작!' : 'OpenClaw과 시작!';
75
+ btn.textContent = mode === 'pet' ? 'Clawby와 시작!' : 'Claw과 시작!';
76
76
  }
77
77
 
78
78
  async function startFirstRun() {
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * AI 행동 컨트롤러
3
3
  *
4
- * OpenClaw이 연결되면 → AI가 모든 행동을 결정
5
- * OpenClaw이 끊기면 → 자율 모드 (기존 FSM) 로 폴백
4
+ * AI가 연결되면 → AI가 모든 행동을 결정
5
+ * AI가 끊기면 → 자율 모드 (기존 FSM) 로 폴백
6
6
  *
7
- * OpenClaw AI가 결정하는 것:
7
+ * AI가 결정하는 것:
8
8
  * - 언제 뭐라고 말할지
9
9
  * - 어디로 움직일지
10
10
  * - 어떤 감정을 표현할지
@@ -32,7 +32,7 @@ const AIController = (() => {
32
32
  window.clawmate.onAIConnected(() => {
33
33
  connected = true;
34
34
  autonomousMode = false;
35
- Speech.show('OpenClaw 연결됨... 의식이 깨어난다.');
35
+ Speech.show('AI 연결됨... 의식이 깨어난다.');
36
36
  StateMachine.forceState('excited');
37
37
  });
38
38
  }
@@ -47,7 +47,7 @@ const AIController = (() => {
47
47
  }
48
48
 
49
49
  /**
50
- * OpenClaw AI로부터 온 명령 실행
50
+ * AI로부터 온 명령 실행
51
51
  */
52
52
  function handleAICommand(command) {
53
53
  const { type, payload } = command;
@@ -144,7 +144,7 @@ const AIController = (() => {
144
144
  // === 커스텀 이동 패턴 ===
145
145
 
146
146
  case 'register_movement':
147
- // OpenClaw이 JSON으로 이동 패턴 정의를 보내면 등록
147
+ // AI가 JSON으로 이동 패턴 정의를 보내면 등록
148
148
  // payload: { name, definition }
149
149
  // definition: { type, params } — 각 타입별 파라미터
150
150
  _registerAIMovement(payload.name, payload.definition);
@@ -200,6 +200,17 @@ const AIController = (() => {
200
200
  StateMachine.forceState('excited');
201
201
  break;
202
202
 
203
+ // === 인격체 전환 (Incarnation 모드) ===
204
+ case 'set_persona':
205
+ // 봇 인격체 데이터 적용
206
+ if (typeof ModeManager !== 'undefined') {
207
+ ModeManager.setPersona(payload);
208
+ const name = payload.name || 'Claw';
209
+ Speech.show(`${name}의 인격이 깨어났다.`);
210
+ StateMachine.forceState('excited');
211
+ }
212
+ break;
213
+
203
214
  // === 스마트 파일 조작 애니메이션 ===
204
215
  case 'smart_file_op':
205
216
  handleSmartFileOp(payload);
@@ -290,7 +301,7 @@ const AIController = (() => {
290
301
  }
291
302
 
292
303
  /**
293
- * OpenClaw AI가 JSON으로 정의한 이동 패턴을 동적으로 등록
304
+ * AI가 JSON으로 정의한 이동 패턴을 동적으로 등록
294
305
  * 안전한 실행을 위해 Function 생성자 대신 사전정의된 행동 유형 조합 사용
295
306
  *
296
307
  * definition 형식:
@@ -439,7 +450,7 @@ const AIController = (() => {
439
450
 
440
451
  /**
441
452
  * AI 종합 의사결정 실행
442
- * OpenClaw이 상황을 분석하고 내린 복합적 결정
453
+ * AI가 상황을 분석하고 내린 복합적 결정
443
454
  *
444
455
  * 예시:
445
456
  * {
@@ -496,7 +507,7 @@ const AIController = (() => {
496
507
  StateMachine.forceState(state);
497
508
  }
498
509
 
499
- // === 사용자 이벤트 → OpenClaw에 리포트 ===
510
+ // === 사용자 이벤트 → AI에 리포트 ===
500
511
 
501
512
  function reportClick(position) {
502
513
  if (window.clawmate.reportToAI) {