@thesashadev/girl-agent 0.1.8 → 0.1.10

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/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.10
4
+
5
+ Дата: 2026-05-07
6
+
7
+ - Merge pull request #51 from TheSashaDev/devin/1778179420-proxy-wss-support
8
+ - refactor: remove proxy support, WSS enabled by default
9
+ - feat: add numeric identifiers to stages for convenience
10
+ - feat: add proxy/WSS support for Telegram (fixes #38, #32)
11
+
12
+ ## 0.1.9
13
+
14
+ Дата: 2026-05-07
15
+
16
+ - Merge pull request #48 from TheSashaDev/devin/1778156776-auto-release-workflow
17
+ - Merge pull request #50 from TheSashaDev/devin/1778176335-fix-markdown-escape
18
+ - fix: also switch userbot editLastMessage to HTML spoilers, remove dead escapeMarkdownV2
19
+ - fix(telegram): replace MarkdownV2 with HTML spoilers, plain text default (#46)
20
+ - feat(ci): auto-release workflow — hourly patch bump + changelog
21
+ - Merge pull request #47 from TheSashaDev/devin/1778156514-docker-latest-on-master
22
+ - fix(docker): tag latest on master pushes, not main
23
+ - Merge pull request #45 from TheSashaDev/devin/1778149966-fix-dockerfile-build-stage
24
+ - fix(docker): add build tools to build stage for arm64 native modules
25
+ - Merge pull request #44 from TheSashaDev/devin/1778149678-fix-dockerfile-arm64
26
+ - fix(docker): add build tools for native modules on alpine arm64
27
+ - Merge pull request #43 from TheSashaDev/devin/1778149272-fix-docker-install
28
+ - fix: docker install — fix branch ref (main→master), fallback to local on pull failure
29
+ - Merge pull request #37 from TheSashaDev/devin/1778090781-windows-installer-webui
30
+ - feat(server): curl|sh installer + docker image + headless server mode
31
+ - fix(cli): fail loudly on non-TTY terminals + catch unhandled rejections
32
+ - feat(installer/desktop): paste, ClaudeHub referral, tournament names, custom sleep, profile picker
33
+ - fix(installer): silent crash on Windows — panic=abort + windowed subsystem hid the panic
34
+ - fix(installer): replace empty-text widgets in progress header with Space
35
+ - feat(installer): bundle portable Node + cli.js, full TS-wizard parity, Cyrillic fonts
36
+ - perf: add release-fast profile, mold linker, windows_subsystem
37
+ - Добавить ссылки на Telegram канал и сообщество
38
+ - feat: native Windows installer + desktop app + web UI (Rust/iced)
39
+ - Merge pull request #36 from TheSashaDev/devin/1778089384-changelog-pr35
40
+ - docs: add PR 35 to changelog
41
+
3
42
  ## 0.1.8 — OpenAI-compatible API compatibility
4
43
 
5
44
  Дата: 2026-05-06
@@ -7,6 +46,7 @@
7
46
  - JSON-ответы теперь сначала запрашиваются через `json_schema`, с fallback на `json_object` и `text` для разных OpenAI-compatible API. (#33)
8
47
  - LM Studio и Ollama больше не требуют реальный API ключ в wizard/headless setup.
9
48
  - Добавлена совместимость с OpenAI-compatible прокси, которые возвращают SSE/event-stream даже на обычный chat completions запрос.
49
+ - Добавлена Docker-поддержка для 24/7 запуска на сервере: `Dockerfile`, `docker-compose.yml`, volume для `data` и инструкции в README. (#35)
10
50
 
11
51
  ## 0.1.7 — MarkdownV2 escaping fix
12
52
 
package/README.md CHANGED
@@ -10,6 +10,8 @@
10
10
  Со всеми проблемами и багами пишите в Issues.
11
11
  ТГ создателя - @voided_net
12
12
 
13
+ Тг канал: https://t.me/GirlAgentAI/
14
+ Тг сообщество: https://t.me/GirlAgentAI_chat/
13
15
  ---
14
16
 
15
17
  ## Содержание
@@ -35,35 +37,97 @@
35
37
 
36
38
  ## Быстрый старт
37
39
 
38
- **Через NPX (рекомендуется):**
40
+ ### linux / macos / wsl — одной командой (без node на машине)
39
41
 
40
- ```powershell
41
- npx @thesashadev/girl-agent
42
+ ```sh
43
+ curl -fsSL https://raw.githubusercontent.com/TheSashaDev/girl-agent/master/scripts/install.sh | sh
44
+ ```
45
+
46
+ Что произойдёт:
47
+ - определит OS + arch (linux x64/arm64, macos x64/arm64, wsl)
48
+ - если есть docker → поставит docker-обёртку (полная изоляция от системы)
49
+ - иначе → скачает [official Node.js 22 LTS](https://nodejs.org) в `~/.local/share/girl-agent/runtime/` и поставит туда же `@thesashadev/girl-agent` (system node не трогается)
50
+ - shim-скрипт `girl-agent` положит в `~/.local/bin/girl-agent`
51
+ - ничего не пишется в `/usr/local/`, `sudo` не нужен
52
+
53
+ Дальше:
54
+ ```sh
55
+ girl-agent # ink-визард для интерактивной первичной настройки
56
+ girl-agent --profile=arina # запустить готовый профиль
57
+ girl-agent server --help # серверный режим (без TTY, для systemd / cron / CI)
58
+ ```
59
+
60
+ Опции установщика:
61
+ ```sh
62
+ # форсировать docker
63
+ curl -fsSL .../install.sh | sh -s -- --docker
64
+
65
+ # форсировать локальную ноду
66
+ curl -fsSL .../install.sh | sh -s -- --local
67
+
68
+ # конкретная версия пакета
69
+ curl -fsSL .../install.sh | sh -s -- --version=0.1.9
42
70
  ```
43
71
 
44
- Wizard задаст пару вопросов — имя, возраст, Telegram-подключение, LLM-ключ. Всё.
72
+ Удаление: `rm -rf ~/.local/share/girl-agent ~/.local/bin/girl-agent`
45
73
 
46
- Если профиль уже есть:
74
+ ### windows десктоп-приложение
75
+
76
+ В папке `desktop-rs/` лежит нативный десктоп-клиент на Rust (iced) и инсталлер-визард: ставит Node-пакет, создаёт профиль, открывает дашборд. Параллельно поднимается локальный веб-UI на `http://127.0.0.1:7777` с тем же дашбордом — открыть из соседнего окна / телефона по локалке. Без WebView, без Electron.
47
77
 
48
78
  ```powershell
79
+ cd desktop-rs
80
+ cargo run -p girl-agent-installer # визард настройки персоны
81
+ cargo run -p girl-agent-desktop # открыть дашборд
82
+ ```
83
+
84
+ Готовые бинари будут собираться в CI чуть позже — пока нужно `cargo build --release`.
85
+
86
+ ### если уже есть node ≥ 20
87
+
88
+ ```sh
89
+ npx @thesashadev/girl-agent # ink-визард
49
90
  npx @thesashadev/girl-agent --profile=arina
50
91
  ```
51
92
 
52
- **Через Docker (рекомендуется для сервера):**
93
+ ### docker (для серверов; нулевые зависимости на хосте)
94
+
95
+ Интерактивная первичная настройка (ink-визард внутри контейнера):
96
+ ```sh
97
+ docker run -it --rm -v girl-agent-data:/data ghcr.io/thesashadev/girl-agent:latest
98
+ ```
53
99
 
54
- Первый запуск с интерактивом (для настройки через визард):
55
- ```bash
56
- docker-compose run --rm -it girl-agent
100
+ Headless (для systemd / docker compose / k8s) — сначала готовим конфиг, потом запускаем без TTY:
101
+ ```sh
102
+ # 1) шаблон конфига
103
+ docker run --rm ghcr.io/thesashadev/girl-agent:latest server --print-config > bot.json
104
+ # 2) отредактировать bot.json (token, api-key)
105
+ # 3) поднять в фоне
106
+ docker run -d --name girl-agent --restart=unless-stopped \
107
+ -v girl-agent-data:/data \
108
+ -v $PWD/bot.json:/config/bot.json:ro \
109
+ ghcr.io/thesashadev/girl-agent:latest \
110
+ server --config /config/bot.json --headless
57
111
  ```
58
- *(пройдите все шаги и после появления дашборда нажмите `Ctrl+C`)*
59
112
 
60
- Последующие (запуск в фоне):
61
- ```bash
62
- docker-compose up -d
113
+ Или совсем без файла, через env-vars (k8s secrets, docker compose):
114
+ ```sh
115
+ docker run -d --name girl-agent --restart=unless-stopped \
116
+ -v girl-agent-data:/data \
117
+ -e GIRL_AGENT_MODE=bot \
118
+ -e GIRL_AGENT_TOKEN=... \
119
+ -e GIRL_AGENT_API_PRESET=claudehub \
120
+ -e GIRL_AGENT_API_KEY=... \
121
+ -e GIRL_AGENT_NAME='Аня' -e GIRL_AGENT_AGE=22 \
122
+ ghcr.io/thesashadev/girl-agent:latest \
123
+ server --headless
63
124
  ```
64
- *(если профилей несколько, запустите конкретный так: `docker-compose run -d girl-agent node dist/cli.js --profile=arina`)*
65
125
 
66
- *(посмотреть логи: `docker-compose logs -f`)*
126
+ Готовые шаблоны:
127
+ - `girl-agent server --print-config` — bot.json
128
+ - `girl-agent server --print-systemd` — `/etc/systemd/system/girl-agent.service`
129
+ - `girl-agent server --print-docker` — Dockerfile / compose / k8s snippets
130
+ - [`docker-compose.example.yml`](./docker-compose.example.yml) в корне репо
67
131
 
68
132
  **Из исходников:**
69
133
 
package/dist/cli.js CHANGED
@@ -19,15 +19,19 @@ var init_esm_shims = __esm({
19
19
  });
20
20
 
21
21
  // src/telegram/markdown.ts
22
- function escapeMarkdownV2(text) {
23
- return text.replace(MD2_RESERVED, "\\$1");
22
+ function hasSpoilers(text) {
23
+ return /\|\|.+?\|\|/.test(text);
24
+ }
25
+ function escapeHtml(text) {
26
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
27
+ }
28
+ function toHtmlWithSpoilers(text) {
29
+ return escapeHtml(text).replace(/\|\|(.+?)\|\|/g, "<tg-spoiler>$1</tg-spoiler>");
24
30
  }
25
- var MD2_RESERVED;
26
31
  var init_markdown = __esm({
27
32
  "src/telegram/markdown.ts"() {
28
33
  "use strict";
29
34
  init_esm_shims();
30
- MD2_RESERVED = /([_*\[\]()~`>#+\-=|{}.!\\])/g;
31
35
  }
32
36
  });
33
37
 
@@ -55,13 +59,15 @@ function makeUserbotAdapter(cfg) {
55
59
  const apiHash = cfg.telegram.apiHash;
56
60
  const session = cfg.telegram.sessionString ?? "";
57
61
  if (!apiId || !apiHash) throw new Error("API_ID/API_HASH missing for userbot");
58
- debug("[userbot] creating TelegramClient\u2026");
62
+ const useWSS = cfg.telegram.useWSS !== false;
63
+ debug(`[userbot] creating TelegramClient (useWSS=${useWSS})\u2026`);
59
64
  const client = new TelegramClient(new StringSession(session), apiId, apiHash, {
60
65
  connectionRetries: 5,
61
66
  requestRetries: 5,
62
67
  retryDelay: 3e3,
63
68
  autoReconnect: true,
64
- floodSleepThreshold: 120
69
+ floodSleepThreshold: 120,
70
+ useWSS
65
71
  });
66
72
  client.onError = async () => {
67
73
  };
@@ -190,11 +196,14 @@ function makeUserbotAdapter(cfg) {
190
196
  },
191
197
  async editLastMessage(chatId, messageId, text) {
192
198
  const peer = await resolvePeer(chatId);
193
- try {
194
- await client.editMessage(peer, { message: messageId, text: escapeMarkdownV2(text), parseMode: "MarkdownV2" });
195
- } catch {
196
- await client.editMessage(peer, { message: messageId, text });
199
+ if (hasSpoilers(text)) {
200
+ try {
201
+ await client.editMessage(peer, { message: messageId, text: toHtmlWithSpoilers(text), parseMode: "html" });
202
+ return;
203
+ } catch {
204
+ }
197
205
  }
206
+ await client.editMessage(peer, { message: messageId, text });
198
207
  },
199
208
  async deleteMessages(chatId, messageIds, revoke = false) {
200
209
  const peer = await resolvePeer(chatId);
@@ -207,7 +216,8 @@ function makeUserbotAdapter(cfg) {
207
216
  }
208
217
  async function userbotLogin(opts) {
209
218
  const client = new TelegramClient(new StringSession(""), opts.apiId, opts.apiHash, {
210
- connectionRetries: 5
219
+ connectionRetries: 5,
220
+ useWSS: true
211
221
  });
212
222
  await client.start({
213
223
  phoneNumber: async () => opts.phone,
@@ -293,13 +303,15 @@ function makeBotAdapter(cfg) {
293
303
  });
294
304
  },
295
305
  async sendText(chatId, text) {
296
- try {
297
- const msg = await bot.api.sendMessage(chatId, escapeMarkdownV2(text), { parse_mode: "MarkdownV2" });
298
- return msg.message_id;
299
- } catch {
300
- const msg = await bot.api.sendMessage(chatId, text);
301
- return msg.message_id;
306
+ if (hasSpoilers(text)) {
307
+ try {
308
+ const msg2 = await bot.api.sendMessage(chatId, toHtmlWithSpoilers(text), { parse_mode: "HTML" });
309
+ return msg2.message_id;
310
+ } catch {
311
+ }
302
312
  }
313
+ const msg = await bot.api.sendMessage(chatId, text);
314
+ return msg.message_id;
303
315
  },
304
316
  async setTyping(chatId, on) {
305
317
  if (on) {
@@ -581,6 +593,7 @@ init_esm_shims();
581
593
  var STAGE_PRESETS = [
582
594
  {
583
595
  id: "met-irl-got-tg",
596
+ num: 1,
584
597
  label: "\u0412\u0441\u0442\u0440\u0435\u0442\u0438\u043B\u0438\u0441\u044C \u0432 \u0440\u0435\u0430\u043B\u0435 \u2014 \u0434\u0430\u043B\u0430 \u0442\u0433",
585
598
  description: "\u0422\u043E\u043B\u044C\u043A\u043E \u0447\u0442\u043E \u043E\u0431\u043C\u0435\u043D\u044F\u043B\u0438\u0441\u044C \u0442\u0433. \u041F\u043E\u043C\u043D\u0438\u0442 \u043B\u0438\u0446\u043E, \u0433\u043E\u043B\u043E\u0441. \u041B\u0451\u0433\u043A\u0438\u0439 \u0438\u043D\u0442\u0435\u0440\u0435\u0441.",
586
599
  defaults: {
@@ -595,6 +608,7 @@ var STAGE_PRESETS = [
595
608
  },
596
609
  {
597
610
  id: "tg-given-cold",
611
+ num: 2,
598
612
  label: "\u0414\u0430\u043B\u0430 \u0442\u0433, \u043D\u043E \u043D\u0435 \u0443\u0431\u0435\u0434\u0438\u043B \u043E\u0442\u0432\u0435\u0447\u0430\u0442\u044C",
599
613
  description: "\u0421\u043E\u043C\u043D\u0435\u0432\u0430\u0435\u0442\u0441\u044F. \u0427\u0430\u0441\u0442\u043E \u0438\u0433\u043D\u043E\u0440\u0438\u0442, \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u043E\u0434\u043D\u043E\u0441\u043B\u043E\u0436\u043D\u043E. \u041D\u0443\u0436\u043D\u043E \u0434\u043E\u0431\u0438\u0432\u0430\u0442\u044C\u0441\u044F.",
600
614
  defaults: {
@@ -609,6 +623,7 @@ var STAGE_PRESETS = [
609
623
  },
610
624
  {
611
625
  id: "tg-given-warming",
626
+ num: 3,
612
627
  label: "\u0414\u0430\u043B\u0430 \u0442\u0433, \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u043E\u0441\u0442\u043E\u0440\u043E\u0436\u043D\u043E",
613
628
  description: "\u041E\u0442\u0442\u0430\u0438\u0432\u0430\u0435\u0442. \u041E\u0442\u0432\u0435\u0447\u0430\u0435\u0442, \u043D\u043E \u043A\u043E\u0440\u043E\u0442\u043A\u043E. \u0422\u0435\u0441\u0442\u0438\u0442 \u0442\u0435\u0431\u044F.",
614
629
  defaults: {
@@ -623,6 +638,7 @@ var STAGE_PRESETS = [
623
638
  },
624
639
  {
625
640
  id: "convinced",
641
+ num: 4,
626
642
  label: "\u0423\u0431\u0435\u0434\u0438\u043B \u043E\u0442\u0432\u0435\u0447\u0430\u0442\u044C \u0441\u0442\u0430\u0431\u0438\u043B\u044C\u043D\u043E",
627
643
  description: "\u041E\u0431\u0449\u0430\u0435\u0442\u0435\u0441\u044C \u0440\u0435\u0433\u0443\u043B\u044F\u0440\u043D\u043E, \u0444\u043B\u0438\u0440\u0442\u0443\u0435\u0442, \u0435\u0449\u0451 \u043D\u0435 \u0432\u0438\u0434\u0435\u043B\u0438\u0441\u044C \u043F\u043E\u0441\u043B\u0435 \u0437\u043D\u0430\u043A\u043E\u043C\u0441\u0442\u0432\u0430.",
628
644
  defaults: {
@@ -637,6 +653,7 @@ var STAGE_PRESETS = [
637
653
  },
638
654
  {
639
655
  id: "first-date-done",
656
+ num: 5,
640
657
  label: "\u0421\u0445\u043E\u0434\u0438\u043B\u0438 \u043E\u0434\u0438\u043D \u0440\u0430\u0437",
641
658
  description: "\u041F\u0435\u0440\u0432\u043E\u0435 \u0441\u0432\u0438\u0434\u0430\u043D\u0438\u0435 \u0431\u044B\u043B\u043E, \u0432 \u043F\u043E\u0434\u0432\u0435\u0448\u0435\u043D\u043D\u043E\u043C \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0438 \u2014 \u043D\u0440\u0430\u0432\u0438\u0442\u0441\u044F, \u043D\u043E \u043D\u0435 \u043F\u0430\u0440\u0430.",
642
659
  defaults: {
@@ -651,6 +668,7 @@ var STAGE_PRESETS = [
651
668
  },
652
669
  {
653
670
  id: "dating-early",
671
+ num: 6,
654
672
  label: "\u0422\u043E\u043B\u044C\u043A\u043E \u043D\u0430\u0447\u0430\u043B\u0438 \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u0442\u044C\u0441\u044F",
655
673
  description: "\u041E\u043A\u043E\u043B\u043E \u043C\u0435\u0441\u044F\u0446\u0430 \u0432\u043C\u0435\u0441\u0442\u0435. \u0411\u0430\u0431\u043E\u0447\u043A\u0438, \u0432\u0441\u0451 \u0432\u043D\u043E\u0432\u0435, \u043D\u043E \u0433\u0440\u0430\u043D\u0438\u0446\u044B \u0435\u0449\u0451 \u0445\u0440\u0443\u043F\u043A\u0438\u0435.",
656
674
  defaults: {
@@ -665,6 +683,7 @@ var STAGE_PRESETS = [
665
683
  },
666
684
  {
667
685
  id: "dating-stable",
686
+ num: 7,
668
687
  label: "\u041F\u0430\u0440\u0430, \u043E\u0431\u0449\u0430\u0435\u0442\u0435\u0441\u044C \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u043E",
669
688
  description: "\u0421\u0442\u0430\u0431\u0438\u043B\u044C\u043D\u044B\u0435 \u043E\u0442\u043D\u043E\u0448\u0435\u043D\u0438\u044F, \u0448\u0443\u0442\u043A\u0438, \u0431\u044B\u0442\u043E\u0432\u0443\u0445\u0430, \u0434\u043E\u0432\u0435\u0440\u0438\u0435.",
670
689
  defaults: {
@@ -679,6 +698,7 @@ var STAGE_PRESETS = [
679
698
  },
680
699
  {
681
700
  id: "long-term",
701
+ num: 8,
682
702
  label: "\u0414\u0430\u0432\u043D\u043E \u0432\u043C\u0435\u0441\u0442\u0435",
683
703
  description: "\u0413\u043E\u0434+ \u0432\u043C\u0435\u0441\u0442\u0435. \u0418\u043D\u043E\u0433\u0434\u0430 \u0440\u0430\u0437\u0434\u0440\u0430\u0436\u0435\u043D\u0438\u0435, \u0440\u0443\u0442\u0438\u043D\u0430, \u0433\u043B\u0443\u0431\u043E\u043A\u043E\u0435 \u0434\u043E\u0432\u0435\u0440\u0438\u0435.",
684
704
  defaults: {
@@ -693,6 +713,7 @@ var STAGE_PRESETS = [
693
713
  },
694
714
  {
695
715
  id: "dumped",
716
+ num: 9,
696
717
  label: "\u041E\u0442\u0448\u0438\u043B\u0430 (\u0441\u043B\u0443\u0436\u0435\u0431\u043D\u043E\u0435)",
697
718
  description: "\u041D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442. \u0421\u043D\u0438\u043C\u0430\u0435\u0442\u0441\u044F \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 :reset.",
698
719
  defaults: {
@@ -707,6 +728,10 @@ var STAGE_PRESETS = [
707
728
  }
708
729
  ];
709
730
  function findStage(id) {
731
+ if (typeof id === "number" || /^\d+$/.test(String(id))) {
732
+ const num = Number(id);
733
+ return STAGE_PRESETS.find((s) => s.num === num) ?? STAGE_PRESETS[1];
734
+ }
710
735
  return STAGE_PRESETS.find((s) => s.id === id) ?? STAGE_PRESETS[1];
711
736
  }
712
737
 
@@ -800,7 +825,7 @@ function sameProfile(a, b) {
800
825
  init_esm_shims();
801
826
  import { promises as fs } from "fs";
802
827
  import path2 from "path";
803
- var DATA_ROOT = path2.resolve(process.cwd(), "data");
828
+ var DATA_ROOT = process.env.GIRL_AGENT_DATA ? path2.resolve(process.env.GIRL_AGENT_DATA) : path2.resolve(process.cwd(), "data");
804
829
  function profileDir(slug) {
805
830
  return path2.join(DATA_ROOT, slug);
806
831
  }
@@ -1534,14 +1559,14 @@ var DEFAULT_PROXY = "https://tgproxy.girl-agent.com";
1534
1559
  function proxyUrl() {
1535
1560
  return process.env.GIRL_AGENT_AUTH_PROXY ?? DEFAULT_PROXY;
1536
1561
  }
1537
- async function post(path5, body) {
1538
- const res = await fetch(`${proxyUrl()}${path5}`, {
1562
+ async function post(path6, body) {
1563
+ const res = await fetch(`${proxyUrl()}${path6}`, {
1539
1564
  method: "POST",
1540
1565
  headers: { "Content-Type": "application/json" },
1541
1566
  body: JSON.stringify(body)
1542
1567
  });
1543
1568
  const data = await res.json();
1544
- if (!res.ok) throw new Error(data.error ?? `proxy ${path5} failed (${res.status})`);
1569
+ if (!res.ok) throw new Error(data.error ?? `proxy ${path6} failed (${res.status})`);
1545
1570
  return data;
1546
1571
  }
1547
1572
  function remoteSendCode(phone) {
@@ -2425,7 +2450,7 @@ function Wizard({ initial, onDone }) {
2425
2450
  SelectInput,
2426
2451
  {
2427
2452
  limit: 10,
2428
- items: STAGE_PRESETS.filter((s) => s.id !== "dumped").map((s) => ({ label: `${s.label} \xB7 ${s.description}`, value: s.id })),
2453
+ items: STAGE_PRESETS.filter((s) => s.id !== "dumped").map((s) => ({ label: `${s.num}. ${s.label} \xB7 ${s.description}`, value: s.id })),
2429
2454
  onSelect: async (it) => {
2430
2455
  const nextStage = it.value;
2431
2456
  setStage(nextStage);
@@ -2670,7 +2695,7 @@ function Dashboard({ runtime }) {
2670
2695
  break;
2671
2696
  }
2672
2697
  case "help":
2673
- append(":status :why :amnesia <\u043C\u0438\u043D> [chatId] :reset :stage <id> :wake [chatId] :debug [chatId] :pause :resume :cringe :relationship :persona :log [YYYY-MM-DD] [chars] :block [chatId] :unblock [chatId] :read [chatId] :clear-chat [chatId] [--revoke] :report-spam [chatId] :delete-last [chatId] [--local] :edit-last <text> :sticker [chatId] :quit");
2698
+ append(":status :why :amnesia <\u043C\u0438\u043D> [chatId] :reset :stage <id|num> :wake [chatId] :debug [chatId] :pause :resume :cringe :relationship :persona :log [YYYY-MM-DD] [chars] :block [chatId] :unblock [chatId] :read [chatId] :clear-chat [chatId] [--revoke] :report-spam [chatId] :delete-last [chatId] [--local] :edit-last <text> :sticker [chatId] :quit");
2674
2699
  break;
2675
2700
  case "quit":
2676
2701
  case "exit":
@@ -2688,7 +2713,7 @@ function Dashboard({ runtime }) {
2688
2713
  const line = cmd.trim();
2689
2714
  setCmd("");
2690
2715
  if (line) await execute(line);
2691
- } })), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "\u043A\u043E\u043C\u0430\u043D\u0434\u044B: :status :why :amnesia <\u043C\u0438\u043D> :reset :stage <id> :pause :resume :cringe :persona :log [day] :block :unblock :read :clear-chat :delete-last :edit-last :sticker :quit"));
2716
+ } })), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "\u043A\u043E\u043C\u0430\u043D\u0434\u044B: :status :why :amnesia <\u043C\u0438\u043D> :reset :stage <id|num> :pause :resume :cringe :persona :log [day] :block :unblock :read :clear-chat :delete-last :edit-last :sticker :quit"));
2692
2717
  }
2693
2718
 
2694
2719
  // src/engine/runtime.ts
@@ -3093,8 +3118,8 @@ phoneAvailable=false \u043A\u043E\u0433\u0434\u0430: \u0441\u043F\u0438\u0442, \
3093
3118
  }
3094
3119
  async function loadOrGenerateDailyLife(llm, cfg, now = /* @__PURE__ */ new Date(), conflict = null) {
3095
3120
  const dateLocal = localDateStr(cfg.tz, now);
3096
- const path5 = `daily-life/${dateLocal}.md`;
3097
- const existing = await readMd(cfg.slug, path5);
3121
+ const path6 = `daily-life/${dateLocal}.md`;
3122
+ const existing = await readMd(cfg.slug, path6);
3098
3123
  if (existing) {
3099
3124
  try {
3100
3125
  const m = existing.match(/<!--daily:(.+?)-->/s);
@@ -3126,7 +3151,7 @@ async function loadOrGenerateDailyLife(llm, cfg, now = /* @__PURE__ */ new Date(
3126
3151
  dl = { dateLocal, vibe: "\u043E\u0431\u044B\u0447\u043D\u044B\u0439 \u0434\u0435\u043D\u044C", blocks: [], events: [], wants: [] };
3127
3152
  }
3128
3153
  const human = renderDailyLifeHuman(dl);
3129
- await writeMd(cfg.slug, path5, `${human}
3154
+ await writeMd(cfg.slug, path6, `${human}
3130
3155
 
3131
3156
  <!--daily:${JSON.stringify(dl)}-->
3132
3157
  `);
@@ -3356,9 +3381,9 @@ async function ensureDefaults(cfg) {
3356
3381
  ["time/promises.md", "# promises\n"],
3357
3382
  ["memory/uncertain.md", "# uncertain\n"]
3358
3383
  ];
3359
- await Promise.all(defaults.map(async ([path5, content]) => {
3360
- const current = await readMd(cfg.slug, path5);
3361
- if (!current.trim()) await writeMd(cfg.slug, path5, content + "\n");
3384
+ await Promise.all(defaults.map(async ([path6, content]) => {
3385
+ const current = await readMd(cfg.slug, path6);
3386
+ if (!current.trim()) await writeMd(cfg.slug, path6, content + "\n");
3362
3387
  }));
3363
3388
  }
3364
3389
  async function loadRealismContext(cfg, incoming) {
@@ -5380,10 +5405,11 @@ ${tick.intent === "short" ? "\u041E\u0442\u0432\u0435\u0447\u0430\u0439 \u043E\u
5380
5405
  }
5381
5406
  async cmdSetStage(stageId) {
5382
5407
  const prev = this.cfg.stage;
5383
- this.cfg.stage = stageId;
5408
+ const resolved = findStage(stageId);
5409
+ this.cfg.stage = resolved.id;
5384
5410
  await writeConfig(this.cfg);
5385
- await maybeAdvanceRelationshipTimeline(this.cfg, prev, stageId);
5386
- return `\u0441\u0442\u0430\u0434\u0438\u044F \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430: ${stageId}`;
5411
+ await maybeAdvanceRelationshipTimeline(this.cfg, prev, resolved.id);
5412
+ return `\u0441\u0442\u0430\u0434\u0438\u044F \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430: ${resolved.num}=${resolved.id}`;
5387
5413
  }
5388
5414
  async cmdWake(chatId) {
5389
5415
  const now = Date.now();
@@ -5730,6 +5756,536 @@ function sleep(ms) {
5730
5756
  return new Promise((r) => setTimeout(r, ms));
5731
5757
  }
5732
5758
 
5759
+ // src/headless.ts
5760
+ init_esm_shims();
5761
+ import readline from "readline";
5762
+ async function runHeadlessJsonEvents(rt) {
5763
+ const out = (obj) => {
5764
+ process.stdout.write(JSON.stringify(obj) + "\n");
5765
+ };
5766
+ out({ type: "ready", profile: profileSummary(rt.cfg) });
5767
+ rt.on("event", (e) => {
5768
+ out({ ...e, t: Date.now() });
5769
+ });
5770
+ try {
5771
+ const r = await readRelationship(rt.cfg.slug);
5772
+ out({ type: "score", score: r.score, t: Date.now() });
5773
+ } catch {
5774
+ }
5775
+ let paused = false;
5776
+ const rl = readline.createInterface({ input: process.stdin, terminal: false });
5777
+ rl.on("line", async (raw) => {
5778
+ const line = raw.trim();
5779
+ if (!line) return;
5780
+ if (!line.startsWith(":")) {
5781
+ out({ type: "response", ok: false, text: "\u043A\u043E\u043C\u0430\u043D\u0434\u044B \u043D\u0430\u0447\u0438\u043D\u0430\u044E\u0442\u0441\u044F \u0441 :" });
5782
+ return;
5783
+ }
5784
+ const [head, ...rest] = line.slice(1).split(" ");
5785
+ try {
5786
+ let text = "";
5787
+ switch (head) {
5788
+ case "status":
5789
+ text = await rt.cmdStatus();
5790
+ break;
5791
+ case "reset":
5792
+ text = await rt.cmdReset();
5793
+ break;
5794
+ case "stage":
5795
+ text = await rt.cmdSetStage(rest.join(" "));
5796
+ break;
5797
+ case "wake":
5798
+ text = await rt.cmdWake(rest[0]);
5799
+ break;
5800
+ case "debug":
5801
+ text = await rt.cmdDebug(rest[0]);
5802
+ break;
5803
+ case "why":
5804
+ text = await rt.cmdWhy(rest[0]);
5805
+ break;
5806
+ case "amnesia":
5807
+ text = await rt.cmdAmnesia(rest[0], rest[1]);
5808
+ break;
5809
+ case "block":
5810
+ text = await rt.cmdBlock(rest[0]);
5811
+ break;
5812
+ case "unblock":
5813
+ text = await rt.cmdUnblock(rest[0]);
5814
+ break;
5815
+ case "read":
5816
+ text = await rt.cmdRead(rest[0]);
5817
+ break;
5818
+ case "clear-chat":
5819
+ text = await rt.cmdClearChat(rest.find((x) => !x.startsWith("--")), rest.includes("--revoke"));
5820
+ break;
5821
+ case "report-spam":
5822
+ text = await rt.cmdReportSpam(rest[0]);
5823
+ break;
5824
+ case "delete-last":
5825
+ text = await rt.cmdDeleteLast(rest.find((x) => !x.startsWith("--")), !rest.includes("--local"));
5826
+ break;
5827
+ case "edit-last":
5828
+ text = await rt.cmdEditLast(rest.join(" "));
5829
+ break;
5830
+ case "sticker":
5831
+ text = await rt.cmdSticker(rest[0]);
5832
+ break;
5833
+ case "pause":
5834
+ rt.pause();
5835
+ paused = true;
5836
+ text = "\u23F8 pause";
5837
+ break;
5838
+ case "resume":
5839
+ rt.resume();
5840
+ paused = false;
5841
+ text = "\u25B6 resume";
5842
+ break;
5843
+ case "cringe": {
5844
+ const r = await readRelationship(rt.cfg.slug);
5845
+ text = `cringe=${r.score.cringe}; \u0441\u043C. memory/long-term.md \u0438 log/`;
5846
+ break;
5847
+ }
5848
+ case "relationship": {
5849
+ const r = await readRelationship(rt.cfg.slug);
5850
+ text = `stage=${r.stage} score=${JSON.stringify(r.score)}`;
5851
+ break;
5852
+ }
5853
+ case "persona": {
5854
+ const p = await readMd(rt.cfg.slug, "persona.md");
5855
+ text = p.slice(0, 4e3);
5856
+ break;
5857
+ }
5858
+ case "log": {
5859
+ const day = /^\d{4}-\d{2}-\d{2}$/.test(rest[0] ?? "") ? rest[0] : sessionDate(rt.cfg.tz);
5860
+ const limit = Number(rest.find((x) => /^\d+$/.test(x)) ?? 3e3);
5861
+ const p = await readSessionLog(rt.cfg.slug, day);
5862
+ text = p.trim() ? p.slice(-Math.max(500, Math.min(limit, 2e4))) : `(log/${day}.md \u043F\u0443\u0441\u0442)`;
5863
+ break;
5864
+ }
5865
+ case "snapshot": {
5866
+ const r = await readRelationship(rt.cfg.slug);
5867
+ out({
5868
+ type: "snapshot",
5869
+ t: Date.now(),
5870
+ paused,
5871
+ profile: profileSummary(rt.cfg),
5872
+ stage: { id: r.stage, num: findStage(r.stage).num, label: findStage(r.stage).label },
5873
+ score: r.score
5874
+ });
5875
+ return;
5876
+ }
5877
+ case "help":
5878
+ text = ":status :why :amnesia :reset :stage :wake :debug :pause :resume :cringe :relationship :persona :log :block :unblock :read :clear-chat :report-spam :delete-last :edit-last :sticker :snapshot :quit";
5879
+ break;
5880
+ case "quit":
5881
+ case "exit":
5882
+ await rt.stop();
5883
+ out({ type: "response", ok: true, text: "bye" });
5884
+ process.exit(0);
5885
+ default:
5886
+ out({ type: "response", ok: false, text: `\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043A\u043E\u043C\u0430\u043D\u0434\u0430: ${head}` });
5887
+ return;
5888
+ }
5889
+ out({ type: "response", ok: true, text });
5890
+ } catch (e) {
5891
+ out({ type: "response", ok: false, text: "err: " + e.message });
5892
+ }
5893
+ });
5894
+ const shutdown = async () => {
5895
+ try {
5896
+ await rt.stop();
5897
+ } catch {
5898
+ }
5899
+ out({ type: "stopped", t: Date.now() });
5900
+ process.exit(0);
5901
+ };
5902
+ process.on("SIGINT", shutdown);
5903
+ process.on("SIGTERM", shutdown);
5904
+ await new Promise(() => {
5905
+ });
5906
+ }
5907
+ function profileSummary(cfg) {
5908
+ const stage = findStage(cfg.stage);
5909
+ return {
5910
+ slug: cfg.slug,
5911
+ name: cfg.name,
5912
+ age: cfg.age,
5913
+ mode: cfg.mode,
5914
+ nationality: cfg.nationality,
5915
+ tz: cfg.tz,
5916
+ stage: { id: cfg.stage, num: stage.num, label: stage.label }
5917
+ };
5918
+ }
5919
+
5920
+ // src/server.ts
5921
+ init_esm_shims();
5922
+ import fs4 from "fs/promises";
5923
+ import path5 from "path";
5924
+ import os from "os";
5925
+ var SERVER_HELP = `
5926
+ girl-agent server \u2014 automation / ops mode (no TTY required)
5927
+
5928
+ usage:
5929
+ girl-agent server --print-config > bot.json
5930
+ # \u043E\u0442\u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u0443\u0439 bot.json
5931
+ girl-agent server --config bot.json --headless
5932
+
5933
+ girl-agent server --list
5934
+ girl-agent server --profile=<slug> --headless
5935
+
5936
+ girl-agent server --print-systemd > /etc/systemd/system/girl-agent.service
5937
+ girl-agent server --print-docker
5938
+
5939
+ env-vars (\u0434\u043B\u044F CI / docker secrets / k8s):
5940
+ GIRL_AGENT_DATA \u043F\u0443\u0442\u044C \u043A \u043F\u0440\u043E\u0444\u0438\u043B\u044F\u043C (default: ./data)
5941
+ GIRL_AGENT_MODE bot|userbot
5942
+ GIRL_AGENT_TOKEN telegram bot token
5943
+ GIRL_AGENT_API_PRESET openai|anthropic|claudehub|...
5944
+ GIRL_AGENT_API_KEY \u043A\u043B\u044E\u0447 \u043E\u0442 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430
5945
+ GIRL_AGENT_MODEL, _NAME, _AGE, _NATIONALITY, _TZ, _STAGE (id \u0438\u043B\u0438 \u043D\u043E\u043C\u0435\u0440 1-8), _COMM_PRESET
5946
+
5947
+ \u0434\u043B\u044F \u0438\u043D\u0442\u0435\u0440\u0430\u043A\u0442\u0438\u0432\u043D\u043E\u0439 \u043F\u0435\u0440\u0432\u0438\u0447\u043D\u043E\u0439 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0439 \u0431\u0435\u0437 \u0444\u043B\u0430\u0433\u043E\u0432 \u0432 \u043E\u0431\u044B\u0447\u043D\u043E\u043C \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0435 \u2014
5948
+ \u043E\u0442\u043A\u0440\u043E\u0435\u0442\u0441\u044F ink-\u0432\u0438\u0437\u0430\u0440\u0434.
5949
+ `;
5950
+ function parseServerArgs(argv) {
5951
+ return {
5952
+ config: typeof argv.config === "string" ? argv.config : void 0,
5953
+ printConfig: !!argv["print-config"],
5954
+ printSystemd: !!argv["print-systemd"],
5955
+ printDocker: !!argv["print-docker"],
5956
+ headless: !!argv.headless,
5957
+ jsonEvents: !!argv["json-events"],
5958
+ noStart: !!argv["no-start"] || argv.start === false,
5959
+ profile: typeof argv.profile === "string" ? argv.profile : void 0,
5960
+ list: !!argv.list,
5961
+ help: !!argv.help
5962
+ };
5963
+ }
5964
+ async function runServer(rawArgv) {
5965
+ const args = parseServerArgs(rawArgv);
5966
+ if (args.help) {
5967
+ process.stdout.write(SERVER_HELP);
5968
+ return;
5969
+ }
5970
+ if (args.printConfig) {
5971
+ process.stdout.write(buildConfigTemplate());
5972
+ return;
5973
+ }
5974
+ if (args.printSystemd) {
5975
+ process.stdout.write(buildSystemdUnit());
5976
+ return;
5977
+ }
5978
+ if (args.printDocker) {
5979
+ process.stdout.write(buildDockerArtifacts());
5980
+ return;
5981
+ }
5982
+ if (args.list) {
5983
+ const list = await listProfiles();
5984
+ process.stdout.write(list.length ? list.join("\n") + "\n" : "(\u043D\u0435\u0442 \u043F\u0440\u043E\u0444\u0438\u043B\u0435\u0439)\n");
5985
+ process.stdout.write(`data: ${DATA_ROOT}
5986
+ `);
5987
+ return;
5988
+ }
5989
+ if (args.profile) {
5990
+ const cfg = await readConfig(args.profile);
5991
+ if (!cfg) {
5992
+ process.stderr.write(`profile not found: ${args.profile}
5993
+ `);
5994
+ process.stderr.write(`data dir: ${DATA_ROOT}
5995
+ `);
5996
+ process.exit(1);
5997
+ }
5998
+ await startRuntime(cfg, args);
5999
+ return;
6000
+ }
6001
+ if (args.config) {
6002
+ const cfg = await loadConfigFile(args.config);
6003
+ await persistAndMaybeStart(cfg, args);
6004
+ return;
6005
+ }
6006
+ const cfgFromEnv = configFromEnv();
6007
+ if (cfgFromEnv) {
6008
+ process.stderr.write("[server] \u043F\u0440\u043E\u0432\u0438\u0436\u0443 \u043F\u0440\u043E\u0444\u0438\u043B\u044C \u0438\u0437 env vars\n");
6009
+ await persistAndMaybeStart(cfgFromEnv, args);
6010
+ return;
6011
+ }
6012
+ process.stderr.write(SERVER_HELP);
6013
+ process.stderr.write("\n[server] \u0434\u043B\u044F \u0438\u043D\u0442\u0435\u0440\u0430\u043A\u0442\u0438\u0432\u043D\u043E\u0439 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u0437\u0430\u043F\u0443\u0441\u0442\u0438 \u0431\u0435\u0437 \u0444\u043B\u0430\u0433\u043E\u0432 \u0432 TTY-\u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0435.\n");
6014
+ process.exit(1);
6015
+ }
6016
+ async function persistAndMaybeStart(cfg, args) {
6017
+ await writeConfig(cfg);
6018
+ process.stderr.write(`[server] \u043F\u0440\u043E\u0444\u0438\u043B\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D: ${path5.join(DATA_ROOT, cfg.slug)}
6019
+ `);
6020
+ if (cfg.llm.apiKey || findPreset(cfg.llm.presetId)?.apiKeyRequired === false) {
6021
+ try {
6022
+ process.stderr.write("[server] \u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u043C persona/speech/communication...\n");
6023
+ const llm = makeLLM(cfg.llm);
6024
+ const generated = await generatePersonaPack(llm, cfg.slug, cfg.name, cfg.age, cfg.nationality, cfg.personaNotes ?? "");
6025
+ cfg.busySchedule = generated.busySchedule;
6026
+ await writeConfig(cfg);
6027
+ process.stderr.write("[server] \u043F\u0435\u0440\u0441\u043E\u043D\u0430 \u0433\u043E\u0442\u043E\u0432\u0430.\n");
6028
+ } catch (e) {
6029
+ process.stderr.write(`[server] \u043E\u0448\u0438\u0431\u043A\u0430 \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u0438 \u043F\u0435\u0440\u0441\u043E\u043D\u044B: ${e?.message ?? e}
6030
+ `);
6031
+ process.stderr.write("[server] \u043F\u0440\u043E\u0444\u0438\u043B\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D, \u043D\u043E \u0431\u0435\u0437 persona.md. \u041C\u043E\u0436\u043D\u043E \u043F\u0435\u0440\u0435\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043F\u043E\u0437\u0436\u0435.\n");
6032
+ }
6033
+ } else {
6034
+ process.stderr.write("[server] api-\u043A\u043B\u044E\u0447 \u043D\u0435 \u0437\u0430\u0434\u0430\u043D \u2014 \u043F\u0440\u043E\u043F\u0443\u0441\u043A\u0430\u0435\u043C \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u044E \u043F\u0435\u0440\u0441\u043E\u043D\u044B.\n");
6035
+ }
6036
+ if (args.noStart) {
6037
+ process.stderr.write(`[server] --no-start: \u0437\u0430\u043F\u0443\u0441\u043A \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D.
6038
+ `);
6039
+ return;
6040
+ }
6041
+ await startRuntime(cfg, args);
6042
+ }
6043
+ async function startRuntime(cfg, args) {
6044
+ const rt = new Runtime(cfg);
6045
+ await rt.start();
6046
+ const wantsHeadless = !!(args.headless || args.jsonEvents);
6047
+ if (wantsHeadless) {
6048
+ await runHeadlessJsonEvents(rt);
6049
+ return;
6050
+ }
6051
+ process.stderr.write(`[server] \u0431\u043E\u0442 \u0437\u0430\u043F\u0443\u0449\u0435\u043D: ${cfg.name} (${cfg.slug})
6052
+ `);
6053
+ rt.on("event", (e) => {
6054
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
6055
+ const t = e.type ?? "event";
6056
+ process.stdout.write(`${ts} ${t} ${JSON.stringify(e)}
6057
+ `);
6058
+ });
6059
+ const stop = async () => {
6060
+ process.stderr.write("[server] \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430...\n");
6061
+ await rt.stop();
6062
+ process.exit(0);
6063
+ };
6064
+ process.on("SIGINT", stop);
6065
+ process.on("SIGTERM", stop);
6066
+ }
6067
+ function configFromEnv() {
6068
+ const e = process.env;
6069
+ if (!e.GIRL_AGENT_MODE && !e.GIRL_AGENT_TOKEN && !e.GIRL_AGENT_API_KEY) return null;
6070
+ const mode = e.GIRL_AGENT_MODE === "userbot" ? "userbot" : "bot";
6071
+ const presetId = e.GIRL_AGENT_API_PRESET ?? "claudehub";
6072
+ const preset = findPreset(presetId);
6073
+ if (!preset) {
6074
+ process.stderr.write(`[server] unknown api preset in env: ${presetId}
6075
+ `);
6076
+ process.exit(1);
6077
+ }
6078
+ const nationality = e.GIRL_AGENT_NATIONALITY === "UA" ? "UA" : "RU";
6079
+ const name = e.GIRL_AGENT_NAME || pickRandomNames(nationality, 1)[0];
6080
+ const age = Number(e.GIRL_AGENT_AGE ?? 18);
6081
+ const tz = e.GIRL_AGENT_TZ ? parseTzFlag(e.GIRL_AGENT_TZ) ?? defaultTzForNationality(nationality) : defaultTzForNationality(nationality);
6082
+ const stage = e.GIRL_AGENT_STAGE ? findStage(e.GIRL_AGENT_STAGE).id : "tg-given-cold";
6083
+ const commPreset = COMMUNICATION_PRESETS.find((c) => c.id === (e.GIRL_AGENT_COMM_PRESET ?? "normal")) ?? COMMUNICATION_PRESETS[0];
6084
+ return {
6085
+ slug: slugify(name),
6086
+ name,
6087
+ age,
6088
+ nationality,
6089
+ tz,
6090
+ mode,
6091
+ stage,
6092
+ llm: {
6093
+ presetId,
6094
+ proto: preset.proto,
6095
+ baseURL: preset.baseURL,
6096
+ apiKey: e.GIRL_AGENT_API_KEY ?? preset.defaultApiKey ?? "",
6097
+ model: e.GIRL_AGENT_MODEL ?? preset.defaultModel
6098
+ },
6099
+ telegram: mode === "bot" ? { botToken: e.GIRL_AGENT_TOKEN ?? "" } : {
6100
+ apiId: Number(e.GIRL_AGENT_TG_API_ID ?? 0),
6101
+ apiHash: e.GIRL_AGENT_TG_API_HASH ?? "",
6102
+ phone: e.GIRL_AGENT_TG_PHONE ?? ""
6103
+ },
6104
+ mcp: [],
6105
+ privacy: "owner-only",
6106
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6107
+ sleepFrom: Number(e.GIRL_AGENT_SLEEP_FROM ?? 23),
6108
+ sleepTo: Number(e.GIRL_AGENT_SLEEP_TO ?? 8),
6109
+ nightWakeChance: Number(e.GIRL_AGENT_NIGHT_WAKE ?? 0.05),
6110
+ communication: commPreset.profile,
6111
+ vibe: commPreset.profile.messageStyle === "one-liners" ? "short" : "warm",
6112
+ busySchedule: []
6113
+ };
6114
+ }
6115
+ async function loadConfigFile(file) {
6116
+ const abs = path5.isAbsolute(file) ? file : path5.join(process.cwd(), file);
6117
+ let raw;
6118
+ try {
6119
+ raw = await fs4.readFile(abs, "utf-8");
6120
+ } catch (e) {
6121
+ process.stderr.write(`[server] \u043D\u0435 \u043C\u043E\u0433\u0443 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C ${abs}: ${e?.message ?? e}
6122
+ `);
6123
+ process.exit(1);
6124
+ }
6125
+ let parsed;
6126
+ try {
6127
+ parsed = JSON.parse(raw);
6128
+ } catch (e) {
6129
+ process.stderr.write(`[server] ${abs} \u043D\u0435 \u044F\u0432\u043B\u044F\u0435\u0442\u0441\u044F \u0432\u0430\u043B\u0438\u0434\u043D\u044B\u043C JSON: ${e?.message ?? e}
6130
+ `);
6131
+ process.exit(1);
6132
+ }
6133
+ return validateConfig(parsed);
6134
+ }
6135
+ function validateConfig(raw) {
6136
+ const c = raw;
6137
+ const errs = [];
6138
+ if (!c.name) errs.push("name");
6139
+ if (!c.age || c.age < 14 || c.age > 99) errs.push("age (14..99)");
6140
+ if (!c.nationality || c.nationality !== "RU" && c.nationality !== "UA") errs.push("nationality (RU|UA)");
6141
+ if (!c.tz) errs.push("tz");
6142
+ if (!c.mode || c.mode !== "bot" && c.mode !== "userbot") errs.push("mode (bot|userbot)");
6143
+ if (!c.stage) errs.push("stage");
6144
+ if (!c.llm?.presetId) errs.push("llm.presetId");
6145
+ if (!c.llm?.model) errs.push("llm.model");
6146
+ if (errs.length) {
6147
+ process.stderr.write(`[server] \u043A\u043E\u043D\u0444\u0438\u0433 \u043D\u0435\u0432\u0430\u043B\u0438\u0434\u0435\u043D, \u043D\u0435\u0434\u043E\u0441\u0442\u0430\u044E\u0449\u0438\u0435 \u043F\u043E\u043B\u044F:
6148
+ - ${errs.join("\n - ")}
6149
+ `);
6150
+ process.stderr.write(`[server] \u0441\u043C. \u0448\u0430\u0431\u043B\u043E\u043D: girl-agent server --print-config
6151
+ `);
6152
+ process.exit(1);
6153
+ }
6154
+ const filled = {
6155
+ slug: c.slug || slugify(c.name),
6156
+ name: c.name,
6157
+ age: c.age,
6158
+ nationality: c.nationality,
6159
+ tz: c.tz,
6160
+ mode: c.mode,
6161
+ stage: c.stage,
6162
+ llm: {
6163
+ presetId: c.llm.presetId,
6164
+ proto: c.llm.proto ?? findPreset(c.llm.presetId)?.proto ?? "openai",
6165
+ baseURL: c.llm.baseURL ?? findPreset(c.llm.presetId)?.baseURL,
6166
+ apiKey: c.llm.apiKey ?? "",
6167
+ model: c.llm.model
6168
+ },
6169
+ telegram: c.telegram ?? {},
6170
+ mcp: c.mcp ?? [],
6171
+ privacy: c.privacy ?? "owner-only",
6172
+ createdAt: c.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
6173
+ sleepFrom: c.sleepFrom ?? 23,
6174
+ sleepTo: c.sleepTo ?? 8,
6175
+ nightWakeChance: c.nightWakeChance ?? 0.05,
6176
+ communication: c.communication ?? COMMUNICATION_PRESETS[0].profile,
6177
+ vibe: c.vibe,
6178
+ personaNotes: c.personaNotes,
6179
+ busySchedule: c.busySchedule ?? []
6180
+ };
6181
+ return filled;
6182
+ }
6183
+ function buildConfigTemplate() {
6184
+ const sample = {
6185
+ slug: "anya",
6186
+ name: "\u0410\u043D\u044F",
6187
+ age: 22,
6188
+ nationality: "RU",
6189
+ tz: "Europe/Moscow",
6190
+ mode: "bot",
6191
+ stage: "tg-given-cold",
6192
+ llm: {
6193
+ presetId: "claudehub",
6194
+ proto: "anthropic",
6195
+ baseURL: "https://api.claudehub.fun",
6196
+ apiKey: "REPLACE_ME",
6197
+ model: "claude-sonnet-4.6"
6198
+ },
6199
+ telegram: { botToken: "REPLACE_ME" },
6200
+ mcp: [],
6201
+ privacy: "owner-only",
6202
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6203
+ sleepFrom: 23,
6204
+ sleepTo: 8,
6205
+ nightWakeChance: 0.05,
6206
+ communication: COMMUNICATION_PRESETS[0].profile,
6207
+ vibe: "warm",
6208
+ busySchedule: []
6209
+ };
6210
+ return JSON.stringify(sample, null, 2) + "\n";
6211
+ }
6212
+ function buildSystemdUnit() {
6213
+ const home = os.homedir();
6214
+ return `# /etc/systemd/system/girl-agent.service
6215
+ # install: sudo cp this.service /etc/systemd/system/girl-agent.service
6216
+ # sudo systemctl daemon-reload
6217
+ # sudo systemctl enable --now girl-agent
6218
+
6219
+ [Unit]
6220
+ Description=girl-agent (Telegram AI girl)
6221
+ After=network-online.target
6222
+ Wants=network-online.target
6223
+
6224
+ [Service]
6225
+ Type=simple
6226
+ User=%i
6227
+ WorkingDirectory=${home}
6228
+ ExecStart=${home}/.local/bin/girl-agent server --config ${home}/.config/girl-agent/bot.json --headless
6229
+ Restart=on-failure
6230
+ RestartSec=10
6231
+ StandardOutput=journal
6232
+ StandardError=journal
6233
+ Environment=NODE_ENV=production
6234
+ # uncomment for env-driven setup:
6235
+ # Environment=GIRL_AGENT_MODE=bot
6236
+ # Environment=GIRL_AGENT_TOKEN=...
6237
+ # Environment=GIRL_AGENT_API_PRESET=claudehub
6238
+ # Environment=GIRL_AGENT_API_KEY=...
6239
+
6240
+ [Install]
6241
+ WantedBy=multi-user.target
6242
+ `;
6243
+ }
6244
+ function buildDockerArtifacts() {
6245
+ return `# === \u043E\u0434\u043D\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 ===
6246
+ docker run -it --rm \\
6247
+ -v girl-agent-data:/data \\
6248
+ -e GIRL_AGENT_DATA=/data \\
6249
+ ghcr.io/thesashadev/girl-agent:latest
6250
+
6251
+ # === headless \u0441 \u0433\u043E\u0442\u043E\u0432\u044B\u043C \u043A\u043E\u043D\u0444\u0438\u0433\u043E\u043C ===
6252
+ docker run -d --name girl-agent --restart=unless-stopped \\
6253
+ -v girl-agent-data:/data \\
6254
+ -v "$PWD/bot.json:/config/bot.json:ro" \\
6255
+ -e GIRL_AGENT_DATA=/data \\
6256
+ ghcr.io/thesashadev/girl-agent:latest \\
6257
+ server --config /config/bot.json --headless
6258
+
6259
+ # === \u0442\u043E\u043B\u044C\u043A\u043E env vars (\u0431\u0435\u0437 \u0444\u0430\u0439\u043B\u0430) ===
6260
+ docker run -d --name girl-agent --restart=unless-stopped \\
6261
+ -v girl-agent-data:/data \\
6262
+ -e GIRL_AGENT_DATA=/data \\
6263
+ -e GIRL_AGENT_MODE=bot \\
6264
+ -e GIRL_AGENT_TOKEN=... \\
6265
+ -e GIRL_AGENT_API_PRESET=claudehub \\
6266
+ -e GIRL_AGENT_API_KEY=... \\
6267
+ -e GIRL_AGENT_NAME='\u0410\u043D\u044F' \\
6268
+ -e GIRL_AGENT_AGE=22 \\
6269
+ ghcr.io/thesashadev/girl-agent:latest \\
6270
+ server --headless
6271
+
6272
+ # === docker-compose.yml ===
6273
+ # version: "3.9"
6274
+ # services:
6275
+ # girl-agent:
6276
+ # image: ghcr.io/thesashadev/girl-agent:latest
6277
+ # command: ["server", "--config", "/config/bot.json", "--headless"]
6278
+ # environment:
6279
+ # GIRL_AGENT_DATA: /data
6280
+ # volumes:
6281
+ # - girl-agent-data:/data
6282
+ # - ./bot.json:/config/bot.json:ro
6283
+ # restart: unless-stopped
6284
+ # volumes:
6285
+ # girl-agent-data:
6286
+ `;
6287
+ }
6288
+
5733
6289
  // src/cli.tsx
5734
6290
  var HELP = `
5735
6291
  girl-agent \u2014 AI girl for Telegram
@@ -5741,6 +6297,14 @@ usage:
5741
6297
  npx girl-agent --reset --profile=<slug>
5742
6298
  npx girl-agent <flags> # \u043F\u0440\u043E\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0432\u0438\u0437\u0430\u0440\u0434 \u0441 \u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442\u0430\u043C\u0438
5743
6299
 
6300
+ server (\u0434\u043B\u044F \u0441\u0438\u0441\u0442\u0435\u043C \u0431\u0435\u0437 TTY: docker / systemd / cron / CI):
6301
+ npx girl-agent server --print-config > bot.json
6302
+ npx girl-agent server --config bot.json --headless
6303
+ npx girl-agent server --print-systemd | --print-docker | --list
6304
+
6305
+ \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u043E\u0434\u043D\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 (\u0431\u0435\u0437 node \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435):
6306
+ curl -fsSL https://raw.githubusercontent.com/TheSashaDev/girl-agent/main/scripts/install.sh | sh
6307
+
5744
6308
  required flags \u0434\u043B\u044F headless setup (--name --age --stage --api-preset --mode; --api-key \u043D\u0443\u0436\u0435\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u043E\u0432 \u0441 \u0430\u0432\u0442\u043E\u0440\u0438\u0437\u0430\u0446\u0438\u0435\u0439):
5745
6309
  --profile=<slug> slug \u043F\u0440\u043E\u0444\u0438\u043B\u044F
5746
6310
  --mode=bot|userbot
@@ -5762,13 +6326,13 @@ required flags \u0434\u043B\u044F headless setup (--name --age --stage --api-pre
5762
6326
  --privacy=<mode> owner-only|allow-strangers (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E owner-only)
5763
6327
  --nationality=RU|UA (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E RU)
5764
6328
  --tz=<value> IANA "Europe/Moscow" / "GMT+3" / "+3" / "\u041A\u0438\u0435\u0432" \u2014 \u043F\u043E\u0438\u0441\u043A
5765
- --stage=<id> met-irl-got-tg|tg-given-cold|tg-given-warming|convinced|first-date-done|dating-early|dating-stable|long-term
6329
+ --stage=<id|num> 1=met-irl-got-tg 2=tg-given-cold 3=tg-given-warming 4=convinced 5=first-date-done 6=dating-early 7=dating-stable 8=long-term
5766
6330
  --mcp=exa:KEY \u043C\u043E\u0436\u043D\u043E \u043D\u0435\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0440\u0430\u0437
5767
6331
  --new \u043F\u0440\u0438\u043D\u0443\u0434\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0432\u0438\u0437\u0430\u0440\u0434 \u0434\u043B\u044F \u043D\u043E\u0432\u043E\u0433\u043E \u043F\u0440\u043E\u0444\u0438\u043B\u044F
5768
6332
  --list \u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u043F\u0440\u043E\u0444\u0438\u043B\u0438
5769
6333
  --help
5770
6334
 
5771
- \u043A\u043E\u043C\u0430\u043D\u0434\u044B \u0432 \u0440\u0430\u0431\u043E\u0442\u0430\u044E\u0449\u0435\u043C \u0434\u0430\u0448\u0431\u043E\u0440\u0434\u0435: :status :reset :stage <id> :pause :resume :cringe :persona :log :quit
6335
+ \u043A\u043E\u043C\u0430\u043D\u0434\u044B \u0432 \u0440\u0430\u0431\u043E\u0442\u0430\u044E\u0449\u0435\u043C \u0434\u0430\u0448\u0431\u043E\u0440\u0434\u0435: :status :reset :stage <id|num> :pause :resume :cringe :persona :log :quit
5772
6336
  `;
5773
6337
  async function main() {
5774
6338
  const argv = mri(process.argv.slice(2), {
@@ -5796,15 +6360,71 @@ async function main() {
5796
6360
  "message-style",
5797
6361
  "initiative",
5798
6362
  "life-sharing",
5799
- "privacy"
6363
+ "privacy",
6364
+ "config"
6365
+ ],
6366
+ boolean: [
6367
+ "help",
6368
+ "list",
6369
+ "reset",
6370
+ "new",
6371
+ "json-events",
6372
+ "headless",
6373
+ "server",
6374
+ "print-config",
6375
+ "print-systemd",
6376
+ "print-docker",
6377
+ "no-start"
5800
6378
  ],
5801
- boolean: ["help", "list", "reset", "new"],
5802
6379
  alias: { h: "help" }
5803
6380
  });
6381
+ const positional = argv._ ?? [];
6382
+ const isServer = positional[0] === "server" || !!argv.server || !!argv["print-config"] || !!argv["print-systemd"] || !!argv["print-docker"];
6383
+ if (isServer) {
6384
+ await runServer(argv);
6385
+ return;
6386
+ }
5804
6387
  if (argv.help) {
5805
6388
  process.stdout.write(HELP);
5806
6389
  return;
5807
6390
  }
6391
+ const isHeadless = !!(argv["json-events"] || argv.headless || argv.list || argv.help);
6392
+ if (!isHeadless) {
6393
+ const stdin = process.stdin;
6394
+ const stdout = process.stdout;
6395
+ const stdinOk = !!stdin.isTTY;
6396
+ const stdoutOk = !!stdout.isTTY;
6397
+ if (!stdinOk || !stdoutOk) {
6398
+ process.stderr.write(
6399
+ `
6400
+ [girl-agent] \u044D\u0442\u043E\u0442 \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0438\u043D\u0442\u0435\u0440\u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0439 ink-\u0432\u0438\u0437\u0430\u0440\u0434 (\u043D\u0435\u0442 TTY).
6401
+ stdin.isTTY = ${stdinOk}, stdout.isTTY = ${stdoutOk}
6402
+
6403
+ \u0447\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C (\u0434\u043B\u044F \u0441\u0435\u0440\u0432\u0435\u0440\u043E\u0432 / docker / ssh \u0431\u0435\u0437 -t / cron / CI):
6404
+
6405
+ 1. \u043F\u043E\u0441\u0442\u0430\u0432\u044C \u0441\u0435\u0431\u0435 girl-agent \u043E\u0434\u043D\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 (\u0431\u0435\u0437 node \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435):
6406
+ curl -fsSL https://raw.githubusercontent.com/TheSashaDev/girl-agent/main/scripts/install.sh | sh
6407
+ \u0434\u0430\u043B\u044C\u0448\u0435: girl-agent # ink-\u0432\u0438\u0437\u0430\u0440\u0434 \u0432 \u043E\u0431\u044B\u0447\u043D\u043E\u043C tty
6408
+
6409
+ 2. \u0433\u043E\u0442\u043E\u0432\u044B\u0439 \u043A\u043E\u043D\u0444\u0438\u0433 + headless (\u0434\u043B\u044F systemd / cron / CI):
6410
+ girl-agent server --print-config > bot.json
6411
+ # \u043E\u0442\u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u0443\u0439 bot.json
6412
+ girl-agent server --config bot.json --headless
6413
+
6414
+ 3. docker (\u0432\u0441\u0451 \u0432\u043D\u0443\u0442\u0440\u0438 \u043A\u043E\u043D\u0442\u0435\u0439\u043D\u0435\u0440\u0430, \u043D\u043E\u043B\u044C \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439 \u043D\u0430 \u0445\u043E\u0441\u0442\u0435):
6415
+ docker run -it --rm -v girl-agent-data:/data \\
6416
+ ghcr.io/thesashadev/girl-agent:latest
6417
+
6418
+ 4. systemd: girl-agent server --print-systemd
6419
+ docker: girl-agent server --print-docker
6420
+
6421
+ 5. \u043D\u0430 windows \u0431\u044B\u0441\u0442\u0440\u0435\u0435 \u0432\u0441\u0435\u0433\u043E \u2014 \u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043A\u0438\u0439 \u0438\u043D\u0441\u0442\u0430\u043B\u043B\u0435\u0440 girl-agent-installer.exe.
6422
+ `
6423
+ );
6424
+ process.exit(2);
6425
+ }
6426
+ }
6427
+ const jsonEvents = !!(argv["json-events"] || argv.headless);
5808
6428
  if (argv.age != null) {
5809
6429
  const a = Number(argv.age);
5810
6430
  if (!Number.isFinite(a) || a < 13 || a > 99) {
@@ -5834,7 +6454,7 @@ ${profiles.join("\n")}
5834
6454
  cfg.stage = "tg-given-cold";
5835
6455
  await writeConfig(cfg);
5836
6456
  }
5837
- await runRuntime(cfg);
6457
+ await runRuntime(cfg, { jsonEvents });
5838
6458
  return;
5839
6459
  }
5840
6460
  const presetForFlags = argv["api-preset"] ? findPreset(String(argv["api-preset"])) : void 0;
@@ -5850,7 +6470,7 @@ ${profiles.join("\n")}
5850
6470
  const generated = await generatePersonaPack(llm, cfg.slug, cfg.name, cfg.age, cfg.nationality, personaNotesForGeneration2(cfg));
5851
6471
  cfg.busySchedule = generated.busySchedule;
5852
6472
  await writeConfig(cfg);
5853
- await runRuntime(cfg);
6473
+ await runRuntime(cfg, { jsonEvents });
5854
6474
  return;
5855
6475
  }
5856
6476
  if (!argv.new && !argv.profile && !haveEnoughForFlags) {
@@ -5860,7 +6480,7 @@ ${profiles.join("\n")}
5860
6480
  if (cfg) {
5861
6481
  process.stdout.write(`\u0437\u0430\u0433\u0440\u0443\u0436\u0430\u044E \u043F\u0440\u043E\u0444\u0438\u043B\u044C: ${cfg.name}
5862
6482
  `);
5863
- await runRuntime(cfg);
6483
+ await runRuntime(cfg, { jsonEvents });
5864
6484
  return;
5865
6485
  }
5866
6486
  } else if (profiles.length > 1) {
@@ -5876,7 +6496,7 @@ ${profiles.join("\n")}
5876
6496
  const inst = render(
5877
6497
  /* @__PURE__ */ React3.createElement(Wizard, { onDone: async (cfg) => {
5878
6498
  inst.unmount();
5879
- await runRuntime(cfg);
6499
+ await runRuntime(cfg, { jsonEvents });
5880
6500
  resolve();
5881
6501
  } }),
5882
6502
  { exitOnCtrlC: true }
@@ -5910,7 +6530,7 @@ async function buildConfigFromFlags(argv) {
5910
6530
  nationality,
5911
6531
  tz,
5912
6532
  mode,
5913
- stage: argv.stage,
6533
+ stage: findStage(argv.stage).id,
5914
6534
  llm: { presetId, proto, baseURL, apiKey: String(argv["api-key"] ?? preset?.defaultApiKey ?? ""), model },
5915
6535
  telegram: mode === "bot" ? { botToken: String(argv.token ?? "") } : {
5916
6536
  apiId: Number(argv["api-id"] ?? 0),
@@ -5949,9 +6569,13 @@ function personaNotesForGeneration2(cfg) {
5949
6569
  ].filter(Boolean);
5950
6570
  return parts.join("\n\n");
5951
6571
  }
5952
- async function runRuntime(cfg) {
6572
+ async function runRuntime(cfg, opts = {}) {
5953
6573
  const rt = new Runtime(cfg);
5954
6574
  await rt.start();
6575
+ if (opts.jsonEvents) {
6576
+ await runHeadlessJsonEvents(rt);
6577
+ return;
6578
+ }
5955
6579
  const inst = render(/* @__PURE__ */ React3.createElement(Dashboard, { runtime: rt }), { exitOnCtrlC: true });
5956
6580
  process.on("SIGINT", async () => {
5957
6581
  await rt.stop();
@@ -5961,7 +6585,17 @@ async function runRuntime(cfg) {
5961
6585
  await inst.waitUntilExit();
5962
6586
  await rt.stop();
5963
6587
  }
6588
+ process.on("unhandledRejection", (reason) => {
6589
+ const r = reason;
6590
+ const text = typeof r === "object" && r && r.stack ? r.stack : String(reason);
6591
+ process.stderr.write("[girl-agent] unhandled rejection: " + text + "\n");
6592
+ process.exit(1);
6593
+ });
6594
+ process.on("uncaughtException", (err) => {
6595
+ process.stderr.write("[girl-agent] uncaught: " + (err?.stack ?? err) + "\n");
6596
+ process.exit(1);
6597
+ });
5964
6598
  main().catch((e) => {
5965
- process.stderr.write("fatal: " + (e?.stack ?? e) + "\n");
6599
+ process.stderr.write("[girl-agent] fatal: " + (e?.stack ?? e) + "\n");
5966
6600
  process.exit(1);
5967
6601
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thesashadev/girl-agent",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Telegram AI persona engine with memory, schedule, relationship state and MTProto userbot mode.",
5
5
  "type": "module",
6
6
  "bin": {