@iola_adm/iola-cli 0.1.6 → 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/README.md +42 -5
- package/package.json +1 -1
- package/src/cli.js +317 -9
package/README.md
CHANGED
|
@@ -79,6 +79,9 @@ iola agent
|
|
|
79
79
|
iola ai doctor
|
|
80
80
|
iola ai setup ollama
|
|
81
81
|
iola ai ask "Какие школы есть на улице Петрова?"
|
|
82
|
+
iola ai context "школа 29"
|
|
83
|
+
iola ai key set openai
|
|
84
|
+
iola ai key status
|
|
82
85
|
iola ai setup openai --model gpt-4.1-mini
|
|
83
86
|
iola ai setup openrouter --model openai/gpt-4.1-mini
|
|
84
87
|
iola health
|
|
@@ -113,6 +116,11 @@ iola agent
|
|
|
113
116
|
/search лицей --limit 3
|
|
114
117
|
/mcp-info
|
|
115
118
|
/ai doctor
|
|
119
|
+
/context школа 29
|
|
120
|
+
/use ollama
|
|
121
|
+
/use openai
|
|
122
|
+
/key status
|
|
123
|
+
/key set openai
|
|
116
124
|
/model
|
|
117
125
|
/provider
|
|
118
126
|
/config
|
|
@@ -135,7 +143,7 @@ iola ai ask "Какие школы есть на улице Петрова?"
|
|
|
135
143
|
OpenAI:
|
|
136
144
|
|
|
137
145
|
```bash
|
|
138
|
-
set
|
|
146
|
+
iola ai key set openai
|
|
139
147
|
iola ai setup openai --model gpt-4.1-mini
|
|
140
148
|
iola ai ask "Найди школу 29"
|
|
141
149
|
```
|
|
@@ -143,19 +151,48 @@ iola ai ask "Найди школу 29"
|
|
|
143
151
|
OpenRouter:
|
|
144
152
|
|
|
145
153
|
```bash
|
|
146
|
-
set
|
|
154
|
+
iola ai key set openrouter
|
|
147
155
|
iola ai setup openrouter --model openai/gpt-4.1-mini
|
|
148
156
|
iola ai ask "Покажи контакты лицея"
|
|
149
157
|
```
|
|
150
158
|
|
|
159
|
+
Проверить, какие данные попадут в AI-контекст:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
iola ai context "школа 29"
|
|
163
|
+
iola ai context "1215067180" --json
|
|
164
|
+
iola ai context "улица Петрова"
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Поиск контекста учитывает номера учреждений, ИНН и улицы.
|
|
168
|
+
|
|
169
|
+
Ключи OpenAI/OpenRouter сохраняются локально на компьютере пользователя:
|
|
170
|
+
|
|
171
|
+
```text
|
|
172
|
+
%USERPROFILE%\.iola\secrets.json
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Управление ключами:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
iola ai key set openai
|
|
179
|
+
iola ai key set openrouter
|
|
180
|
+
iola ai key status
|
|
181
|
+
iola ai key delete openai
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Если одновременно задана переменная окружения (`OPENAI_API_KEY` или
|
|
185
|
+
`OPENROUTER_API_KEY`) и сохранен локальный ключ, CLI использует переменную
|
|
186
|
+
окружения как более приоритетную.
|
|
187
|
+
|
|
151
188
|
AI-ответ строится по контексту из публичного API. Если данных в контексте
|
|
152
189
|
недостаточно, ассистент должен сообщить об этом, а не выдумывать сведения.
|
|
153
190
|
|
|
154
191
|
## Назначение
|
|
155
192
|
|
|
156
|
-
|
|
157
|
-
подключения MCP/skill
|
|
158
|
-
|
|
193
|
+
CLI дает прямой терминальный доступ к открытым данным городского округа,
|
|
194
|
+
командам подключения MCP/skill, AI-запросам через Ollama/OpenAI/OpenRouter и
|
|
195
|
+
интерактивному агентному режиму.
|
|
159
196
|
|
|
160
197
|
## Переменные окружения
|
|
161
198
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -9,6 +9,7 @@ const API_BASE_URL = process.env.IOLA_API_BASE_URL || "https://apiiola.yasg.ru/a
|
|
|
9
9
|
const MCP_BASE_URL = process.env.IOLA_MCP_BASE_URL || "https://apiiola.yasg.ru";
|
|
10
10
|
const CONFIG_DIR = path.join(os.homedir(), ".iola");
|
|
11
11
|
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
12
|
+
const SECRETS_FILE = path.join(CONFIG_DIR, "secrets.json");
|
|
12
13
|
const DEFAULT_AI_CONFIG = {
|
|
13
14
|
ai: {
|
|
14
15
|
provider: "ollama",
|
|
@@ -64,6 +65,11 @@ Usage:
|
|
|
64
65
|
iola banner
|
|
65
66
|
iola agent
|
|
66
67
|
iola ai ask TEXT [--provider ollama|openai|openrouter] [--model MODEL]
|
|
68
|
+
iola ai context TEXT [--json]
|
|
69
|
+
iola ai key set openai
|
|
70
|
+
iola ai key set openrouter
|
|
71
|
+
iola ai key status
|
|
72
|
+
iola ai key delete openai|openrouter
|
|
67
73
|
iola ai doctor [--json]
|
|
68
74
|
iola ai setup
|
|
69
75
|
iola ai setup ollama [--yes] [--model MODEL]
|
|
@@ -158,6 +164,21 @@ async function handleAgentLine(line, state) {
|
|
|
158
164
|
return false;
|
|
159
165
|
}
|
|
160
166
|
|
|
167
|
+
if (command === "context") {
|
|
168
|
+
await aiContext(args);
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (command === "use") {
|
|
173
|
+
await useAiProvider(args);
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (command === "key") {
|
|
178
|
+
await handleAiKey(args);
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
|
|
161
182
|
if (command === "provider") {
|
|
162
183
|
await printAiConfigField("provider");
|
|
163
184
|
return false;
|
|
@@ -210,8 +231,13 @@ function printAgentHelp() {
|
|
|
210
231
|
/kindergartens get --inn 1215077421
|
|
211
232
|
/search лицей --limit 3
|
|
212
233
|
/mcp-info
|
|
234
|
+
/context школа 29
|
|
213
235
|
/ai doctor
|
|
214
236
|
/ai setup ollama
|
|
237
|
+
/use openai
|
|
238
|
+
/use ollama
|
|
239
|
+
/key status
|
|
240
|
+
/key set openai
|
|
215
241
|
/config
|
|
216
242
|
/provider
|
|
217
243
|
/model
|
|
@@ -285,6 +311,11 @@ async function handleAi(args) {
|
|
|
285
311
|
showBanner();
|
|
286
312
|
console.log(`AI-команды:
|
|
287
313
|
iola ai ask TEXT [--provider ollama|openai|openrouter] [--model MODEL]
|
|
314
|
+
iola ai context TEXT [--json]
|
|
315
|
+
iola ai key set openai
|
|
316
|
+
iola ai key set openrouter
|
|
317
|
+
iola ai key status
|
|
318
|
+
iola ai key delete openai|openrouter
|
|
288
319
|
iola ai doctor [--json]
|
|
289
320
|
iola ai setup
|
|
290
321
|
iola ai setup ollama [--yes] [--model MODEL]
|
|
@@ -300,6 +331,16 @@ async function handleAi(args) {
|
|
|
300
331
|
return;
|
|
301
332
|
}
|
|
302
333
|
|
|
334
|
+
if (subcommand === "context") {
|
|
335
|
+
await aiContext(rest);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (subcommand === "key") {
|
|
340
|
+
await handleAiKey(rest);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
303
344
|
if (subcommand === "doctor") {
|
|
304
345
|
await aiDoctor(rest);
|
|
305
346
|
return;
|
|
@@ -364,6 +405,133 @@ async function aiSetup(args) {
|
|
|
364
405
|
throw new Error(`Unknown AI provider: ${provider}`);
|
|
365
406
|
}
|
|
366
407
|
|
|
408
|
+
async function handleAiKey(args) {
|
|
409
|
+
const [action, provider] = args;
|
|
410
|
+
|
|
411
|
+
if (action === "set") {
|
|
412
|
+
await setAiKey(provider);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (action === "status") {
|
|
417
|
+
await printAiKeyStatus();
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (action === "delete") {
|
|
422
|
+
await deleteAiKey(provider);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
throw new Error(`Unknown key command. Use:
|
|
427
|
+
iola ai key set openai
|
|
428
|
+
iola ai key set openrouter
|
|
429
|
+
iola ai key status
|
|
430
|
+
iola ai key delete openai|openrouter`);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async function useAiProvider(args) {
|
|
434
|
+
const [provider] = args;
|
|
435
|
+
|
|
436
|
+
if (provider !== "ollama" && provider !== "openai" && provider !== "openrouter") {
|
|
437
|
+
throw new Error("Провайдер должен быть ollama, openai или openrouter.");
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const config = await loadConfig();
|
|
441
|
+
const defaultModel = {
|
|
442
|
+
ollama: config.ai.provider === "ollama" ? config.ai.model : "llama3.2:1b",
|
|
443
|
+
openai: config.ai.provider === "openai" ? config.ai.model : "gpt-4.1-mini",
|
|
444
|
+
openrouter: config.ai.provider === "openrouter" ? config.ai.model : "openai/gpt-4.1-mini",
|
|
445
|
+
}[provider];
|
|
446
|
+
|
|
447
|
+
await saveConfig({
|
|
448
|
+
ai: {
|
|
449
|
+
provider,
|
|
450
|
+
model: defaultModel,
|
|
451
|
+
baseUrl: provider === "ollama"
|
|
452
|
+
? "http://127.0.0.1:11434"
|
|
453
|
+
: provider === "openai"
|
|
454
|
+
? "https://api.openai.com/v1"
|
|
455
|
+
: "https://openrouter.ai/api/v1",
|
|
456
|
+
},
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
console.log(`AI-провайдер переключен: ${provider}, модель: ${defaultModel}`);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async function aiContext(args) {
|
|
463
|
+
const options = parseOptions(args);
|
|
464
|
+
const query = options._.join(" ").trim();
|
|
465
|
+
|
|
466
|
+
if (!query) {
|
|
467
|
+
throw new Error('Текст запроса обязателен. Пример: iola ai context "школа 29"');
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const context = await buildDataContext(query);
|
|
471
|
+
|
|
472
|
+
if (options.json) {
|
|
473
|
+
printJson(context);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
printContext(context);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async function setAiKey(provider) {
|
|
481
|
+
assertKeyProvider(provider);
|
|
482
|
+
|
|
483
|
+
if (!process.stdin.isTTY) {
|
|
484
|
+
throw new Error("Для сохранения ключа запустите команду в интерактивном терминале.");
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const envName = provider === "openai" ? "OPENAI_API_KEY" : "OPENROUTER_API_KEY";
|
|
488
|
+
const rl = readline.createInterface({ input, output });
|
|
489
|
+
|
|
490
|
+
try {
|
|
491
|
+
const key = (await rl.question(`Введите ${envName}: `)).trim();
|
|
492
|
+
|
|
493
|
+
if (!key) {
|
|
494
|
+
throw new Error("Ключ пустой, сохранение отменено.");
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const secrets = await loadSecrets();
|
|
498
|
+
secrets[provider] = { apiKey: key };
|
|
499
|
+
await saveSecrets(secrets);
|
|
500
|
+
console.log(`Ключ ${provider} сохранен локально: ${SECRETS_FILE}`);
|
|
501
|
+
} finally {
|
|
502
|
+
rl.close();
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
async function printAiKeyStatus() {
|
|
507
|
+
const secrets = await loadSecrets();
|
|
508
|
+
const rows = ["openai", "openrouter"].map((provider) => ({
|
|
509
|
+
provider,
|
|
510
|
+
env: provider === "openai" ? (process.env.OPENAI_API_KEY ? "yes" : "no") : (process.env.OPENROUTER_API_KEY ? "yes" : "no"),
|
|
511
|
+
local: secrets[provider]?.apiKey ? "yes" : "no",
|
|
512
|
+
}));
|
|
513
|
+
|
|
514
|
+
printTable(rows, [
|
|
515
|
+
["provider", "Провайдер"],
|
|
516
|
+
["env", "Env"],
|
|
517
|
+
["local", "Локально"],
|
|
518
|
+
]);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async function deleteAiKey(provider) {
|
|
522
|
+
assertKeyProvider(provider);
|
|
523
|
+
const secrets = await loadSecrets();
|
|
524
|
+
delete secrets[provider];
|
|
525
|
+
await saveSecrets(secrets);
|
|
526
|
+
console.log(`Локальный ключ ${provider} удален.`);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function assertKeyProvider(provider) {
|
|
530
|
+
if (provider !== "openai" && provider !== "openrouter") {
|
|
531
|
+
throw new Error("Провайдер должен быть openai или openrouter.");
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
367
535
|
async function chooseAiProvider() {
|
|
368
536
|
console.log("Выберите режим AI:");
|
|
369
537
|
console.log("1. Локальная модель через Ollama");
|
|
@@ -456,11 +624,23 @@ async function buildDataContext(question) {
|
|
|
456
624
|
fetchJson(`${API_BASE_URL}/kindergartens?limit=100&offset=0`),
|
|
457
625
|
]);
|
|
458
626
|
const queryTerms = extractSearchTerms(question);
|
|
459
|
-
const
|
|
460
|
-
const
|
|
627
|
+
const patterns = extractStructuredPatterns(question);
|
|
628
|
+
const includeSchools = patterns.targetLayers.length === 0 || patterns.targetLayers.includes("schools");
|
|
629
|
+
const includeKindergartens = patterns.targetLayers.length === 0 || patterns.targetLayers.includes("kindergartens");
|
|
630
|
+
const schoolItems = includeSchools
|
|
631
|
+
? findRelevantItems(normalizeItems(schools), queryTerms, patterns, "schools").slice(0, 8)
|
|
632
|
+
: [];
|
|
633
|
+
const kindergartenItems = includeKindergartens
|
|
634
|
+
? findRelevantItems(normalizeItems(kindergartens), queryTerms, patterns, "kindergartens").slice(0, 8)
|
|
635
|
+
: [];
|
|
461
636
|
|
|
462
637
|
return {
|
|
463
638
|
layers: layers.data_layers || [],
|
|
639
|
+
query: {
|
|
640
|
+
text: question,
|
|
641
|
+
terms: queryTerms,
|
|
642
|
+
patterns,
|
|
643
|
+
},
|
|
464
644
|
schools: schoolItems.map(selectPublicSummary),
|
|
465
645
|
kindergartens: kindergartenItems.map(selectPublicSummary),
|
|
466
646
|
};
|
|
@@ -478,20 +658,83 @@ function extractSearchTerms(question) {
|
|
|
478
658
|
return normalized.length > 0 ? normalized : [question];
|
|
479
659
|
}
|
|
480
660
|
|
|
481
|
-
function
|
|
661
|
+
function extractStructuredPatterns(question) {
|
|
662
|
+
const normalized = question.toLocaleLowerCase("ru-RU");
|
|
663
|
+
const numbers = [...new Set([...normalized.matchAll(/\b\d{1,3}\b/g)].map((match) => match[0]))];
|
|
664
|
+
const inns = [...new Set([...normalized.matchAll(/\b\d{10,12}\b/g)].map((match) => match[0]))];
|
|
665
|
+
const targetLayers = [];
|
|
666
|
+
if (/(^|[^а-яёa-z])(школа|школы|лицей|лицея|гимназия|гимназии)(?=$|[^а-яёa-z])/iu.test(normalized)) {
|
|
667
|
+
targetLayers.push("schools");
|
|
668
|
+
}
|
|
669
|
+
if (/(^|[^а-яёa-z])(сад|сады|детсад|детский|детские|доу|мбдоу)(?=$|[^а-яёa-z])/iu.test(normalized)) {
|
|
670
|
+
targetLayers.push("kindergartens");
|
|
671
|
+
}
|
|
672
|
+
const streetMatches = [
|
|
673
|
+
...normalized.matchAll(/(?:улица|ул\.?)\s+([а-яёa-z0-9 .-]+)/giu),
|
|
674
|
+
...normalized.matchAll(/([а-яёa-z0-9 .-]+)\s+(?:улица|ул\.?)/giu),
|
|
675
|
+
];
|
|
676
|
+
const streets = [...new Set(streetMatches.map((match) => cleanupPattern(match[1])).filter(Boolean))];
|
|
677
|
+
|
|
678
|
+
return { numbers, inns, streets, targetLayers: [...new Set(targetLayers)] };
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function cleanupPattern(value) {
|
|
682
|
+
return value
|
|
683
|
+
.replace(/\b(школа|школы|сад|детский|детские|сады|лицей|гимназия|контакты|телефон|адрес|найди|покажи)\b/giu, " ")
|
|
684
|
+
.replace(/\s+/g, " ")
|
|
685
|
+
.trim();
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function findRelevantItems(items, terms, patterns, layer) {
|
|
482
689
|
return items
|
|
483
690
|
.map((item) => ({
|
|
484
691
|
item,
|
|
485
|
-
score: scoreItem(item, terms),
|
|
692
|
+
score: scoreItem(item, terms, patterns, layer),
|
|
486
693
|
}))
|
|
487
694
|
.filter((entry) => entry.score > 0)
|
|
488
695
|
.sort((left, right) => right.score - left.score)
|
|
489
696
|
.map((entry) => entry.item);
|
|
490
697
|
}
|
|
491
698
|
|
|
492
|
-
function scoreItem(item, terms) {
|
|
493
|
-
const
|
|
494
|
-
|
|
699
|
+
function scoreItem(item, terms, patterns, layer) {
|
|
700
|
+
const summary = selectPublicSummary(item);
|
|
701
|
+
const text = JSON.stringify(summary).toLocaleLowerCase("ru-RU");
|
|
702
|
+
const name = String(summary.name || "").toLocaleLowerCase("ru-RU");
|
|
703
|
+
const address = String(summary.address || "").toLocaleLowerCase("ru-RU");
|
|
704
|
+
const generalTerms = terms.filter((term) => !/^\d+$/.test(term));
|
|
705
|
+
let score = generalTerms.reduce((value, term) => value + (text.includes(term.toLocaleLowerCase("ru-RU")) ? 1 : 0), 0);
|
|
706
|
+
|
|
707
|
+
for (const inn of patterns.inns) {
|
|
708
|
+
if (String(summary.inn) === inn) {
|
|
709
|
+
score += 20;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
for (const number of patterns.numbers) {
|
|
714
|
+
const numberPatterns = [
|
|
715
|
+
`№ ${number}`,
|
|
716
|
+
`№${number}`,
|
|
717
|
+
`школа ${number}`,
|
|
718
|
+
`сад ${number}`,
|
|
719
|
+
`лицей ${number}`,
|
|
720
|
+
`гимназия ${number}`,
|
|
721
|
+
];
|
|
722
|
+
|
|
723
|
+
if (numberPatterns.some((pattern) => name.includes(pattern))) {
|
|
724
|
+
score += 12;
|
|
725
|
+
if (patterns.targetLayers.length === 0 || patterns.targetLayers.includes(layer)) {
|
|
726
|
+
score += 5;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
for (const street of patterns.streets) {
|
|
732
|
+
if (street && address.includes(street)) {
|
|
733
|
+
score += 8;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
return score;
|
|
495
738
|
}
|
|
496
739
|
|
|
497
740
|
function buildAiMessages(question, dataContext, history) {
|
|
@@ -522,11 +765,11 @@ async function callAiProvider(config, messages) {
|
|
|
522
765
|
}
|
|
523
766
|
|
|
524
767
|
if (config.provider === "openai") {
|
|
525
|
-
return callOpenAiCompatible(config, messages,
|
|
768
|
+
return callOpenAiCompatible(config, messages, await getApiKey("openai"), "OpenAI");
|
|
526
769
|
}
|
|
527
770
|
|
|
528
771
|
if (config.provider === "openrouter") {
|
|
529
|
-
return callOpenAiCompatible(config, messages,
|
|
772
|
+
return callOpenAiCompatible(config, messages, await getApiKey("openrouter"), "OpenRouter");
|
|
530
773
|
}
|
|
531
774
|
|
|
532
775
|
throw new Error(`Неизвестный AI-провайдер: ${config.provider}`);
|
|
@@ -586,6 +829,19 @@ async function callOpenAiCompatible(config, messages, apiKey, providerName) {
|
|
|
586
829
|
return payload.choices?.[0]?.message?.content || "";
|
|
587
830
|
}
|
|
588
831
|
|
|
832
|
+
async function getApiKey(provider) {
|
|
833
|
+
if (provider === "openai" && process.env.OPENAI_API_KEY) {
|
|
834
|
+
return process.env.OPENAI_API_KEY;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (provider === "openrouter" && process.env.OPENROUTER_API_KEY) {
|
|
838
|
+
return process.env.OPENROUTER_API_KEY;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const secrets = await loadSecrets();
|
|
842
|
+
return secrets[provider]?.apiKey || "";
|
|
843
|
+
}
|
|
844
|
+
|
|
589
845
|
async function listLayers(args) {
|
|
590
846
|
const options = parseOptions(args);
|
|
591
847
|
const info = await fetchJson(`${MCP_BASE_URL}/mcp-version`);
|
|
@@ -972,6 +1228,19 @@ function mergeConfig(base, override) {
|
|
|
972
1228
|
};
|
|
973
1229
|
}
|
|
974
1230
|
|
|
1231
|
+
async function loadSecrets() {
|
|
1232
|
+
try {
|
|
1233
|
+
return JSON.parse(await readFile(SECRETS_FILE, "utf8"));
|
|
1234
|
+
} catch {
|
|
1235
|
+
return {};
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
async function saveSecrets(value) {
|
|
1240
|
+
await mkdir(CONFIG_DIR, { recursive: true });
|
|
1241
|
+
await writeFile(SECRETS_FILE, `${JSON.stringify(value, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
1242
|
+
}
|
|
1243
|
+
|
|
975
1244
|
async function printAiConfig() {
|
|
976
1245
|
const config = await loadConfig();
|
|
977
1246
|
printJson({
|
|
@@ -980,6 +1249,45 @@ async function printAiConfig() {
|
|
|
980
1249
|
});
|
|
981
1250
|
}
|
|
982
1251
|
|
|
1252
|
+
function printContext(context) {
|
|
1253
|
+
const layerNames = context.layers.map((layer) => layer.name || layer.id || String(layer));
|
|
1254
|
+
console.log(`Запрос: ${context.query.text}`);
|
|
1255
|
+
console.log(`Слова поиска: ${context.query.terms.length > 0 ? context.query.terms.join(", ") : "-"}`);
|
|
1256
|
+
console.log(`Номера: ${context.query.patterns.numbers.length > 0 ? context.query.patterns.numbers.join(", ") : "-"}`);
|
|
1257
|
+
console.log(`ИНН: ${context.query.patterns.inns.length > 0 ? context.query.patterns.inns.join(", ") : "-"}`);
|
|
1258
|
+
console.log(`Улицы: ${context.query.patterns.streets.length > 0 ? context.query.patterns.streets.join(", ") : "-"}`);
|
|
1259
|
+
console.log(`Целевые слои: ${context.query.patterns.targetLayers.length > 0 ? context.query.patterns.targetLayers.join(", ") : "все"}`);
|
|
1260
|
+
console.log("");
|
|
1261
|
+
console.log(`Слои данных: ${layerNames.length > 0 ? layerNames.join(", ") : "-"}`);
|
|
1262
|
+
console.log("");
|
|
1263
|
+
|
|
1264
|
+
if (context.schools.length > 0) {
|
|
1265
|
+
console.log("Школы в контексте:");
|
|
1266
|
+
printTable(context.schools, [
|
|
1267
|
+
["name", "Название"],
|
|
1268
|
+
["address", "Адрес"],
|
|
1269
|
+
["phone", "Телефон"],
|
|
1270
|
+
["inn", "ИНН"],
|
|
1271
|
+
]);
|
|
1272
|
+
} else {
|
|
1273
|
+
console.log("Школы в контексте: нет совпадений");
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
console.log("");
|
|
1277
|
+
|
|
1278
|
+
if (context.kindergartens.length > 0) {
|
|
1279
|
+
console.log("Детские сады в контексте:");
|
|
1280
|
+
printTable(context.kindergartens, [
|
|
1281
|
+
["name", "Название"],
|
|
1282
|
+
["address", "Адрес"],
|
|
1283
|
+
["phone", "Телефон"],
|
|
1284
|
+
["inn", "ИНН"],
|
|
1285
|
+
]);
|
|
1286
|
+
} else {
|
|
1287
|
+
console.log("Детские сады в контексте: нет совпадений");
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
|
|
983
1291
|
async function printAiConfigField(field) {
|
|
984
1292
|
const config = await loadConfig();
|
|
985
1293
|
console.log(config.ai[field] || "-");
|