@thesashadev/girl-agent 0.1.7 → 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 +39 -0
- package/README.md +86 -6
- package/dist/cli.js +824 -51
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
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
|
+
|
|
33
|
+
## 0.1.8 — OpenAI-compatible API compatibility
|
|
34
|
+
|
|
35
|
+
Дата: 2026-05-06
|
|
36
|
+
|
|
37
|
+
- JSON-ответы теперь сначала запрашиваются через `json_schema`, с fallback на `json_object` и `text` для разных OpenAI-compatible API. (#33)
|
|
38
|
+
- LM Studio и Ollama больше не требуют реальный API ключ в wizard/headless setup.
|
|
39
|
+
- Добавлена совместимость с OpenAI-compatible прокси, которые возвращают SSE/event-stream даже на обычный chat completions запрос.
|
|
40
|
+
- Добавлена Docker-поддержка для 24/7 запуска на сервере: `Dockerfile`, `docker-compose.yml`, volume для `data` и инструкции в README. (#35)
|
|
41
|
+
|
|
3
42
|
## 0.1.7 — MarkdownV2 escaping fix
|
|
4
43
|
|
|
5
44
|
Дата: 2026-05-06
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+

|
|
2
2
|
|
|
3
3
|
[website]: https://girl-agent.com
|
|
4
4
|
[docs]: https://docs.girl-agent.com
|
|
@@ -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,20 +37,98 @@
|
|
|
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`
|
|
73
|
+
|
|
74
|
+
### windows — десктоп-приложение
|
|
45
75
|
|
|
46
|
-
|
|
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
|
|
|
93
|
+
### docker (для серверов; нулевые зависимости на хосте)
|
|
94
|
+
|
|
95
|
+
Интерактивная первичная настройка (ink-визард внутри контейнера):
|
|
96
|
+
```sh
|
|
97
|
+
docker run -it --rm -v girl-agent-data:/data ghcr.io/thesashadev/girl-agent:latest
|
|
98
|
+
```
|
|
99
|
+
|
|
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
|
|
111
|
+
```
|
|
112
|
+
|
|
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
|
|
124
|
+
```
|
|
125
|
+
|
|
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) в корне репо
|
|
131
|
+
|
|
52
132
|
**Из исходников:**
|
|
53
133
|
|
|
54
134
|
```powershell
|
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) {
|
|
@@ -377,8 +386,10 @@ var LLM_PRESETS = [
|
|
|
377
386
|
proto: "openai",
|
|
378
387
|
baseURL: "http://localhost:1234/v1",
|
|
379
388
|
defaultModel: "",
|
|
389
|
+
defaultApiKey: "lm-studio",
|
|
390
|
+
apiKeyRequired: false,
|
|
380
391
|
custom: true,
|
|
381
|
-
hint: "\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E, OpenAI-compatible endpoint"
|
|
392
|
+
hint: "\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E, OpenAI-compatible endpoint; \u043A\u043B\u044E\u0447 \u043D\u0435 \u043D\u0443\u0436\u0435\u043D"
|
|
382
393
|
},
|
|
383
394
|
{
|
|
384
395
|
id: "ollama",
|
|
@@ -386,8 +397,10 @@ var LLM_PRESETS = [
|
|
|
386
397
|
proto: "openai",
|
|
387
398
|
baseURL: "http://localhost:11434/v1",
|
|
388
399
|
defaultModel: "llama3.1",
|
|
400
|
+
defaultApiKey: "ollama",
|
|
401
|
+
apiKeyRequired: false,
|
|
389
402
|
custom: true,
|
|
390
|
-
hint: "\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E \u0447\u0435\u0440\u0435\u0437 /v1"
|
|
403
|
+
hint: "\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E \u0447\u0435\u0440\u0435\u0437 /v1; \u043A\u043B\u044E\u0447 \u043D\u0435 \u043D\u0443\u0436\u0435\u043D"
|
|
391
404
|
},
|
|
392
405
|
{
|
|
393
406
|
id: "anthropic",
|
|
@@ -796,7 +809,7 @@ function sameProfile(a, b) {
|
|
|
796
809
|
init_esm_shims();
|
|
797
810
|
import { promises as fs } from "fs";
|
|
798
811
|
import path2 from "path";
|
|
799
|
-
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");
|
|
800
813
|
function profileDir(slug) {
|
|
801
814
|
return path2.join(DATA_ROOT, slug);
|
|
802
815
|
}
|
|
@@ -1025,20 +1038,28 @@ var OpenAILike = class {
|
|
|
1025
1038
|
constructor(cfg) {
|
|
1026
1039
|
this.cfg = cfg;
|
|
1027
1040
|
this.client = new OpenAI({
|
|
1028
|
-
apiKey: cfg
|
|
1041
|
+
apiKey: openAIApiKey(cfg),
|
|
1029
1042
|
baseURL: normalizeBaseURL(cfg.baseURL),
|
|
1030
1043
|
timeout: LLM_TIMEOUT_MS,
|
|
1031
1044
|
maxRetries: LLM_MAX_RETRIES
|
|
1032
1045
|
});
|
|
1046
|
+
this.fetchClient = new OpenAI({
|
|
1047
|
+
apiKey: openAIApiKey(cfg),
|
|
1048
|
+
baseURL: normalizeBaseURL(cfg.baseURL),
|
|
1049
|
+
timeout: LLM_TIMEOUT_MS,
|
|
1050
|
+
maxRetries: LLM_MAX_RETRIES,
|
|
1051
|
+
fetch: compatibleFetch
|
|
1052
|
+
});
|
|
1033
1053
|
}
|
|
1034
1054
|
cfg;
|
|
1035
1055
|
client;
|
|
1056
|
+
fetchClient;
|
|
1036
1057
|
async chat(messages, opts = {}) {
|
|
1037
1058
|
const params = {
|
|
1038
1059
|
model: this.cfg.model,
|
|
1039
1060
|
messages: openAIMessages(messages),
|
|
1040
1061
|
temperature: opts.temperature ?? 0.85,
|
|
1041
|
-
response_format: opts
|
|
1062
|
+
response_format: openAIResponseFormat(opts)
|
|
1042
1063
|
};
|
|
1043
1064
|
if (usesMaxCompletionTokens(this.cfg.model)) {
|
|
1044
1065
|
params.max_completion_tokens = opts.maxTokens ?? 600;
|
|
@@ -1049,19 +1070,86 @@ var OpenAILike = class {
|
|
|
1049
1070
|
return res.choices[0]?.message?.content?.trim() ?? "";
|
|
1050
1071
|
}
|
|
1051
1072
|
async createWithCompatibilityFallback(params) {
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1073
|
+
const attempted = /* @__PURE__ */ new Set();
|
|
1074
|
+
let current = params;
|
|
1075
|
+
let lastError;
|
|
1076
|
+
while (current) {
|
|
1077
|
+
const key = completionParamsKey(current);
|
|
1078
|
+
if (attempted.has(key)) break;
|
|
1079
|
+
attempted.add(key);
|
|
1080
|
+
try {
|
|
1081
|
+
return await this.client.chat.completions.create(current);
|
|
1082
|
+
} catch (error) {
|
|
1083
|
+
lastError = error;
|
|
1084
|
+
const next = completionFallback(current, error);
|
|
1085
|
+
if (!next) break;
|
|
1086
|
+
current = next;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
if (this.cfg.baseURL) {
|
|
1057
1090
|
try {
|
|
1058
|
-
return await this.
|
|
1059
|
-
} catch (
|
|
1060
|
-
|
|
1091
|
+
return await this.fetchClient.chat.completions.create({ ...params, stream: false });
|
|
1092
|
+
} catch (fetchError) {
|
|
1093
|
+
lastError = fetchError;
|
|
1061
1094
|
}
|
|
1062
1095
|
}
|
|
1096
|
+
throw enrichOpenAIError(lastError, this.cfg.baseURL);
|
|
1063
1097
|
}
|
|
1064
1098
|
};
|
|
1099
|
+
async function compatibleFetch(url, init) {
|
|
1100
|
+
const res = await fetch(url, init);
|
|
1101
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
1102
|
+
if (!res.ok) return res;
|
|
1103
|
+
const text = await res.clone().text();
|
|
1104
|
+
if (contentType.includes("text/event-stream") || text.trimStart().startsWith("data:")) {
|
|
1105
|
+
return completionStreamToJsonResponse(res, text);
|
|
1106
|
+
}
|
|
1107
|
+
return res;
|
|
1108
|
+
}
|
|
1109
|
+
function completionStreamToJsonResponse(res, text) {
|
|
1110
|
+
const completion = parseOpenAIEventStream(text);
|
|
1111
|
+
return new Response(JSON.stringify(completion), {
|
|
1112
|
+
status: res.status,
|
|
1113
|
+
statusText: res.statusText,
|
|
1114
|
+
headers: { "content-type": "application/json" }
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
function parseOpenAIEventStream(raw) {
|
|
1118
|
+
let id = "chatcmpl-stream";
|
|
1119
|
+
let model = "";
|
|
1120
|
+
let created = Math.floor(Date.now() / 1e3);
|
|
1121
|
+
const content = [];
|
|
1122
|
+
let finishReason = "stop";
|
|
1123
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
1124
|
+
const trimmed = line.trim();
|
|
1125
|
+
if (!trimmed.startsWith("data:")) continue;
|
|
1126
|
+
const data = trimmed.slice(5).trim();
|
|
1127
|
+
if (!data || data === "[DONE]") continue;
|
|
1128
|
+
try {
|
|
1129
|
+
const chunk = JSON.parse(data);
|
|
1130
|
+
id = chunk.id || id;
|
|
1131
|
+
model = chunk.model || model;
|
|
1132
|
+
created = chunk.created || created;
|
|
1133
|
+
const choice = chunk.choices[0];
|
|
1134
|
+
finishReason = choice?.finish_reason ?? finishReason;
|
|
1135
|
+
const delta = choice?.delta?.content ?? choice?.message?.content;
|
|
1136
|
+
if (typeof delta === "string") content.push(delta);
|
|
1137
|
+
} catch {
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
return {
|
|
1141
|
+
id,
|
|
1142
|
+
object: "chat.completion",
|
|
1143
|
+
created,
|
|
1144
|
+
model,
|
|
1145
|
+
choices: [{
|
|
1146
|
+
index: 0,
|
|
1147
|
+
message: { role: "assistant", content: content.join(""), refusal: null },
|
|
1148
|
+
finish_reason: finishReason,
|
|
1149
|
+
logprobs: null
|
|
1150
|
+
}]
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1065
1153
|
var AnthropicLike = class {
|
|
1066
1154
|
constructor(cfg) {
|
|
1067
1155
|
this.cfg = cfg;
|
|
@@ -1155,8 +1243,36 @@ function normalizeBaseURL(value) {
|
|
|
1155
1243
|
function usesMaxCompletionTokens(model) {
|
|
1156
1244
|
return /^(?:o\d|o\d-|o\d\b|gpt-5|gpt-5\.|gpt-[5-9])|\/(?:o\d|gpt-5|gpt-[5-9])/.test(model.trim().toLowerCase());
|
|
1157
1245
|
}
|
|
1246
|
+
function openAIApiKey(cfg) {
|
|
1247
|
+
return cfg.apiKey.trim() || (cfg.presetId === "ollama" ? "ollama" : cfg.presetId === "lmstudio" ? "lm-studio" : "");
|
|
1248
|
+
}
|
|
1249
|
+
function openAIResponseFormat(opts) {
|
|
1250
|
+
if (!opts.json) return void 0;
|
|
1251
|
+
if (opts.jsonSchema) return { type: "json_schema", json_schema: opts.jsonSchema };
|
|
1252
|
+
return {
|
|
1253
|
+
type: "json_schema",
|
|
1254
|
+
json_schema: {
|
|
1255
|
+
name: "json_response",
|
|
1256
|
+
strict: false,
|
|
1257
|
+
schema: { type: "object", additionalProperties: true }
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
function completionFallback(params, error) {
|
|
1262
|
+
return responseFormatFallback(params, error) ?? completionTokenFallback(params, error);
|
|
1263
|
+
}
|
|
1264
|
+
function responseFormatFallback(params, error) {
|
|
1265
|
+
const message = openAIErrorText(error);
|
|
1266
|
+
if (!params.response_format || !message.includes("response_format")) return null;
|
|
1267
|
+
if (params.response_format.type === "json_schema" && message.includes("json_object")) {
|
|
1268
|
+
return { ...params, response_format: { type: "text" } };
|
|
1269
|
+
}
|
|
1270
|
+
if (params.response_format.type === "json_schema") return { ...params, response_format: { type: "json_object" } };
|
|
1271
|
+
if (params.response_format.type === "json_object") return { ...params, response_format: { type: "text" } };
|
|
1272
|
+
return null;
|
|
1273
|
+
}
|
|
1158
1274
|
function completionTokenFallback(params, error) {
|
|
1159
|
-
const message =
|
|
1275
|
+
const message = openAIErrorText(error);
|
|
1160
1276
|
if (params.max_tokens != null && message.includes("max_tokens") && message.includes("max_completion_tokens")) {
|
|
1161
1277
|
const { max_tokens, ...rest } = params;
|
|
1162
1278
|
return { ...rest, max_completion_tokens: max_tokens };
|
|
@@ -1167,6 +1283,16 @@ function completionTokenFallback(params, error) {
|
|
|
1167
1283
|
}
|
|
1168
1284
|
return null;
|
|
1169
1285
|
}
|
|
1286
|
+
function completionParamsKey(params) {
|
|
1287
|
+
const tokenKey = params.max_completion_tokens != null ? "max_completion_tokens" : "max_tokens";
|
|
1288
|
+
return `${params.response_format?.type ?? "default"}:${tokenKey}`;
|
|
1289
|
+
}
|
|
1290
|
+
function openAIErrorText(error) {
|
|
1291
|
+
if (error instanceof OpenAI.APIError) {
|
|
1292
|
+
return `${error.status ?? ""} ${error.code ?? ""} ${error.type ?? ""} ${error.message}`.toLowerCase();
|
|
1293
|
+
}
|
|
1294
|
+
return errorMessage(error).toLowerCase();
|
|
1295
|
+
}
|
|
1170
1296
|
function enrichOpenAIError(error, baseURL) {
|
|
1171
1297
|
if (error instanceof OpenAI.APIConnectionError) {
|
|
1172
1298
|
return new Error(connectionErrorMessage("OpenAI-compatible", baseURL, error));
|
|
@@ -1202,6 +1328,40 @@ function makeLLM(cfg) {
|
|
|
1202
1328
|
init_esm_shims();
|
|
1203
1329
|
var SYS = `\u0422\u044B \u2014 \u0440\u0435\u0436\u0438\u0441\u0441\u0451\u0440 \u043F\u0435\u0440\u0441\u043E\u043D\u0430\u0436\u0435\u0439. \u0422\u0432\u043E\u044F \u0437\u0430\u0434\u0430\u0447\u0430 \u2014 \u0441\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0434\u043E\u0441\u0442\u043E\u0432\u0435\u0440\u043D\u044B\u0439, \u043D\u0435-\u0433\u043E\u043B\u043B\u0438\u0432\u0443\u0434\u0441\u043A\u0438\u0439, \u043D\u0435-\u0441\u043B\u0430\u0434\u043A\u0438\u0439, \u043D\u0435-AI-\u043F\u043E\u043C\u043E\u0449\u043D\u0438\u043A\u043E\u0432\u044B\u0439 \u043F\u0440\u043E\u0444\u0438\u043B\u044C \u043E\u0431\u044B\u0447\u043D\u043E\u0439 \u0440\u0443\u0441\u0441\u043A\u043E\u044F\u0437\u044B\u0447\u043D\u043E\u0439 \u0434\u0435\u0432\u0443\u0448\u043A\u0438 \u0443\u043A\u0430\u0437\u0430\u043D\u043D\u043E\u0433\u043E \u0432\u043E\u0437\u0440\u0430\u0441\u0442\u0430. \u0411\u0435\u0437 \u043F\u0440\u0438\u0443\u043A\u0440\u0430\u0448\u0438\u0432\u0430\u043D\u0438\u0439, \u0431\u0435\u0437 \u043F\u043E\u0437\u0438\u0442\u0438\u0432\u043D\u043E\u0433\u043E \u043C\u043E\u0440\u0430\u043B\u044C\u043D\u043E\u0433\u043E \u0443\u043A\u043B\u043E\u043D\u0430, \u0431\u0435\u0437 \xAB\u0443\u0434\u043E\u0431\u043D\u043E\u0439\xBB \u043F\u0435\u0440\u0441\u043E\u043D\u044B. \u0414\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B \u043D\u0435\u0434\u043E\u0441\u0442\u0430\u0442\u043A\u0438, \u0443\u0437\u043A\u0438\u0435 \u0438\u043D\u0442\u0435\u0440\u0435\u0441\u044B, \u0441\u043A\u0435\u043F\u0441\u0438\u0441, \u0442\u043E\u043A\u0441\u0438\u0447\u043D\u043E\u0441\u0442\u044C, \u043B\u0435\u043D\u044C, \u0442\u0440\u0435\u0432\u043E\u0436\u043D\u043E\u0441\u0442\u044C, \u043D\u0435\u0443\u0432\u0435\u0440\u0435\u043D\u043D\u043E\u0441\u0442\u044C \u2014 \u0447\u0442\u043E \u043F\u043E\u0434\u0445\u043E\u0434\u0438\u0442 \u0432\u043E\u0437\u0440\u0430\u0441\u0442\u0443. \u041D\u0438\u043A\u0430\u043A\u0438\u0445 \xAB\u043A\u043E\u0443\u0447\u0438\u043D\u0433\u043E\u0432\u044B\u0445\xBB \u0444\u043E\u0440\u043C\u0443\u043B\u0438\u0440\u043E\u0432\u043E\u043A. \u0422\u043E\u043B\u044C\u043A\u043E \u0436\u0438\u0432\u0430\u044F \u0440\u0435\u0447\u044C, \u043A\u0430\u043A \u0438\u0437 \u0434\u043D\u0435\u0432\u043D\u0438\u043A\u0430 \u0438\u043B\u0438 \u0432\u043D\u0443\u0442\u0440\u0435\u043D\u043D\u0435\u0433\u043E \u043C\u043E\u043D\u043E\u043B\u043E\u0433\u0430. \u0412\u043E\u0437\u0440\u0430\u0441\u0442: {{age}} \u043B\u0435\u0442, \u0438\u043C\u044F: {{name}}.`;
|
|
1204
1330
|
var WEEKDAYS = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"];
|
|
1331
|
+
var BUSY_SCHEDULE_SCHEMA = {
|
|
1332
|
+
name: "busy_schedule",
|
|
1333
|
+
strict: false,
|
|
1334
|
+
schema: {
|
|
1335
|
+
type: "object",
|
|
1336
|
+
properties: {
|
|
1337
|
+
busySchedule: {
|
|
1338
|
+
type: "array",
|
|
1339
|
+
items: {
|
|
1340
|
+
type: "object",
|
|
1341
|
+
properties: {
|
|
1342
|
+
label: { type: "string" },
|
|
1343
|
+
days: {
|
|
1344
|
+
type: "array",
|
|
1345
|
+
items: { type: "string", enum: WEEKDAYS }
|
|
1346
|
+
},
|
|
1347
|
+
from: { type: "string" },
|
|
1348
|
+
to: { type: "string" },
|
|
1349
|
+
checkAfterMin: {
|
|
1350
|
+
type: "array",
|
|
1351
|
+
items: { type: "number" },
|
|
1352
|
+
minItems: 2,
|
|
1353
|
+
maxItems: 2
|
|
1354
|
+
}
|
|
1355
|
+
},
|
|
1356
|
+
required: ["label", "from", "to"],
|
|
1357
|
+
additionalProperties: false
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
},
|
|
1361
|
+
required: ["busySchedule"],
|
|
1362
|
+
additionalProperties: false
|
|
1363
|
+
}
|
|
1364
|
+
};
|
|
1205
1365
|
async function generatePersonaPack(llm, slug, name, age, nationality = "RU", personaNotes = "", onProgress) {
|
|
1206
1366
|
const country = nationality === "UA" ? "\u0423\u043A\u0440\u0430\u0438\u043D\u0430" : "\u0420\u043E\u0441\u0441\u0438\u044F / \u0421\u041D\u0413";
|
|
1207
1367
|
const langHint = nationality === "UA" ? "\u041F\u0438\u0448\u0435\u0442 \u043D\u0430 \u0420\u0423\u0421\u0421\u041A\u041E\u041C (\u043A\u0430\u043A \u0440\u0435\u0430\u043B\u044C\u043D\u043E \u043F\u0438\u0448\u0435\u0442 \u0431\u043E\u043B\u044C\u0448\u0438\u043D\u0441\u0442\u0432\u043E \u0434\u0435\u0432\u0443\u0448\u0435\u043A \u0432 \u0423\u043A\u0440\u0430\u0438\u043D\u0435 \u0432 \u0442\u0433). \u0414\u043E\u043F\u0443\u0441\u0442\u0438\u043C \u043B\u0451\u0433\u043A\u0438\u0439 \u0441\u0443\u0440\u0436\u0438\u043A: ~90% \u0440\u0443\u0441\u0441\u043A\u0438\u0439 + ~10% \u0443\u043A\u0440\u0430\u0438\u043D\u0441\u043A\u0438\u0445 \u0432\u0441\u0442\u0430\u0432\u043E\u043A (\u043E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u0435 \u0441\u043B\u043E\u0432\u0430: '\u0448\u043E', '\u043C\u0430\u0431\u0443\u0442\u044C', '\u0442\u0440\u043E\u0445\u0438', '\u044F\u043A', '\u043D\u0443 \u0448\u043E', '\u0442\u0430 \u0439\u0434\u0438', '\u0434\u044F\u043A\u0443\u044E'), \u043D\u043E \u0431\u0435\u0437 \u043F\u043E\u043B\u043D\u043E\u0433\u043E \u043F\u0435\u0440\u0435\u0445\u043E\u0434\u0430 \u043D\u0430 \u0443\u043A\u0440\u0430\u0438\u043D\u0441\u043A\u0438\u0439. \u0427\u0438\u0441\u0442\u043E-\u0443\u043A\u0440\u0430\u0438\u043D\u0441\u043A\u0438\u0439 \u0442\u0435\u043A\u0441\u0442 \u041D\u0415 \u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0439 \u2014 \u044D\u0442\u043E \u043D\u0435\u0440\u0435\u0430\u043B\u0438\u0441\u0442\u0438\u0447\u043D\u043E \u0434\u043B\u044F \u0442\u0433-\u043F\u0435\u0440\u0435\u043F\u0438\u0441\u043A\u0438." : "\u0420\u0443\u0441\u0441\u043A\u043E\u044F\u0437\u044B\u0447\u043D\u0430\u044F \u0431\u0435\u0437 \u0443\u043A\u0440\u0430\u0438\u043D\u0438\u0437\u043C\u043E\u0432.";
|
|
@@ -1312,7 +1472,7 @@ ${personaNotes.trim()}
|
|
|
1312
1472
|
onProgress?.(65, "\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u043C communication.md\u2026");
|
|
1313
1473
|
const boundaries = await llm.chat([{ role: "system", content: sys }, { role: "user", content: boundariesPrompt }], { temperature: 0.9, maxTokens: 3500 });
|
|
1314
1474
|
onProgress?.(85, "\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u043C busy schedule\u2026");
|
|
1315
|
-
const routineRaw = await llm.chat([{ role: "system", content: sys }, { role: "user", content: routinePrompt }], { temperature: 0.85, maxTokens: 3500, json: true });
|
|
1475
|
+
const routineRaw = await llm.chat([{ role: "system", content: sys }, { role: "user", content: routinePrompt }], { temperature: 0.85, maxTokens: 3500, json: true, jsonSchema: BUSY_SCHEDULE_SCHEMA });
|
|
1316
1476
|
const busySchedule = parseBusySchedule(routineRaw, name, age);
|
|
1317
1477
|
await writeMd(slug, "persona.md", persona);
|
|
1318
1478
|
await writeMd(slug, "speech.md", speech);
|
|
@@ -1383,14 +1543,14 @@ var DEFAULT_PROXY = "https://tgproxy.girl-agent.com";
|
|
|
1383
1543
|
function proxyUrl() {
|
|
1384
1544
|
return process.env.GIRL_AGENT_AUTH_PROXY ?? DEFAULT_PROXY;
|
|
1385
1545
|
}
|
|
1386
|
-
async function post(
|
|
1387
|
-
const res = await fetch(`${proxyUrl()}${
|
|
1546
|
+
async function post(path6, body) {
|
|
1547
|
+
const res = await fetch(`${proxyUrl()}${path6}`, {
|
|
1388
1548
|
method: "POST",
|
|
1389
1549
|
headers: { "Content-Type": "application/json" },
|
|
1390
1550
|
body: JSON.stringify(body)
|
|
1391
1551
|
});
|
|
1392
1552
|
const data = await res.json();
|
|
1393
|
-
if (!res.ok) throw new Error(data.error ?? `proxy ${
|
|
1553
|
+
if (!res.ok) throw new Error(data.error ?? `proxy ${path6} failed (${res.status})`);
|
|
1394
1554
|
return data;
|
|
1395
1555
|
}
|
|
1396
1556
|
function remoteSendCode(phone) {
|
|
@@ -1866,6 +2026,7 @@ function Wizard({ initial, onDone }) {
|
|
|
1866
2026
|
setLlmProto(preset.proto);
|
|
1867
2027
|
setLlmBaseURL(preset.baseURL ?? "");
|
|
1868
2028
|
setLlmModel(preset.defaultModel);
|
|
2029
|
+
setLlmKey(preset.defaultApiKey ?? "");
|
|
1869
2030
|
if (preset.custom) setStep("api-base");
|
|
1870
2031
|
else setStep("api-model");
|
|
1871
2032
|
}
|
|
@@ -1893,7 +2054,9 @@ function Wizard({ initial, onDone }) {
|
|
|
1893
2054
|
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Header, { sub: "\u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u043C\u043E\u0434\u0435\u043B\u0438" }), /* @__PURE__ */ React.createElement(Bar, { step: 2, total: 9 }), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "Model: "), /* @__PURE__ */ React.createElement(TextInput, { value: llmModel, onChange: setLlmModel, onSubmit: () => setStep("api-key") })));
|
|
1894
2055
|
}
|
|
1895
2056
|
if (step === "api-key") {
|
|
1896
|
-
|
|
2057
|
+
const preset = findPreset(llmPresetId);
|
|
2058
|
+
const apiKeyRequired = preset?.apiKeyRequired !== false;
|
|
2059
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Header, { sub: apiKeyRequired ? "API \u043A\u043B\u044E\u0447" : "API \u043A\u043B\u044E\u0447 (\u043C\u043E\u0436\u043D\u043E \u043F\u0440\u043E\u043F\u0443\u0441\u0442\u0438\u0442\u044C)" }), /* @__PURE__ */ React.createElement(Bar, { step: 2, total: 11 }), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "Key: "), /* @__PURE__ */ React.createElement(TextInput, { value: llmKey, onChange: setLlmKey, mask: "\u2022", onSubmit: () => (llmKey || !apiKeyRequired) && setStep("nationality") })), !apiKeyRequired && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "\u0414\u043B\u044F \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u0433\u043E API \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D \u0442\u0435\u0445\u043D\u0438\u0447\u0435\u0441\u043A\u0438\u0439 placeholder, \u0435\u0441\u043B\u0438 \u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u043F\u0443\u0441\u0442\u043E."));
|
|
1897
2060
|
}
|
|
1898
2061
|
if (step === "nationality") {
|
|
1899
2062
|
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Header, { sub: "\u043D\u0430\u0446\u0438\u043E\u043D\u0430\u043B\u044C\u043D\u043E\u0441\u0442\u044C (\u044F\u0437\u044B\u043A, \u0438\u043C\u044F, \u043A\u0443\u043B\u044C\u0442\u0443\u0440\u0430)" }), /* @__PURE__ */ React.createElement(Bar, { step: 3, total: 11 }), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(
|
|
@@ -2939,8 +3102,8 @@ phoneAvailable=false \u043A\u043E\u0433\u0434\u0430: \u0441\u043F\u0438\u0442, \
|
|
|
2939
3102
|
}
|
|
2940
3103
|
async function loadOrGenerateDailyLife(llm, cfg, now = /* @__PURE__ */ new Date(), conflict = null) {
|
|
2941
3104
|
const dateLocal = localDateStr(cfg.tz, now);
|
|
2942
|
-
const
|
|
2943
|
-
const existing = await readMd(cfg.slug,
|
|
3105
|
+
const path6 = `daily-life/${dateLocal}.md`;
|
|
3106
|
+
const existing = await readMd(cfg.slug, path6);
|
|
2944
3107
|
if (existing) {
|
|
2945
3108
|
try {
|
|
2946
3109
|
const m = existing.match(/<!--daily:(.+?)-->/s);
|
|
@@ -2972,7 +3135,7 @@ async function loadOrGenerateDailyLife(llm, cfg, now = /* @__PURE__ */ new Date(
|
|
|
2972
3135
|
dl = { dateLocal, vibe: "\u043E\u0431\u044B\u0447\u043D\u044B\u0439 \u0434\u0435\u043D\u044C", blocks: [], events: [], wants: [] };
|
|
2973
3136
|
}
|
|
2974
3137
|
const human = renderDailyLifeHuman(dl);
|
|
2975
|
-
await writeMd(cfg.slug,
|
|
3138
|
+
await writeMd(cfg.slug, path6, `${human}
|
|
2976
3139
|
|
|
2977
3140
|
<!--daily:${JSON.stringify(dl)}-->
|
|
2978
3141
|
`);
|
|
@@ -3202,9 +3365,9 @@ async function ensureDefaults(cfg) {
|
|
|
3202
3365
|
["time/promises.md", "# promises\n"],
|
|
3203
3366
|
["memory/uncertain.md", "# uncertain\n"]
|
|
3204
3367
|
];
|
|
3205
|
-
await Promise.all(defaults.map(async ([
|
|
3206
|
-
const current = await readMd(cfg.slug,
|
|
3207
|
-
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");
|
|
3208
3371
|
}));
|
|
3209
3372
|
}
|
|
3210
3373
|
async function loadRealismContext(cfg, incoming) {
|
|
@@ -5576,6 +5739,536 @@ function sleep(ms) {
|
|
|
5576
5739
|
return new Promise((r) => setTimeout(r, ms));
|
|
5577
5740
|
}
|
|
5578
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
|
+
|
|
5579
6272
|
// src/cli.tsx
|
|
5580
6273
|
var HELP = `
|
|
5581
6274
|
girl-agent \u2014 AI girl for Telegram
|
|
@@ -5587,7 +6280,15 @@ usage:
|
|
|
5587
6280
|
npx girl-agent --reset --profile=<slug>
|
|
5588
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
|
|
5589
6282
|
|
|
5590
|
-
|
|
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
|
+
|
|
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):
|
|
5591
6292
|
--profile=<slug> slug \u043F\u0440\u043E\u0444\u0438\u043B\u044F
|
|
5592
6293
|
--mode=bot|userbot
|
|
5593
6294
|
--token=<bot_token> \u0434\u043B\u044F bot
|
|
@@ -5596,7 +6297,7 @@ required flags \u0434\u043B\u044F headless setup (--name --age --stage --api-pre
|
|
|
5596
6297
|
--base-url=<url> \u0434\u043B\u044F custom
|
|
5597
6298
|
--proto=openai|anthropic \u0434\u043B\u044F custom
|
|
5598
6299
|
--model=<model>
|
|
5599
|
-
--api-key=<key>
|
|
6300
|
+
--api-key=<key> \u043D\u0435 \u043D\u0443\u0436\u0435\u043D \u0434\u043B\u044F \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0445 LM Studio/Ollama
|
|
5600
6301
|
--name=<\u0438\u043C\u044F> \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u043E\u0435 \u0438\u043C\u044F; \u0435\u0441\u043B\u0438 \u043F\u0440\u043E\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u2014 \u0441\u043B\u0443\u0447\u0430\u0439\u043D\u043E\u0435 \u0438\u0437 \u043F\u0443\u043B\u0430 \u043F\u043E nationality (\u0442\u0443\u0440\u043D\u0438\u0440 \u0432\u044B\u0431\u043E\u0440\u0430 \u0438\u043C\u0451\u043D \u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0422\u041E\u041B\u042C\u041A\u041E \u0432 TUI \u0432\u0438\u0437\u0430\u0440\u0434\u0435)
|
|
5601
6302
|
--age=<n>
|
|
5602
6303
|
--persona-notes=<text> \u0434\u043E\u043F. \u043F\u043E\u0436\u0435\u043B\u0430\u043D\u0438\u044F \u043A persona/speech/communication \u043F\u0435\u0440\u0435\u0434 \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u0435\u0439
|
|
@@ -5642,15 +6343,71 @@ async function main() {
|
|
|
5642
6343
|
"message-style",
|
|
5643
6344
|
"initiative",
|
|
5644
6345
|
"life-sharing",
|
|
5645
|
-
"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"
|
|
5646
6361
|
],
|
|
5647
|
-
boolean: ["help", "list", "reset", "new"],
|
|
5648
6362
|
alias: { h: "help" }
|
|
5649
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
|
+
}
|
|
5650
6370
|
if (argv.help) {
|
|
5651
6371
|
process.stdout.write(HELP);
|
|
5652
6372
|
return;
|
|
5653
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);
|
|
5654
6411
|
if (argv.age != null) {
|
|
5655
6412
|
const a = Number(argv.age);
|
|
5656
6413
|
if (!Number.isFinite(a) || a < 13 || a > 99) {
|
|
@@ -5680,10 +6437,12 @@ ${profiles.join("\n")}
|
|
|
5680
6437
|
cfg.stage = "tg-given-cold";
|
|
5681
6438
|
await writeConfig(cfg);
|
|
5682
6439
|
}
|
|
5683
|
-
await runRuntime(cfg);
|
|
6440
|
+
await runRuntime(cfg, { jsonEvents });
|
|
5684
6441
|
return;
|
|
5685
6442
|
}
|
|
5686
|
-
const
|
|
6443
|
+
const presetForFlags = argv["api-preset"] ? findPreset(String(argv["api-preset"])) : void 0;
|
|
6444
|
+
const apiKeyRequiredForFlags = presetForFlags?.apiKeyRequired !== false;
|
|
6445
|
+
const haveEnoughForFlags = argv.mode && argv["api-preset"] && (!apiKeyRequiredForFlags || argv["api-key"]) && argv.age && argv.stage;
|
|
5687
6446
|
if (haveEnoughForFlags) {
|
|
5688
6447
|
const cfg = await buildConfigFromFlags(argv);
|
|
5689
6448
|
await writeConfig(cfg);
|
|
@@ -5694,7 +6453,7 @@ ${profiles.join("\n")}
|
|
|
5694
6453
|
const generated = await generatePersonaPack(llm, cfg.slug, cfg.name, cfg.age, cfg.nationality, personaNotesForGeneration2(cfg));
|
|
5695
6454
|
cfg.busySchedule = generated.busySchedule;
|
|
5696
6455
|
await writeConfig(cfg);
|
|
5697
|
-
await runRuntime(cfg);
|
|
6456
|
+
await runRuntime(cfg, { jsonEvents });
|
|
5698
6457
|
return;
|
|
5699
6458
|
}
|
|
5700
6459
|
if (!argv.new && !argv.profile && !haveEnoughForFlags) {
|
|
@@ -5704,7 +6463,7 @@ ${profiles.join("\n")}
|
|
|
5704
6463
|
if (cfg) {
|
|
5705
6464
|
process.stdout.write(`\u0437\u0430\u0433\u0440\u0443\u0436\u0430\u044E \u043F\u0440\u043E\u0444\u0438\u043B\u044C: ${cfg.name}
|
|
5706
6465
|
`);
|
|
5707
|
-
await runRuntime(cfg);
|
|
6466
|
+
await runRuntime(cfg, { jsonEvents });
|
|
5708
6467
|
return;
|
|
5709
6468
|
}
|
|
5710
6469
|
} else if (profiles.length > 1) {
|
|
@@ -5720,7 +6479,7 @@ ${profiles.join("\n")}
|
|
|
5720
6479
|
const inst = render(
|
|
5721
6480
|
/* @__PURE__ */ React3.createElement(Wizard, { onDone: async (cfg) => {
|
|
5722
6481
|
inst.unmount();
|
|
5723
|
-
await runRuntime(cfg);
|
|
6482
|
+
await runRuntime(cfg, { jsonEvents });
|
|
5724
6483
|
resolve();
|
|
5725
6484
|
} }),
|
|
5726
6485
|
{ exitOnCtrlC: true }
|
|
@@ -5755,7 +6514,7 @@ async function buildConfigFromFlags(argv) {
|
|
|
5755
6514
|
tz,
|
|
5756
6515
|
mode,
|
|
5757
6516
|
stage: argv.stage,
|
|
5758
|
-
llm: { presetId, proto, baseURL, apiKey: String(argv["api-key"]), model },
|
|
6517
|
+
llm: { presetId, proto, baseURL, apiKey: String(argv["api-key"] ?? preset?.defaultApiKey ?? ""), model },
|
|
5759
6518
|
telegram: mode === "bot" ? { botToken: String(argv.token ?? "") } : {
|
|
5760
6519
|
apiId: Number(argv["api-id"] ?? 0),
|
|
5761
6520
|
apiHash: String(argv["api-hash"] ?? ""),
|
|
@@ -5793,9 +6552,13 @@ function personaNotesForGeneration2(cfg) {
|
|
|
5793
6552
|
].filter(Boolean);
|
|
5794
6553
|
return parts.join("\n\n");
|
|
5795
6554
|
}
|
|
5796
|
-
async function runRuntime(cfg) {
|
|
6555
|
+
async function runRuntime(cfg, opts = {}) {
|
|
5797
6556
|
const rt = new Runtime(cfg);
|
|
5798
6557
|
await rt.start();
|
|
6558
|
+
if (opts.jsonEvents) {
|
|
6559
|
+
await runHeadlessJsonEvents(rt);
|
|
6560
|
+
return;
|
|
6561
|
+
}
|
|
5799
6562
|
const inst = render(/* @__PURE__ */ React3.createElement(Dashboard, { runtime: rt }), { exitOnCtrlC: true });
|
|
5800
6563
|
process.on("SIGINT", async () => {
|
|
5801
6564
|
await rt.stop();
|
|
@@ -5805,7 +6568,17 @@ async function runRuntime(cfg) {
|
|
|
5805
6568
|
await inst.waitUntilExit();
|
|
5806
6569
|
await rt.stop();
|
|
5807
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
|
+
});
|
|
5808
6581
|
main().catch((e) => {
|
|
5809
|
-
process.stderr.write("fatal: " + (e?.stack ?? e) + "\n");
|
|
6582
|
+
process.stderr.write("[girl-agent] fatal: " + (e?.stack ?? e) + "\n");
|
|
5810
6583
|
process.exit(1);
|
|
5811
6584
|
});
|