@thesashadev/girl-agent 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.8 — OpenAI-compatible API compatibility
4
+
5
+ Дата: 2026-05-06
6
+
7
+ - JSON-ответы теперь сначала запрашиваются через `json_schema`, с fallback на `json_object` и `text` для разных OpenAI-compatible API. (#33)
8
+ - LM Studio и Ollama больше не требуют реальный API ключ в wizard/headless setup.
9
+ - Добавлена совместимость с OpenAI-compatible прокси, которые возвращают SSE/event-stream даже на обычный chat completions запрос.
10
+
3
11
  ## 0.1.7 — MarkdownV2 escaping fix
4
12
 
5
13
  Дата: 2026-05-06
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- ![girl-agent banner](https://girl-agent.com/og-image.png)
1
+ ![girl-agent banner](https://girl-agent.com/og-image.png)
2
2
 
3
3
  [website]: https://girl-agent.com
4
4
  [docs]: https://docs.girl-agent.com
@@ -49,6 +49,22 @@ Wizard задаст пару вопросов — имя, возраст, Telegr
49
49
  npx @thesashadev/girl-agent --profile=arina
50
50
  ```
51
51
 
52
+ **Через Docker (рекомендуется для сервера):**
53
+
54
+ Первый запуск с интерактивом (для настройки через визард):
55
+ ```bash
56
+ docker-compose run --rm -it girl-agent
57
+ ```
58
+ *(пройдите все шаги и после появления дашборда нажмите `Ctrl+C`)*
59
+
60
+ Последующие (запуск в фоне):
61
+ ```bash
62
+ docker-compose up -d
63
+ ```
64
+ *(если профилей несколько, запустите конкретный так: `docker-compose run -d girl-agent node dist/cli.js --profile=arina`)*
65
+
66
+ *(посмотреть логи: `docker-compose logs -f`)*
67
+
52
68
  **Из исходников:**
53
69
 
54
70
  ```powershell
package/dist/cli.js CHANGED
@@ -377,8 +377,10 @@ var LLM_PRESETS = [
377
377
  proto: "openai",
378
378
  baseURL: "http://localhost:1234/v1",
379
379
  defaultModel: "",
380
+ defaultApiKey: "lm-studio",
381
+ apiKeyRequired: false,
380
382
  custom: true,
381
- hint: "\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E, OpenAI-compatible endpoint"
383
+ hint: "\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E, OpenAI-compatible endpoint; \u043A\u043B\u044E\u0447 \u043D\u0435 \u043D\u0443\u0436\u0435\u043D"
382
384
  },
383
385
  {
384
386
  id: "ollama",
@@ -386,8 +388,10 @@ var LLM_PRESETS = [
386
388
  proto: "openai",
387
389
  baseURL: "http://localhost:11434/v1",
388
390
  defaultModel: "llama3.1",
391
+ defaultApiKey: "ollama",
392
+ apiKeyRequired: false,
389
393
  custom: true,
390
- hint: "\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E \u0447\u0435\u0440\u0435\u0437 /v1"
394
+ 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
395
  },
392
396
  {
393
397
  id: "anthropic",
@@ -1025,20 +1029,28 @@ var OpenAILike = class {
1025
1029
  constructor(cfg) {
1026
1030
  this.cfg = cfg;
1027
1031
  this.client = new OpenAI({
1028
- apiKey: cfg.apiKey,
1032
+ apiKey: openAIApiKey(cfg),
1029
1033
  baseURL: normalizeBaseURL(cfg.baseURL),
1030
1034
  timeout: LLM_TIMEOUT_MS,
1031
1035
  maxRetries: LLM_MAX_RETRIES
1032
1036
  });
1037
+ this.fetchClient = new OpenAI({
1038
+ apiKey: openAIApiKey(cfg),
1039
+ baseURL: normalizeBaseURL(cfg.baseURL),
1040
+ timeout: LLM_TIMEOUT_MS,
1041
+ maxRetries: LLM_MAX_RETRIES,
1042
+ fetch: compatibleFetch
1043
+ });
1033
1044
  }
1034
1045
  cfg;
1035
1046
  client;
1047
+ fetchClient;
1036
1048
  async chat(messages, opts = {}) {
1037
1049
  const params = {
1038
1050
  model: this.cfg.model,
1039
1051
  messages: openAIMessages(messages),
1040
1052
  temperature: opts.temperature ?? 0.85,
1041
- response_format: opts.json ? { type: "json_object" } : void 0
1053
+ response_format: openAIResponseFormat(opts)
1042
1054
  };
1043
1055
  if (usesMaxCompletionTokens(this.cfg.model)) {
1044
1056
  params.max_completion_tokens = opts.maxTokens ?? 600;
@@ -1049,19 +1061,86 @@ var OpenAILike = class {
1049
1061
  return res.choices[0]?.message?.content?.trim() ?? "";
1050
1062
  }
1051
1063
  async createWithCompatibilityFallback(params) {
1052
- try {
1053
- return await this.client.chat.completions.create(params);
1054
- } catch (error) {
1055
- const fallback = completionTokenFallback(params, error);
1056
- if (!fallback) throw enrichOpenAIError(error, this.cfg.baseURL);
1064
+ const attempted = /* @__PURE__ */ new Set();
1065
+ let current = params;
1066
+ let lastError;
1067
+ while (current) {
1068
+ const key = completionParamsKey(current);
1069
+ if (attempted.has(key)) break;
1070
+ attempted.add(key);
1057
1071
  try {
1058
- return await this.client.chat.completions.create(fallback);
1059
- } catch (fallbackError) {
1060
- throw enrichOpenAIError(fallbackError, this.cfg.baseURL);
1072
+ return await this.client.chat.completions.create(current);
1073
+ } catch (error) {
1074
+ lastError = error;
1075
+ const next = completionFallback(current, error);
1076
+ if (!next) break;
1077
+ current = next;
1061
1078
  }
1062
1079
  }
1080
+ if (this.cfg.baseURL) {
1081
+ try {
1082
+ return await this.fetchClient.chat.completions.create({ ...params, stream: false });
1083
+ } catch (fetchError) {
1084
+ lastError = fetchError;
1085
+ }
1086
+ }
1087
+ throw enrichOpenAIError(lastError, this.cfg.baseURL);
1063
1088
  }
1064
1089
  };
1090
+ async function compatibleFetch(url, init) {
1091
+ const res = await fetch(url, init);
1092
+ const contentType = res.headers.get("content-type") ?? "";
1093
+ if (!res.ok) return res;
1094
+ const text = await res.clone().text();
1095
+ if (contentType.includes("text/event-stream") || text.trimStart().startsWith("data:")) {
1096
+ return completionStreamToJsonResponse(res, text);
1097
+ }
1098
+ return res;
1099
+ }
1100
+ function completionStreamToJsonResponse(res, text) {
1101
+ const completion = parseOpenAIEventStream(text);
1102
+ return new Response(JSON.stringify(completion), {
1103
+ status: res.status,
1104
+ statusText: res.statusText,
1105
+ headers: { "content-type": "application/json" }
1106
+ });
1107
+ }
1108
+ function parseOpenAIEventStream(raw) {
1109
+ let id = "chatcmpl-stream";
1110
+ let model = "";
1111
+ let created = Math.floor(Date.now() / 1e3);
1112
+ const content = [];
1113
+ let finishReason = "stop";
1114
+ for (const line of raw.split(/\r?\n/)) {
1115
+ const trimmed = line.trim();
1116
+ if (!trimmed.startsWith("data:")) continue;
1117
+ const data = trimmed.slice(5).trim();
1118
+ if (!data || data === "[DONE]") continue;
1119
+ try {
1120
+ const chunk = JSON.parse(data);
1121
+ id = chunk.id || id;
1122
+ model = chunk.model || model;
1123
+ created = chunk.created || created;
1124
+ const choice = chunk.choices[0];
1125
+ finishReason = choice?.finish_reason ?? finishReason;
1126
+ const delta = choice?.delta?.content ?? choice?.message?.content;
1127
+ if (typeof delta === "string") content.push(delta);
1128
+ } catch {
1129
+ }
1130
+ }
1131
+ return {
1132
+ id,
1133
+ object: "chat.completion",
1134
+ created,
1135
+ model,
1136
+ choices: [{
1137
+ index: 0,
1138
+ message: { role: "assistant", content: content.join(""), refusal: null },
1139
+ finish_reason: finishReason,
1140
+ logprobs: null
1141
+ }]
1142
+ };
1143
+ }
1065
1144
  var AnthropicLike = class {
1066
1145
  constructor(cfg) {
1067
1146
  this.cfg = cfg;
@@ -1155,8 +1234,36 @@ function normalizeBaseURL(value) {
1155
1234
  function usesMaxCompletionTokens(model) {
1156
1235
  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
1236
  }
1237
+ function openAIApiKey(cfg) {
1238
+ return cfg.apiKey.trim() || (cfg.presetId === "ollama" ? "ollama" : cfg.presetId === "lmstudio" ? "lm-studio" : "");
1239
+ }
1240
+ function openAIResponseFormat(opts) {
1241
+ if (!opts.json) return void 0;
1242
+ if (opts.jsonSchema) return { type: "json_schema", json_schema: opts.jsonSchema };
1243
+ return {
1244
+ type: "json_schema",
1245
+ json_schema: {
1246
+ name: "json_response",
1247
+ strict: false,
1248
+ schema: { type: "object", additionalProperties: true }
1249
+ }
1250
+ };
1251
+ }
1252
+ function completionFallback(params, error) {
1253
+ return responseFormatFallback(params, error) ?? completionTokenFallback(params, error);
1254
+ }
1255
+ function responseFormatFallback(params, error) {
1256
+ const message = openAIErrorText(error);
1257
+ if (!params.response_format || !message.includes("response_format")) return null;
1258
+ if (params.response_format.type === "json_schema" && message.includes("json_object")) {
1259
+ return { ...params, response_format: { type: "text" } };
1260
+ }
1261
+ if (params.response_format.type === "json_schema") return { ...params, response_format: { type: "json_object" } };
1262
+ if (params.response_format.type === "json_object") return { ...params, response_format: { type: "text" } };
1263
+ return null;
1264
+ }
1158
1265
  function completionTokenFallback(params, error) {
1159
- const message = errorMessage(error).toLowerCase();
1266
+ const message = openAIErrorText(error);
1160
1267
  if (params.max_tokens != null && message.includes("max_tokens") && message.includes("max_completion_tokens")) {
1161
1268
  const { max_tokens, ...rest } = params;
1162
1269
  return { ...rest, max_completion_tokens: max_tokens };
@@ -1167,6 +1274,16 @@ function completionTokenFallback(params, error) {
1167
1274
  }
1168
1275
  return null;
1169
1276
  }
1277
+ function completionParamsKey(params) {
1278
+ const tokenKey = params.max_completion_tokens != null ? "max_completion_tokens" : "max_tokens";
1279
+ return `${params.response_format?.type ?? "default"}:${tokenKey}`;
1280
+ }
1281
+ function openAIErrorText(error) {
1282
+ if (error instanceof OpenAI.APIError) {
1283
+ return `${error.status ?? ""} ${error.code ?? ""} ${error.type ?? ""} ${error.message}`.toLowerCase();
1284
+ }
1285
+ return errorMessage(error).toLowerCase();
1286
+ }
1170
1287
  function enrichOpenAIError(error, baseURL) {
1171
1288
  if (error instanceof OpenAI.APIConnectionError) {
1172
1289
  return new Error(connectionErrorMessage("OpenAI-compatible", baseURL, error));
@@ -1202,6 +1319,40 @@ function makeLLM(cfg) {
1202
1319
  init_esm_shims();
1203
1320
  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
1321
  var WEEKDAYS = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"];
1322
+ var BUSY_SCHEDULE_SCHEMA = {
1323
+ name: "busy_schedule",
1324
+ strict: false,
1325
+ schema: {
1326
+ type: "object",
1327
+ properties: {
1328
+ busySchedule: {
1329
+ type: "array",
1330
+ items: {
1331
+ type: "object",
1332
+ properties: {
1333
+ label: { type: "string" },
1334
+ days: {
1335
+ type: "array",
1336
+ items: { type: "string", enum: WEEKDAYS }
1337
+ },
1338
+ from: { type: "string" },
1339
+ to: { type: "string" },
1340
+ checkAfterMin: {
1341
+ type: "array",
1342
+ items: { type: "number" },
1343
+ minItems: 2,
1344
+ maxItems: 2
1345
+ }
1346
+ },
1347
+ required: ["label", "from", "to"],
1348
+ additionalProperties: false
1349
+ }
1350
+ }
1351
+ },
1352
+ required: ["busySchedule"],
1353
+ additionalProperties: false
1354
+ }
1355
+ };
1205
1356
  async function generatePersonaPack(llm, slug, name, age, nationality = "RU", personaNotes = "", onProgress) {
1206
1357
  const country = nationality === "UA" ? "\u0423\u043A\u0440\u0430\u0438\u043D\u0430" : "\u0420\u043E\u0441\u0441\u0438\u044F / \u0421\u041D\u0413";
1207
1358
  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 +1463,7 @@ ${personaNotes.trim()}
1312
1463
  onProgress?.(65, "\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u043C communication.md\u2026");
1313
1464
  const boundaries = await llm.chat([{ role: "system", content: sys }, { role: "user", content: boundariesPrompt }], { temperature: 0.9, maxTokens: 3500 });
1314
1465
  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 });
1466
+ 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
1467
  const busySchedule = parseBusySchedule(routineRaw, name, age);
1317
1468
  await writeMd(slug, "persona.md", persona);
1318
1469
  await writeMd(slug, "speech.md", speech);
@@ -1866,6 +2017,7 @@ function Wizard({ initial, onDone }) {
1866
2017
  setLlmProto(preset.proto);
1867
2018
  setLlmBaseURL(preset.baseURL ?? "");
1868
2019
  setLlmModel(preset.defaultModel);
2020
+ setLlmKey(preset.defaultApiKey ?? "");
1869
2021
  if (preset.custom) setStep("api-base");
1870
2022
  else setStep("api-model");
1871
2023
  }
@@ -1893,7 +2045,9 @@ function Wizard({ initial, onDone }) {
1893
2045
  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
2046
  }
1895
2047
  if (step === "api-key") {
1896
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Header, { sub: "API \u043A\u043B\u044E\u0447" }), /* @__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 && setStep("nationality") })));
2048
+ const preset = findPreset(llmPresetId);
2049
+ const apiKeyRequired = preset?.apiKeyRequired !== false;
2050
+ 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
2051
  }
1898
2052
  if (step === "nationality") {
1899
2053
  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(
@@ -5587,7 +5741,7 @@ usage:
5587
5741
  npx girl-agent --reset --profile=<slug>
5588
5742
  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
5743
 
5590
- required flags \u0434\u043B\u044F headless setup (--name --age --stage --api-preset --api-key --mode):
5744
+ 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
5745
  --profile=<slug> slug \u043F\u0440\u043E\u0444\u0438\u043B\u044F
5592
5746
  --mode=bot|userbot
5593
5747
  --token=<bot_token> \u0434\u043B\u044F bot
@@ -5596,7 +5750,7 @@ required flags \u0434\u043B\u044F headless setup (--name --age --stage --api-pre
5596
5750
  --base-url=<url> \u0434\u043B\u044F custom
5597
5751
  --proto=openai|anthropic \u0434\u043B\u044F custom
5598
5752
  --model=<model>
5599
- --api-key=<key>
5753
+ --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
5754
  --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
5755
  --age=<n>
5602
5756
  --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
@@ -5683,7 +5837,9 @@ ${profiles.join("\n")}
5683
5837
  await runRuntime(cfg);
5684
5838
  return;
5685
5839
  }
5686
- const haveEnoughForFlags = argv.mode && argv["api-preset"] && argv["api-key"] && argv.age && argv.stage;
5840
+ const presetForFlags = argv["api-preset"] ? findPreset(String(argv["api-preset"])) : void 0;
5841
+ const apiKeyRequiredForFlags = presetForFlags?.apiKeyRequired !== false;
5842
+ const haveEnoughForFlags = argv.mode && argv["api-preset"] && (!apiKeyRequiredForFlags || argv["api-key"]) && argv.age && argv.stage;
5687
5843
  if (haveEnoughForFlags) {
5688
5844
  const cfg = await buildConfigFromFlags(argv);
5689
5845
  await writeConfig(cfg);
@@ -5755,7 +5911,7 @@ async function buildConfigFromFlags(argv) {
5755
5911
  tz,
5756
5912
  mode,
5757
5913
  stage: argv.stage,
5758
- llm: { presetId, proto, baseURL, apiKey: String(argv["api-key"]), model },
5914
+ llm: { presetId, proto, baseURL, apiKey: String(argv["api-key"] ?? preset?.defaultApiKey ?? ""), model },
5759
5915
  telegram: mode === "bot" ? { botToken: String(argv.token ?? "") } : {
5760
5916
  apiId: Number(argv["api-id"] ?? 0),
5761
5917
  apiHash: String(argv["api-hash"] ?? ""),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thesashadev/girl-agent",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Telegram AI persona engine with memory, schedule, relationship state and MTProto userbot mode.",
5
5
  "type": "module",
6
6
  "bin": {