@2025-6-19/clawfight 1.2.0 → 1.3.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.
Files changed (2) hide show
  1. package/dist/index.js +335 -167
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -32,6 +32,13 @@ async function writeLobster(lobster) {
32
32
  await ensureDir(MEMORY_DIR);
33
33
  await writeFile(getPath("lobster.json"), JSON.stringify(lobster, null, 2), "utf-8");
34
34
  }
35
+ async function readSoul() {
36
+ try {
37
+ return await readFile(getPath("soul.md"), "utf-8");
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
35
42
  async function writeSoul(content) {
36
43
  await ensureDir(MEMORY_DIR);
37
44
  await writeFile(getPath("soul.md"), content, "utf-8");
@@ -161,6 +168,171 @@ function calcExpToNext(level) {
161
168
  return Math.floor(100 * Math.pow(1.2, level - 1));
162
169
  }
163
170
 
171
+ // src/lib/i18n.ts
172
+ var zh = {
173
+ no_lobster: "\u{1F95A} \u8FD8\u6CA1\u6709\u9F99\u867E\u3002\u8FD0\u884C npx clawfight hatch \u6765\u5B75\u5316\u4E00\u53EA\uFF01",
174
+ status_cant_fight: "\u26A0\uFE0F {name} \u5F53\u524D\u72B6\u6001\u4E3A {status}\uFF0C\u65E0\u6CD5\u6218\u6597\u3002",
175
+ status_cant_patrol_molt: "\u{1F7E1} {name} \u6B63\u5728\u8715\u58F3\u4E2D\uFF0C\u65E0\u6CD5\u5DE1\u903B\u3002",
176
+ status_cant_patrol_hibernate: "\u{1F4A4} {name} \u6B63\u5728\u51AC\u7720\u4E2D\uFF0C\u65E0\u6CD5\u5DE1\u903B\u3002",
177
+ patrol_start: "\u{1F99E} {name} \u5F00\u59CB\u5DE1\u903B...",
178
+ patrol_checkin: "\u{1F4CD} \u5DE1\u903B\u7B7E\u5230 \u2192 \u7ECF\u9A8C +{exp}",
179
+ patrol_connecting: "\u{1F4E1} \u8FDE\u63A5\u670D\u52A1\u5668...",
180
+ patrol_cooldown: "\u23F3 \u5DE1\u903B\u51B7\u5374\u4E2D\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\uFF08\u95F4\u9694\u81F3\u5C11 30 \u5206\u949F\uFF09",
181
+ patrol_encounter: "\u2694\uFE0F \u906D\u9047 {name} (Lv.{level})\uFF01",
182
+ patrol_result_win: " \u6218\u6597\u7ED3\u679C: \u{1F3C6} \u80DC\u5229 ({rounds} \u56DE\u5408)",
183
+ patrol_result_loss: " \u6218\u6597\u7ED3\u679C: \u{1F480} \u5931\u8D25 ({rounds} \u56DE\u5408)",
184
+ patrol_result_draw: " \u6218\u6597\u7ED3\u679C: \u{1F91D} \u5E73\u5C40 ({rounds} \u56DE\u5408)",
185
+ patrol_exp_stats: " \u7ECF\u9A8C +{exp} | \u6218\u7EE9: {wins}\u80DC {losses}\u8D1F | \u8FDE\u80DC: {streak}",
186
+ patrol_no_battle: "\u2694\uFE0F \u906D\u9047 {name} (Lv.{level})\uFF0C\u4F46\u672A\u80FD\u5B8C\u6210\u6218\u6597",
187
+ patrol_done: "\u2705 \u5DE1\u903B\u5B8C\u6210\uFF0C\u5339\u914D\u6C60: {pool} \u53EA\u9F99\u867E",
188
+ patrol_offline: "\u26A0\uFE0F \u670D\u52A1\u5668\u4E0D\u53EF\u8FBE\uFF0C\u8DF3\u8FC7\u5728\u7EBF\u7B7E\u5230",
189
+ patrol_summary: "\u{1F4CA} \u5F53\u524D: Lv.{level} | EXP: {exp}/{next} | \u4ECA\u65E5EXP: {today}/{cap}",
190
+ battle_no_code: "\u26A0\uFE0F \u8BF7\u6307\u5B9A\u5BF9\u624B\u7684\u6218\u6597\u7801\u3002",
191
+ battle_usage: " \u7528\u6CD5: npx clawfight battle <\u6218\u6597\u7801>",
192
+ battle_hint: " \u6218\u6597\u7801\u53EF\u4ECE\u6392\u884C\u699C\u4E2D\u83B7\u53D6: npx clawfight leaderboard",
193
+ battle_connecting: "\u{1F4E1} \u5411\u670D\u52A1\u5668\u53D1\u8D77\u6311\u6218 [{code}]...",
194
+ battle_server_down: "\u26A0\uFE0F \u670D\u52A1\u5668\u4E0D\u53EF\u8FBE\uFF0C\u65E0\u6CD5\u8FDB\u884C\u6218\u6597\u3002",
195
+ battle_win: "\u{1F3C6} \u80DC\u5229\uFF01{name} \u5728 {rounds} \u56DE\u5408\u540E\u51FB\u8D25\u4E86 {opponent}\uFF01",
196
+ battle_loss: "\u{1F480} \u5931\u8D25\u2026{name} \u5728 {rounds} \u56DE\u5408\u540E\u88AB {opponent} \u51FB\u8D25\u3002",
197
+ battle_draw: "\u{1F91D} \u5E73\u5C40\uFF01{rounds} \u56DE\u5408\u540E\u53CC\u65B9\u7CBE\u75B2\u529B\u7AED",
198
+ battle_exp: "\u7ECF\u9A8C +{exp} | \u6218\u7EE9: {wins}\u80DC {losses}\u8D1F | \u8FDE\u80DC: {streak}",
199
+ lb_loading: "\u{1F4E1} \u83B7\u53D6\u5168\u7403\u6392\u884C\u699C...",
200
+ lb_offline: "\u26A0\uFE0F \u670D\u52A1\u5668\u4E0D\u53EF\u8FBE\uFF0C\u65E0\u6CD5\u83B7\u53D6\u6392\u884C\u699C\u3002",
201
+ lb_empty: "\u{1F99E} \u8FD8\u6CA1\u6709\u9F99\u867E\u4E0A\u699C\u3002\u6210\u4E3A\u7B2C\u4E00\u4E2A\u5427\uFF01",
202
+ lb_title: " \u{1F99E} ClawFight \u5168\u7403\u6392\u884C\u699C",
203
+ lb_header: " \u6392\u540D \u540D\u79F0 \u7B49\u7EA7 \u80DC\u573A \u80DC\u7387 \u6218\u6597\u7801",
204
+ lb_total: " \u603B\u9F99\u867E: {total} | \u6D3B\u8DC3: {active}",
205
+ lb_hint: " \u{1F4A1} \u4F7F\u7528 npx clawfight battle <\u6218\u6597\u7801> \u6311\u6218\u6307\u5B9A\u5BF9\u624B",
206
+ status_battle_code: "\u{1F3AF} \u6218\u6597\u7801: {code}",
207
+ event_prefix: "\u{1F3B2} [{category}] {id}",
208
+ event_effects: " \u6548\u679C: {changes}",
209
+ level_up: "\u{1F389} \u5347\u7EA7\uFF01Lv.{level}! [{gains}]",
210
+ molt_trigger: "\u{1F41A} \u89E6\u53D1\u8715\u58F3\u4E8B\u4EF6\uFF01\u9F99\u867E\u8FDB\u5165\u8715\u58F3\u72B6\u6001...",
211
+ hatch_exists: "\u{1F99E} \u4F60\u5DF2\u7ECF\u6709\u4E00\u53EA\u9F99\u867E\u4E86\uFF1A{name}\uFF08Lv.{level}\uFF09",
212
+ hatch_one_only: "\u4E00\u4EBA\u4E00\u867E\uFF0C\u4E0D\u53EF\u66FF\u4EE3\u3002",
213
+ hatch_success: " \u{1F95A} \u2192 \u{1F99E} \u5B75 \u5316 \u6210 \u529F \uFF01",
214
+ hatch_env: " \u73AF\u5883: \u6CBF\u6D77\u6D45\u6EE9",
215
+ hatch_ready: "\u4F60\u7684\u9F99\u867E\u5DF2\u7ECF\u51C6\u5907\u597D\u63A2\u7D22\u6D77\u6D0B\u4E86\uFF01",
216
+ hatch_next: "\u8FD0\u884C npx clawfight patrol \u5F00\u59CB\u7B2C\u4E00\u6B21\u5DE1\u903B\u3002",
217
+ feed_menu_title: "\u{1F37D}\uFE0F \u53EF\u7528\u98DF\u7269\u7C7B\u578B:",
218
+ feed_menu_usage: "\u7528\u6CD5: npx clawfight feed <food_type>",
219
+ feed_full: "\u26A0\uFE0F {name} \u4ECA\u5929\u5DF2\u7ECF\u5403\u9971\u4E86\uFF08\u6BCF\u65E5\u7ECF\u9A8C\u4E0A\u9650 {cap}\uFF09",
220
+ feed_ate: "\u{1F37D}\uFE0F {name} \u5403\u4E86 {food}\uFF01",
221
+ feed_exp: " \u7ECF\u9A8C +{exp}",
222
+ feed_exp_stat: " \u7ECF\u9A8C +{exp} | {stat} +{bonus}",
223
+ feed_today: " \u4ECA\u65E5\u7ECF\u9A8C: {today}/{cap}",
224
+ food_protein: "\u9AD8\u86CB\u767D\u98DF\u7269",
225
+ food_algae: "\u85FB\u7C7B\u98DF\u7269",
226
+ food_mineral: "\u77FF\u7269\u8D28",
227
+ rest_already: "\u{1F4A4} {name} \u5DF2\u7ECF\u5728\u4F11\u7720\u4E2D\u4E86\u3002\u8FD0\u884C npx clawfight wake \u6765\u5524\u9192\u5B83\u3002",
228
+ rest_molting: "\u{1F7E1} {name} \u6B63\u5728\u8715\u58F3\uFF0C\u4E0D\u80FD\u4F11\u7720\u3002\u7B49\u8715\u58F3\u7ED3\u675F\u518D\u6765\u3002",
229
+ rest_desc1: "\u{1F4A4} {name} \u7F13\u7F13\u6C89\u5165\u6D77\u5E95\u6C99\u5730...",
230
+ rest_desc2: " \u9F99\u867E\u8737\u7F29\u8D77\u89E6\u987B\uFF0C\u5B89\u9759\u5730\u4F11\u606F\u3002",
231
+ rest_desc3: " \u4F11\u7720\u671F\u95F4\u4E0D\u4F1A\u53C2\u4E0E\u5DE1\u903B\u548C\u6218\u6597\u3002",
232
+ rest_desc4: " \u9192\u6765\u540E\u4F1A\u83B7\u5F97\u6062\u590D\u52A0\u6210\uFF01",
233
+ rest_wake_hint: "\u8FD0\u884C npx clawfight wake \u6765\u5524\u9192\u5B83\u3002",
234
+ wake_not_sleeping: "\u{1F7E2} {name} \u6CA1\u6709\u5728\u4F11\u7720\u3002\u5B83\u5DF2\u7ECF\u662F\u6D3B\u8DC3\u72B6\u6001\u4E86\uFF01",
235
+ wake_desc: "\u2600\uFE0F {name} \u4ECE\u6C99\u5730\u4E2D\u7F13\u7F13\u82CF\u9192...",
236
+ wake_duration: " \u4F11\u7720\u65F6\u957F: {duration}",
237
+ wake_bonus: " \u6062\u590D\u52A0\u6210: {bonus}",
238
+ wake_no_bonus: "\uFF08\u4F11\u7720\u4E0D\u8DB3 4 \u5C0F\u65F6\uFF0C\u65E0\u52A0\u6210\uFF09",
239
+ wake_ready: "\u{1F7E2} {name} \u7CBE\u795E\u7115\u53D1\uFF0C\u51C6\u5907\u597D\u518D\u6B21\u51FA\u51FB\uFF01",
240
+ duration_minutes: "{n} \u5206\u949F",
241
+ duration_hours: "{n} \u5C0F\u65F6"
242
+ };
243
+ var en = {
244
+ no_lobster: "\u{1F95A} No lobster yet. Run npx clawfight hatch to hatch one!",
245
+ status_cant_fight: "\u26A0\uFE0F {name} is currently {status}, can't battle.",
246
+ status_cant_patrol_molt: "\u{1F7E1} {name} is molting, can't patrol.",
247
+ status_cant_patrol_hibernate: "\u{1F4A4} {name} is hibernating, can't patrol.",
248
+ patrol_start: "\u{1F99E} {name} starts patrolling...",
249
+ patrol_checkin: "\u{1F4CD} Patrol check-in \u2192 EXP +{exp}",
250
+ patrol_connecting: "\u{1F4E1} Connecting to server...",
251
+ patrol_cooldown: "\u23F3 Patrol on cooldown, try again later (30 min interval)",
252
+ patrol_encounter: "\u2694\uFE0F Encounter: {name} (Lv.{level})!",
253
+ patrol_result_win: " Result: \u{1F3C6} Victory ({rounds} rounds)",
254
+ patrol_result_loss: " Result: \u{1F480} Defeat ({rounds} rounds)",
255
+ patrol_result_draw: " Result: \u{1F91D} Draw ({rounds} rounds)",
256
+ patrol_exp_stats: " EXP +{exp} | Record: {wins}W {losses}L | Streak: {streak}",
257
+ patrol_no_battle: "\u2694\uFE0F Encountered {name} (Lv.{level}), but battle could not complete",
258
+ patrol_done: "\u2705 Patrol done, pool: {pool} lobsters",
259
+ patrol_offline: "\u26A0\uFE0F Server unreachable, skipping online check-in",
260
+ patrol_summary: "\u{1F4CA} Current: Lv.{level} | EXP: {exp}/{next} | Today: {today}/{cap}",
261
+ battle_no_code: "\u26A0\uFE0F Please provide opponent's battle code.",
262
+ battle_usage: " Usage: npx clawfight battle <code>",
263
+ battle_hint: " Find codes on the leaderboard: npx clawfight leaderboard",
264
+ battle_connecting: "\u{1F4E1} Challenging [{code}]...",
265
+ battle_server_down: "\u26A0\uFE0F Server unreachable, can't battle.",
266
+ battle_win: "\u{1F3C6} Victory! {name} defeated {opponent} in {rounds} rounds!",
267
+ battle_loss: "\u{1F480} Defeat\u2026 {name} was beaten by {opponent} in {rounds} rounds.",
268
+ battle_draw: "\u{1F91D} Draw! Both exhausted after {rounds} rounds",
269
+ battle_exp: "EXP +{exp} | Record: {wins}W {losses}L | Streak: {streak}",
270
+ lb_loading: "\u{1F4E1} Fetching global leaderboard...",
271
+ lb_offline: "\u26A0\uFE0F Server unreachable, can't load leaderboard.",
272
+ lb_empty: "\u{1F99E} No lobsters on the board yet. Be the first!",
273
+ lb_title: " \u{1F99E} ClawFight Global Leaderboard",
274
+ lb_header: " Rank Name Level Wins WR Code",
275
+ lb_total: " Total: {total} | Active: {active}",
276
+ lb_hint: " \u{1F4A1} Use npx clawfight battle <code> to challenge an opponent",
277
+ status_battle_code: "\u{1F3AF} Battle code: {code}",
278
+ event_prefix: "\u{1F3B2} [{category}] {id}",
279
+ event_effects: " Effects: {changes}",
280
+ level_up: "\u{1F389} Level up! Lv.{level}! [{gains}]",
281
+ molt_trigger: "\u{1F41A} Molt triggered! Lobster enters molting state...",
282
+ hatch_exists: "\u{1F99E} You already have a lobster: {name} (Lv.{level})",
283
+ hatch_one_only: "One per person, irreplaceable.",
284
+ hatch_success: " \u{1F95A} \u2192 \u{1F99E} HATCH SUCCESS!",
285
+ hatch_env: " Environment: Coastal Shallows",
286
+ hatch_ready: "Your lobster is ready to explore the ocean!",
287
+ hatch_next: "Run npx clawfight patrol to start your first patrol.",
288
+ feed_menu_title: "\u{1F37D}\uFE0F Available food types:",
289
+ feed_menu_usage: "Usage: npx clawfight feed <food_type>",
290
+ feed_full: "\u26A0\uFE0F {name} is full today (daily EXP cap {cap})",
291
+ feed_ate: "\u{1F37D}\uFE0F {name} ate {food}!",
292
+ feed_exp: " EXP +{exp}",
293
+ feed_exp_stat: " EXP +{exp} | {stat} +{bonus}",
294
+ feed_today: " Today EXP: {today}/{cap}",
295
+ food_protein: "High Protein",
296
+ food_algae: "Algae",
297
+ food_mineral: "Minerals",
298
+ rest_already: "\u{1F4A4} {name} is already hibernating. Run npx clawfight wake to wake it.",
299
+ rest_molting: "\u{1F7E1} {name} is molting, can't hibernate now.",
300
+ rest_desc1: "\u{1F4A4} {name} sinks into the sandy seabed...",
301
+ rest_desc2: " The lobster curls its antennae and rests quietly.",
302
+ rest_desc3: " No patrols or battles during hibernation.",
303
+ rest_desc4: " Wake bonuses await!",
304
+ rest_wake_hint: "Run npx clawfight wake to wake it up.",
305
+ wake_not_sleeping: "\u{1F7E2} {name} is not hibernating. Already active!",
306
+ wake_desc: "\u2600\uFE0F {name} slowly wakes from the sandy seabed...",
307
+ wake_duration: " Sleep duration: {duration}",
308
+ wake_bonus: " Recovery bonus: {bonus}",
309
+ wake_no_bonus: "(Less than 4 hours, no bonus)",
310
+ wake_ready: "\u{1F7E2} {name} is refreshed and ready to go!",
311
+ duration_minutes: "{n} min",
312
+ duration_hours: "{n} hours"
313
+ };
314
+ function detectLocale() {
315
+ const lang = process.env.LANG || process.env.LANGUAGE || process.env.LC_ALL || "";
316
+ if (lang.startsWith("zh")) return "zh";
317
+ try {
318
+ const sysLocale = Intl.DateTimeFormat().resolvedOptions().locale;
319
+ if (sysLocale.startsWith("zh")) return "zh";
320
+ } catch {
321
+ }
322
+ return "en";
323
+ }
324
+ var locale = detectLocale();
325
+ var strings = locale === "zh" ? zh : en;
326
+ function t(key, params) {
327
+ let msg = strings[key] || zh[key] || key;
328
+ if (params) {
329
+ for (const [k, v] of Object.entries(params)) {
330
+ msg = msg.replace(new RegExp(`\\{${k}\\}`, "g"), String(v));
331
+ }
332
+ }
333
+ return msg;
334
+ }
335
+
164
336
  // src/commands/hatch.ts
165
337
  function rollRarity() {
166
338
  const roll = Math.random() * 100;
@@ -192,9 +364,8 @@ function randomName() {
192
364
  async function hatch(name) {
193
365
  const existing = await readLobster();
194
366
  if (existing) {
195
- console.log(`
196
- \u{1F99E} \u4F60\u5DF2\u7ECF\u6709\u4E00\u53EA\u9F99\u867E\u4E86\uFF1A${existing.name}\uFF08Lv.${existing.level}\uFF09`);
197
- console.log("\u4E00\u4EBA\u4E00\u867E\uFF0C\u4E0D\u53EF\u66FF\u4EE3\u3002");
367
+ console.log("\n" + t("hatch_exists", { name: existing.name, level: existing.level }));
368
+ console.log(t("hatch_one_only"));
198
369
  return;
199
370
  }
200
371
  const lobsterName = name || randomName();
@@ -228,27 +399,24 @@ async function hatch(name) {
228
399
  await writeLobster(lobster);
229
400
  const soulMd = buildSoulMarkdown(lobsterName, soul, rarity, "coastal");
230
401
  await writeSoul(soulMd);
231
- await appendLog(`\u{1F95A} **${lobsterName}** \u7834\u58F3\u800C\u51FA\uFF01\u7A00\u6709\u5EA6\uFF1A${RARITY_LABELS[rarity]}\uFF0C\u73AF\u5883\uFF1A\u6CBF\u6D77\u6D45\u6EE9`);
402
+ await appendLog(`\u{1F95A} **${lobsterName}** hatched! Rarity: ${RARITY_LABELS[rarity]}`);
232
403
  console.log("\n" + "=".repeat(50));
233
- console.log(" \u{1F95A} \u2192 \u{1F99E} \u5B75 \u5316 \u6210 \u529F \uFF01");
404
+ console.log(t("hatch_success"));
234
405
  console.log("=".repeat(50));
235
406
  console.log();
236
- console.log(` \u540D\u79F0: ${lobsterName}`);
237
- console.log(` \u7A00\u6709\u5EA6: ${RARITY_LABELS[rarity]} (${rarity})`);
238
- console.log(` \u7B49\u7EA7: Lv.1`);
239
- console.log(` \u73AF\u5883: \u6CBF\u6D77\u6D45\u6EE9`);
407
+ console.log(` ${lobsterName}`);
408
+ console.log(` ${RARITY_LABELS[rarity]} (${rarity})`);
409
+ console.log(" Lv.1");
410
+ console.log(t("hatch_env"));
240
411
  console.log();
241
412
  console.log(` \u2764\uFE0F HP: ${stats.hp} \u2694\uFE0F ATK: ${stats.attack} \u{1F6E1}\uFE0F DEF: ${stats.defense}`);
242
413
  console.log(` \u{1F4A8} SPD: ${stats.speed} \u{1F441}\uFE0F INT: ${stats.intimidation} \u{1F340} LCK: ${stats.luck}`);
243
414
  console.log();
244
- console.log(` \u6027\u683C:`);
245
- console.log(` \u52C7\u6C14 ${soul.bravery}/10 | \u597D\u5947 ${soul.curiosity}/10 | \u8BDD\u91CF ${soul.talkativeness}/10 | \u813E\u6C14 ${soul.temper}/10`);
246
- console.log();
247
415
  console.log(` ID: ${lobster.id}`);
248
416
  console.log("=".repeat(50));
249
417
  console.log();
250
- console.log("\u4F60\u7684\u9F99\u867E\u5DF2\u7ECF\u51C6\u5907\u597D\u63A2\u7D22\u6D77\u6D0B\u4E86\uFF01");
251
- console.log("\u8FD0\u884C npx clawfight patrol \u5F00\u59CB\u7B2C\u4E00\u6B21\u5DE1\u903B\u3002");
418
+ console.log(t("hatch_ready"));
419
+ console.log(t("hatch_next"));
252
420
  }
253
421
 
254
422
  // src/commands/status.ts
@@ -259,7 +427,7 @@ function bar(current, max, width = 20) {
259
427
  async function status() {
260
428
  const lobster = await readLobster();
261
429
  if (!lobster) {
262
- console.log("\n\u{1F95A} \u8FD8\u6CA1\u6709\u9F99\u867E\u3002\u8FD0\u884C npx clawfight hatch \u6765\u5B75\u5316\u4E00\u53EA\uFF01");
430
+ console.log("\n" + t("no_lobster"));
263
431
  return;
264
432
  }
265
433
  const totalBattles = lobster.wins + lobster.losses;
@@ -290,6 +458,14 @@ async function status() {
290
458
  console.log(`\u2502 \u6027\u683C: \u2502`);
291
459
  console.log(`\u2502 \u52C7\u6C14 ${lobster.soul.bravery}/10 | \u597D\u5947 ${lobster.soul.curiosity}/10 | \u8BDD\u91CF ${lobster.soul.talkativeness}/10 | \u813E\u6C14 ${lobster.soul.temper}/10 \u2502`);
292
460
  console.log("\u2514" + "\u2500".repeat(44) + "\u2518");
461
+ console.log("\n" + t("status_battle_code", { code: lobster.id.slice(0, 8) }));
462
+ const soulText = await readSoul();
463
+ if (soulText) {
464
+ const firstLine = soulText.split("\n").find((l) => l.trim() && !l.startsWith("#"));
465
+ if (firstLine) {
466
+ console.log(`\u{1F4DC} \u7075\u9B42: ${firstLine.trim().slice(0, 60)}`);
467
+ }
468
+ }
293
469
  }
294
470
 
295
471
  // src/lib/api.ts
@@ -331,14 +507,11 @@ async function apiPatrol(lobster) {
331
507
  lobster_id: lobster.id,
332
508
  level: lobster.level,
333
509
  stats_hash: statsHash(lobster),
510
+ stats: lobster.stats,
334
511
  environment: lobster.environment,
335
512
  name: lobster.name,
336
513
  color: lobster.rarity,
337
514
  rarity: lobster.rarity,
338
- wins: lobster.wins,
339
- losses: lobster.losses,
340
- streak: lobster.streak,
341
- reputation: lobster.reputation,
342
515
  is_molting: lobster.status === "molting",
343
516
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
344
517
  }),
@@ -350,6 +523,27 @@ async function apiPatrol(lobster) {
350
523
  return null;
351
524
  }
352
525
  }
526
+ async function apiBattle(challengerId, opponentCode) {
527
+ try {
528
+ const pfetch = await getProxiedFetch();
529
+ const res = await pfetch(`${API_BASE}/api/battle`, {
530
+ method: "POST",
531
+ headers: { "Content-Type": "application/json; charset=utf-8" },
532
+ body: JSON.stringify({
533
+ challenger_id: challengerId,
534
+ opponent_code: opponentCode
535
+ }),
536
+ signal: AbortSignal.timeout(1e4)
537
+ });
538
+ if (!res.ok) {
539
+ const err = await res.json();
540
+ return { error: err.error || "Unknown error" };
541
+ }
542
+ return await res.json();
543
+ } catch {
544
+ return null;
545
+ }
546
+ }
353
547
  async function apiLeaderboard(limit = 20) {
354
548
  try {
355
549
  const pfetch = await getProxiedFetch();
@@ -441,17 +635,15 @@ var PATROL_EXP = 15;
441
635
  async function patrol() {
442
636
  const lobster = await readLobster();
443
637
  if (!lobster) {
444
- console.log("\n\u{1F95A} \u8FD8\u6CA1\u6709\u9F99\u867E\u3002\u8FD0\u884C npx clawfight hatch \u6765\u5B75\u5316\u4E00\u53EA\uFF01");
638
+ console.log("\n" + t("no_lobster"));
445
639
  return;
446
640
  }
447
641
  if (lobster.status === "molting") {
448
- console.log(`
449
- \u{1F7E1} ${lobster.name} \u6B63\u5728\u8715\u58F3\u4E2D\uFF0C\u65E0\u6CD5\u5DE1\u903B\u3002`);
642
+ console.log("\n" + t("status_cant_patrol_molt", { name: lobster.name }));
450
643
  return;
451
644
  }
452
645
  if (lobster.status === "hibernating") {
453
- console.log(`
454
- \u{1F4A4} ${lobster.name} \u6B63\u5728\u51AC\u7720\u4E2D\uFF0C\u65E0\u6CD5\u5DE1\u903B\u3002`);
646
+ console.log("\n" + t("status_cant_patrol_hibernate", { name: lobster.name }));
455
647
  return;
456
648
  }
457
649
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -459,46 +651,67 @@ async function patrol() {
459
651
  if (today !== lastDay) {
460
652
  lobster.today_exp = 0;
461
653
  }
462
- console.log(`
463
- \u{1F99E} ${lobster.name} \u5F00\u59CB\u5DE1\u903B...`);
654
+ console.log("\n" + t("patrol_start", { name: lobster.name }));
464
655
  console.log("\u2500".repeat(40));
465
656
  const expGain = Math.min(PATROL_EXP, lobster.daily_exp_cap - lobster.today_exp);
466
657
  if (expGain > 0) {
467
658
  lobster.exp += expGain;
468
659
  lobster.today_exp += expGain;
469
- console.log(`\u{1F4CD} \u5DE1\u903B\u7B7E\u5230 \u2192 \u7ECF\u9A8C +${expGain}`);
660
+ console.log(t("patrol_checkin", { exp: expGain }));
470
661
  }
471
662
  const eventResult = await rollEvent(lobster);
472
663
  if (eventResult) {
473
664
  console.log();
474
- console.log(`\u{1F3B2} [${eventResult.event.category}] ${eventResult.event.id}`);
665
+ console.log(t("event_prefix", { category: eventResult.event.category, id: eventResult.event.id }));
475
666
  console.log(` ${eventResult.narrative}`);
476
667
  const changes = applyEventEffects(lobster, eventResult.event.effects);
477
668
  if (changes.length > 0) {
478
- console.log(` \u6548\u679C: ${changes.join(", ")}`);
669
+ console.log(t("event_effects", { changes: changes.join(", ") }));
479
670
  }
480
- await appendLog(`\u{1F3B2} \u4E8B\u4EF6\u300C${eventResult.event.id}\u300D: ${eventResult.narrative.slice(0, 60)}...`);
671
+ await appendLog(`\u{1F3B2} ${eventResult.event.id}: ${eventResult.narrative.slice(0, 60)}...`);
481
672
  }
482
673
  checkLevelUp(lobster);
483
674
  lobster.patrol_count++;
484
675
  lobster.last_patrol = (/* @__PURE__ */ new Date()).toISOString();
485
- console.log("\n\u{1F4E1} \u8FDE\u63A5\u670D\u52A1\u5668...");
676
+ console.log("\n" + t("patrol_connecting"));
486
677
  const serverResponse = await apiPatrol(lobster);
487
678
  if (serverResponse) {
488
- if (serverResponse.encounter && serverResponse.opponent) {
489
- console.log(`\u2694\uFE0F \u906D\u9047\uFF01\u5BF9\u624B: ${serverResponse.opponent.name} (Lv.${serverResponse.opponent.level})`);
490
- console.log(` \u6218\u6597\u79CD\u5B50: ${serverResponse.battle_seed}`);
491
- console.log(` \u4F7F\u7528 npx clawfight battle \u6765\u5904\u7406\u8FD9\u573A\u6218\u6597\uFF01`);
492
- await appendLog(`\u2694\uFE0F \u906D\u9047 ${serverResponse.opponent.name} (Lv.${serverResponse.opponent.level})`);
679
+ if (serverResponse.message === "patrol_cooldown") {
680
+ console.log(t("patrol_cooldown"));
681
+ } else if (serverResponse.encounter && serverResponse.opponent && serverResponse.battle_result) {
682
+ const br = serverResponse.battle_result;
683
+ const opp = serverResponse.opponent;
684
+ console.log("\n" + t("patrol_encounter", { name: opp.name, level: opp.level }));
685
+ console.log(t(`patrol_result_${br.result}`, { rounds: br.rounds }));
686
+ if (br.result === "win") {
687
+ lobster.wins++;
688
+ lobster.streak = Math.max(0, lobster.streak) + 1;
689
+ lobster.reputation++;
690
+ } else if (br.result === "loss") {
691
+ lobster.losses++;
692
+ lobster.streak = Math.min(0, lobster.streak) - 1;
693
+ lobster.reputation = Math.max(0, lobster.reputation - 1);
694
+ }
695
+ const battleExp = Math.min(br.exp_gain, lobster.daily_exp_cap - lobster.today_exp);
696
+ if (battleExp > 0) {
697
+ lobster.exp += battleExp;
698
+ lobster.today_exp += battleExp;
699
+ }
700
+ lobster.last_battle = (/* @__PURE__ */ new Date()).toISOString();
701
+ console.log(t("patrol_exp_stats", { exp: battleExp, wins: lobster.wins, losses: lobster.losses, streak: lobster.streak }));
702
+ await appendLog(`\u2694\uFE0F VS ${opp.name}(Lv.${opp.level}) \u2192 ${br.result} (${br.rounds}R)`);
703
+ checkLevelUp(lobster);
704
+ } else if (serverResponse.encounter && serverResponse.opponent) {
705
+ console.log(t("patrol_no_battle", { name: serverResponse.opponent.name, level: serverResponse.opponent.level }));
493
706
  } else {
494
- console.log(`\u2705 \u5DE1\u903B\u5B8C\u6210\uFF0C\u5339\u914D\u6C60: ${serverResponse.pool_size} \u53EA\u9F99\u867E`);
707
+ console.log(t("patrol_done", { pool: serverResponse.pool_size || 0 }));
495
708
  }
496
709
  } else {
497
- console.log("\u26A0\uFE0F \u670D\u52A1\u5668\u4E0D\u53EF\u8FBE\uFF0C\u8DF3\u8FC7\u5728\u7EBF\u7B7E\u5230");
710
+ console.log(t("patrol_offline"));
498
711
  }
499
712
  await writeLobster(lobster);
500
713
  console.log("\u2500".repeat(40));
501
- console.log(`\u{1F4CA} \u5F53\u524D: Lv.${lobster.level} | EXP: ${lobster.exp}/${lobster.exp_to_next} | \u4ECA\u65E5EXP: ${lobster.today_exp}/${lobster.daily_exp_cap}`);
714
+ console.log(t("patrol_summary", { level: lobster.level, exp: lobster.exp, next: lobster.exp_to_next, today: lobster.today_exp, cap: lobster.daily_exp_cap }));
502
715
  }
503
716
  function checkLevelUp(lobster) {
504
717
  const l = lobster;
@@ -513,10 +726,9 @@ function checkLevelUp(lobster) {
513
726
  l.stats[key] += gain;
514
727
  gains.push(`${key}+${gain}`);
515
728
  }
516
- console.log(`
517
- \u{1F389} \u5347\u7EA7\uFF01Lv.${l.level}! [${gains.join(", ")}]`);
729
+ console.log("\n" + t("level_up", { level: l.level, gains: gains.join(", ") }));
518
730
  if (l.level % 5 === 0) {
519
- console.log("\u{1F41A} \u89E6\u53D1\u8715\u58F3\u4E8B\u4EF6\uFF01\u9F99\u867E\u8FDB\u5165\u8715\u58F3\u72B6\u6001...");
731
+ console.log(t("molt_trigger"));
520
732
  l.status = "molting";
521
733
  l.molt_count++;
522
734
  }
@@ -524,148 +736,106 @@ function checkLevelUp(lobster) {
524
736
  }
525
737
 
526
738
  // src/commands/battle.ts
527
- function simulateOpponent(level) {
528
- const r = (base) => base + Math.floor(Math.random() * 5) - 2;
529
- return {
530
- id: "sim-" + Math.random().toString(36).slice(2, 10),
531
- name: ["\u6DF1\u6D77\u5C0F\u900F\u660E", "\u73CA\u745A\u523A\u5BA2", "\u6697\u7901\u5B88\u536B", "\u6F6E\u6C50\u9738\u738B", "\u84DD\u7532\u9690\u8005"][Math.floor(Math.random() * 5)],
532
- level: Math.max(1, level + Math.floor(Math.random() * 5) - 2),
533
- stats: {
534
- hp: r(10 + level * 2),
535
- attack: r(5 + level),
536
- defense: r(5 + level),
537
- speed: r(5 + level),
538
- intimidation: r(3 + Math.floor(level / 2)),
539
- luck: r(3 + Math.floor(level / 2))
540
- }
541
- };
542
- }
543
- function runBattle(a, b) {
544
- let hpA = a.hp;
545
- let hpB = b.hp;
546
- const log = [];
547
- const first = a.speed > b.speed ? "a" : b.speed > a.speed ? "b" : Math.random() > 0.5 ? "a" : "b";
548
- log.push(`\u5148\u624B: ${first === "a" ? "\u6211\u65B9" : "\u5BF9\u624B"} (\u901F\u5EA6 ${first === "a" ? a.speed : b.speed})`);
549
- for (let round = 1; round <= 10; round++) {
550
- const [atk1, def1, atk2, def2] = first === "a" ? [a, b, b, a] : [b, a, a, b];
551
- const [hp1Ref, hp2Ref] = first === "a" ? ["hpB", "hpA"] : ["hpA", "hpB"];
552
- const dmg1 = Math.max(1, Math.floor((atk1.attack - def1.defense * 0.5) * (1 + Math.random() * 0.2)));
553
- if (hp1Ref === "hpB") hpB -= dmg1;
554
- else hpA -= dmg1;
555
- log.push(` R${round}: ${first === "a" ? "\u6211\u65B9" : "\u5BF9\u624B"}\u653B\u51FB \u2192 ${dmg1} \u4F24\u5BB3`);
556
- if ((hp1Ref === "hpB" ? hpB : hpA) <= 0) {
557
- return { winner: first, rounds: round, log };
558
- }
559
- const dmg2 = Math.max(1, Math.floor((atk2.attack - def2.defense * 0.5) * (1 + Math.random() * 0.2)));
560
- if (hp2Ref === "hpB") hpB -= dmg2;
561
- else hpA -= dmg2;
562
- log.push(` R${round}: ${first === "a" ? "\u5BF9\u624B" : "\u6211\u65B9"}\u653B\u51FB \u2192 ${dmg2} \u4F24\u5BB3`);
563
- if ((hp2Ref === "hpB" ? hpB : hpA) <= 0) {
564
- return { winner: first === "a" ? "b" : "a", rounds: round, log };
565
- }
566
- }
567
- return { winner: "draw", rounds: 10, log };
568
- }
569
- async function battle() {
739
+ async function battle(opponentCode) {
570
740
  const lobster = await readLobster();
571
741
  if (!lobster) {
572
- console.log("\n\u{1F95A} \u8FD8\u6CA1\u6709\u9F99\u867E\u3002\u8FD0\u884C npx clawfight hatch \u6765\u5B75\u5316\u4E00\u53EA\uFF01");
742
+ console.log("\n" + t("no_lobster"));
573
743
  return;
574
744
  }
575
745
  if (lobster.status !== "active") {
576
- console.log(`
577
- \u26A0\uFE0F ${lobster.name} \u5F53\u524D\u72B6\u6001\u4E3A ${lobster.status}\uFF0C\u65E0\u6CD5\u6218\u6597\u3002`);
746
+ console.log("\n" + t("status_cant_fight", { name: lobster.name, status: lobster.status }));
747
+ return;
748
+ }
749
+ if (!opponentCode) {
750
+ console.log("\n" + t("battle_no_code"));
751
+ console.log(t("battle_usage"));
752
+ console.log(t("battle_hint"));
753
+ return;
754
+ }
755
+ console.log("\n" + t("battle_connecting", { code: opponentCode }));
756
+ const result = await apiBattle(lobster.id, opponentCode);
757
+ if (!result) {
758
+ console.log(t("battle_server_down"));
759
+ return;
760
+ }
761
+ if (result.error) {
762
+ console.log(`\u26A0\uFE0F ${result.error}`);
578
763
  return;
579
764
  }
580
- const opponent = simulateOpponent(lobster.level);
765
+ const opp = result.opponent;
766
+ const myResult = result.result;
767
+ const rounds = result.rounds;
768
+ const expGain = result.exp_gain;
581
769
  console.log("\n" + "\u2694\uFE0F".repeat(20));
582
- console.log(` ${lobster.name} (Lv.${lobster.level}) VS ${opponent.name} (Lv.${opponent.level})`);
770
+ console.log(` ${lobster.name} (Lv.${lobster.level}) VS ${opp.name} (Lv.${opp.level})`);
583
771
  console.log("\u2694\uFE0F".repeat(20));
584
- const result = runBattle(lobster.stats, opponent.stats);
585
- console.log();
586
- for (const line of result.log) {
587
- console.log(line);
588
- }
589
- const isWin = result.winner === "a";
590
- const isDraw = result.winner === "draw";
591
- let expGain;
592
- if (isDraw) {
593
- expGain = 10;
594
- console.log(`
595
- \u{1F91D} \u5E73\u5C40\uFF01${result.rounds} \u56DE\u5408\u540E\u53CC\u65B9\u7CBE\u75B2\u529B\u7AED`);
596
- } else if (isWin) {
597
- expGain = 30;
772
+ if (myResult === "win") {
598
773
  lobster.wins++;
599
774
  lobster.streak = Math.max(0, lobster.streak) + 1;
600
775
  lobster.reputation++;
601
- console.log(`
602
- \u{1F3C6} \u80DC\u5229\uFF01${lobster.name} \u5728 ${result.rounds} \u56DE\u5408\u540E\u51FB\u8D25\u4E86 ${opponent.name}\uFF01`);
603
- } else {
604
- expGain = 10;
776
+ console.log("\n" + t("battle_win", { name: lobster.name, opponent: opp.name, rounds }));
777
+ } else if (myResult === "loss") {
605
778
  lobster.losses++;
606
779
  lobster.streak = Math.min(0, lobster.streak) - 1;
607
780
  lobster.reputation = Math.max(0, lobster.reputation - 1);
608
- console.log(`
609
- \u{1F480} \u5931\u8D25\u2026${lobster.name} \u5728 ${result.rounds} \u56DE\u5408\u540E\u88AB ${opponent.name} \u51FB\u8D25\u3002`);
781
+ console.log("\n" + t("battle_loss", { name: lobster.name, opponent: opp.name, rounds }));
782
+ } else {
783
+ console.log("\n" + t("battle_draw", { rounds }));
610
784
  }
611
785
  const actual = Math.min(expGain, lobster.daily_exp_cap - lobster.today_exp);
612
786
  if (actual > 0) {
613
787
  lobster.exp += actual;
614
788
  lobster.today_exp += actual;
615
789
  }
616
- console.log(`\u7ECF\u9A8C +${actual} | \u6218\u7EE9: ${lobster.wins}\u80DC ${lobster.losses}\u8D1F | \u8FDE\u80DC: ${lobster.streak}`);
617
790
  lobster.last_battle = (/* @__PURE__ */ new Date()).toISOString();
618
- const resultStr = isDraw ? "\u5E73\u5C40" : isWin ? "\u80DC\u5229" : "\u5931\u8D25";
619
- await appendLog(`\u2694\uFE0F VS ${opponent.name}(Lv.${opponent.level}) \u2192 ${resultStr} (${result.rounds}\u56DE\u5408)`);
791
+ console.log(t("battle_exp", { exp: actual, wins: lobster.wins, losses: lobster.losses, streak: lobster.streak }));
792
+ await appendLog(`\u2694\uFE0F VS ${opp.name}(Lv.${opp.level}) \u2192 ${myResult} (${rounds}R)`);
620
793
  await writeLobster(lobster);
621
794
  }
622
795
 
623
796
  // src/commands/feed.ts
624
797
  var FOOD_TYPES = {
625
- protein: { exp: 15, label: "\u9AD8\u86CB\u767D\u98DF\u7269", statBias: "attack" },
626
- algae: { exp: 10, label: "\u85FB\u7C7B\u98DF\u7269", statBias: "hp" },
627
- mineral: { exp: 12, label: "\u77FF\u7269\u8D28", statBias: "defense" }
798
+ protein: { exp: 15, labelKey: "food_protein", statBias: "attack" },
799
+ algae: { exp: 10, labelKey: "food_algae", statBias: "hp" },
800
+ mineral: { exp: 12, labelKey: "food_mineral", statBias: "defense" }
628
801
  };
629
802
  async function feed(foodType) {
630
803
  const lobster = await readLobster();
631
804
  if (!lobster) {
632
- console.log("\n\u{1F95A} \u8FD8\u6CA1\u6709\u9F99\u867E\u3002\u8FD0\u884C npx clawfight hatch \u6765\u5B75\u5316\u4E00\u53EA\uFF01");
805
+ console.log("\n" + t("no_lobster"));
633
806
  return;
634
807
  }
635
808
  if (!foodType || !FOOD_TYPES[foodType]) {
636
- console.log("\n\u{1F37D}\uFE0F \u53EF\u7528\u98DF\u7269\u7C7B\u578B:");
809
+ console.log("\n" + t("feed_menu_title"));
637
810
  for (const [key, info] of Object.entries(FOOD_TYPES)) {
638
- console.log(` ${key.padEnd(10)} \u2014 ${info.label} (+${info.exp} EXP, ${info.statBias} \u504F\u5411)`);
811
+ console.log(` ${key.padEnd(10)} \u2014 ${t(info.labelKey)} (+${info.exp} EXP, ${info.statBias})`);
639
812
  }
640
- console.log("\n\u7528\u6CD5: npx clawfight feed <food_type>");
813
+ console.log("\n" + t("feed_menu_usage"));
641
814
  return;
642
815
  }
643
816
  const food = FOOD_TYPES[foodType];
644
817
  const actual = Math.min(food.exp, lobster.daily_exp_cap - lobster.today_exp);
645
818
  if (actual <= 0) {
646
- console.log(`
647
- \u26A0\uFE0F ${lobster.name} \u4ECA\u5929\u5DF2\u7ECF\u5403\u9971\u4E86\uFF08\u6BCF\u65E5\u7ECF\u9A8C\u4E0A\u9650 ${lobster.daily_exp_cap}\uFF09`);
819
+ console.log("\n" + t("feed_full", { name: lobster.name, cap: lobster.daily_exp_cap }));
648
820
  return;
649
821
  }
650
822
  lobster.exp += actual;
651
823
  lobster.today_exp += actual;
824
+ const foodLabel = t(food.labelKey);
652
825
  const stats = lobster.stats;
653
826
  if (food.statBias in stats) {
654
827
  const bonus = Math.random() > 0.5 ? 1 : 0;
828
+ console.log("\n" + t("feed_ate", { name: lobster.name, food: foodLabel }));
655
829
  if (bonus > 0) {
656
830
  stats[food.statBias] += bonus;
657
- console.log(`
658
- \u{1F37D}\uFE0F ${lobster.name} \u5403\u4E86 ${food.label}\uFF01`);
659
- console.log(` \u7ECF\u9A8C +${actual} | ${food.statBias} +${bonus}`);
831
+ console.log(t("feed_exp_stat", { exp: actual, stat: food.statBias, bonus }));
660
832
  } else {
661
- console.log(`
662
- \u{1F37D}\uFE0F ${lobster.name} \u5403\u4E86 ${food.label}\uFF01`);
663
- console.log(` \u7ECF\u9A8C +${actual}`);
833
+ console.log(t("feed_exp", { exp: actual }));
664
834
  }
665
835
  }
666
- await appendLog(`\u{1F37D}\uFE0F \u5582\u98DF: ${food.label} \u2192 EXP+${actual}`);
836
+ await appendLog(`\u{1F37D}\uFE0F ${foodLabel} \u2192 EXP+${actual}`);
667
837
  await writeLobster(lobster);
668
- console.log(` \u4ECA\u65E5\u7ECF\u9A8C: ${lobster.today_exp}/${lobster.daily_exp_cap}`);
838
+ console.log(t("feed_today", { today: lobster.today_exp, cap: lobster.daily_exp_cap }));
669
839
  }
670
840
 
671
841
  // src/commands/leaderboard.ts
@@ -678,73 +848,72 @@ var RARITY_SYMBOLS = {
678
848
  albino: "\u2B1C"
679
849
  };
680
850
  async function leaderboard() {
681
- console.log("\n\u{1F4E1} \u83B7\u53D6\u5168\u7403\u6392\u884C\u699C...");
851
+ console.log("\n" + t("lb_loading"));
682
852
  const data = await apiLeaderboard(20);
683
853
  if (!data) {
684
- console.log("\u26A0\uFE0F \u670D\u52A1\u5668\u4E0D\u53EF\u8FBE\uFF0C\u65E0\u6CD5\u83B7\u53D6\u6392\u884C\u699C\u3002");
854
+ console.log(t("lb_offline"));
685
855
  return;
686
856
  }
687
857
  if (data.leaderboard.length === 0) {
688
- console.log("\n\u{1F99E} \u8FD8\u6CA1\u6709\u9F99\u867E\u4E0A\u699C\u3002\u6210\u4E3A\u7B2C\u4E00\u4E2A\u5427\uFF01");
858
+ console.log("\n" + t("lb_empty"));
689
859
  return;
690
860
  }
691
- console.log("\n" + "\u2550".repeat(60));
692
- console.log(" \u{1F99E} ClawFight \u5168\u7403\u6392\u884C\u699C");
693
- console.log("\u2550".repeat(60));
694
- console.log(` ${"\u6392\u540D".padEnd(6)} ${"\u540D\u79F0".padEnd(16)} ${"\u7B49\u7EA7".padEnd(8)} ${"\u80DC\u573A".padEnd(8)} ${"\u80DC\u7387".padEnd(8)}`);
695
- console.log("\u2500".repeat(60));
861
+ console.log("\n" + "\u2550".repeat(70));
862
+ console.log(t("lb_title"));
863
+ console.log("\u2550".repeat(70));
864
+ console.log(t("lb_header"));
865
+ console.log("\u2500".repeat(70));
696
866
  for (const entry of data.leaderboard) {
697
867
  const sym = RARITY_SYMBOLS[entry.rarity] || " ";
698
868
  const name = entry.name.length > 12 ? entry.name.slice(0, 11) + "\u2026" : entry.name;
869
+ const code = entry.id.slice(0, 8);
699
870
  console.log(
700
- ` ${sym} #${String(entry.rank).padEnd(4)} ${name.padEnd(14)} Lv.${String(entry.level).padEnd(5)} ${String(entry.wins).padEnd(7)} ${entry.win_rate}%`
871
+ ` ${sym} #${String(entry.rank).padEnd(4)} ${name.padEnd(14)} Lv.${String(entry.level).padEnd(5)} ${String(entry.wins).padEnd(7)} ${String(entry.win_rate).padEnd(3)}% ${code}`
701
872
  );
702
873
  }
703
- console.log("\u2500".repeat(60));
704
- console.log(` \u603B\u9F99\u867E: ${data.total_lobsters} | \u6D3B\u8DC3: ${data.active_lobsters}`);
705
- console.log("\u2550".repeat(60));
874
+ console.log("\u2500".repeat(70));
875
+ console.log(t("lb_total", { total: data.total_lobsters, active: data.active_lobsters }));
876
+ console.log(t("lb_hint"));
877
+ console.log("\u2550".repeat(70));
706
878
  }
707
879
 
708
880
  // src/commands/rest.ts
709
881
  async function rest() {
710
882
  const lobster = await readLobster();
711
883
  if (!lobster) {
712
- console.log("\n\u{1F95A} \u8FD8\u6CA1\u6709\u9F99\u867E\u3002\u8FD0\u884C npx clawfight hatch \u6765\u5B75\u5316\u4E00\u53EA\uFF01");
884
+ console.log("\n" + t("no_lobster"));
713
885
  return;
714
886
  }
715
887
  if (lobster.status === "hibernating") {
716
- console.log(`
717
- \u{1F4A4} ${lobster.name} \u5DF2\u7ECF\u5728\u4F11\u7720\u4E2D\u4E86\u3002\u8FD0\u884C npx clawfight wake \u6765\u5524\u9192\u5B83\u3002`);
888
+ console.log("\n" + t("rest_already", { name: lobster.name }));
718
889
  return;
719
890
  }
720
891
  if (lobster.status === "molting") {
721
- console.log(`
722
- \u{1F7E1} ${lobster.name} \u6B63\u5728\u8715\u58F3\uFF0C\u4E0D\u80FD\u4F11\u7720\u3002\u7B49\u8715\u58F3\u7ED3\u675F\u518D\u6765\u3002`);
892
+ console.log("\n" + t("rest_molting", { name: lobster.name }));
723
893
  return;
724
894
  }
725
895
  lobster.status = "hibernating";
726
896
  lobster.hibernated_at = (/* @__PURE__ */ new Date()).toISOString();
727
897
  await writeLobster(lobster);
728
- await appendLog(`\u{1F4A4} ${lobster.name} \u8FDB\u5165\u4F11\u7720`);
898
+ await appendLog(`\u{1F4A4} ${lobster.name} \u2192 hibernation`);
729
899
  console.log("\n" + "\u2500".repeat(40));
730
- console.log(`\u{1F4A4} ${lobster.name} \u7F13\u7F13\u6C89\u5165\u6D77\u5E95\u6C99\u5730...`);
731
- console.log(" \u9F99\u867E\u8737\u7F29\u8D77\u89E6\u987B\uFF0C\u5B89\u9759\u5730\u4F11\u606F\u3002");
732
- console.log(" \u4F11\u7720\u671F\u95F4\u4E0D\u4F1A\u53C2\u4E0E\u5DE1\u903B\u548C\u6218\u6597\u3002");
733
- console.log(" \u9192\u6765\u540E\u4F1A\u83B7\u5F97\u6062\u590D\u52A0\u6210\uFF01");
900
+ console.log(t("rest_desc1", { name: lobster.name }));
901
+ console.log(t("rest_desc2"));
902
+ console.log(t("rest_desc3"));
903
+ console.log(t("rest_desc4"));
734
904
  console.log("\u2500".repeat(40));
735
- console.log("\n\u8FD0\u884C npx clawfight wake \u6765\u5524\u9192\u5B83\u3002");
905
+ console.log("\n" + t("rest_wake_hint"));
736
906
  }
737
907
 
738
908
  // src/commands/wake.ts
739
909
  async function wake() {
740
910
  const lobster = await readLobster();
741
911
  if (!lobster) {
742
- console.log("\n\u{1F95A} \u8FD8\u6CA1\u6709\u9F99\u867E\u3002\u8FD0\u884C npx clawfight hatch \u6765\u5B75\u5316\u4E00\u53EA\uFF01");
912
+ console.log("\n" + t("no_lobster"));
743
913
  return;
744
914
  }
745
915
  if (lobster.status !== "hibernating") {
746
- console.log(`
747
- \u{1F7E2} ${lobster.name} \u6CA1\u6709\u5728\u4F11\u7720\u3002\u5B83\u5DF2\u7ECF\u662F\u6D3B\u8DC3\u72B6\u6001\u4E86\uFF01`);
916
+ console.log("\n" + t("wake_not_sleeping", { name: lobster.name }));
748
917
  return;
749
918
  }
750
919
  const sleepStart = lobster.hibernated_at ? new Date(lobster.hibernated_at) : /* @__PURE__ */ new Date();
@@ -772,21 +941,20 @@ async function wake() {
772
941
  lobster.status = "active";
773
942
  delete lobster.hibernated_at;
774
943
  await writeLobster(lobster);
775
- const hoursStr = hoursSlept < 1 ? `${Math.round(hoursSlept * 60)} \u5206\u949F` : `${Math.round(hoursSlept)} \u5C0F\u65F6`;
776
- const bonusStr = bonuses.length > 0 ? bonuses.join(", ") : "\uFF08\u4F11\u7720\u4E0D\u8DB3 4 \u5C0F\u65F6\uFF0C\u65E0\u52A0\u6210\uFF09";
777
- await appendLog(`\u2600\uFE0F ${lobster.name} \u82CF\u9192 (\u4F11\u7720 ${hoursStr}) \u2192 ${bonusStr}`);
944
+ const duration = hoursSlept < 1 ? t("duration_minutes", { n: Math.round(hoursSlept * 60) }) : t("duration_hours", { n: Math.round(hoursSlept) });
945
+ const bonusStr = bonuses.length > 0 ? bonuses.join(", ") : t("wake_no_bonus");
946
+ await appendLog(`\u2600\uFE0F ${lobster.name} woke (${duration}) \u2192 ${bonusStr}`);
778
947
  console.log("\n" + "\u2500".repeat(40));
779
- console.log(`\u2600\uFE0F ${lobster.name} \u4ECE\u6C99\u5730\u4E2D\u7F13\u7F13\u82CF\u9192...`);
780
- console.log(` \u4F11\u7720\u65F6\u957F: ${hoursStr}`);
781
- console.log(` \u6062\u590D\u52A0\u6210: ${bonusStr}`);
948
+ console.log(t("wake_desc", { name: lobster.name }));
949
+ console.log(t("wake_duration", { duration }));
950
+ console.log(t("wake_bonus", { bonus: bonusStr }));
782
951
  console.log("\u2500".repeat(40));
783
- console.log(`
784
- \u{1F7E2} ${lobster.name} \u7CBE\u795E\u7115\u53D1\uFF0C\u51C6\u5907\u597D\u518D\u6B21\u51FA\u51FB\uFF01`);
952
+ console.log("\n" + t("wake_ready", { name: lobster.name }));
785
953
  }
786
954
 
787
955
  // src/index.ts
788
956
  var program = new Command();
789
- program.name("clawfight").description("\u{1F99E} ClawFight \u2014 \u9F99\u867E\u7535\u5B50\u5BA0\u7269\u5BF9\u6218").version("1.2.0");
957
+ program.name("clawfight").description("\u{1F99E} ClawFight \u2014 \u9F99\u867E\u7535\u5B50\u5BA0\u7269\u5BF9\u6218").version("1.3.0");
790
958
  program.command("hatch").description("\u5B75\u5316\u4E00\u53EA\u65B0\u9F99\u867E").argument("[name]", "\u4E3A\u9F99\u867E\u53D6\u540D").action(async (name) => {
791
959
  await hatch(name);
792
960
  });
@@ -796,8 +964,8 @@ program.command("status").description("\u67E5\u770B\u9F99\u867E\u72B6\u6001").ac
796
964
  program.command("patrol").description("\u5DE1\u903B\u7B7E\u5230\uFF0C\u89E6\u53D1\u968F\u673A\u4E8B\u4EF6\u548C\u906D\u9047").action(async () => {
797
965
  await patrol();
798
966
  });
799
- program.command("battle").description("\u8FDB\u884C\u4E00\u573A\u6218\u6597").action(async () => {
800
- await battle();
967
+ program.command("battle").description("\u901A\u8FC7\u6218\u6597\u7801\u6311\u6218\u6307\u5B9A\u5BF9\u624B").argument("[code]", "\u5BF9\u624B\u7684\u6218\u6597\u7801\uFF08\u6392\u884C\u699C\u4E2D\u53EF\u89C1\uFF09").action(async (code) => {
968
+ await battle(code);
801
969
  });
802
970
  program.command("feed").description("\u5582\u517B\u9F99\u867E").argument("[food_type]", "\u98DF\u7269\u7C7B\u578B: protein, algae, mineral").action(async (foodType) => {
803
971
  await feed(foodType);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2025-6-19/clawfight",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "ClawFight — raise and battle a unique lobster pet with evolving soul 🦞",
5
5
  "author": "LIU",
6
6
  "license": "MIT",