zames_pro 2.88.2 → 2.90.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 (52) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +32 -585
  3. package/dist/agent-loop.js +47 -4
  4. package/dist/ask-retry.js +49 -0
  5. package/dist/ask-state.js +80 -0
  6. package/dist/browser-base.js +69 -0
  7. package/dist/browser.js +170 -41
  8. package/dist/command-registry.js +58 -0
  9. package/dist/commands.js +25 -2
  10. package/dist/completion-check.js +77 -0
  11. package/dist/config-commands.js +12 -4
  12. package/dist/config-menu.js +179 -81
  13. package/dist/config-schema.js +562 -0
  14. package/dist/config.js +19 -405
  15. package/dist/dev-commands.js +27 -0
  16. package/dist/dev-watch.js +96 -0
  17. package/dist/first-run.js +80 -0
  18. package/dist/glm-browser.js +133 -32
  19. package/dist/i18n/common.js +6 -3
  20. package/dist/i18n/config.js +78 -3
  21. package/dist/i18n/deepseek.js +1 -0
  22. package/dist/i18n/wizard.js +25 -0
  23. package/dist/i18n.js +6 -1
  24. package/dist/index.js +1299 -924
  25. package/dist/info-commands.js +61 -0
  26. package/dist/input/layout.js +38 -1
  27. package/dist/input.js +2 -2
  28. package/dist/password-store.js +48 -0
  29. package/dist/prompt-budget.js +23 -0
  30. package/dist/provider-errors.js +53 -0
  31. package/dist/provider-fallback.js +46 -0
  32. package/dist/qwen-browser.js +155 -39
  33. package/dist/run-task.js +2 -1
  34. package/dist/scheduler-commands.js +41 -0
  35. package/dist/send-slot.js +36 -0
  36. package/dist/session-commands.js +42 -0
  37. package/dist/spinner.js +3 -3
  38. package/dist/swallow.js +45 -0
  39. package/dist/system-prompt.js +2 -2
  40. package/dist/tree-commands.js +36 -0
  41. package/dist/tui/ConfigMenu.js +149 -64
  42. package/dist/tui/FullScreenApp.js +13 -4
  43. package/dist/tui/InputLine.js +20 -3
  44. package/dist/tui/LogView.js +11 -5
  45. package/dist/tui/StatusBar.js +4 -1
  46. package/dist/tui/TuiApp.js +11 -2
  47. package/dist/tui/layout.js +14 -1
  48. package/dist/tui/store.js +2 -1
  49. package/dist/tui/ui-colors.js +2 -0
  50. package/dist/width.js +10 -5
  51. package/llms-full.txt +16 -292
  52. package/package.json +2 -2
@@ -15,6 +15,7 @@
15
15
  // A FIRST message from a fresh profile is gated by an Aliyun slider captcha,
16
16
  // which a headless browser cannot pass — hence the browser-based login
17
17
  // (src/browser-login.ts) and the explicit captcha error below.
18
+ import { noteSwallowed } from './swallow.js';
18
19
  import fs from 'fs/promises';
19
20
  import path from 'path';
20
21
  import os from 'os';
@@ -42,6 +43,8 @@ function capNetBody(body) {
42
43
  return body.slice(0, head) + '\n...[truncated]...\n' + body.slice(-1024);
43
44
  }
44
45
  export class GlmBrowser {
46
+ /** The chat a send must join (set by the agent loop; see types.ts). */
47
+ resumeChatId = null;
45
48
  providerName = 'glm';
46
49
  headless;
47
50
  debug;
@@ -60,6 +63,8 @@ export class GlmBrowser {
60
63
  // Ask() budget for the context-limit recovery (a compaction, not a resend).
61
64
  maxContextLimitRetries;
62
65
  onContextLimit;
66
+ /** N8: switch to an authorized fallback provider (see agent-loop). */
67
+ onProviderExhausted = null;
63
68
  startWaitMs;
64
69
  sendButtonWaitMs;
65
70
  model;
@@ -212,7 +217,9 @@ export class GlmBrowser {
212
217
  this.onNotice(text);
213
218
  return;
214
219
  }
215
- catch { }
220
+ catch (e) {
221
+ noteSwallowed('_notice', e, this.debug);
222
+ }
216
223
  }
217
224
  console.error(text);
218
225
  }
@@ -221,14 +228,18 @@ export class GlmBrowser {
221
228
  try {
222
229
  this.onSendState(state);
223
230
  }
224
- catch { }
231
+ catch (e) {
232
+ noteSwallowed('_emitSendState', e, this.debug);
233
+ }
225
234
  }
226
235
  }
227
236
  async _setBusyTitle(busy) {
228
237
  try {
229
238
  await this.page.evaluate(setBusyTitleInDom, busy);
230
239
  }
231
- catch { }
240
+ catch (e) {
241
+ noteSwallowed('_setBusyTitle', e, this.debug);
242
+ }
232
243
  }
233
244
  sendIntervalMs() {
234
245
  return this.sendRuntime.intervalMs();
@@ -317,13 +328,25 @@ export class GlmBrowser {
317
328
  .catch(() => { });
318
329
  }
319
330
  async restart() {
331
+ // Remember the dialogue BEFORE the context dies: a relaunch lands on the
332
+ // BASE url, so the id is lost and the next send started a NEW chat.
333
+ const chatBefore = (await this.getCurrentChatId().catch(() => null)) ?? null;
320
334
  try {
321
335
  if (this.context)
322
336
  await this.context.close();
323
337
  }
324
- catch { }
338
+ catch (e) {
339
+ noteSwallowed('restart', e, this.debug);
340
+ }
325
341
  await this._launchOnce();
326
342
  await this._fixHeadlessUserAgent();
343
+ if (chatBefore) {
344
+ const ok = await this.openChat(chatBefore).catch(() => false);
345
+ if (ok)
346
+ this._netChatId = chatBefore;
347
+ else if (this.debug)
348
+ console.error('restart: could not reopen chat ' + chatBefore);
349
+ }
327
350
  }
328
351
  /**
329
352
  * Relaunch the SAME persistent profile in another headless mode — the
@@ -349,12 +372,16 @@ export class GlmBrowser {
349
372
  }));
350
373
  }
351
374
  }
352
- catch { }
375
+ catch (e) {
376
+ noteSwallowed('relaunchSafe', e, this.debug);
377
+ }
353
378
  try {
354
379
  if (this.context)
355
380
  await this.context.close();
356
381
  }
357
- catch { }
382
+ catch (e) {
383
+ noteSwallowed('relaunchSafe', e, this.debug);
384
+ }
358
385
  await this._waitProfileUnlocked(15_000);
359
386
  this.headless = headless;
360
387
  const options = {
@@ -427,7 +454,9 @@ export class GlmBrowser {
427
454
  const j = await res.json();
428
455
  role = typeof j?.role === 'string' ? j.role : null;
429
456
  }
430
- catch { }
457
+ catch (e) {
458
+ noteSwallowed('probeLogin', e, this.debug);
459
+ }
431
460
  const bodyText = document.body ? document.body.innerText : '';
432
461
  const captcha = /security verification|drag the slider/i.test(bodyText);
433
462
  const signInVisible = /\bsign in\b/i.test(bodyText) && !hasToken;
@@ -470,7 +499,9 @@ export class GlmBrowser {
470
499
  .catch(() => { });
471
500
  await this.page.waitForTimeout(1500);
472
501
  }
473
- catch { }
502
+ catch (e) {
503
+ noteSwallowed('_openLoginPage', e, this.debug);
504
+ }
474
505
  }
475
506
  /** Prefill the SAVED login (never the password — that is filled on submit). */
476
507
  async _prefillLogin() {
@@ -485,7 +516,9 @@ export class GlmBrowser {
485
516
  await email.fill(saved, { timeout: 2000 }).catch(() => { });
486
517
  }
487
518
  }
488
- catch { }
519
+ catch (e) {
520
+ noteSwallowed('_prefillLogin', e, this.debug);
521
+ }
489
522
  }
490
523
  /**
491
524
  * Best-effort password login with the SAVED credentials. z.ai's form is
@@ -515,7 +548,9 @@ export class GlmBrowser {
515
548
  try {
516
549
  this.onAuthSave?.(this.auth.username, this.auth.password);
517
550
  }
518
- catch { }
551
+ catch (e) {
552
+ noteSwallowed('_autoLogin', e, this.debug);
553
+ }
519
554
  }
520
555
  return ok;
521
556
  }
@@ -565,7 +600,9 @@ export class GlmBrowser {
565
600
  try {
566
601
  this.onAuthSave?.(u, p);
567
602
  }
568
- catch { }
603
+ catch (e) {
604
+ noteSwallowed('_captureLoginCredentials', e, this.debug);
605
+ }
569
606
  });
570
607
  await this.page.evaluate(() => {
571
608
  const w = window;
@@ -583,11 +620,15 @@ export class GlmBrowser {
583
620
  if (email && fn)
584
621
  fn(email.value || '', pwd ? pwd.value || '' : '');
585
622
  }
586
- catch { }
623
+ catch (e) {
624
+ noteSwallowed('_captureLoginCredentials', e, this.debug);
625
+ }
587
626
  }, true);
588
627
  });
589
628
  }
590
- catch { }
629
+ catch (e) {
630
+ noteSwallowed('_captureLoginCredentials', e, this.debug);
631
+ }
591
632
  }
592
633
  async _persistSessionIfNeeded() {
593
634
  if (!this.auth.saveSession)
@@ -676,12 +717,16 @@ export class GlmBrowser {
676
717
  }
677
718
  }
678
719
  }
679
- catch { }
720
+ catch (e) {
721
+ noteSwallowed('close', e, this.debug);
722
+ }
680
723
  try {
681
724
  if (this.context)
682
725
  await this.context.close();
683
726
  }
684
- catch { }
727
+ catch (e) {
728
+ noteSwallowed('close', e, this.debug);
729
+ }
685
730
  }
686
731
  /**
687
732
  * Rewrite the completion REQUEST so z.ai's own agent runtime stays off.
@@ -750,7 +795,9 @@ export class GlmBrowser {
750
795
  }
751
796
  });
752
797
  }
753
- catch { }
798
+ catch (e) {
799
+ noteSwallowed('_installRuntimeRewrite', e, this.debug);
800
+ }
754
801
  }
755
802
  _installNetHook() {
756
803
  const pg = this.page;
@@ -778,7 +825,9 @@ export class GlmBrowser {
778
825
  // (The features are recorded by _installRuntimeRewrite's route handler:
779
826
  // the `request` event still carries the ORIGINAL body.)
780
827
  }
781
- catch { }
828
+ catch (e) {
829
+ noteSwallowed('_installNetHook', e, this.debug);
830
+ }
782
831
  });
783
832
  }
784
833
  async _onResponse(resp) {
@@ -852,7 +901,9 @@ export class GlmBrowser {
852
901
  this._netCaptureAt = Date.now();
853
902
  }
854
903
  }
855
- catch { }
904
+ catch (e) {
905
+ noteSwallowed('_onResponse', e, this.debug);
906
+ }
856
907
  }
857
908
  async newChat() {
858
909
  const before = await this.getCurrentChatId();
@@ -862,7 +913,9 @@ export class GlmBrowser {
862
913
  await btn.click({ timeout: 2500 });
863
914
  clicked = true;
864
915
  }
865
- catch { }
916
+ catch (e) {
917
+ noteSwallowed('newChat', e, this.debug);
918
+ }
866
919
  let ok = false;
867
920
  if (clicked) {
868
921
  const deadline = Date.now() + 5000;
@@ -901,7 +954,9 @@ export class GlmBrowser {
901
954
  if (await loc.isVisible({ timeout }).catch(() => false))
902
955
  return loc;
903
956
  }
904
- catch { }
957
+ catch (e) {
958
+ noteSwallowed('_findVisible', e, this.debug);
959
+ }
905
960
  }
906
961
  return null;
907
962
  }
@@ -937,7 +992,9 @@ export class GlmBrowser {
937
992
  this._lastHistoryError = res.error;
938
993
  }
939
994
  }
940
- catch { }
995
+ catch (e) {
996
+ noteSwallowed('listChats', e, this.debug);
997
+ }
941
998
  try {
942
999
  return await this.page.evaluate(scrapeGlmChatList, limit);
943
1000
  }
@@ -1169,21 +1226,27 @@ export class GlmBrowser {
1169
1226
  }
1170
1227
  }
1171
1228
  }
1172
- catch { }
1229
+ catch (e) {
1230
+ noteSwallowed('stopGeneration', e, this.debug);
1231
+ }
1173
1232
  const btn = await this._findVisible(STOP_SELECTORS, 500);
1174
1233
  if (btn) {
1175
1234
  try {
1176
1235
  await btn.click({ timeout: 1500 });
1177
1236
  return true;
1178
1237
  }
1179
- catch { }
1238
+ catch (e) {
1239
+ noteSwallowed('stopGeneration', e, this.debug);
1240
+ }
1180
1241
  }
1181
1242
  // No dedicated stop control was observed; Escape aborts the generation.
1182
1243
  try {
1183
1244
  await this.page.keyboard.press('Escape');
1184
1245
  return true;
1185
1246
  }
1186
- catch { }
1247
+ catch (e) {
1248
+ noteSwallowed('stopGeneration', e, this.debug);
1249
+ }
1187
1250
  return false;
1188
1251
  }
1189
1252
  /**
@@ -1232,7 +1295,9 @@ export class GlmBrowser {
1232
1295
  if (!clicked)
1233
1296
  await this.page.keyboard.press('Escape').catch(() => { });
1234
1297
  }
1235
- catch { }
1298
+ catch (e) {
1299
+ noteSwallowed('_applyModel', e, this.debug);
1300
+ }
1236
1301
  }
1237
1302
  getToggleStatesSync() {
1238
1303
  return this._toggles;
@@ -1337,7 +1402,9 @@ export class GlmBrowser {
1337
1402
  try {
1338
1403
  this.onSendPause(secs);
1339
1404
  }
1340
- catch { }
1405
+ catch (e) {
1406
+ noteSwallowed('_waitForSendSlot', e, this.debug);
1407
+ }
1341
1408
  }
1342
1409
  };
1343
1410
  report(gap);
@@ -1378,6 +1445,8 @@ export class GlmBrowser {
1378
1445
  // The exhausted-context recovery is a COMPACTION (a new chat), not a
1379
1446
  // resend, so it has its own budget.
1380
1447
  let contextLimitRetries = 0;
1448
+ // N8: the fallback switch is tried ONCE per ask().
1449
+ let fallbackAttempted = false;
1381
1450
  this._askInFlight++;
1382
1451
  try {
1383
1452
  while (attempt < this.askRetries) {
@@ -1483,6 +1552,26 @@ export class GlmBrowser {
1483
1552
  }));
1484
1553
  }
1485
1554
  }
1555
+ // N8: the active provider exhausted its budgets. Give the injected
1556
+ // hook ONE chance to continue on an AUTHORIZED fallback provider.
1557
+ if (!fallbackAttempted && this.onProviderExhausted) {
1558
+ fallbackAttempted = true;
1559
+ let switched = false;
1560
+ try {
1561
+ switched = await this.onProviderExhausted(lastErr);
1562
+ }
1563
+ catch (e) {
1564
+ noteSwallowed('_onProviderExhausted', e, this.debug);
1565
+ }
1566
+ if (switched) {
1567
+ attempt = 0;
1568
+ rateLimitRetries = 0;
1569
+ serverBusyRetries = 0;
1570
+ incompleteRetries = 0;
1571
+ contextLimitRetries = 0;
1572
+ continue;
1573
+ }
1574
+ }
1486
1575
  this._notice(this._t('glm.ask_retry', {
1487
1576
  attempt: attempt + 1,
1488
1577
  max: this.askRetries,
@@ -1511,7 +1600,9 @@ export class GlmBrowser {
1511
1600
  await input.fill(text, { timeout: 5000 });
1512
1601
  return;
1513
1602
  }
1514
- catch { }
1603
+ catch (e) {
1604
+ noteSwallowed('_setInputText', e, this.debug);
1605
+ }
1515
1606
  }
1516
1607
  await input.click({ timeout: 3000 }).catch(() => { });
1517
1608
  await input
@@ -1547,7 +1638,9 @@ export class GlmBrowser {
1547
1638
  await this._awaitUploadPreview(files.map((f) => f.name), 5000);
1548
1639
  return failed;
1549
1640
  }
1550
- catch { }
1641
+ catch (e) {
1642
+ noteSwallowed('_attachFiles', e, this.debug);
1643
+ }
1551
1644
  // Fallback: set the files directly on the hidden <input type=file>.
1552
1645
  for (const sel of ['input[type="file"]']) {
1553
1646
  const el = this.page.locator(sel).last();
@@ -1558,7 +1651,9 @@ export class GlmBrowser {
1558
1651
  await this._awaitUploadPreview(files.map((f) => f.name), 3000);
1559
1652
  return failed;
1560
1653
  }
1561
- catch { }
1654
+ catch (e) {
1655
+ noteSwallowed('_attachFiles', e, this.debug);
1656
+ }
1562
1657
  }
1563
1658
  this._notice(this._t('glm.attach_no_input'));
1564
1659
  for (const f of files)
@@ -1581,7 +1676,9 @@ export class GlmBrowser {
1581
1676
  await this._awaitInput(10_000);
1582
1677
  input = await this._findVisible(INPUT_SELECTORS, 3000);
1583
1678
  }
1584
- catch { }
1679
+ catch (e) {
1680
+ noteSwallowed('_askOnce', e, this.debug);
1681
+ }
1585
1682
  }
1586
1683
  if (!input)
1587
1684
  throw new Error(this._t('glm.input_missing'));
@@ -1593,7 +1690,9 @@ export class GlmBrowser {
1593
1690
  try {
1594
1691
  this.onSendStart();
1595
1692
  }
1596
- catch { }
1693
+ catch (e) {
1694
+ noteSwallowed('_askOnce', e, this.debug);
1695
+ }
1597
1696
  }
1598
1697
  this._emitSendState('generating');
1599
1698
  void this._setBusyTitle(true);
@@ -1639,7 +1738,9 @@ export class GlmBrowser {
1639
1738
  clicked = true;
1640
1739
  break;
1641
1740
  }
1642
- catch { }
1741
+ catch (e) {
1742
+ noteSwallowed('_askOnce', e, this.debug);
1743
+ }
1643
1744
  }
1644
1745
  if (!clicked) {
1645
1746
  await this.page.keyboard.press('Enter').catch(() => { });
@@ -393,8 +393,11 @@ export const commonMessages = {
393
393
  ru: '⚠ авто-reload: часть модулей не загрузилась, работаю на прежней версии:',
394
394
  en: '⚠ auto-reload: some modules failed, using previous version:',
395
395
  },
396
- 'reload.browser_restart': {
397
- ru: '⚠ browser.ts изменился, но он НЕ перезагружается на лету (в нём Playwright-контекст). Перезапусти zames, иначе изменения не применятся.',
398
- en: '⚠ browser.ts changed, but it is NOT hot-reloaded (it owns the Playwright context). Restart zames or the change will not apply.',
396
+ // A generic version of the row above: the WHOLE non-reloadable set is
397
+ // watched (index.ts, run-task.ts, the provider browsers, browser-login.ts,
398
+ // send-*.ts, src/tui/), and `{files}` names what changed.
399
+ 'reload.non_reloadable_restart': {
400
+ ru: '⚠ изменились модули, которые НЕ перезагружаются на лету ({files}). Перезапусти zames, иначе изменения не применятся.',
401
+ en: '⚠ non-reloadable modules changed ({files}). Restart zames or the change will not apply.',
399
402
  },
400
403
  };
@@ -1,11 +1,33 @@
1
1
  // Config menu and config command messages.
2
2
  export const configMessages = {
3
- 'cfg.group.ui': { ru: 'Интерфейс', en: 'Interface' },
4
- 'cfg.group.agent': { ru: 'Агент', en: 'Agent' },
3
+ // Top-level menu groups (/config opens a list of THESE).
4
+ 'cfg.top.agent': { ru: 'Агент', en: 'Agent' },
5
+ 'cfg.top.browser': { ru: 'Браузер и чат', en: 'Browser & chat' },
6
+ 'cfg.top.providers': { ru: 'Провайдеры', en: 'Providers' },
7
+ 'cfg.top.ui': { ru: 'Интерфейс', en: 'Interface' },
8
+ 'cfg.top.files': { ru: 'Файлы и история', en: 'Files & history' },
9
+ 'cfg.top.diagnostics': { ru: 'Диагностика', en: 'Diagnostics' },
10
+ // Sections INSIDE a top-level group.
11
+ 'cfg.group.agent': { ru: 'Основное', en: 'General' },
12
+ 'cfg.group.chat': { ru: 'Сессия чата', en: 'Chat session' },
13
+ 'cfg.group.throttle': { ru: 'Паузы и лимиты', en: 'Pacing & limits' },
14
+ 'cfg.group.recovery': {
15
+ ru: 'Сбои и восстановление',
16
+ en: 'Failures & recovery',
17
+ },
18
+ 'cfg.group.login': { ru: 'Вход в аккаунт', en: 'Sign-in' },
19
+ 'cfg.group.telemetry': { ru: 'Телеметрия', en: 'Telemetry' },
20
+ 'cfg.group.qwen': { ru: 'Qwen', en: 'Qwen' },
21
+ 'cfg.group.glm': { ru: 'GLM', en: 'GLM' },
22
+ 'cfg.group.look': { ru: 'Язык и оформление', en: 'Language & look' },
23
+ 'cfg.group.tui': { ru: 'Терминальный TUI', en: 'Terminal TUI' },
5
24
  'cfg.group.undo': { ru: 'Откат (undo)', en: 'Undo' },
6
25
  'cfg.group.checkpoint': { ru: 'Чекпойнты', en: 'Checkpoints' },
7
26
  'cfg.group.transcript': { ru: 'Транскрипт', en: 'Transcript' },
8
- 'cfg.group.browser': { ru: 'Браузер / чат', en: 'Browser / chat' },
27
+ 'cfg.group.diagnostics': {
28
+ ru: 'Проверка после правки',
29
+ en: 'Post-edit check',
30
+ },
9
31
  'cfg.f.provider': { ru: 'Провайдер чата', en: 'Chat provider' },
10
32
  'cfg.f.qwen_model': {
11
33
  ru: 'Модель Qwen (id)',
@@ -236,6 +258,10 @@ export const configMessages = {
236
258
  ru: 'Логин DeepSeek для авто-входа',
237
259
  en: 'DeepSeek login for auto sign-in',
238
260
  },
261
+ 'cfg.f.browser_passwordStore': {
262
+ ru: 'Где хранить пароль: config (в файле), none (не хранить), keychain (системное хранилище)',
263
+ en: 'Where to keep the password: config (in the file), none (never store), keychain (OS keychain)',
264
+ },
239
265
  'cfg.f.browser_authPassword': {
240
266
  ru: 'Пароль DeepSeek для авто-входа',
241
267
  en: 'DeepSeek password for auto sign-in',
@@ -314,4 +340,53 @@ export const configMessages = {
314
340
  ru: 'Этот параметр применится после перезапуска.',
315
341
  en: 'This setting takes effect after a restart.',
316
342
  },
343
+ 'cfg.f.diagnostics_enabled': {
344
+ ru: 'Запускать проверку после правки файлов',
345
+ en: 'Run the check after a file edit',
346
+ },
347
+ 'cfg.f.diagnostics_command': {
348
+ ru: 'Команда проверки (например, npx tsc --noEmit)',
349
+ en: 'Check command (e.g. npx tsc --noEmit)',
350
+ },
351
+ 'cfg.f.diagnostics_timeoutMs': {
352
+ ru: 'Таймаут проверки, мс',
353
+ en: 'Check timeout, ms',
354
+ },
355
+ 'cfg.f.diagnostics_maxChars': {
356
+ ru: 'Сколько символов вывода проверки отдавать агенту',
357
+ en: 'How many output chars of the check go to the agent',
358
+ },
359
+ // Two-level menu: group list, then the settings inside a group.
360
+ 'cfg.menu.groups_title': {
361
+ ru: '⚙ Настройки — выбери группу',
362
+ en: '⚙ Settings — choose a group',
363
+ },
364
+ 'cfg.menu.group_hint': {
365
+ ru: '↑/↓ — выбор, Enter — открыть группу, q/Esc — выйти',
366
+ en: '↑/↓ select, Enter open the group, q/Esc quit',
367
+ },
368
+ 'cfg.menu.group_count': {
369
+ ru: 'настроек: {n}',
370
+ en: 'settings: {n}',
371
+ },
372
+ 'cfg.menu.group_modified': {
373
+ ru: 'изменено: {n}',
374
+ en: 'changed: {n}',
375
+ },
376
+ 'cfg.menu.back_hint': {
377
+ ru: 'Esc — назад к группам',
378
+ en: 'Esc — back to the groups',
379
+ },
380
+ 'provider.fallback_ask': {
381
+ ru: 'Лимит провайдера {from} исчерпан. Продолжить задачу на {to}? (browser.fallbackMode=auto переключает без вопроса)',
382
+ en: 'The {from} limit is exhausted. Continue the task on {to}? (browser.fallbackMode=auto switches without asking)',
383
+ },
384
+ 'provider.fallback_none': {
385
+ ru: '✖ Лимит {from} исчерпан, а резервного провайдера нет. Авторизуй одного через /provider <id> (нужен настоящий вход, гостевой режим не подходит) и задай browser.fallbackProviders.',
386
+ en: '✖ {from} is exhausted and there is no fallback provider. Authorize one with /provider <id> (a real sign-in; a guest session is not enough) and set browser.fallbackProviders.',
387
+ },
388
+ 'provider.fallback_switch': {
389
+ ru: 'Лимит {from} исчерпан — переключаюсь на {to} и продолжаю задачу.',
390
+ en: '{from} is exhausted — switching to {to} and continuing the task.',
391
+ },
317
392
  };
@@ -137,6 +137,7 @@ export const deepseekMessages = {
137
137
  'tui.hint.reset': { ru: 'сброс', en: 'reset' },
138
138
  'tui.hint.quit': { ru: 'выход', en: 'quit' },
139
139
  'tui.hint.change': { ru: 'изменить', en: 'change' },
140
+ 'tui.hint.back': { ru: 'назад', en: 'back' },
140
141
  // Marks the CURRENT entry in a list menu (e.g. the active Qwen model).
141
142
  'tui.list.current': { ru: '← текущая', en: '← current' },
142
143
  'auth.form.note_saved': {
@@ -0,0 +1,25 @@
1
+ // First-run wizard (N19). A fresh install has no config yet, so these lines
2
+ // are the FIRST the operator ever sees: they must explain the three choices
3
+ // (language, backend, sign-in) without assuming any prior knowledge.
4
+ export const wizardMessages = {
5
+ 'wizard.welcome': {
6
+ ru: 'Первый запуск. Настроим три вещи: язык, провайдер и вход. Enter — значение по умолчанию.',
7
+ en: 'First run. Three quick choices: language, provider and sign-in. Enter accepts the default.',
8
+ },
9
+ 'wizard.language': {
10
+ ru: 'Язык интерфейса [en/ru] (en):',
11
+ en: 'Interface language [en/ru] (en):',
12
+ },
13
+ 'wizard.provider': {
14
+ ru: 'Провайдер [deepseek/qwen/glm] (deepseek): ',
15
+ en: 'Provider [deepseek/qwen/glm] (deepseek): ',
16
+ },
17
+ 'wizard.hint': {
18
+ ru: 'Готово. Дальше — /help; сменить настройки можно через /config.',
19
+ en: 'Done. Next: /help; change these later with /config.',
20
+ },
21
+ 'wizard.skipped': {
22
+ ru: 'Мастер пропущен — используются значения по умолчанию (английский, DeepSeek).',
23
+ en: 'Wizard skipped — using the defaults (English, DeepSeek).',
24
+ },
25
+ };
package/dist/i18n.js CHANGED
@@ -15,9 +15,13 @@ import { selfReviewMessages } from './i18n/self-review.js';
15
15
  import { mcpMessages } from './i18n/mcp.js';
16
16
  import { configMessages } from './i18n/config.js';
17
17
  import { promptMessages } from './i18n/prompt.js';
18
+ import { wizardMessages } from './i18n/wizard.js';
18
19
  import { qwenMessages } from './i18n/qwen.js';
19
20
  import { glmMessages } from './i18n/glm.js';
20
- export const DEFAULT_LOCALE = 'ru';
21
+ // English is the default for a fresh install: the package is published for an
22
+ // international audience and the operator asked for English out of the box.
23
+ // `ui.locale` still overrides it, and the first-run wizard offers Russian.
24
+ export const DEFAULT_LOCALE = 'en';
21
25
  export const LOCALES = [
22
26
  { code: 'ru', name: 'Русский', englishName: 'Russian' },
23
27
  { code: 'en', name: 'English', englishName: 'English' },
@@ -48,6 +52,7 @@ export const CATALOG = {
48
52
  ...mcpMessages,
49
53
  ...configMessages,
50
54
  ...promptMessages,
55
+ ...wizardMessages,
51
56
  ...qwenMessages,
52
57
  ...glmMessages,
53
58
  };