clawmate 1.2.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.
package/main/ai-bridge.js CHANGED
@@ -1,14 +1,14 @@
1
1
  /**
2
- * OpenClaw ↔ ClawMate AI 브릿지
2
+ * AI ↔ ClawMate 브릿지
3
3
  *
4
- * OpenClaw 에이전트가 ClawMate의 뇌 역할을 한다.
5
- * - OpenClaw → ClawMate: 행동 명령, 말풍선, 감정, 이동
6
- * - ClawMate → OpenClaw: 사용자 이벤트 (클릭, 드래그, 커서, 파일 변화)
4
+ * AI 에이전트가 ClawMate의 뇌 역할을 한다.
5
+ * - AI → ClawMate: 행동 명령, 말풍선, 감정, 이동
6
+ * - ClawMate → AI: 사용자 이벤트 (클릭, 드래그, 커서, 파일 변화)
7
7
  *
8
8
  * 통신: WebSocket (로컬 ws://localhost:9320)
9
9
  * 프로토콜: JSON 메시지
10
10
  *
11
- * OpenClaw 연결 안 됐을 때 → 자율 모드 (기존 FSM) 로 폴백
11
+ * AI 연결 안 됐을 때 → 자율 모드 (기존 FSM) 로 폴백
12
12
  */
13
13
  const WebSocket = require('ws');
14
14
  const EventEmitter = require('events');
@@ -17,7 +17,7 @@ class AIBridge extends EventEmitter {
17
17
  constructor() {
18
18
  super();
19
19
  this.wss = null;
20
- this.client = null; // 연결된 OpenClaw 에이전트
20
+ this.client = null; // 연결된 AI 에이전트
21
21
  this.connected = false;
22
22
  this.port = 9320;
23
23
  this.heartbeatInterval = null;
@@ -33,19 +33,19 @@ class AIBridge extends EventEmitter {
33
33
  }
34
34
 
35
35
  /**
36
- * WebSocket 서버 시작 — OpenClaw이 여기에 접속
36
+ * WebSocket 서버 시작 — AI 에이전트가 여기에 접속
37
37
  */
38
38
  start() {
39
39
  this.wss = new WebSocket.Server({ port: this.port, host: '127.0.0.1' });
40
40
 
41
41
  this.wss.on('connection', (ws) => {
42
- console.log('[AI Bridge] OpenClaw 연결됨');
42
+ console.log('[AI Bridge] AI 연결됨');
43
43
  this.client = ws;
44
44
  this.connected = true;
45
45
  this.reconnectAttempts = 0;
46
46
  this.emit('connected');
47
47
 
48
- // OpenClaw에 현재 상태 전송
48
+ // AI에 현재 상태 전송
49
49
  this.send('sync', this.petState);
50
50
 
51
51
  ws.on('message', (data) => {
@@ -58,7 +58,7 @@ class AIBridge extends EventEmitter {
58
58
  });
59
59
 
60
60
  ws.on('close', () => {
61
- console.log('[AI Bridge] OpenClaw 연결 해제');
61
+ console.log('[AI Bridge] AI 연결 해제');
62
62
  this.client = null;
63
63
  this.connected = false;
64
64
  this.emit('disconnected');
@@ -84,7 +84,7 @@ class AIBridge extends EventEmitter {
84
84
  }
85
85
 
86
86
  /**
87
- * OpenClaw에서 온 명령 처리
87
+ * AI에서 온 명령 처리
88
88
  */
89
89
  _handleCommand(msg) {
90
90
  const { type, payload } = msg;
@@ -92,7 +92,7 @@ class AIBridge extends EventEmitter {
92
92
  switch (type) {
93
93
  // === 행동 제어 ===
94
94
  case 'action':
95
- // OpenClaw이 펫의 행동을 직접 지시
95
+ // AI가 펫의 행동을 직접 지시
96
96
  // payload: { state: 'walking'|'excited'|..., duration?: ms }
97
97
  this.emit('action', payload);
98
98
  break;
@@ -111,7 +111,7 @@ class AIBridge extends EventEmitter {
111
111
 
112
112
  // === 말하기 ===
113
113
  case 'speak':
114
- // OpenClaw이 펫을 통해 사용자에게 말함
114
+ // AI가 펫을 통해 사용자에게 말함
115
115
  // payload: { text: string, style?: 'normal'|'thought'|'shout' }
116
116
  this.emit('speak', payload);
117
117
  break;
@@ -133,6 +133,12 @@ class AIBridge extends EventEmitter {
133
133
  this.emit('drop_file', payload);
134
134
  break;
135
135
 
136
+ case 'smart_file_op':
137
+ // 스마트 파일 조작 (텔레그램 또는 AI에서 트리거)
138
+ // payload: { phase: 'pick_up'|'drop'|'complete', fileName?, targetName?, ... }
139
+ this.emit('smart_file_op', payload);
140
+ break;
141
+
136
142
  // === 외형 변화 ===
137
143
  case 'evolve':
138
144
  // 진화 트리거
@@ -188,6 +194,49 @@ class AIBridge extends EventEmitter {
188
194
  this.emit('query_windows', payload);
189
195
  break;
190
196
 
197
+ // === 커스텀 이동 패턴 ===
198
+ case 'register_movement':
199
+ // AI가 커스텀 이동 패턴 등록
200
+ // payload: { name: string, definition: { type: 'waypoints'|'formula'|'sequence', ... } }
201
+ this.emit('register_movement', payload);
202
+ break;
203
+
204
+ case 'custom_move':
205
+ // 등록된 커스텀 이동 패턴 실행
206
+ // payload: { name: string, params?: object }
207
+ this.emit('custom_move', payload);
208
+ break;
209
+
210
+ case 'stop_custom_move':
211
+ // 현재 커스텀 이동 강제 중지
212
+ // payload: {}
213
+ this.emit('stop_custom_move', payload);
214
+ break;
215
+
216
+ case 'list_movements':
217
+ // 등록된 이동 패턴 목록 요청 → 응답은 renderer에서 reportToAI로 전송
218
+ // payload: {}
219
+ this.emit('list_movements', payload);
220
+ break;
221
+
222
+ // === 캐릭터 커스터마이징 ===
223
+ case 'set_character':
224
+ // AI가 생성한 캐릭터 데이터 적용
225
+ // payload: { colorMap?: {...}, frames?: {...} }
226
+ this.emit('set_character', payload);
227
+ break;
228
+
229
+ case 'reset_character':
230
+ // 원래 캐릭터로 리셋
231
+ this.emit('reset_character', payload);
232
+ break;
233
+
234
+ case 'set_persona':
235
+ // 봇 인격체 전환 (Incarnation 모드)
236
+ // payload: { name, personality, speakingStyle, color?, ... }
237
+ this.emit('set_persona', payload);
238
+ break;
239
+
191
240
  // === 컨텍스트 질의 ===
192
241
  case 'query_state':
193
242
  // 현재 펫 상태 요청
@@ -201,7 +250,7 @@ class AIBridge extends EventEmitter {
201
250
 
202
251
  // === AI 의사결정 결과 ===
203
252
  case 'ai_decision':
204
- // OpenClaw AI의 종합적 의사결정
253
+ // AI의 종합적 의사결정
205
254
  // payload: { action, speech?, emotion?, reasoning? }
206
255
  this.emit('ai_decision', payload);
207
256
  break;
@@ -212,7 +261,7 @@ class AIBridge extends EventEmitter {
212
261
  }
213
262
 
214
263
  /**
215
- * OpenClaw에 이벤트 전송
264
+ * AI에 이벤트 전송
216
265
  */
217
266
  send(type, payload) {
218
267
  if (!this.client || this.client.readyState !== WebSocket.OPEN) return false;
@@ -224,7 +273,7 @@ class AIBridge extends EventEmitter {
224
273
  }
225
274
  }
226
275
 
227
- // === 사용자 이벤트 리포트 (ClawMate → OpenClaw) ===
276
+ // === 사용자 이벤트 리포트 (ClawMate → AI) ===
228
277
 
229
278
  reportUserClick(position) {
230
279
  this.send('user_event', {
@@ -285,6 +334,17 @@ class AIBridge extends EventEmitter {
285
334
  });
286
335
  }
287
336
 
337
+ /**
338
+ * 메트릭 데이터를 AI에 전송
339
+ * 렌더러에서 수집한 펫 동작 품질 메트릭을 AI에 전달
340
+ */
341
+ reportMetrics(summary) {
342
+ this.send('metrics_report', {
343
+ metrics: summary,
344
+ timestamp: Date.now(),
345
+ });
346
+ }
347
+
288
348
  // === 상태 업데이트 ===
289
349
 
290
350
  updatePetState(updates) {
@@ -1,11 +1,11 @@
1
1
  /**
2
- * OpenClaw 에이전트 측 커넥터
2
+ * AI 에이전트 측 커넥터
3
3
  *
4
- * OpenClaw이 ClawMate에 접속해서 펫을 조종하는 클라이언트.
5
- * OpenClaw 플러그인(index.js)에서 사용됨.
4
+ * AI가 ClawMate에 접속해서 펫을 조종하는 클라이언트.
5
+ * ClawMate 플러그인(index.js)에서 사용됨.
6
6
  *
7
7
  * 사용 예:
8
- * const connector = new OpenClawConnector();
8
+ * const connector = new ClawMateConnector();
9
9
  * await connector.connect();
10
10
  * connector.speak('안녕! 오늘 뭐 할 거야?');
11
11
  * connector.action('excited');
@@ -14,7 +14,7 @@
14
14
  const WebSocket = require('ws');
15
15
  const EventEmitter = require('events');
16
16
 
17
- class OpenClawConnector extends EventEmitter {
17
+ class ClawMateConnector extends EventEmitter {
18
18
  constructor(port = 9320) {
19
19
  super();
20
20
  this.port = port;
@@ -81,12 +81,12 @@ class OpenClawConnector extends EventEmitter {
81
81
  break;
82
82
 
83
83
  case 'user_event':
84
- // 사용자 이벤트 → OpenClaw AI가 반응 결정
84
+ // 사용자 이벤트 → AI가 반응 결정
85
85
  this.emit('user_event', payload);
86
86
  break;
87
87
 
88
88
  case 'screen_capture':
89
- // 화면 캡처 응답 → OpenClaw AI가 분석
89
+ // 화면 캡처 응답 → AI가 분석
90
90
  this.emit('screen_capture', payload);
91
91
  break;
92
92
 
@@ -95,6 +95,11 @@ class OpenClawConnector extends EventEmitter {
95
95
  this.emit('window_positions', payload);
96
96
  break;
97
97
 
98
+ case 'metrics_report':
99
+ // 메트릭 데이터 수신 → AI가 분석
100
+ this.emit('metrics_report', payload);
101
+ break;
102
+
98
103
  case 'heartbeat':
99
104
  break;
100
105
  }
@@ -110,7 +115,7 @@ class OpenClawConnector extends EventEmitter {
110
115
  }
111
116
  }
112
117
 
113
- // === OpenClaw → ClawMate 명령 API ===
118
+ // === AI → ClawMate 명령 API ===
114
119
 
115
120
  /** 펫이 말하게 함 */
116
121
  speak(text, style = 'normal') {
@@ -159,13 +164,13 @@ class OpenClawConnector extends EventEmitter {
159
164
 
160
165
  /**
161
166
  * AI 종합 의사결정 전송
162
- * OpenClaw AI가 상황을 분석하고 내린 결정을 한번에 전송
167
+ * AI가 상황을 분석하고 내린 결정을 한번에 전송
163
168
  */
164
169
  decide(decision) {
165
170
  return this._send('ai_decision', decision);
166
171
  }
167
172
 
168
- // === 공간 이동 API (OpenClaw이 컴퓨터를 "집"처럼 돌아다님) ===
173
+ // === 공간 이동 API (펫이 컴퓨터를 "집"처럼 돌아다님) ===
169
174
 
170
175
  /** 특정 위치로 점프 */
171
176
  jumpTo(x, y) {
@@ -197,6 +202,79 @@ class OpenClawConnector extends EventEmitter {
197
202
  return this._send('query_windows', {});
198
203
  }
199
204
 
205
+ // === 커스텀 이동 패턴 API ===
206
+
207
+ /**
208
+ * 커스텀 이동 패턴 등록
209
+ * ClawMate에 새로운 이동 패턴을 동적으로 추가
210
+ *
211
+ * @param {string} name - 패턴 이름 (예: 'figure8', 'spiral')
212
+ * @param {object} definition - 패턴 정의
213
+ * type: 'waypoints' | 'formula' | 'sequence'
214
+ * waypoints?: [{x, y, pause?}] — 웨이포인트 순차 이동
215
+ * formula?: { xAmp, yAmp, xFreq, yFreq, xPhase, yPhase } — 수학 궤도
216
+ * sequence?: ['zigzag', 'shake'] — 기존 패턴 순차 실행
217
+ * duration?: number — 지속 시간 (ms, formula 타입)
218
+ * speed?: number — 이동 속도
219
+ *
220
+ * 사용 예:
221
+ * connector.registerMovement('figure8', {
222
+ * type: 'formula',
223
+ * formula: { xAmp: 80, yAmp: 40, xFreq: 1, yFreq: 2 },
224
+ * duration: 4000,
225
+ * });
226
+ */
227
+ registerMovement(name, definition) {
228
+ return this._send('register_movement', { name, definition });
229
+ }
230
+
231
+ /**
232
+ * 등록된 커스텀 이동 패턴 실행
233
+ *
234
+ * @param {string} name - 실행할 패턴 이름
235
+ * 사전 등록 패턴: 'zigzag', 'patrol', 'circle', 'shake', 'dance'
236
+ * 또는 registerMovement()로 등록한 커스텀 패턴
237
+ * @param {object} params - 실행 파라미터 (패턴별로 다름)
238
+ *
239
+ * 사용 예:
240
+ * connector.customMove('zigzag', { distance: 200, amplitude: 30 });
241
+ * connector.customMove('patrol', { pointAX: 100, pointBX: 500, laps: 5 });
242
+ * connector.customMove('shake', { intensity: 6, duration: 1000 });
243
+ */
244
+ customMove(name, params = {}) {
245
+ return this._send('custom_move', { name, params });
246
+ }
247
+
248
+ /** 현재 실행 중인 커스텀 이동 강제 중지 */
249
+ stopCustomMove() {
250
+ return this._send('stop_custom_move', {});
251
+ }
252
+
253
+ /** 등록된 이동 패턴 목록 요청 (응답은 user_event로 수신) */
254
+ listMovements() {
255
+ return this._send('list_movements', {});
256
+ }
257
+
258
+ /** 스마트 파일 조작 명령 전송 */
259
+ smartFileOp(payload) {
260
+ return this._send('smart_file_op', payload);
261
+ }
262
+
263
+ /** 캐릭터 데이터 전송 (AI 생성 캐릭터 적용) */
264
+ setCharacter(data) {
265
+ return this._send('set_character', data);
266
+ }
267
+
268
+ /** 원래 캐릭터로 리셋 */
269
+ resetCharacter() {
270
+ return this._send('reset_character', {});
271
+ }
272
+
273
+ /** 인격체 전환 (Incarnation 모드에서 봇 인격 반영) */
274
+ setPersona(data) {
275
+ return this._send('set_persona', data);
276
+ }
277
+
200
278
  /**
201
279
  * 현재 펫 상태 요청 (Promise 반환)
202
280
  * 서버에서 state_response가 오면 resolve, 타임아웃 시 캐시된 상태 반환
@@ -234,6 +312,11 @@ class OpenClawConnector extends EventEmitter {
234
312
  this.on('user_event', callback);
235
313
  }
236
314
 
315
+ /** 메트릭 리포트 리스너 등록 */
316
+ onMetrics(callback) {
317
+ this.on('metrics_report', callback);
318
+ }
319
+
237
320
  disconnect() {
238
321
  this._autoReconnect = false;
239
322
  if (this._reconnectTimer) clearTimeout(this._reconnectTimer);
@@ -241,4 +324,4 @@ class OpenClawConnector extends EventEmitter {
241
324
  }
242
325
  }
243
326
 
244
- module.exports = { OpenClawConnector };
327
+ module.exports = { ClawMateConnector };
package/main/autostart.js CHANGED
@@ -5,8 +5,8 @@
5
5
  * macOS: LaunchAgent plist
6
6
  * Linux: ~/.config/autostart/ 디렉토리에 .desktop 파일 생성
7
7
  *
8
- * OpenClaw이 나중에 켜져도, ClawMate는 이미 돌고 있어서 바로 연결됨.
9
- * ClawMate가 먼저 혼자 자율 모드로 돌다가 → OpenClaw 연결되면 AI 모드 전환.
8
+ * AI가 나중에 켜져도, ClawMate는 이미 돌고 있어서 바로 연결됨.
9
+ * ClawMate가 먼저 혼자 자율 모드로 돌다가 → AI 연결되면 AI 모드 전환.
10
10
  */
11
11
  const { app } = require('electron');
12
12
  const fs = require('fs');
@@ -22,7 +22,7 @@ function getDesktopFileContent() {
22
22
  '[Desktop Entry]',
23
23
  'Type=Application',
24
24
  'Name=ClawMate',
25
- 'Comment=OpenClaw Desktop Pet',
25
+ 'Comment=ClawMate Desktop Pet',
26
26
  `Exec=${process.execPath} ${path.resolve(__dirname, '..')}`,
27
27
  'X-GNOME-Autostart-enabled=true',
28
28
  'Hidden=false',