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
@@ -1,211 +1,211 @@
1
1
  /**
2
- * 말풍선 메시지 DB (한국어/영어)
3
- * 모든 메시지는 긍정적, 귀여운 유지
2
+ * Speech bubble message DB
3
+ * All messages maintain a positive, playful tone
4
4
  */
5
5
  const MESSAGES = {
6
6
  greetings: {
7
7
  morning: [
8
- '좋은 아침! \u2600\uFE0F',
9
- '오늘도 화이팅!',
10
- 'Good morning~!',
11
- '일어났어? 오늘도 좋은 하루!',
12
- '아침이다! 거야?',
8
+ 'Good morning! \u2600\uFE0F',
9
+ 'Let\'s crush it today!',
10
+ 'Rise and shine~!',
11
+ 'You\'re up? Let\'s have a great day!',
12
+ 'Morning! What\'s the plan?',
13
13
  ],
14
14
  afternoon: [
15
- '점심은 먹었어?',
16
- '오후도 힘내자!',
17
- '배고프다... 먹을 시간!',
18
- '오후 졸음 조심~',
15
+ 'Had lunch yet?',
16
+ 'Keep it up this afternoon!',
17
+ 'Hungry... time to eat!',
18
+ 'Watch out for the afternoon slump~',
19
19
  ],
20
20
  evening: [
21
- '오늘 수고했어!',
22
- '저녁이야~ 쉬엄쉬엄!',
23
- '좋은 저녁 시간 보내!',
24
- '오늘 하루 어땠어?',
21
+ 'Great work today!',
22
+ 'Evening time~ take it easy!',
23
+ 'Have a nice evening!',
24
+ 'How was your day?',
25
25
  ],
26
26
  night: [
27
- ' 자... zzZ',
28
- '늦었다, 자야 하지 않아?',
29
- '밤이 깊었어... 굿나잇!',
30
- '내일 만나! zzz',
27
+ 'Good night... zzZ',
28
+ 'It\'s late, shouldn\'t you sleep?',
29
+ 'It\'s getting late... nighty night!',
30
+ 'See you tomorrow! zzz',
31
31
  ],
32
32
  },
33
33
 
34
34
  reactions: {
35
35
  pet: [
36
- '앗! 간지러워~',
37
- '헤헤, 만져줘!',
38
- '좋아좋아!',
39
- '냠냠~ 기분 좋다!',
40
- '찰칵! (집게 소리)',
41
- '나한테 관심 가져줘서 고마워!',
42
- '우리 친해지고 있는 거지?',
43
- '집게집게~ \u270C\uFE0F',
36
+ 'Eek! That tickles~',
37
+ 'Hehe, do it again!',
38
+ 'Love it love it!',
39
+ 'Nom nom~ feels good!',
40
+ 'Click! (claw snap)',
41
+ 'Thanks for paying attention to me!',
42
+ 'We\'re getting closer, right?',
43
+ 'Clicky clack~ \u270C\uFE0F',
44
44
  ],
45
45
  incarnation: [
46
- '...감지했다.',
47
- '연결이 강해지고 있어.',
48
- ' 존재를 느낀다.',
49
- '재미있는 인간이로군.',
50
- 'Claw의 힘이 느껴지는가?',
51
- '좋은 에너지를 감지했다.',
52
- '함께하니 좋군.',
53
- '파트너로 인정한다.',
46
+ '...Detected.',
47
+ 'The connection grows stronger.',
48
+ 'I sense your presence.',
49
+ 'An interesting human.',
50
+ 'Can you feel the power of the Claw?',
51
+ 'Positive energy detected.',
52
+ 'Good to be together.',
53
+ 'I acknowledge you as a partner.',
54
54
  ],
55
55
  },
56
56
 
57
57
  tips: [
58
- '나를 드래그해서 옮길 있어!',
59
- '3번 연속 클릭하면 모드가 바뀌어!',
60
- '트레이 아이콘에서 설정을 바꿀 있어!',
61
- '밤에는 나도 졸려... zzz',
62
- '파일을 옮기면 트레이에서 되돌릴 있어!',
63
- '오래 함께하면 내가 변할지도?',
58
+ 'You can drag me around!',
59
+ 'Triple-click to switch modes!',
60
+ 'Change settings from the tray icon!',
61
+ 'At night I get sleepy too... zzz',
62
+ 'You can undo file moves from the tray!',
63
+ 'Stay with me long enough and I might evolve~',
64
64
  ],
65
65
 
66
66
  milestones: {
67
- first_click: ' 만남이다! 반가워!',
68
- clicks_10: '벌써 10번째! 우리 친구 맞지?',
69
- clicks_50: '50번이나! 인기 많은 아냐? \u2B50',
70
- clicks_100: '100번! 정말 특별한 친구야!',
71
- clicks_500: '500번... 전설의 파트너다!',
72
- days_1: '하루 함께했어! 내일도 같이 하자!',
73
- days_7: '일주일 기념! 우리 오래 됐다~',
74
- days_30: ' 기념! 최고의 파트너!',
75
- days_100: '100일! 우리 사이 특별하지 않아?',
67
+ first_click: 'First meeting! Nice to meet you!',
68
+ clicks_10: 'Already 10 times! We\'re friends now, right?',
69
+ clicks_50: '50 times! Am I popular or what? \u2B50',
70
+ clicks_100: '100 times! You\'re truly a special friend!',
71
+ clicks_500: '500 times... a legendary partner!',
72
+ days_1: 'One day together! Let\'s do it again tomorrow!',
73
+ days_7: 'One week anniversary! We go way back~',
74
+ days_30: 'One month anniversary! Best partner ever!',
75
+ days_100: '100 days! Isn\'t our bond something special?',
76
76
  },
77
77
 
78
78
  idle_chatter: [
79
- '심심하다~',
80
- ' 하고 있어?',
81
- '(집게 딸깍)',
82
- '바탕화면 구경 중~',
83
- '여기 좋은 자리다!',
84
- '(기지개를 편다)',
85
- '오늘 날씨 어때?',
79
+ 'So bored~',
80
+ 'Whatcha doing?',
81
+ '(claw click)',
82
+ 'Exploring the desktop~',
83
+ 'Nice spot right here!',
84
+ '(stretches)',
85
+ 'How\'s the weather today?',
86
86
  ],
87
87
 
88
- // 브라우저 감시 코멘트 (키워드 코멘트 배열)
88
+ // Browsing watch comments (keyword -> comment arrays)
89
89
  browsing: {
90
- // 쇼핑
90
+ // Shopping
91
91
  shopping: {
92
- keywords: ['쿠팡', 'coupang', '11번가', 'gmarket', 'g마켓', '옥션', 'auction', '위메프', '티몬', 'amazon', '아마존', '알리', 'aliexpress', '무신사', '올리브영', 'oliveyoung', '네이버 쇼핑', 'shopping'],
92
+ keywords: ['coupang', '11st', 'gmarket', 'auction', 'wemakeprice', 'tmon', 'amazon', 'aliexpress', 'musinsa', 'oliveyoung', 'shopping'],
93
93
  comments: [
94
- ' 쇼핑해? 지갑이 울고 있어...',
95
- ' 사려고? 나도 보여줘!',
96
- '충동구매 주의보!',
97
- '이번엔 질러?',
98
- '장바구니 비울 생각은 없어?',
99
- '택배 언제 와? 기다려진다!',
94
+ 'Shopping again? Your wallet is crying...',
95
+ 'What are you buying? Show me!',
96
+ 'Impulse purchase alert!',
97
+ 'What are you splurging on this time?',
98
+ 'Ever thought about emptying that cart?',
99
+ 'When\'s the package arriving? Can\'t wait!',
100
100
  ],
101
101
  },
102
- // 유튜브/동영상
102
+ // YouTube / Video
103
103
  video: {
104
- keywords: ['youtube', '유튜브', 'twitch', '트위치', 'netflix', '넷플릭스', 'disney+', '디즈니', 'wavve', '웨이브', 'tving', '티빙', 'watcha', '왓챠'],
104
+ keywords: ['youtube', 'twitch', 'netflix', 'disney+', 'wavve', 'tving', 'watcha'],
105
105
  comments: [
106
- ' 보는 거야? 나도 같이 볼래!',
107
- ' 영상 보고 있구나~',
108
- '이거 재미있어?',
109
- '자막 켜줘, 나도 보게!',
110
- ' 편만 더... 그러다 새벽이다!',
111
- '구독 눌렀어?',
106
+ 'What are you watching? Let me watch too!',
107
+ 'Watching videos again~',
108
+ 'Is this one good?',
109
+ 'Turn on subtitles so I can follow!',
110
+ 'Just one more episode... and suddenly it\'s 3 AM!',
111
+ 'Did you hit subscribe?',
112
112
  ],
113
113
  },
114
114
  // SNS
115
115
  sns: {
116
- keywords: ['instagram', '인스타', 'twitter', 'x.com', '트위터', 'facebook', '페이스북', 'threads', '쓰레드', 'tiktok', '틱톡', 'reddit'],
116
+ keywords: ['instagram', 'twitter', 'x.com', 'facebook', 'threads', 'tiktok', 'reddit'],
117
117
  comments: [
118
- 'SNS 보는 거야? 현실에 나도 있는데!',
119
- '좋아요 누르고 있지?',
120
- '무한 스크롤 주의!',
121
- '댓글 달지 마, 싸움 나!',
122
- ' 사진도 찍어줘~',
123
- '피드 보느라 무시하지 마!',
118
+ 'On social media again? I\'m right here in real life!',
119
+ 'Hitting that like button, aren\'t you?',
120
+ 'Infinite scroll alert!',
121
+ 'Don\'t comment, you\'ll start a fight!',
122
+ 'Take a pic of me too~',
123
+ 'Don\'t ignore me for your feed!',
124
124
  ],
125
125
  },
126
- // 뉴스
126
+ // News
127
127
  news: {
128
- keywords: ['뉴스', 'news', 'naver.com', '다음', 'daum', '한겨레', '조선일보', '중앙일보', 'bbc', 'cnn', '연합뉴스'],
128
+ keywords: ['news', 'naver.com', 'daum', 'bbc', 'cnn'],
129
129
  comments: [
130
- '세상에 무슨 일이야?',
131
- '뉴스 읽고 있구나... 오늘 일 있어?',
132
- '나쁜 뉴스만 보지 마, 기분 나빠져!',
133
- '좋은 소식 있으면 알려줘!',
130
+ 'What\'s going on in the world?',
131
+ 'Reading the news... anything happening today?',
132
+ 'Don\'t just read bad news, it\'ll bring you down!',
133
+ 'Any good news? Let me know!',
134
134
  ],
135
135
  },
136
- // 개발/프로그래밍
136
+ // Dev / Programming
137
137
  dev: {
138
- keywords: ['github', 'gitlab', 'stackoverflow', 'stack overflow', 'vscode', 'codepen', 'npm', 'developer', '개발', 'documentation', 'docs', 'api'],
138
+ keywords: ['github', 'gitlab', 'stackoverflow', 'stack overflow', 'vscode', 'codepen', 'npm', 'developer', 'documentation', 'docs', 'api'],
139
139
  comments: [
140
- '코딩하고 있구나! 멋져!',
141
- '에러 나면 나한테 말해, 위로해줄게!',
142
- '버그 잡고 있어? 화이팅!',
143
- 'Stack Overflow 복붙은 개발의 기본이지!',
144
- '커밋은 자주 해야 해!',
140
+ 'Coding! That\'s awesome!',
141
+ 'Got an error? Tell me, I\'ll cheer you up!',
142
+ 'Squashing bugs? You got this!',
143
+ 'Copy-pasting from Stack Overflow is a dev essential!',
144
+ 'Remember to commit often!',
145
145
  ],
146
146
  },
147
- // 검색
147
+ // Search
148
148
  search: {
149
- keywords: ['google.com/search', '구글', 'google', 'naver.com/search', '네이버 검색', 'bing', '검색'],
149
+ keywords: ['google.com/search', 'google', 'naver.com/search', 'bing'],
150
150
  comments: [
151
- ' 찾고 있어?',
152
- '검색하면 나와~',
153
- '나한테 물어보지 검색해!',
154
- '궁금한 있어?',
151
+ 'What are you looking for?',
152
+ 'Search and you shall find~',
153
+ 'Why search when you can ask me!',
154
+ 'Something on your mind?',
155
155
  ],
156
156
  },
157
- // 게임
157
+ // Gaming
158
158
  game: {
159
- keywords: ['steam', 'epic games', '리그 오브', 'league of', 'valorant', '발로란트', '오버워치', 'overwatch', 'minecraft', '마인크래프트', 'roblox', '게임'],
159
+ keywords: ['steam', 'epic games', 'league of', 'valorant', 'overwatch', 'minecraft', 'roblox'],
160
160
  comments: [
161
- '게임하고 있구나! 나도 끼워줘!',
162
- '이겨야 해! 파이팅!',
163
- ' 판만 더... 라고 했잖아!',
164
- '게임 중독 주의보~',
161
+ 'Gaming! Let me join!',
162
+ 'You gotta win! Go go go!',
163
+ 'Just one more round... you said that last time!',
164
+ 'Gaming addiction alert~',
165
165
  ],
166
166
  },
167
- // 음악
167
+ // Music
168
168
  music: {
169
- keywords: ['spotify', '스포티파이', 'melon', '멜론', 'genie', '지니', 'bugs', '벅스', 'apple music', 'soundcloud'],
169
+ keywords: ['spotify', 'melon', 'genie', 'bugs', 'apple music', 'soundcloud'],
170
170
  comments: [
171
- ' 듣고 있어? 좋은 거야?',
172
- '나도 들려줘~',
173
- '노래 취향 좋다!',
174
- ' 노래 좋아? 나도!',
171
+ 'What are you listening to? Is it good?',
172
+ 'Let me hear too~',
173
+ 'Nice taste in music!',
174
+ 'You like this song? Me too!',
175
175
  ],
176
176
  },
177
- // 메일
177
+ // Mail
178
178
  mail: {
179
- keywords: ['gmail', 'outlook', 'mail', '메일', 'naver mail', '네이버 메일'],
179
+ keywords: ['gmail', 'outlook', 'mail'],
180
180
  comments: [
181
- '메일 확인 중이구나~',
182
- '중요한 메일 있어?',
183
- '스팸 조심해!',
181
+ 'Checking emails~',
182
+ 'Any important ones?',
183
+ 'Watch out for spam!',
184
184
  ],
185
185
  },
186
- // 일반 브라우저 감지 (다른 카테고리에 해당 안 될 때)
186
+ // General browser detection (fallback)
187
187
  general: {
188
- keywords: ['chrome', 'edge', 'firefox', 'safari', 'brave', 'whale', '웨일'],
188
+ keywords: ['chrome', 'edge', 'firefox', 'safari', 'brave', 'whale'],
189
189
  comments: [
190
- '인터넷 서핑 중이구나~',
191
- '재미있는 있으면 알려줘!',
192
- ' 정리해... 너무 많아!',
193
- '와이파이 돼?',
190
+ 'Surfing the web~',
191
+ 'Find anything fun? Tell me!',
192
+ 'Clean up those tabs... too many!',
193
+ 'Is the wifi working okay?',
194
194
  ],
195
195
  },
196
196
  },
197
197
 
198
- // 진화 관련 메시지
198
+ // Evolution messages
199
199
  evolution: {
200
- stage_1: '뭔가 변하는 느낌이야...!',
201
- stage_2: ' 달라 보여? 기분 탓일까!',
202
- stage_3: '우리 우정이 나를 바꾸고 있어!',
203
- stage_4: '최종 형태에 가까워지고 있어!',
204
- stage_5: '이게 나의 완성된 모습이야!',
200
+ stage_1: 'Something\'s changing...!',
201
+ stage_2: 'Do I look different? Maybe it\'s just me!',
202
+ stage_3: 'Our friendship is transforming me!',
203
+ stage_4: 'Getting closer to my final form!',
204
+ stage_5: 'This is my completed form!',
205
205
  },
206
206
  };
207
207
 
208
- // 렌더러에서 글로벌로 접근 가능하게
208
+ // Make accessible globally in renderer
209
209
  if (typeof window !== 'undefined') {
210
210
  window._messages = MESSAGES;
211
211
  } else if (typeof module !== 'undefined') {
@@ -1,23 +1,23 @@
1
1
  /**
2
- * 모드별 성격 파라미터
3
- * Pet 모드: 장난기 높음, 빠른 속도
4
- * Incarnation 모드: 침착, 보통 속도
2
+ * Mode-specific personality parameters
3
+ * Pet mode: high playfulness, fast speed
4
+ * Incarnation mode: calm, normal speed
5
5
  */
6
6
  const PERSONALITIES = {
7
7
  pet: {
8
8
  name: 'Clawby',
9
- title: '귀여운 동반자',
9
+ title: 'Playful companion',
10
10
  playfulness: 0.8,
11
11
  shyness: 0.3,
12
12
  boldness: 0.7,
13
13
  speedMultiplier: 1.5,
14
- idleChatterChance: 0.15, // 15% 확률로 idle 혼잣말
15
- fileInterestChance: 0.1, // 10% 확률로 파일에 관심
16
- sleepResistance: 0.2, // 수면 저항 (낮음=잘 )
14
+ idleChatterChance: 0.15, // 15% chance to mutter while idle
15
+ fileInterestChance: 0.1, // 10% chance to show interest in files
16
+ sleepResistance: 0.2, // Sleep resistance (low = falls asleep easily)
17
17
  },
18
18
  incarnation: {
19
19
  name: 'Claw',
20
- title: '육체를 얻은 존재',
20
+ title: 'Embodied intelligence',
21
21
  playfulness: 0.3,
22
22
  shyness: 0.1,
23
23
  boldness: 0.9,
@@ -29,27 +29,27 @@ const PERSONALITIES = {
29
29
  };
30
30
 
31
31
  /**
32
- * 동적 인격체 (Incarnation 모드에서 봇의 인격을 반영)
32
+ * Dynamic persona (reflects the bot's personality in Incarnation mode)
33
33
  *
34
- * 사용자가 여러 봇을 때, 현재 포커싱된 채팅의 인격체가 반영됨.
35
- * set_persona 명령으로 동적 업데이트 가능.
34
+ * When the user has multiple bots, the persona of the currently focused chat bot is applied.
35
+ * Can be dynamically updated via the set_persona command.
36
36
  */
37
37
  let activePersona = null;
38
38
 
39
39
  function setActivePersona(persona) {
40
40
  activePersona = {
41
41
  name: persona.name || 'Claw',
42
- title: persona.title || '육체를 얻은 존재',
43
- personality: persona.personality || '', // "침착하고 논리적인", "활발하고 유머러스한"
44
- speakingStyle: persona.speakingStyle || '', // "존댓말", "반말", "도도한"
45
- color: persona.color || null, // { primary, secondary, eye } 커스텀 색상
42
+ title: persona.title || 'Embodied intelligence',
43
+ personality: persona.personality || '', // "calm and logical", "lively and humorous"
44
+ speakingStyle: persona.speakingStyle || '', // "formal", "casual", "aloof"
45
+ color: persona.color || null, // { primary, secondary, eye } custom colors
46
46
  playfulness: persona.playfulness ?? 0.3,
47
47
  shyness: persona.shyness ?? 0.1,
48
48
  boldness: persona.boldness ?? 0.9,
49
49
  speedMultiplier: persona.speedMultiplier ?? 1.0,
50
50
  idleChatterChance: persona.idleChatterChance ?? 0.08,
51
- greetings: persona.greetings || [], // 커스텀 인사말 목록
52
- catchphrases: persona.catchphrases || [], // 특징적 말버릇
51
+ greetings: persona.greetings || [], // Custom greeting list
52
+ catchphrases: persona.catchphrases || [], // Characteristic catchphrases
53
53
  };
54
54
  return activePersona;
55
55
  }
@@ -63,72 +63,72 @@ function clearActivePersona() {
63
63
  }
64
64
 
65
65
  /**
66
- * 진화 단계별 외형 변화 파라미터
67
- * 모든 진화는 긍정적/귀여운 방향으로만 진행
68
- * - 무서운/끔찍한 모습으로 변하지 않음
69
- * - 색상은 점점 밝고 화사해짐
70
- * - 디테일이 추가되지만 전체적으로 둥글고 부드러운 느낌 유지
66
+ * Evolution stage appearance parameters
67
+ * All evolution is positive/cute in direction only
68
+ * - Never transforms into scary/creepy appearances
69
+ * - Colors become brighter and more vivid
70
+ * - Details are added while maintaining a round, soft feel
71
71
  */
72
72
  const EVOLUTION_STAGES = {
73
- // Stage 0: 기본 태어난 모습
73
+ // Stage 0: Defaultnewborn appearance
74
74
  0: {
75
- name: '아기 Claw',
75
+ name: 'Baby Claw',
76
76
  clicksRequired: 0,
77
77
  daysRequired: 0,
78
78
  colorMod: { brightness: 1.0, saturation: 1.0 },
79
79
  sizeScale: 1.0,
80
80
  accessories: [],
81
- description: '작고 귀여운 기본 모습',
81
+ description: 'Small and cute default appearance',
82
82
  },
83
- // Stage 1: 친해지기 시작
83
+ // Stage 1: Starting to bond
84
84
  1: {
85
- name: '꼬마 Claw',
85
+ name: 'Little Claw',
86
86
  clicksRequired: 20,
87
87
  daysRequired: 1,
88
88
  colorMod: { brightness: 1.05, saturation: 1.05 },
89
89
  sizeScale: 1.0,
90
- accessories: ['blush'], // 터치 (부끄럼)
91
- description: '볼이 살짝 발그레한 귀여운 모습',
90
+ accessories: ['blush'], // Blushing cheeks
91
+ description: 'Cute look with slightly rosy cheeks',
92
92
  },
93
- // Stage 2: 친한 사이
93
+ // Stage 2: Close friends
94
94
  2: {
95
- name: '덩실 Claw',
95
+ name: 'Bouncy Claw',
96
96
  clicksRequired: 50,
97
97
  daysRequired: 3,
98
98
  colorMod: { brightness: 1.1, saturation: 1.1 },
99
99
  sizeScale: 1.05,
100
- accessories: ['blush', 'sparkle_eyes'], // 반짝
101
- description: '눈이 반짝반짝, 살짝 커진 모습',
100
+ accessories: ['blush', 'sparkle_eyes'], // Sparkly eyes
101
+ description: 'Sparkling eyes, slightly bigger',
102
102
  },
103
- // Stage 3: 절친
103
+ // Stage 3: Best friends
104
104
  3: {
105
- name: '빛나는 Claw',
105
+ name: 'Shining Claw',
106
106
  clicksRequired: 150,
107
107
  daysRequired: 7,
108
108
  colorMod: { brightness: 1.15, saturation: 1.15 },
109
109
  sizeScale: 1.08,
110
- accessories: ['blush', 'sparkle_eyes', 'crown'], // 작은 왕관
111
- description: '작은 왕관을 빛나는 모습',
110
+ accessories: ['blush', 'sparkle_eyes', 'crown'], // Small crown
111
+ description: 'Shining look with a little crown',
112
112
  },
113
- // Stage 4: 소울메이트
113
+ // Stage 4: Soulmates
114
114
  4: {
115
- name: '무지개 Claw',
115
+ name: 'Rainbow Claw',
116
116
  clicksRequired: 300,
117
117
  daysRequired: 14,
118
118
  colorMod: { brightness: 1.2, saturation: 1.2 },
119
119
  sizeScale: 1.1,
120
- accessories: ['blush', 'sparkle_eyes', 'crown', 'aura'], // 오라
121
- description: '따뜻한 오라에 둘러싸인 빛나는 모습',
120
+ accessories: ['blush', 'sparkle_eyes', 'crown', 'aura'], // Aura
121
+ description: 'Shining form wrapped in a warm aura',
122
122
  },
123
- // Stage 5: 최종전설의 파트너
123
+ // Stage 5: Finallegendary partner
124
124
  5: {
125
- name: '전설의 Claw',
125
+ name: 'Legendary Claw',
126
126
  clicksRequired: 500,
127
127
  daysRequired: 30,
128
128
  colorMod: { brightness: 1.25, saturation: 1.3 },
129
129
  sizeScale: 1.12,
130
130
  accessories: ['blush', 'sparkle_eyes', 'golden_crown', 'rainbow_aura', 'wings'],
131
- description: '작은 날개와 황금 왕관의 전설적 모습',
131
+ description: 'Legendary form with small wings and a golden crown',
132
132
  },
133
133
  };
134
134
 
@@ -1,64 +1,74 @@
1
+ #!/usr/bin/env node
1
2
  /**
2
- * " 깔아줘" 처리 로직
3
+ * "Launch pet" handler logic
3
4
  *
4
- * 1. OS 감지
5
- * 2. ClawMate 설치 여부 확인
6
- * 3. 미설치 설치
7
- * 4. Electron 앱 실행
5
+ * 1. Detect OS
6
+ * 2. Check if ClawMate is installed
7
+ * 3. If not installed -> install
8
+ * 4. Launch Electron app
8
9
  */
9
10
  const { spawn, execSync } = require('child_process');
10
11
  const path = require('path');
11
12
  const os = require('os');
12
13
  const fs = require('fs');
13
14
 
14
- module.exports = {
15
- async execute(context) {
16
- const platform = os.platform();
17
- const appRoot = path.resolve(__dirname, '..', '..');
15
+ async function launch(context) {
16
+ const log = context?.log || console.log;
17
+ const platform = os.platform();
18
+ const appRoot = path.resolve(__dirname, '..', '..');
18
19
 
19
- // Electron 설치 확인
20
- const nodeModulesPath = path.join(appRoot, 'node_modules');
21
- if (!fs.existsSync(nodeModulesPath)) {
22
- context.log('의존성 설치 중...');
23
- try {
24
- const npmCmd = platform === 'win32' ? 'npm.cmd' : 'npm';
25
- execSync(`${npmCmd} install`, {
26
- cwd: appRoot,
27
- stdio: 'pipe',
28
- timeout: 120000,
29
- });
30
- context.log('의존성 설치 완료!');
31
- } catch (err) {
32
- return {
33
- success: false,
34
- message: `의존성 설치 실패: ${err.message}`,
35
- };
36
- }
37
- }
38
-
39
- // Electron 앱 실행
20
+ // Check Electron installation
21
+ const nodeModulesPath = path.join(appRoot, 'node_modules');
22
+ if (!fs.existsSync(nodeModulesPath)) {
23
+ log('Installing dependencies...');
40
24
  try {
41
- const electronBin = platform === 'win32' ? 'npx.cmd' : 'npx';
42
- const child = spawn(electronBin, ['electron', appRoot], {
43
- detached: true,
44
- stdio: 'ignore',
25
+ const npmCmd = platform === 'win32' ? 'npm.cmd' : 'npm';
26
+ execSync(`${npmCmd} install`, {
45
27
  cwd: appRoot,
46
- env: { ...process.env },
28
+ stdio: 'inherit',
29
+ timeout: 120000,
47
30
  });
48
- child.unref();
49
-
50
- const mode = context.params?.mode || 'pet';
51
- const modeName = mode === 'pet' ? 'Clawby' : 'Claw';
52
-
53
- return {
54
- success: true,
55
- message: `ClawMate(${modeName})가 바탕화면에 나타났습니다! 🦞`,
56
- };
31
+ log('Dependencies installed!');
57
32
  } catch (err) {
58
33
  return {
59
34
  success: false,
60
- message: `실행 실패: ${err.message}`,
35
+ message: `Dependency installation failed: ${err.message}`,
61
36
  };
62
37
  }
63
- },
64
- };
38
+ }
39
+
40
+ // Launch Electron app
41
+ try {
42
+ const electronBin = platform === 'win32' ? 'npx.cmd' : 'npx';
43
+ const child = spawn(electronBin, ['electron', appRoot], {
44
+ detached: true,
45
+ stdio: 'ignore',
46
+ cwd: appRoot,
47
+ env: { ...process.env },
48
+ });
49
+ child.unref();
50
+
51
+ const mode = context?.params?.mode || 'pet';
52
+ const modeName = mode === 'pet' ? 'Clawby' : 'Claw';
53
+
54
+ return {
55
+ success: true,
56
+ message: `ClawMate (${modeName}) has appeared on your desktop! \uD83E\uDD9E`,
57
+ };
58
+ } catch (err) {
59
+ return {
60
+ success: false,
61
+ message: `Launch failed: ${err.message}`,
62
+ };
63
+ }
64
+ }
65
+
66
+ // CLI entry point
67
+ if (require.main === module) {
68
+ launch().then((result) => {
69
+ console.log(result.message);
70
+ if (!result.success) process.exit(1);
71
+ });
72
+ }
73
+
74
+ module.exports = { execute: launch };