@thesashadev/girl-agent 0.1.8 → 0.1.9
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 +31 -0
- package/README.md +79 -15
- package/dist/cli.js +649 -32
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.9
|
|
4
|
+
|
|
5
|
+
Дата: 2026-05-07
|
|
6
|
+
|
|
7
|
+
- Merge pull request #48 from TheSashaDev/devin/1778156776-auto-release-workflow
|
|
8
|
+
- Merge pull request #50 from TheSashaDev/devin/1778176335-fix-markdown-escape
|
|
9
|
+
- fix: also switch userbot editLastMessage to HTML spoilers, remove dead escapeMarkdownV2
|
|
10
|
+
- fix(telegram): replace MarkdownV2 with HTML spoilers, plain text default (#46)
|
|
11
|
+
- feat(ci): auto-release workflow — hourly patch bump + changelog
|
|
12
|
+
- Merge pull request #47 from TheSashaDev/devin/1778156514-docker-latest-on-master
|
|
13
|
+
- fix(docker): tag latest on master pushes, not main
|
|
14
|
+
- Merge pull request #45 from TheSashaDev/devin/1778149966-fix-dockerfile-build-stage
|
|
15
|
+
- fix(docker): add build tools to build stage for arm64 native modules
|
|
16
|
+
- Merge pull request #44 from TheSashaDev/devin/1778149678-fix-dockerfile-arm64
|
|
17
|
+
- fix(docker): add build tools for native modules on alpine arm64
|
|
18
|
+
- Merge pull request #43 from TheSashaDev/devin/1778149272-fix-docker-install
|
|
19
|
+
- fix: docker install — fix branch ref (main→master), fallback to local on pull failure
|
|
20
|
+
- Merge pull request #37 from TheSashaDev/devin/1778090781-windows-installer-webui
|
|
21
|
+
- feat(server): curl|sh installer + docker image + headless server mode
|
|
22
|
+
- fix(cli): fail loudly on non-TTY terminals + catch unhandled rejections
|
|
23
|
+
- feat(installer/desktop): paste, ClaudeHub referral, tournament names, custom sleep, profile picker
|
|
24
|
+
- fix(installer): silent crash on Windows — panic=abort + windowed subsystem hid the panic
|
|
25
|
+
- fix(installer): replace empty-text widgets in progress header with Space
|
|
26
|
+
- feat(installer): bundle portable Node + cli.js, full TS-wizard parity, Cyrillic fonts
|
|
27
|
+
- perf: add release-fast profile, mold linker, windows_subsystem
|
|
28
|
+
- Добавить ссылки на Telegram канал и сообщество
|
|
29
|
+
- feat: native Windows installer + desktop app + web UI (Rust/iced)
|
|
30
|
+
- Merge pull request #36 from TheSashaDev/devin/1778089384-changelog-pr35
|
|
31
|
+
- docs: add PR 35 to changelog
|
|
32
|
+
|
|
3
33
|
## 0.1.8 — OpenAI-compatible API compatibility
|
|
4
34
|
|
|
5
35
|
Дата: 2026-05-06
|
|
@@ -7,6 +37,7 @@
|
|
|
7
37
|
- JSON-ответы теперь сначала запрашиваются через `json_schema`, с fallback на `json_object` и `text` для разных OpenAI-compatible API. (#33)
|
|
8
38
|
- LM Studio и Ollama больше не требуют реальный API ключ в wizard/headless setup.
|
|
9
39
|
- Добавлена совместимость с OpenAI-compatible прокси, которые возвращают SSE/event-stream даже на обычный chat completions запрос.
|
|
40
|
+
- Добавлена Docker-поддержка для 24/7 запуска на сервере: `Dockerfile`, `docker-compose.yml`, volume для `data` и инструкции в README. (#35)
|
|
10
41
|
|
|
11
42
|
## 0.1.7 — MarkdownV2 escaping fix
|
|
12
43
|
|
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
|
-
|
|
40
|
+
### linux / macos / wsl — одной командой (без node на машине)
|
|
39
41
|
|
|
40
|
-
```
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
56
|
-
|
|
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
|
-
```
|
|
62
|
-
docker-
|
|
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
|
-
|
|
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
|
|
23
|
-
return text
|
|
22
|
+
function hasSpoilers(text) {
|
|
23
|
+
return /\|\|.+?\|\|/.test(text);
|
|
24
|
+
}
|
|
25
|
+
function escapeHtml(text) {
|
|
26
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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
|
|
|
@@ -190,11 +194,14 @@ function makeUserbotAdapter(cfg) {
|
|
|
190
194
|
},
|
|
191
195
|
async editLastMessage(chatId, messageId, text) {
|
|
192
196
|
const peer = await resolvePeer(chatId);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
+
if (hasSpoilers(text)) {
|
|
198
|
+
try {
|
|
199
|
+
await client.editMessage(peer, { message: messageId, text: toHtmlWithSpoilers(text), parseMode: "html" });
|
|
200
|
+
return;
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
197
203
|
}
|
|
204
|
+
await client.editMessage(peer, { message: messageId, text });
|
|
198
205
|
},
|
|
199
206
|
async deleteMessages(chatId, messageIds, revoke = false) {
|
|
200
207
|
const peer = await resolvePeer(chatId);
|
|
@@ -293,13 +300,15 @@ function makeBotAdapter(cfg) {
|
|
|
293
300
|
});
|
|
294
301
|
},
|
|
295
302
|
async sendText(chatId, text) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
303
|
+
if (hasSpoilers(text)) {
|
|
304
|
+
try {
|
|
305
|
+
const msg2 = await bot.api.sendMessage(chatId, toHtmlWithSpoilers(text), { parse_mode: "HTML" });
|
|
306
|
+
return msg2.message_id;
|
|
307
|
+
} catch {
|
|
308
|
+
}
|
|
302
309
|
}
|
|
310
|
+
const msg = await bot.api.sendMessage(chatId, text);
|
|
311
|
+
return msg.message_id;
|
|
303
312
|
},
|
|
304
313
|
async setTyping(chatId, on) {
|
|
305
314
|
if (on) {
|
|
@@ -800,7 +809,7 @@ function sameProfile(a, b) {
|
|
|
800
809
|
init_esm_shims();
|
|
801
810
|
import { promises as fs } from "fs";
|
|
802
811
|
import path2 from "path";
|
|
803
|
-
var DATA_ROOT = path2.resolve(process.cwd(), "data");
|
|
812
|
+
var DATA_ROOT = process.env.GIRL_AGENT_DATA ? path2.resolve(process.env.GIRL_AGENT_DATA) : path2.resolve(process.cwd(), "data");
|
|
804
813
|
function profileDir(slug) {
|
|
805
814
|
return path2.join(DATA_ROOT, slug);
|
|
806
815
|
}
|
|
@@ -1534,14 +1543,14 @@ var DEFAULT_PROXY = "https://tgproxy.girl-agent.com";
|
|
|
1534
1543
|
function proxyUrl() {
|
|
1535
1544
|
return process.env.GIRL_AGENT_AUTH_PROXY ?? DEFAULT_PROXY;
|
|
1536
1545
|
}
|
|
1537
|
-
async function post(
|
|
1538
|
-
const res = await fetch(`${proxyUrl()}${
|
|
1546
|
+
async function post(path6, body) {
|
|
1547
|
+
const res = await fetch(`${proxyUrl()}${path6}`, {
|
|
1539
1548
|
method: "POST",
|
|
1540
1549
|
headers: { "Content-Type": "application/json" },
|
|
1541
1550
|
body: JSON.stringify(body)
|
|
1542
1551
|
});
|
|
1543
1552
|
const data = await res.json();
|
|
1544
|
-
if (!res.ok) throw new Error(data.error ?? `proxy ${
|
|
1553
|
+
if (!res.ok) throw new Error(data.error ?? `proxy ${path6} failed (${res.status})`);
|
|
1545
1554
|
return data;
|
|
1546
1555
|
}
|
|
1547
1556
|
function remoteSendCode(phone) {
|
|
@@ -3093,8 +3102,8 @@ phoneAvailable=false \u043A\u043E\u0433\u0434\u0430: \u0441\u043F\u0438\u0442, \
|
|
|
3093
3102
|
}
|
|
3094
3103
|
async function loadOrGenerateDailyLife(llm, cfg, now = /* @__PURE__ */ new Date(), conflict = null) {
|
|
3095
3104
|
const dateLocal = localDateStr(cfg.tz, now);
|
|
3096
|
-
const
|
|
3097
|
-
const existing = await readMd(cfg.slug,
|
|
3105
|
+
const path6 = `daily-life/${dateLocal}.md`;
|
|
3106
|
+
const existing = await readMd(cfg.slug, path6);
|
|
3098
3107
|
if (existing) {
|
|
3099
3108
|
try {
|
|
3100
3109
|
const m = existing.match(/<!--daily:(.+?)-->/s);
|
|
@@ -3126,7 +3135,7 @@ async function loadOrGenerateDailyLife(llm, cfg, now = /* @__PURE__ */ new Date(
|
|
|
3126
3135
|
dl = { dateLocal, vibe: "\u043E\u0431\u044B\u0447\u043D\u044B\u0439 \u0434\u0435\u043D\u044C", blocks: [], events: [], wants: [] };
|
|
3127
3136
|
}
|
|
3128
3137
|
const human = renderDailyLifeHuman(dl);
|
|
3129
|
-
await writeMd(cfg.slug,
|
|
3138
|
+
await writeMd(cfg.slug, path6, `${human}
|
|
3130
3139
|
|
|
3131
3140
|
<!--daily:${JSON.stringify(dl)}-->
|
|
3132
3141
|
`);
|
|
@@ -3356,9 +3365,9 @@ async function ensureDefaults(cfg) {
|
|
|
3356
3365
|
["time/promises.md", "# promises\n"],
|
|
3357
3366
|
["memory/uncertain.md", "# uncertain\n"]
|
|
3358
3367
|
];
|
|
3359
|
-
await Promise.all(defaults.map(async ([
|
|
3360
|
-
const current = await readMd(cfg.slug,
|
|
3361
|
-
if (!current.trim()) await writeMd(cfg.slug,
|
|
3368
|
+
await Promise.all(defaults.map(async ([path6, content]) => {
|
|
3369
|
+
const current = await readMd(cfg.slug, path6);
|
|
3370
|
+
if (!current.trim()) await writeMd(cfg.slug, path6, content + "\n");
|
|
3362
3371
|
}));
|
|
3363
3372
|
}
|
|
3364
3373
|
async function loadRealismContext(cfg, incoming) {
|
|
@@ -5730,6 +5739,536 @@ function sleep(ms) {
|
|
|
5730
5739
|
return new Promise((r) => setTimeout(r, ms));
|
|
5731
5740
|
}
|
|
5732
5741
|
|
|
5742
|
+
// src/headless.ts
|
|
5743
|
+
init_esm_shims();
|
|
5744
|
+
import readline from "readline";
|
|
5745
|
+
async function runHeadlessJsonEvents(rt) {
|
|
5746
|
+
const out = (obj) => {
|
|
5747
|
+
process.stdout.write(JSON.stringify(obj) + "\n");
|
|
5748
|
+
};
|
|
5749
|
+
out({ type: "ready", profile: profileSummary(rt.cfg) });
|
|
5750
|
+
rt.on("event", (e) => {
|
|
5751
|
+
out({ ...e, t: Date.now() });
|
|
5752
|
+
});
|
|
5753
|
+
try {
|
|
5754
|
+
const r = await readRelationship(rt.cfg.slug);
|
|
5755
|
+
out({ type: "score", score: r.score, t: Date.now() });
|
|
5756
|
+
} catch {
|
|
5757
|
+
}
|
|
5758
|
+
let paused = false;
|
|
5759
|
+
const rl = readline.createInterface({ input: process.stdin, terminal: false });
|
|
5760
|
+
rl.on("line", async (raw) => {
|
|
5761
|
+
const line = raw.trim();
|
|
5762
|
+
if (!line) return;
|
|
5763
|
+
if (!line.startsWith(":")) {
|
|
5764
|
+
out({ type: "response", ok: false, text: "\u043A\u043E\u043C\u0430\u043D\u0434\u044B \u043D\u0430\u0447\u0438\u043D\u0430\u044E\u0442\u0441\u044F \u0441 :" });
|
|
5765
|
+
return;
|
|
5766
|
+
}
|
|
5767
|
+
const [head, ...rest] = line.slice(1).split(" ");
|
|
5768
|
+
try {
|
|
5769
|
+
let text = "";
|
|
5770
|
+
switch (head) {
|
|
5771
|
+
case "status":
|
|
5772
|
+
text = await rt.cmdStatus();
|
|
5773
|
+
break;
|
|
5774
|
+
case "reset":
|
|
5775
|
+
text = await rt.cmdReset();
|
|
5776
|
+
break;
|
|
5777
|
+
case "stage":
|
|
5778
|
+
text = await rt.cmdSetStage(rest.join(" "));
|
|
5779
|
+
break;
|
|
5780
|
+
case "wake":
|
|
5781
|
+
text = await rt.cmdWake(rest[0]);
|
|
5782
|
+
break;
|
|
5783
|
+
case "debug":
|
|
5784
|
+
text = await rt.cmdDebug(rest[0]);
|
|
5785
|
+
break;
|
|
5786
|
+
case "why":
|
|
5787
|
+
text = await rt.cmdWhy(rest[0]);
|
|
5788
|
+
break;
|
|
5789
|
+
case "amnesia":
|
|
5790
|
+
text = await rt.cmdAmnesia(rest[0], rest[1]);
|
|
5791
|
+
break;
|
|
5792
|
+
case "block":
|
|
5793
|
+
text = await rt.cmdBlock(rest[0]);
|
|
5794
|
+
break;
|
|
5795
|
+
case "unblock":
|
|
5796
|
+
text = await rt.cmdUnblock(rest[0]);
|
|
5797
|
+
break;
|
|
5798
|
+
case "read":
|
|
5799
|
+
text = await rt.cmdRead(rest[0]);
|
|
5800
|
+
break;
|
|
5801
|
+
case "clear-chat":
|
|
5802
|
+
text = await rt.cmdClearChat(rest.find((x) => !x.startsWith("--")), rest.includes("--revoke"));
|
|
5803
|
+
break;
|
|
5804
|
+
case "report-spam":
|
|
5805
|
+
text = await rt.cmdReportSpam(rest[0]);
|
|
5806
|
+
break;
|
|
5807
|
+
case "delete-last":
|
|
5808
|
+
text = await rt.cmdDeleteLast(rest.find((x) => !x.startsWith("--")), !rest.includes("--local"));
|
|
5809
|
+
break;
|
|
5810
|
+
case "edit-last":
|
|
5811
|
+
text = await rt.cmdEditLast(rest.join(" "));
|
|
5812
|
+
break;
|
|
5813
|
+
case "sticker":
|
|
5814
|
+
text = await rt.cmdSticker(rest[0]);
|
|
5815
|
+
break;
|
|
5816
|
+
case "pause":
|
|
5817
|
+
rt.pause();
|
|
5818
|
+
paused = true;
|
|
5819
|
+
text = "\u23F8 pause";
|
|
5820
|
+
break;
|
|
5821
|
+
case "resume":
|
|
5822
|
+
rt.resume();
|
|
5823
|
+
paused = false;
|
|
5824
|
+
text = "\u25B6 resume";
|
|
5825
|
+
break;
|
|
5826
|
+
case "cringe": {
|
|
5827
|
+
const r = await readRelationship(rt.cfg.slug);
|
|
5828
|
+
text = `cringe=${r.score.cringe}; \u0441\u043C. memory/long-term.md \u0438 log/`;
|
|
5829
|
+
break;
|
|
5830
|
+
}
|
|
5831
|
+
case "relationship": {
|
|
5832
|
+
const r = await readRelationship(rt.cfg.slug);
|
|
5833
|
+
text = `stage=${r.stage} score=${JSON.stringify(r.score)}`;
|
|
5834
|
+
break;
|
|
5835
|
+
}
|
|
5836
|
+
case "persona": {
|
|
5837
|
+
const p = await readMd(rt.cfg.slug, "persona.md");
|
|
5838
|
+
text = p.slice(0, 4e3);
|
|
5839
|
+
break;
|
|
5840
|
+
}
|
|
5841
|
+
case "log": {
|
|
5842
|
+
const day = /^\d{4}-\d{2}-\d{2}$/.test(rest[0] ?? "") ? rest[0] : sessionDate(rt.cfg.tz);
|
|
5843
|
+
const limit = Number(rest.find((x) => /^\d+$/.test(x)) ?? 3e3);
|
|
5844
|
+
const p = await readSessionLog(rt.cfg.slug, day);
|
|
5845
|
+
text = p.trim() ? p.slice(-Math.max(500, Math.min(limit, 2e4))) : `(log/${day}.md \u043F\u0443\u0441\u0442)`;
|
|
5846
|
+
break;
|
|
5847
|
+
}
|
|
5848
|
+
case "snapshot": {
|
|
5849
|
+
const r = await readRelationship(rt.cfg.slug);
|
|
5850
|
+
out({
|
|
5851
|
+
type: "snapshot",
|
|
5852
|
+
t: Date.now(),
|
|
5853
|
+
paused,
|
|
5854
|
+
profile: profileSummary(rt.cfg),
|
|
5855
|
+
stage: { id: r.stage, label: findStage(r.stage).label },
|
|
5856
|
+
score: r.score
|
|
5857
|
+
});
|
|
5858
|
+
return;
|
|
5859
|
+
}
|
|
5860
|
+
case "help":
|
|
5861
|
+
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";
|
|
5862
|
+
break;
|
|
5863
|
+
case "quit":
|
|
5864
|
+
case "exit":
|
|
5865
|
+
await rt.stop();
|
|
5866
|
+
out({ type: "response", ok: true, text: "bye" });
|
|
5867
|
+
process.exit(0);
|
|
5868
|
+
default:
|
|
5869
|
+
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}` });
|
|
5870
|
+
return;
|
|
5871
|
+
}
|
|
5872
|
+
out({ type: "response", ok: true, text });
|
|
5873
|
+
} catch (e) {
|
|
5874
|
+
out({ type: "response", ok: false, text: "err: " + e.message });
|
|
5875
|
+
}
|
|
5876
|
+
});
|
|
5877
|
+
const shutdown = async () => {
|
|
5878
|
+
try {
|
|
5879
|
+
await rt.stop();
|
|
5880
|
+
} catch {
|
|
5881
|
+
}
|
|
5882
|
+
out({ type: "stopped", t: Date.now() });
|
|
5883
|
+
process.exit(0);
|
|
5884
|
+
};
|
|
5885
|
+
process.on("SIGINT", shutdown);
|
|
5886
|
+
process.on("SIGTERM", shutdown);
|
|
5887
|
+
await new Promise(() => {
|
|
5888
|
+
});
|
|
5889
|
+
}
|
|
5890
|
+
function profileSummary(cfg) {
|
|
5891
|
+
const stage = findStage(cfg.stage);
|
|
5892
|
+
return {
|
|
5893
|
+
slug: cfg.slug,
|
|
5894
|
+
name: cfg.name,
|
|
5895
|
+
age: cfg.age,
|
|
5896
|
+
mode: cfg.mode,
|
|
5897
|
+
nationality: cfg.nationality,
|
|
5898
|
+
tz: cfg.tz,
|
|
5899
|
+
stage: { id: cfg.stage, label: stage.label }
|
|
5900
|
+
};
|
|
5901
|
+
}
|
|
5902
|
+
|
|
5903
|
+
// src/server.ts
|
|
5904
|
+
init_esm_shims();
|
|
5905
|
+
import fs4 from "fs/promises";
|
|
5906
|
+
import path5 from "path";
|
|
5907
|
+
import os from "os";
|
|
5908
|
+
var SERVER_HELP = `
|
|
5909
|
+
girl-agent server \u2014 automation / ops mode (no TTY required)
|
|
5910
|
+
|
|
5911
|
+
usage:
|
|
5912
|
+
girl-agent server --print-config > bot.json
|
|
5913
|
+
# \u043E\u0442\u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u0443\u0439 bot.json
|
|
5914
|
+
girl-agent server --config bot.json --headless
|
|
5915
|
+
|
|
5916
|
+
girl-agent server --list
|
|
5917
|
+
girl-agent server --profile=<slug> --headless
|
|
5918
|
+
|
|
5919
|
+
girl-agent server --print-systemd > /etc/systemd/system/girl-agent.service
|
|
5920
|
+
girl-agent server --print-docker
|
|
5921
|
+
|
|
5922
|
+
env-vars (\u0434\u043B\u044F CI / docker secrets / k8s):
|
|
5923
|
+
GIRL_AGENT_DATA \u043F\u0443\u0442\u044C \u043A \u043F\u0440\u043E\u0444\u0438\u043B\u044F\u043C (default: ./data)
|
|
5924
|
+
GIRL_AGENT_MODE bot|userbot
|
|
5925
|
+
GIRL_AGENT_TOKEN telegram bot token
|
|
5926
|
+
GIRL_AGENT_API_PRESET openai|anthropic|claudehub|...
|
|
5927
|
+
GIRL_AGENT_API_KEY \u043A\u043B\u044E\u0447 \u043E\u0442 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430
|
|
5928
|
+
GIRL_AGENT_MODEL, _NAME, _AGE, _NATIONALITY, _TZ, _STAGE, _COMM_PRESET
|
|
5929
|
+
|
|
5930
|
+
\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
|
|
5931
|
+
\u043E\u0442\u043A\u0440\u043E\u0435\u0442\u0441\u044F ink-\u0432\u0438\u0437\u0430\u0440\u0434.
|
|
5932
|
+
`;
|
|
5933
|
+
function parseServerArgs(argv) {
|
|
5934
|
+
return {
|
|
5935
|
+
config: typeof argv.config === "string" ? argv.config : void 0,
|
|
5936
|
+
printConfig: !!argv["print-config"],
|
|
5937
|
+
printSystemd: !!argv["print-systemd"],
|
|
5938
|
+
printDocker: !!argv["print-docker"],
|
|
5939
|
+
headless: !!argv.headless,
|
|
5940
|
+
jsonEvents: !!argv["json-events"],
|
|
5941
|
+
noStart: !!argv["no-start"] || argv.start === false,
|
|
5942
|
+
profile: typeof argv.profile === "string" ? argv.profile : void 0,
|
|
5943
|
+
list: !!argv.list,
|
|
5944
|
+
help: !!argv.help
|
|
5945
|
+
};
|
|
5946
|
+
}
|
|
5947
|
+
async function runServer(rawArgv) {
|
|
5948
|
+
const args = parseServerArgs(rawArgv);
|
|
5949
|
+
if (args.help) {
|
|
5950
|
+
process.stdout.write(SERVER_HELP);
|
|
5951
|
+
return;
|
|
5952
|
+
}
|
|
5953
|
+
if (args.printConfig) {
|
|
5954
|
+
process.stdout.write(buildConfigTemplate());
|
|
5955
|
+
return;
|
|
5956
|
+
}
|
|
5957
|
+
if (args.printSystemd) {
|
|
5958
|
+
process.stdout.write(buildSystemdUnit());
|
|
5959
|
+
return;
|
|
5960
|
+
}
|
|
5961
|
+
if (args.printDocker) {
|
|
5962
|
+
process.stdout.write(buildDockerArtifacts());
|
|
5963
|
+
return;
|
|
5964
|
+
}
|
|
5965
|
+
if (args.list) {
|
|
5966
|
+
const list = await listProfiles();
|
|
5967
|
+
process.stdout.write(list.length ? list.join("\n") + "\n" : "(\u043D\u0435\u0442 \u043F\u0440\u043E\u0444\u0438\u043B\u0435\u0439)\n");
|
|
5968
|
+
process.stdout.write(`data: ${DATA_ROOT}
|
|
5969
|
+
`);
|
|
5970
|
+
return;
|
|
5971
|
+
}
|
|
5972
|
+
if (args.profile) {
|
|
5973
|
+
const cfg = await readConfig(args.profile);
|
|
5974
|
+
if (!cfg) {
|
|
5975
|
+
process.stderr.write(`profile not found: ${args.profile}
|
|
5976
|
+
`);
|
|
5977
|
+
process.stderr.write(`data dir: ${DATA_ROOT}
|
|
5978
|
+
`);
|
|
5979
|
+
process.exit(1);
|
|
5980
|
+
}
|
|
5981
|
+
await startRuntime(cfg, args);
|
|
5982
|
+
return;
|
|
5983
|
+
}
|
|
5984
|
+
if (args.config) {
|
|
5985
|
+
const cfg = await loadConfigFile(args.config);
|
|
5986
|
+
await persistAndMaybeStart(cfg, args);
|
|
5987
|
+
return;
|
|
5988
|
+
}
|
|
5989
|
+
const cfgFromEnv = configFromEnv();
|
|
5990
|
+
if (cfgFromEnv) {
|
|
5991
|
+
process.stderr.write("[server] \u043F\u0440\u043E\u0432\u0438\u0436\u0443 \u043F\u0440\u043E\u0444\u0438\u043B\u044C \u0438\u0437 env vars\n");
|
|
5992
|
+
await persistAndMaybeStart(cfgFromEnv, args);
|
|
5993
|
+
return;
|
|
5994
|
+
}
|
|
5995
|
+
process.stderr.write(SERVER_HELP);
|
|
5996
|
+
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");
|
|
5997
|
+
process.exit(1);
|
|
5998
|
+
}
|
|
5999
|
+
async function persistAndMaybeStart(cfg, args) {
|
|
6000
|
+
await writeConfig(cfg);
|
|
6001
|
+
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)}
|
|
6002
|
+
`);
|
|
6003
|
+
if (cfg.llm.apiKey || findPreset(cfg.llm.presetId)?.apiKeyRequired === false) {
|
|
6004
|
+
try {
|
|
6005
|
+
process.stderr.write("[server] \u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u043C persona/speech/communication...\n");
|
|
6006
|
+
const llm = makeLLM(cfg.llm);
|
|
6007
|
+
const generated = await generatePersonaPack(llm, cfg.slug, cfg.name, cfg.age, cfg.nationality, cfg.personaNotes ?? "");
|
|
6008
|
+
cfg.busySchedule = generated.busySchedule;
|
|
6009
|
+
await writeConfig(cfg);
|
|
6010
|
+
process.stderr.write("[server] \u043F\u0435\u0440\u0441\u043E\u043D\u0430 \u0433\u043E\u0442\u043E\u0432\u0430.\n");
|
|
6011
|
+
} catch (e) {
|
|
6012
|
+
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}
|
|
6013
|
+
`);
|
|
6014
|
+
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");
|
|
6015
|
+
}
|
|
6016
|
+
} else {
|
|
6017
|
+
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");
|
|
6018
|
+
}
|
|
6019
|
+
if (args.noStart) {
|
|
6020
|
+
process.stderr.write(`[server] --no-start: \u0437\u0430\u043F\u0443\u0441\u043A \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D.
|
|
6021
|
+
`);
|
|
6022
|
+
return;
|
|
6023
|
+
}
|
|
6024
|
+
await startRuntime(cfg, args);
|
|
6025
|
+
}
|
|
6026
|
+
async function startRuntime(cfg, args) {
|
|
6027
|
+
const rt = new Runtime(cfg);
|
|
6028
|
+
await rt.start();
|
|
6029
|
+
const wantsHeadless = !!(args.headless || args.jsonEvents);
|
|
6030
|
+
if (wantsHeadless) {
|
|
6031
|
+
await runHeadlessJsonEvents(rt);
|
|
6032
|
+
return;
|
|
6033
|
+
}
|
|
6034
|
+
process.stderr.write(`[server] \u0431\u043E\u0442 \u0437\u0430\u043F\u0443\u0449\u0435\u043D: ${cfg.name} (${cfg.slug})
|
|
6035
|
+
`);
|
|
6036
|
+
rt.on("event", (e) => {
|
|
6037
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
6038
|
+
const t = e.type ?? "event";
|
|
6039
|
+
process.stdout.write(`${ts} ${t} ${JSON.stringify(e)}
|
|
6040
|
+
`);
|
|
6041
|
+
});
|
|
6042
|
+
const stop = async () => {
|
|
6043
|
+
process.stderr.write("[server] \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430...\n");
|
|
6044
|
+
await rt.stop();
|
|
6045
|
+
process.exit(0);
|
|
6046
|
+
};
|
|
6047
|
+
process.on("SIGINT", stop);
|
|
6048
|
+
process.on("SIGTERM", stop);
|
|
6049
|
+
}
|
|
6050
|
+
function configFromEnv() {
|
|
6051
|
+
const e = process.env;
|
|
6052
|
+
if (!e.GIRL_AGENT_MODE && !e.GIRL_AGENT_TOKEN && !e.GIRL_AGENT_API_KEY) return null;
|
|
6053
|
+
const mode = e.GIRL_AGENT_MODE === "userbot" ? "userbot" : "bot";
|
|
6054
|
+
const presetId = e.GIRL_AGENT_API_PRESET ?? "claudehub";
|
|
6055
|
+
const preset = findPreset(presetId);
|
|
6056
|
+
if (!preset) {
|
|
6057
|
+
process.stderr.write(`[server] unknown api preset in env: ${presetId}
|
|
6058
|
+
`);
|
|
6059
|
+
process.exit(1);
|
|
6060
|
+
}
|
|
6061
|
+
const nationality = e.GIRL_AGENT_NATIONALITY === "UA" ? "UA" : "RU";
|
|
6062
|
+
const name = e.GIRL_AGENT_NAME || pickRandomNames(nationality, 1)[0];
|
|
6063
|
+
const age = Number(e.GIRL_AGENT_AGE ?? 18);
|
|
6064
|
+
const tz = e.GIRL_AGENT_TZ ? parseTzFlag(e.GIRL_AGENT_TZ) ?? defaultTzForNationality(nationality) : defaultTzForNationality(nationality);
|
|
6065
|
+
const stage = e.GIRL_AGENT_STAGE || "tg-given-cold";
|
|
6066
|
+
const commPreset = COMMUNICATION_PRESETS.find((c) => c.id === (e.GIRL_AGENT_COMM_PRESET ?? "normal")) ?? COMMUNICATION_PRESETS[0];
|
|
6067
|
+
return {
|
|
6068
|
+
slug: slugify(name),
|
|
6069
|
+
name,
|
|
6070
|
+
age,
|
|
6071
|
+
nationality,
|
|
6072
|
+
tz,
|
|
6073
|
+
mode,
|
|
6074
|
+
stage,
|
|
6075
|
+
llm: {
|
|
6076
|
+
presetId,
|
|
6077
|
+
proto: preset.proto,
|
|
6078
|
+
baseURL: preset.baseURL,
|
|
6079
|
+
apiKey: e.GIRL_AGENT_API_KEY ?? preset.defaultApiKey ?? "",
|
|
6080
|
+
model: e.GIRL_AGENT_MODEL ?? preset.defaultModel
|
|
6081
|
+
},
|
|
6082
|
+
telegram: mode === "bot" ? { botToken: e.GIRL_AGENT_TOKEN ?? "" } : {
|
|
6083
|
+
apiId: Number(e.GIRL_AGENT_TG_API_ID ?? 0),
|
|
6084
|
+
apiHash: e.GIRL_AGENT_TG_API_HASH ?? "",
|
|
6085
|
+
phone: e.GIRL_AGENT_TG_PHONE ?? ""
|
|
6086
|
+
},
|
|
6087
|
+
mcp: [],
|
|
6088
|
+
privacy: "owner-only",
|
|
6089
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6090
|
+
sleepFrom: Number(e.GIRL_AGENT_SLEEP_FROM ?? 23),
|
|
6091
|
+
sleepTo: Number(e.GIRL_AGENT_SLEEP_TO ?? 8),
|
|
6092
|
+
nightWakeChance: Number(e.GIRL_AGENT_NIGHT_WAKE ?? 0.05),
|
|
6093
|
+
communication: commPreset.profile,
|
|
6094
|
+
vibe: commPreset.profile.messageStyle === "one-liners" ? "short" : "warm",
|
|
6095
|
+
busySchedule: []
|
|
6096
|
+
};
|
|
6097
|
+
}
|
|
6098
|
+
async function loadConfigFile(file) {
|
|
6099
|
+
const abs = path5.isAbsolute(file) ? file : path5.join(process.cwd(), file);
|
|
6100
|
+
let raw;
|
|
6101
|
+
try {
|
|
6102
|
+
raw = await fs4.readFile(abs, "utf-8");
|
|
6103
|
+
} catch (e) {
|
|
6104
|
+
process.stderr.write(`[server] \u043D\u0435 \u043C\u043E\u0433\u0443 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C ${abs}: ${e?.message ?? e}
|
|
6105
|
+
`);
|
|
6106
|
+
process.exit(1);
|
|
6107
|
+
}
|
|
6108
|
+
let parsed;
|
|
6109
|
+
try {
|
|
6110
|
+
parsed = JSON.parse(raw);
|
|
6111
|
+
} catch (e) {
|
|
6112
|
+
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}
|
|
6113
|
+
`);
|
|
6114
|
+
process.exit(1);
|
|
6115
|
+
}
|
|
6116
|
+
return validateConfig(parsed);
|
|
6117
|
+
}
|
|
6118
|
+
function validateConfig(raw) {
|
|
6119
|
+
const c = raw;
|
|
6120
|
+
const errs = [];
|
|
6121
|
+
if (!c.name) errs.push("name");
|
|
6122
|
+
if (!c.age || c.age < 14 || c.age > 99) errs.push("age (14..99)");
|
|
6123
|
+
if (!c.nationality || c.nationality !== "RU" && c.nationality !== "UA") errs.push("nationality (RU|UA)");
|
|
6124
|
+
if (!c.tz) errs.push("tz");
|
|
6125
|
+
if (!c.mode || c.mode !== "bot" && c.mode !== "userbot") errs.push("mode (bot|userbot)");
|
|
6126
|
+
if (!c.stage) errs.push("stage");
|
|
6127
|
+
if (!c.llm?.presetId) errs.push("llm.presetId");
|
|
6128
|
+
if (!c.llm?.model) errs.push("llm.model");
|
|
6129
|
+
if (errs.length) {
|
|
6130
|
+
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:
|
|
6131
|
+
- ${errs.join("\n - ")}
|
|
6132
|
+
`);
|
|
6133
|
+
process.stderr.write(`[server] \u0441\u043C. \u0448\u0430\u0431\u043B\u043E\u043D: girl-agent server --print-config
|
|
6134
|
+
`);
|
|
6135
|
+
process.exit(1);
|
|
6136
|
+
}
|
|
6137
|
+
const filled = {
|
|
6138
|
+
slug: c.slug || slugify(c.name),
|
|
6139
|
+
name: c.name,
|
|
6140
|
+
age: c.age,
|
|
6141
|
+
nationality: c.nationality,
|
|
6142
|
+
tz: c.tz,
|
|
6143
|
+
mode: c.mode,
|
|
6144
|
+
stage: c.stage,
|
|
6145
|
+
llm: {
|
|
6146
|
+
presetId: c.llm.presetId,
|
|
6147
|
+
proto: c.llm.proto ?? findPreset(c.llm.presetId)?.proto ?? "openai",
|
|
6148
|
+
baseURL: c.llm.baseURL ?? findPreset(c.llm.presetId)?.baseURL,
|
|
6149
|
+
apiKey: c.llm.apiKey ?? "",
|
|
6150
|
+
model: c.llm.model
|
|
6151
|
+
},
|
|
6152
|
+
telegram: c.telegram ?? {},
|
|
6153
|
+
mcp: c.mcp ?? [],
|
|
6154
|
+
privacy: c.privacy ?? "owner-only",
|
|
6155
|
+
createdAt: c.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6156
|
+
sleepFrom: c.sleepFrom ?? 23,
|
|
6157
|
+
sleepTo: c.sleepTo ?? 8,
|
|
6158
|
+
nightWakeChance: c.nightWakeChance ?? 0.05,
|
|
6159
|
+
communication: c.communication ?? COMMUNICATION_PRESETS[0].profile,
|
|
6160
|
+
vibe: c.vibe,
|
|
6161
|
+
personaNotes: c.personaNotes,
|
|
6162
|
+
busySchedule: c.busySchedule ?? []
|
|
6163
|
+
};
|
|
6164
|
+
return filled;
|
|
6165
|
+
}
|
|
6166
|
+
function buildConfigTemplate() {
|
|
6167
|
+
const sample = {
|
|
6168
|
+
slug: "anya",
|
|
6169
|
+
name: "\u0410\u043D\u044F",
|
|
6170
|
+
age: 22,
|
|
6171
|
+
nationality: "RU",
|
|
6172
|
+
tz: "Europe/Moscow",
|
|
6173
|
+
mode: "bot",
|
|
6174
|
+
stage: "tg-given-cold",
|
|
6175
|
+
llm: {
|
|
6176
|
+
presetId: "claudehub",
|
|
6177
|
+
proto: "anthropic",
|
|
6178
|
+
baseURL: "https://api.claudehub.fun",
|
|
6179
|
+
apiKey: "REPLACE_ME",
|
|
6180
|
+
model: "claude-sonnet-4.6"
|
|
6181
|
+
},
|
|
6182
|
+
telegram: { botToken: "REPLACE_ME" },
|
|
6183
|
+
mcp: [],
|
|
6184
|
+
privacy: "owner-only",
|
|
6185
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6186
|
+
sleepFrom: 23,
|
|
6187
|
+
sleepTo: 8,
|
|
6188
|
+
nightWakeChance: 0.05,
|
|
6189
|
+
communication: COMMUNICATION_PRESETS[0].profile,
|
|
6190
|
+
vibe: "warm",
|
|
6191
|
+
busySchedule: []
|
|
6192
|
+
};
|
|
6193
|
+
return JSON.stringify(sample, null, 2) + "\n";
|
|
6194
|
+
}
|
|
6195
|
+
function buildSystemdUnit() {
|
|
6196
|
+
const home = os.homedir();
|
|
6197
|
+
return `# /etc/systemd/system/girl-agent.service
|
|
6198
|
+
# install: sudo cp this.service /etc/systemd/system/girl-agent.service
|
|
6199
|
+
# sudo systemctl daemon-reload
|
|
6200
|
+
# sudo systemctl enable --now girl-agent
|
|
6201
|
+
|
|
6202
|
+
[Unit]
|
|
6203
|
+
Description=girl-agent (Telegram AI girl)
|
|
6204
|
+
After=network-online.target
|
|
6205
|
+
Wants=network-online.target
|
|
6206
|
+
|
|
6207
|
+
[Service]
|
|
6208
|
+
Type=simple
|
|
6209
|
+
User=%i
|
|
6210
|
+
WorkingDirectory=${home}
|
|
6211
|
+
ExecStart=${home}/.local/bin/girl-agent server --config ${home}/.config/girl-agent/bot.json --headless
|
|
6212
|
+
Restart=on-failure
|
|
6213
|
+
RestartSec=10
|
|
6214
|
+
StandardOutput=journal
|
|
6215
|
+
StandardError=journal
|
|
6216
|
+
Environment=NODE_ENV=production
|
|
6217
|
+
# uncomment for env-driven setup:
|
|
6218
|
+
# Environment=GIRL_AGENT_MODE=bot
|
|
6219
|
+
# Environment=GIRL_AGENT_TOKEN=...
|
|
6220
|
+
# Environment=GIRL_AGENT_API_PRESET=claudehub
|
|
6221
|
+
# Environment=GIRL_AGENT_API_KEY=...
|
|
6222
|
+
|
|
6223
|
+
[Install]
|
|
6224
|
+
WantedBy=multi-user.target
|
|
6225
|
+
`;
|
|
6226
|
+
}
|
|
6227
|
+
function buildDockerArtifacts() {
|
|
6228
|
+
return `# === \u043E\u0434\u043D\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 ===
|
|
6229
|
+
docker run -it --rm \\
|
|
6230
|
+
-v girl-agent-data:/data \\
|
|
6231
|
+
-e GIRL_AGENT_DATA=/data \\
|
|
6232
|
+
ghcr.io/thesashadev/girl-agent:latest
|
|
6233
|
+
|
|
6234
|
+
# === headless \u0441 \u0433\u043E\u0442\u043E\u0432\u044B\u043C \u043A\u043E\u043D\u0444\u0438\u0433\u043E\u043C ===
|
|
6235
|
+
docker run -d --name girl-agent --restart=unless-stopped \\
|
|
6236
|
+
-v girl-agent-data:/data \\
|
|
6237
|
+
-v "$PWD/bot.json:/config/bot.json:ro" \\
|
|
6238
|
+
-e GIRL_AGENT_DATA=/data \\
|
|
6239
|
+
ghcr.io/thesashadev/girl-agent:latest \\
|
|
6240
|
+
server --config /config/bot.json --headless
|
|
6241
|
+
|
|
6242
|
+
# === \u0442\u043E\u043B\u044C\u043A\u043E env vars (\u0431\u0435\u0437 \u0444\u0430\u0439\u043B\u0430) ===
|
|
6243
|
+
docker run -d --name girl-agent --restart=unless-stopped \\
|
|
6244
|
+
-v girl-agent-data:/data \\
|
|
6245
|
+
-e GIRL_AGENT_DATA=/data \\
|
|
6246
|
+
-e GIRL_AGENT_MODE=bot \\
|
|
6247
|
+
-e GIRL_AGENT_TOKEN=... \\
|
|
6248
|
+
-e GIRL_AGENT_API_PRESET=claudehub \\
|
|
6249
|
+
-e GIRL_AGENT_API_KEY=... \\
|
|
6250
|
+
-e GIRL_AGENT_NAME='\u0410\u043D\u044F' \\
|
|
6251
|
+
-e GIRL_AGENT_AGE=22 \\
|
|
6252
|
+
ghcr.io/thesashadev/girl-agent:latest \\
|
|
6253
|
+
server --headless
|
|
6254
|
+
|
|
6255
|
+
# === docker-compose.yml ===
|
|
6256
|
+
# version: "3.9"
|
|
6257
|
+
# services:
|
|
6258
|
+
# girl-agent:
|
|
6259
|
+
# image: ghcr.io/thesashadev/girl-agent:latest
|
|
6260
|
+
# command: ["server", "--config", "/config/bot.json", "--headless"]
|
|
6261
|
+
# environment:
|
|
6262
|
+
# GIRL_AGENT_DATA: /data
|
|
6263
|
+
# volumes:
|
|
6264
|
+
# - girl-agent-data:/data
|
|
6265
|
+
# - ./bot.json:/config/bot.json:ro
|
|
6266
|
+
# restart: unless-stopped
|
|
6267
|
+
# volumes:
|
|
6268
|
+
# girl-agent-data:
|
|
6269
|
+
`;
|
|
6270
|
+
}
|
|
6271
|
+
|
|
5733
6272
|
// src/cli.tsx
|
|
5734
6273
|
var HELP = `
|
|
5735
6274
|
girl-agent \u2014 AI girl for Telegram
|
|
@@ -5741,6 +6280,14 @@ usage:
|
|
|
5741
6280
|
npx girl-agent --reset --profile=<slug>
|
|
5742
6281
|
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
6282
|
|
|
6283
|
+
server (\u0434\u043B\u044F \u0441\u0438\u0441\u0442\u0435\u043C \u0431\u0435\u0437 TTY: docker / systemd / cron / CI):
|
|
6284
|
+
npx girl-agent server --print-config > bot.json
|
|
6285
|
+
npx girl-agent server --config bot.json --headless
|
|
6286
|
+
npx girl-agent server --print-systemd | --print-docker | --list
|
|
6287
|
+
|
|
6288
|
+
\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):
|
|
6289
|
+
curl -fsSL https://raw.githubusercontent.com/TheSashaDev/girl-agent/main/scripts/install.sh | sh
|
|
6290
|
+
|
|
5744
6291
|
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
6292
|
--profile=<slug> slug \u043F\u0440\u043E\u0444\u0438\u043B\u044F
|
|
5746
6293
|
--mode=bot|userbot
|
|
@@ -5796,15 +6343,71 @@ async function main() {
|
|
|
5796
6343
|
"message-style",
|
|
5797
6344
|
"initiative",
|
|
5798
6345
|
"life-sharing",
|
|
5799
|
-
"privacy"
|
|
6346
|
+
"privacy",
|
|
6347
|
+
"config"
|
|
6348
|
+
],
|
|
6349
|
+
boolean: [
|
|
6350
|
+
"help",
|
|
6351
|
+
"list",
|
|
6352
|
+
"reset",
|
|
6353
|
+
"new",
|
|
6354
|
+
"json-events",
|
|
6355
|
+
"headless",
|
|
6356
|
+
"server",
|
|
6357
|
+
"print-config",
|
|
6358
|
+
"print-systemd",
|
|
6359
|
+
"print-docker",
|
|
6360
|
+
"no-start"
|
|
5800
6361
|
],
|
|
5801
|
-
boolean: ["help", "list", "reset", "new"],
|
|
5802
6362
|
alias: { h: "help" }
|
|
5803
6363
|
});
|
|
6364
|
+
const positional = argv._ ?? [];
|
|
6365
|
+
const isServer = positional[0] === "server" || !!argv.server || !!argv["print-config"] || !!argv["print-systemd"] || !!argv["print-docker"];
|
|
6366
|
+
if (isServer) {
|
|
6367
|
+
await runServer(argv);
|
|
6368
|
+
return;
|
|
6369
|
+
}
|
|
5804
6370
|
if (argv.help) {
|
|
5805
6371
|
process.stdout.write(HELP);
|
|
5806
6372
|
return;
|
|
5807
6373
|
}
|
|
6374
|
+
const isHeadless = !!(argv["json-events"] || argv.headless || argv.list || argv.help);
|
|
6375
|
+
if (!isHeadless) {
|
|
6376
|
+
const stdin = process.stdin;
|
|
6377
|
+
const stdout = process.stdout;
|
|
6378
|
+
const stdinOk = !!stdin.isTTY;
|
|
6379
|
+
const stdoutOk = !!stdout.isTTY;
|
|
6380
|
+
if (!stdinOk || !stdoutOk) {
|
|
6381
|
+
process.stderr.write(
|
|
6382
|
+
`
|
|
6383
|
+
[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).
|
|
6384
|
+
stdin.isTTY = ${stdinOk}, stdout.isTTY = ${stdoutOk}
|
|
6385
|
+
|
|
6386
|
+
\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):
|
|
6387
|
+
|
|
6388
|
+
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):
|
|
6389
|
+
curl -fsSL https://raw.githubusercontent.com/TheSashaDev/girl-agent/main/scripts/install.sh | sh
|
|
6390
|
+
\u0434\u0430\u043B\u044C\u0448\u0435: girl-agent # ink-\u0432\u0438\u0437\u0430\u0440\u0434 \u0432 \u043E\u0431\u044B\u0447\u043D\u043E\u043C tty
|
|
6391
|
+
|
|
6392
|
+
2. \u0433\u043E\u0442\u043E\u0432\u044B\u0439 \u043A\u043E\u043D\u0444\u0438\u0433 + headless (\u0434\u043B\u044F systemd / cron / CI):
|
|
6393
|
+
girl-agent server --print-config > bot.json
|
|
6394
|
+
# \u043E\u0442\u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u0443\u0439 bot.json
|
|
6395
|
+
girl-agent server --config bot.json --headless
|
|
6396
|
+
|
|
6397
|
+
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):
|
|
6398
|
+
docker run -it --rm -v girl-agent-data:/data \\
|
|
6399
|
+
ghcr.io/thesashadev/girl-agent:latest
|
|
6400
|
+
|
|
6401
|
+
4. systemd: girl-agent server --print-systemd
|
|
6402
|
+
docker: girl-agent server --print-docker
|
|
6403
|
+
|
|
6404
|
+
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.
|
|
6405
|
+
`
|
|
6406
|
+
);
|
|
6407
|
+
process.exit(2);
|
|
6408
|
+
}
|
|
6409
|
+
}
|
|
6410
|
+
const jsonEvents = !!(argv["json-events"] || argv.headless);
|
|
5808
6411
|
if (argv.age != null) {
|
|
5809
6412
|
const a = Number(argv.age);
|
|
5810
6413
|
if (!Number.isFinite(a) || a < 13 || a > 99) {
|
|
@@ -5834,7 +6437,7 @@ ${profiles.join("\n")}
|
|
|
5834
6437
|
cfg.stage = "tg-given-cold";
|
|
5835
6438
|
await writeConfig(cfg);
|
|
5836
6439
|
}
|
|
5837
|
-
await runRuntime(cfg);
|
|
6440
|
+
await runRuntime(cfg, { jsonEvents });
|
|
5838
6441
|
return;
|
|
5839
6442
|
}
|
|
5840
6443
|
const presetForFlags = argv["api-preset"] ? findPreset(String(argv["api-preset"])) : void 0;
|
|
@@ -5850,7 +6453,7 @@ ${profiles.join("\n")}
|
|
|
5850
6453
|
const generated = await generatePersonaPack(llm, cfg.slug, cfg.name, cfg.age, cfg.nationality, personaNotesForGeneration2(cfg));
|
|
5851
6454
|
cfg.busySchedule = generated.busySchedule;
|
|
5852
6455
|
await writeConfig(cfg);
|
|
5853
|
-
await runRuntime(cfg);
|
|
6456
|
+
await runRuntime(cfg, { jsonEvents });
|
|
5854
6457
|
return;
|
|
5855
6458
|
}
|
|
5856
6459
|
if (!argv.new && !argv.profile && !haveEnoughForFlags) {
|
|
@@ -5860,7 +6463,7 @@ ${profiles.join("\n")}
|
|
|
5860
6463
|
if (cfg) {
|
|
5861
6464
|
process.stdout.write(`\u0437\u0430\u0433\u0440\u0443\u0436\u0430\u044E \u043F\u0440\u043E\u0444\u0438\u043B\u044C: ${cfg.name}
|
|
5862
6465
|
`);
|
|
5863
|
-
await runRuntime(cfg);
|
|
6466
|
+
await runRuntime(cfg, { jsonEvents });
|
|
5864
6467
|
return;
|
|
5865
6468
|
}
|
|
5866
6469
|
} else if (profiles.length > 1) {
|
|
@@ -5876,7 +6479,7 @@ ${profiles.join("\n")}
|
|
|
5876
6479
|
const inst = render(
|
|
5877
6480
|
/* @__PURE__ */ React3.createElement(Wizard, { onDone: async (cfg) => {
|
|
5878
6481
|
inst.unmount();
|
|
5879
|
-
await runRuntime(cfg);
|
|
6482
|
+
await runRuntime(cfg, { jsonEvents });
|
|
5880
6483
|
resolve();
|
|
5881
6484
|
} }),
|
|
5882
6485
|
{ exitOnCtrlC: true }
|
|
@@ -5949,9 +6552,13 @@ function personaNotesForGeneration2(cfg) {
|
|
|
5949
6552
|
].filter(Boolean);
|
|
5950
6553
|
return parts.join("\n\n");
|
|
5951
6554
|
}
|
|
5952
|
-
async function runRuntime(cfg) {
|
|
6555
|
+
async function runRuntime(cfg, opts = {}) {
|
|
5953
6556
|
const rt = new Runtime(cfg);
|
|
5954
6557
|
await rt.start();
|
|
6558
|
+
if (opts.jsonEvents) {
|
|
6559
|
+
await runHeadlessJsonEvents(rt);
|
|
6560
|
+
return;
|
|
6561
|
+
}
|
|
5955
6562
|
const inst = render(/* @__PURE__ */ React3.createElement(Dashboard, { runtime: rt }), { exitOnCtrlC: true });
|
|
5956
6563
|
process.on("SIGINT", async () => {
|
|
5957
6564
|
await rt.stop();
|
|
@@ -5961,7 +6568,17 @@ async function runRuntime(cfg) {
|
|
|
5961
6568
|
await inst.waitUntilExit();
|
|
5962
6569
|
await rt.stop();
|
|
5963
6570
|
}
|
|
6571
|
+
process.on("unhandledRejection", (reason) => {
|
|
6572
|
+
const r = reason;
|
|
6573
|
+
const text = typeof r === "object" && r && r.stack ? r.stack : String(reason);
|
|
6574
|
+
process.stderr.write("[girl-agent] unhandled rejection: " + text + "\n");
|
|
6575
|
+
process.exit(1);
|
|
6576
|
+
});
|
|
6577
|
+
process.on("uncaughtException", (err) => {
|
|
6578
|
+
process.stderr.write("[girl-agent] uncaught: " + (err?.stack ?? err) + "\n");
|
|
6579
|
+
process.exit(1);
|
|
6580
|
+
});
|
|
5964
6581
|
main().catch((e) => {
|
|
5965
|
-
process.stderr.write("fatal: " + (e?.stack ?? e) + "\n");
|
|
6582
|
+
process.stderr.write("[girl-agent] fatal: " + (e?.stack ?? e) + "\n");
|
|
5966
6583
|
process.exit(1);
|
|
5967
6584
|
});
|