@iola_adm/iola-cli 0.1.9 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/package.json +1 -1
- package/src/cli.js +278 -31
package/README.md
CHANGED
|
@@ -80,10 +80,16 @@ iola banner
|
|
|
80
80
|
iola agent
|
|
81
81
|
iola chat
|
|
82
82
|
iola init
|
|
83
|
+
iola doctor
|
|
84
|
+
iola config get
|
|
85
|
+
iola config set api.baseUrl https://apiiola.yasg.ru/api/v1
|
|
86
|
+
iola config reset
|
|
83
87
|
iola update
|
|
84
88
|
iola version --check
|
|
89
|
+
iola ask "Найди школу 29"
|
|
85
90
|
iola data schools --limit 10
|
|
86
91
|
iola data kindergartens --search "29"
|
|
92
|
+
iola data schools --where address=Петрова --columns name,address,phone
|
|
87
93
|
iola data schools --format csv
|
|
88
94
|
iola ai doctor
|
|
89
95
|
iola ai setup ollama
|
|
@@ -120,6 +126,8 @@ iola agent
|
|
|
120
126
|
```text
|
|
121
127
|
/help
|
|
122
128
|
/health
|
|
129
|
+
/doctor
|
|
130
|
+
/config get
|
|
123
131
|
/layers
|
|
124
132
|
/data schools --limit 10
|
|
125
133
|
/schools --limit 10
|
|
@@ -153,6 +161,7 @@ iola agent
|
|
|
153
161
|
```bash
|
|
154
162
|
iola ai setup ollama
|
|
155
163
|
iola ai ask "Какие школы есть на улице Петрова?"
|
|
164
|
+
iola ask "Какие школы есть на улице Петрова?"
|
|
156
165
|
```
|
|
157
166
|
|
|
158
167
|
OpenAI:
|
|
@@ -219,3 +228,12 @@ CLI дает прямой терминальный доступ к открыт
|
|
|
219
228
|
IOLA_API_BASE_URL=https://apiiola.yasg.ru/api/v1
|
|
220
229
|
IOLA_MCP_BASE_URL=https://apiiola.yasg.ru
|
|
221
230
|
```
|
|
231
|
+
|
|
232
|
+
Переменные окружения имеют приоритет над локальной конфигурацией. Локальные
|
|
233
|
+
endpoints можно настроить так:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
iola config set api.baseUrl https://apiiola.yasg.ru/api/v1
|
|
237
|
+
iola config set api.mcpBaseUrl https://apiiola.yasg.ru
|
|
238
|
+
iola config get
|
|
239
|
+
```
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -11,6 +11,10 @@ const CONFIG_DIR = path.join(os.homedir(), ".iola");
|
|
|
11
11
|
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
12
12
|
const SECRETS_FILE = path.join(CONFIG_DIR, "secrets.json");
|
|
13
13
|
const DEFAULT_AI_CONFIG = {
|
|
14
|
+
api: {
|
|
15
|
+
baseUrl: "https://apiiola.yasg.ru/api/v1",
|
|
16
|
+
mcpBaseUrl: "https://apiiola.yasg.ru",
|
|
17
|
+
},
|
|
14
18
|
ai: {
|
|
15
19
|
provider: "ollama",
|
|
16
20
|
model: "llama3.2:1b",
|
|
@@ -45,9 +49,12 @@ const COMMANDS = new Map([
|
|
|
45
49
|
["help", showHelp],
|
|
46
50
|
["version", showVersion],
|
|
47
51
|
["update", checkUpdate],
|
|
52
|
+
["doctor", doctor],
|
|
53
|
+
["config", handleConfig],
|
|
48
54
|
["banner", showBanner],
|
|
49
55
|
["agent", startAgent],
|
|
50
56
|
["chat", startAgent],
|
|
57
|
+
["ask", aiAsk],
|
|
51
58
|
["ai", handleAi],
|
|
52
59
|
["init", initCli],
|
|
53
60
|
["health", checkHealth],
|
|
@@ -80,8 +87,14 @@ Usage:
|
|
|
80
87
|
iola agent
|
|
81
88
|
iola chat
|
|
82
89
|
iola init
|
|
90
|
+
iola doctor
|
|
91
|
+
iola config get
|
|
92
|
+
iola config set api.baseUrl URL
|
|
93
|
+
iola config set api.mcpBaseUrl URL
|
|
94
|
+
iola config reset
|
|
83
95
|
iola update
|
|
84
|
-
iola
|
|
96
|
+
iola ask TEXT
|
|
97
|
+
iola data LAYER [--limit 10] [--search TEXT] [--where FIELD=VALUE] [--columns a,b,c] [--format table|json|csv]
|
|
85
98
|
iola ai ask TEXT [--provider ollama|openai|openrouter] [--model MODEL]
|
|
86
99
|
iola ai context TEXT [--json]
|
|
87
100
|
iola ai key set openai
|
|
@@ -93,9 +106,9 @@ Usage:
|
|
|
93
106
|
iola ai setup ollama [--yes] [--model MODEL]
|
|
94
107
|
iola health [--json]
|
|
95
108
|
iola layers [--json]
|
|
96
|
-
iola schools [--limit 10] [--search TEXT] [--format table|json|csv]
|
|
109
|
+
iola schools [--limit 10] [--search TEXT] [--where FIELD=VALUE] [--columns a,b,c] [--format table|json|csv]
|
|
97
110
|
iola schools get --inn INN [--json]
|
|
98
|
-
iola kindergartens [--limit 10] [--search TEXT] [--format table|json|csv]
|
|
111
|
+
iola kindergartens [--limit 10] [--search TEXT] [--where FIELD=VALUE] [--columns a,b,c] [--format table|json|csv]
|
|
99
112
|
iola kindergartens get --inn INN [--json]
|
|
100
113
|
iola search TEXT [--limit 5] [--format table|json|csv]
|
|
101
114
|
iola mcp-info [--json]
|
|
@@ -178,7 +191,17 @@ async function handleAgentLine(line, state) {
|
|
|
178
191
|
}
|
|
179
192
|
|
|
180
193
|
if (command === "config") {
|
|
181
|
-
await
|
|
194
|
+
await handleConfig(args.length > 0 ? args : ["get"]);
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (command === "doctor") {
|
|
199
|
+
await doctor(args);
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (command === "cfg" || command === "settings") {
|
|
204
|
+
await handleConfig(args);
|
|
182
205
|
return false;
|
|
183
206
|
}
|
|
184
207
|
|
|
@@ -229,6 +252,8 @@ async function handleAgentLine(line, state) {
|
|
|
229
252
|
|
|
230
253
|
const mapped = {
|
|
231
254
|
health: ["health", args],
|
|
255
|
+
doctor: ["doctor", args],
|
|
256
|
+
config: ["config", args],
|
|
232
257
|
layers: ["layers", args],
|
|
233
258
|
data: ["data", args],
|
|
234
259
|
schools: ["schools", args],
|
|
@@ -253,6 +278,9 @@ function printAgentHelp() {
|
|
|
253
278
|
console.log(`Slash-команды:
|
|
254
279
|
/help
|
|
255
280
|
/health
|
|
281
|
+
/doctor
|
|
282
|
+
/config get
|
|
283
|
+
/config set api.baseUrl URL
|
|
256
284
|
/layers
|
|
257
285
|
/data schools --limit 10
|
|
258
286
|
/schools --limit 10
|
|
@@ -355,7 +383,7 @@ async function checkUpdate() {
|
|
|
355
383
|
|
|
356
384
|
async function checkHealth(args) {
|
|
357
385
|
const options = parseOptions(args);
|
|
358
|
-
const health = await fetchJson(`${
|
|
386
|
+
const health = await fetchJson(`${await getMcpBaseUrl()}/mcp-health`);
|
|
359
387
|
|
|
360
388
|
if (options.json) {
|
|
361
389
|
printJson(health);
|
|
@@ -370,6 +398,92 @@ async function checkHealth(args) {
|
|
|
370
398
|
});
|
|
371
399
|
}
|
|
372
400
|
|
|
401
|
+
async function doctor(args = []) {
|
|
402
|
+
const options = parseOptions(args);
|
|
403
|
+
const packageJson = await import("../package.json", { with: { type: "json" } });
|
|
404
|
+
const config = await loadConfig();
|
|
405
|
+
const secrets = await loadSecrets();
|
|
406
|
+
const diagnostics = await getLocalDiagnostics();
|
|
407
|
+
const latest = await getLatestNpmVersion(packageJson.default.name);
|
|
408
|
+
const apiBaseUrl = await getApiBaseUrl();
|
|
409
|
+
const mcpBaseUrl = await getMcpBaseUrl();
|
|
410
|
+
const report = {
|
|
411
|
+
cli: {
|
|
412
|
+
version: packageJson.default.version,
|
|
413
|
+
npmLatest: latest || "-",
|
|
414
|
+
update: getUpdateStatus(packageJson.default.version, latest),
|
|
415
|
+
},
|
|
416
|
+
api: {
|
|
417
|
+
baseUrl: apiBaseUrl,
|
|
418
|
+
mcpBaseUrl,
|
|
419
|
+
health: await probeEndpoint(`${mcpBaseUrl}/mcp-health`),
|
|
420
|
+
},
|
|
421
|
+
ai: {
|
|
422
|
+
provider: config.ai.provider,
|
|
423
|
+
model: config.ai.model,
|
|
424
|
+
modelAvailable: await checkConfiguredModel(config),
|
|
425
|
+
openaiKey: process.env.OPENAI_API_KEY ? "env" : secrets.openai?.apiKey ? "local" : "missing",
|
|
426
|
+
openrouterKey: process.env.OPENROUTER_API_KEY ? "env" : secrets.openrouter?.apiKey ? "local" : "missing",
|
|
427
|
+
ollama: diagnostics.ollama.installed ? diagnostics.ollama.version : "not-installed",
|
|
428
|
+
},
|
|
429
|
+
system: diagnostics,
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
if (options.json) {
|
|
433
|
+
printJson(report);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
console.log("CLI");
|
|
438
|
+
printKeyValue(report.cli);
|
|
439
|
+
console.log("");
|
|
440
|
+
console.log("API/MCP");
|
|
441
|
+
printKeyValue(report.api);
|
|
442
|
+
console.log("");
|
|
443
|
+
console.log("AI");
|
|
444
|
+
printKeyValue(report.ai);
|
|
445
|
+
console.log("");
|
|
446
|
+
printDiagnostics(diagnostics, recommendOllamaModel(diagnostics));
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function getUpdateStatus(current, latest) {
|
|
450
|
+
if (!latest) {
|
|
451
|
+
return "unknown";
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const comparison = compareVersions(latest, current);
|
|
455
|
+
|
|
456
|
+
if (comparison > 0) {
|
|
457
|
+
return "available";
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (comparison < 0) {
|
|
461
|
+
return "local-newer";
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return "ok";
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
async function checkConfiguredModel(config) {
|
|
468
|
+
if (config.ai.provider !== "ollama") {
|
|
469
|
+
return "external-api";
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
try {
|
|
473
|
+
const response = await fetch(`${config.ai.baseUrl || "http://127.0.0.1:11434"}/api/tags`);
|
|
474
|
+
|
|
475
|
+
if (!response.ok) {
|
|
476
|
+
return "unknown";
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const payload = await response.json();
|
|
480
|
+
const models = payload.models || [];
|
|
481
|
+
return models.some((model) => model.name === config.ai.model) ? "installed" : "missing";
|
|
482
|
+
} catch {
|
|
483
|
+
return "ollama-unavailable";
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
373
487
|
async function initCli(args = []) {
|
|
374
488
|
const options = parseOptions(args);
|
|
375
489
|
|
|
@@ -378,8 +492,8 @@ async function initCli(args = []) {
|
|
|
378
492
|
printKeyValue({
|
|
379
493
|
node: process.version,
|
|
380
494
|
npm: await getCommandVersion("npm", ["--version"]),
|
|
381
|
-
api: await probeEndpoint(`${
|
|
382
|
-
mcp:
|
|
495
|
+
api: await probeEndpoint(`${await getMcpBaseUrl()}/mcp-health`),
|
|
496
|
+
mcp: await getMcpBaseUrl(),
|
|
383
497
|
});
|
|
384
498
|
console.log("");
|
|
385
499
|
|
|
@@ -455,6 +569,47 @@ async function handleAi(args) {
|
|
|
455
569
|
throw new Error(`Unknown AI command: ${subcommand}\nRun "iola ai help" to see available commands.`);
|
|
456
570
|
}
|
|
457
571
|
|
|
572
|
+
async function handleConfig(args) {
|
|
573
|
+
const [action = "get", key, ...rest] = args;
|
|
574
|
+
|
|
575
|
+
if (action === "get") {
|
|
576
|
+
const config = await loadConfig();
|
|
577
|
+
if (key) {
|
|
578
|
+
console.log(getConfigValue(config, key) ?? "-");
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
printJson({
|
|
582
|
+
file: CONFIG_FILE,
|
|
583
|
+
config,
|
|
584
|
+
effective: {
|
|
585
|
+
apiBaseUrl: await getApiBaseUrl(),
|
|
586
|
+
mcpBaseUrl: await getMcpBaseUrl(),
|
|
587
|
+
},
|
|
588
|
+
});
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (action === "set") {
|
|
593
|
+
const value = rest.join(" ").trim();
|
|
594
|
+
if (!key || !value) {
|
|
595
|
+
throw new Error("Пример: iola config set api.baseUrl https://apiiola.yasg.ru/api/v1");
|
|
596
|
+
}
|
|
597
|
+
const config = await loadConfig();
|
|
598
|
+
setConfigValue(config, key, value);
|
|
599
|
+
await saveConfig(config);
|
|
600
|
+
console.log(`Сохранено: ${key} = ${value}`);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (action === "reset") {
|
|
605
|
+
await writeConfig(DEFAULT_AI_CONFIG);
|
|
606
|
+
console.log(`Конфигурация сброшена: ${CONFIG_FILE}`);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
throw new Error("Команды config: get, set, reset.");
|
|
611
|
+
}
|
|
612
|
+
|
|
458
613
|
async function aiDoctor(args) {
|
|
459
614
|
const options = parseOptions(args);
|
|
460
615
|
const diagnostics = await getLocalDiagnostics();
|
|
@@ -720,10 +875,12 @@ async function aiAsk(args, context = {}) {
|
|
|
720
875
|
}
|
|
721
876
|
|
|
722
877
|
async function buildDataContext(question) {
|
|
878
|
+
const apiBaseUrl = await getApiBaseUrl();
|
|
879
|
+
const mcpBaseUrl = await getMcpBaseUrl();
|
|
723
880
|
const [layers, schools, kindergartens] = await Promise.all([
|
|
724
|
-
fetchJson(`${
|
|
725
|
-
fetchJson(`${
|
|
726
|
-
fetchJson(`${
|
|
881
|
+
fetchJson(`${mcpBaseUrl}/mcp-version`),
|
|
882
|
+
fetchJson(`${apiBaseUrl}/schools?limit=100&offset=0`),
|
|
883
|
+
fetchJson(`${apiBaseUrl}/kindergartens?limit=100&offset=0`),
|
|
727
884
|
]);
|
|
728
885
|
const queryTerms = extractSearchTerms(question);
|
|
729
886
|
const patterns = extractStructuredPatterns(question);
|
|
@@ -921,7 +1078,7 @@ async function callOllama(config, messages) {
|
|
|
921
1078
|
|
|
922
1079
|
async function callOpenAiCompatible(config, messages, apiKey, providerName) {
|
|
923
1080
|
if (!apiKey) {
|
|
924
|
-
throw new Error(`${providerName} API key не найден.
|
|
1081
|
+
throw new Error(`${providerName} API key не найден. Выполните iola ai key set ${providerName === "OpenAI" ? "openai" : "openrouter"} или задайте ${providerName === "OpenAI" ? "OPENAI_API_KEY" : "OPENROUTER_API_KEY"}.`);
|
|
925
1082
|
}
|
|
926
1083
|
|
|
927
1084
|
const response = await fetch(`${config.baseUrl}/chat/completions`, {
|
|
@@ -963,7 +1120,7 @@ async function getApiKey(provider) {
|
|
|
963
1120
|
|
|
964
1121
|
async function listLayers(args) {
|
|
965
1122
|
const options = parseOptions(args);
|
|
966
|
-
const info = await fetchJson(`${
|
|
1123
|
+
const info = await fetchJson(`${await getMcpBaseUrl()}/mcp-version`);
|
|
967
1124
|
|
|
968
1125
|
if (options.json) {
|
|
969
1126
|
printJson(info.data_layers);
|
|
@@ -980,7 +1137,7 @@ async function listLayers(args) {
|
|
|
980
1137
|
|
|
981
1138
|
async function showMcpInfo(args) {
|
|
982
1139
|
const options = parseOptions(args);
|
|
983
|
-
const info = await fetchJson(`${
|
|
1140
|
+
const info = await fetchJson(`${await getMcpBaseUrl()}/mcp-version`);
|
|
984
1141
|
|
|
985
1142
|
if (options.json) {
|
|
986
1143
|
printJson(info);
|
|
@@ -1039,22 +1196,24 @@ async function listDataset(dataset, args) {
|
|
|
1039
1196
|
params.set("limit", options.limit || "20");
|
|
1040
1197
|
params.set("offset", options.offset || "0");
|
|
1041
1198
|
|
|
1042
|
-
const data = await fetchJson(`${
|
|
1199
|
+
const data = await fetchJson(`${await getApiBaseUrl()}/${DATASETS[dataset].endpoint}?${params}`);
|
|
1043
1200
|
const items = normalizeItems(data);
|
|
1044
|
-
const filtered =
|
|
1201
|
+
const filtered = applyDatasetFilters(items, options);
|
|
1045
1202
|
const limited = filtered.slice(0, Number(options.limit || 20));
|
|
1203
|
+
const summarized = limited.map(selectPublicSummary);
|
|
1204
|
+
const projected = projectColumns(summarized, options.columns);
|
|
1046
1205
|
|
|
1047
1206
|
if (options.json || options.format === "json") {
|
|
1048
|
-
printJson(
|
|
1207
|
+
printJson(projected);
|
|
1049
1208
|
return;
|
|
1050
1209
|
}
|
|
1051
1210
|
|
|
1052
1211
|
if (options.format === "csv") {
|
|
1053
|
-
printCsv(
|
|
1212
|
+
printCsv(projected);
|
|
1054
1213
|
return;
|
|
1055
1214
|
}
|
|
1056
1215
|
|
|
1057
|
-
printDatasetTable(
|
|
1216
|
+
printDatasetTable(projected, options.columns);
|
|
1058
1217
|
}
|
|
1059
1218
|
|
|
1060
1219
|
async function getDatasetItem(dataset, options) {
|
|
@@ -1062,7 +1221,7 @@ async function getDatasetItem(dataset, options) {
|
|
|
1062
1221
|
throw new Error(`INN is required. Example: iola ${dataset} get --inn 1215067180`);
|
|
1063
1222
|
}
|
|
1064
1223
|
|
|
1065
|
-
const data = await fetchJson(`${
|
|
1224
|
+
const data = await fetchJson(`${await getApiBaseUrl()}/${DATASETS[dataset].endpoint}?limit=500&offset=0`);
|
|
1066
1225
|
const item = normalizeItems(data).find((entry) => String(entry.inn) === String(options.inn));
|
|
1067
1226
|
|
|
1068
1227
|
if (!item) {
|
|
@@ -1086,14 +1245,14 @@ async function searchAll(args) {
|
|
|
1086
1245
|
}
|
|
1087
1246
|
|
|
1088
1247
|
const [schools, kindergartens] = await Promise.all([
|
|
1089
|
-
fetchJson(`${
|
|
1090
|
-
fetchJson(`${
|
|
1248
|
+
fetchJson(`${await getApiBaseUrl()}/schools?limit=100&offset=0`),
|
|
1249
|
+
fetchJson(`${await getApiBaseUrl()}/kindergartens?limit=100&offset=0`),
|
|
1091
1250
|
]);
|
|
1092
1251
|
|
|
1093
1252
|
const limit = Number(options.limit || 5);
|
|
1094
1253
|
const result = {
|
|
1095
|
-
schools: filterItems(normalizeItems(schools), query).slice(0, limit),
|
|
1096
|
-
kindergartens: filterItems(normalizeItems(kindergartens), query).slice(0, limit),
|
|
1254
|
+
schools: projectColumns(filterItems(normalizeItems(schools), query).slice(0, limit).map(selectPublicSummary), options.columns),
|
|
1255
|
+
kindergartens: projectColumns(filterItems(normalizeItems(kindergartens), query).slice(0, limit).map(selectPublicSummary), options.columns),
|
|
1097
1256
|
};
|
|
1098
1257
|
|
|
1099
1258
|
if (options.json || options.format === "json") {
|
|
@@ -1103,17 +1262,17 @@ async function searchAll(args) {
|
|
|
1103
1262
|
|
|
1104
1263
|
if (options.format === "csv") {
|
|
1105
1264
|
printCsv([
|
|
1106
|
-
...result.schools.map((item) => ({ layer: "schools", ...
|
|
1107
|
-
...result.kindergartens.map((item) => ({ layer: "kindergartens", ...
|
|
1265
|
+
...result.schools.map((item) => ({ layer: "schools", ...item })),
|
|
1266
|
+
...result.kindergartens.map((item) => ({ layer: "kindergartens", ...item })),
|
|
1108
1267
|
]);
|
|
1109
1268
|
return;
|
|
1110
1269
|
}
|
|
1111
1270
|
|
|
1112
1271
|
console.log("Школы");
|
|
1113
|
-
printDatasetTable(result.schools);
|
|
1272
|
+
printDatasetTable(result.schools, options.columns);
|
|
1114
1273
|
console.log("");
|
|
1115
1274
|
console.log("Детские сады");
|
|
1116
|
-
printDatasetTable(result.kindergartens);
|
|
1275
|
+
printDatasetTable(result.kindergartens, options.columns);
|
|
1117
1276
|
}
|
|
1118
1277
|
|
|
1119
1278
|
async function setupClient(args) {
|
|
@@ -1137,7 +1296,7 @@ function parseOptions(args) {
|
|
|
1137
1296
|
result[arg.slice(2)] = true;
|
|
1138
1297
|
} else if (arg === "--check") {
|
|
1139
1298
|
result.check = true;
|
|
1140
|
-
} else if (arg === "--limit" || arg === "--offset" || arg === "--search" || arg === "--inn" || arg === "--model" || arg === "--provider" || arg === "--format") {
|
|
1299
|
+
} else if (arg === "--limit" || arg === "--offset" || arg === "--search" || arg === "--where" || arg === "--columns" || arg === "--inn" || arg === "--model" || arg === "--provider" || arg === "--format") {
|
|
1141
1300
|
result[arg.slice(2)] = args[index + 1];
|
|
1142
1301
|
index += 1;
|
|
1143
1302
|
} else {
|
|
@@ -1187,6 +1346,42 @@ function filterItems(items, query) {
|
|
|
1187
1346
|
return items.filter((item) => JSON.stringify(item).toLocaleLowerCase("ru-RU").includes(normalized));
|
|
1188
1347
|
}
|
|
1189
1348
|
|
|
1349
|
+
function applyDatasetFilters(items, options) {
|
|
1350
|
+
let result = options.search ? filterItems(items, options.search) : items;
|
|
1351
|
+
|
|
1352
|
+
if (options.where) {
|
|
1353
|
+
const [field, ...valueParts] = String(options.where).split("=");
|
|
1354
|
+
const value = valueParts.join("=").trim().toLocaleLowerCase("ru-RU");
|
|
1355
|
+
const key = field.trim();
|
|
1356
|
+
|
|
1357
|
+
if (!key || !value) {
|
|
1358
|
+
throw new Error('Фильтр --where должен быть в формате field=value. Пример: --where address=Петрова');
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
result = result.filter((item) => {
|
|
1362
|
+
const summary = selectPublicSummary(item);
|
|
1363
|
+
const raw = summary[key] ?? item[key];
|
|
1364
|
+
return String(raw ?? "").toLocaleLowerCase("ru-RU").includes(value);
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
return result;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
function projectColumns(rows, columnsValue) {
|
|
1372
|
+
if (!columnsValue) {
|
|
1373
|
+
return rows;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
const columns = String(columnsValue).split(",").map((column) => column.trim()).filter(Boolean);
|
|
1377
|
+
|
|
1378
|
+
if (columns.length === 0) {
|
|
1379
|
+
return rows;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
return rows.map((row) => Object.fromEntries(columns.map((column) => [column, row[column] ?? ""])));
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1190
1385
|
function normalizeItems(payload) {
|
|
1191
1386
|
if (Array.isArray(payload)) {
|
|
1192
1387
|
return payload;
|
|
@@ -1206,7 +1401,7 @@ function normalizeItems(payload) {
|
|
|
1206
1401
|
function selectPublicSummary(item) {
|
|
1207
1402
|
return {
|
|
1208
1403
|
inn: item.inn,
|
|
1209
|
-
name: item.fns_short_name || item.fns_full_name,
|
|
1404
|
+
name: item.name || item.fns_short_name || item.fns_full_name,
|
|
1210
1405
|
address: item.address || item.legal_address,
|
|
1211
1406
|
phone: item.phone,
|
|
1212
1407
|
email: item.email,
|
|
@@ -1422,8 +1617,12 @@ async function confirm(question) {
|
|
|
1422
1617
|
async function saveConfig(value) {
|
|
1423
1618
|
const current = await loadConfig();
|
|
1424
1619
|
const merged = mergeConfig(current, value);
|
|
1620
|
+
await writeConfig(merged);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
async function writeConfig(value) {
|
|
1425
1624
|
await mkdir(CONFIG_DIR, { recursive: true });
|
|
1426
|
-
await writeFile(CONFIG_FILE, `${JSON.stringify(
|
|
1625
|
+
await writeFile(CONFIG_FILE, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
1427
1626
|
}
|
|
1428
1627
|
|
|
1429
1628
|
async function loadConfig() {
|
|
@@ -1439,6 +1638,10 @@ function mergeConfig(base, override) {
|
|
|
1439
1638
|
return {
|
|
1440
1639
|
...base,
|
|
1441
1640
|
...override,
|
|
1641
|
+
api: {
|
|
1642
|
+
...base.api,
|
|
1643
|
+
...(override.api || {}),
|
|
1644
|
+
},
|
|
1442
1645
|
ai: {
|
|
1443
1646
|
...base.ai,
|
|
1444
1647
|
...(override.ai || {}),
|
|
@@ -1446,6 +1649,40 @@ function mergeConfig(base, override) {
|
|
|
1446
1649
|
};
|
|
1447
1650
|
}
|
|
1448
1651
|
|
|
1652
|
+
async function getApiBaseUrl() {
|
|
1653
|
+
if (process.env.IOLA_API_BASE_URL) {
|
|
1654
|
+
return process.env.IOLA_API_BASE_URL;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
const config = await loadConfig();
|
|
1658
|
+
return config.api.baseUrl;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
async function getMcpBaseUrl() {
|
|
1662
|
+
if (process.env.IOLA_MCP_BASE_URL) {
|
|
1663
|
+
return process.env.IOLA_MCP_BASE_URL;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
const config = await loadConfig();
|
|
1667
|
+
return config.api.mcpBaseUrl;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
function getConfigValue(config, key) {
|
|
1671
|
+
return key.split(".").reduce((value, part) => value?.[part], config);
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
function setConfigValue(config, key, value) {
|
|
1675
|
+
const parts = key.split(".");
|
|
1676
|
+
let current = config;
|
|
1677
|
+
|
|
1678
|
+
for (const part of parts.slice(0, -1)) {
|
|
1679
|
+
current[part] = current[part] && typeof current[part] === "object" ? current[part] : {};
|
|
1680
|
+
current = current[part];
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
current[parts.at(-1)] = value;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1449
1686
|
async function loadSecrets() {
|
|
1450
1687
|
try {
|
|
1451
1688
|
return JSON.parse(await readFile(SECRETS_FILE, "utf8"));
|
|
@@ -1573,7 +1810,17 @@ function csvCell(value) {
|
|
|
1573
1810
|
return `"${text.replace(/"/g, "\"\"")}"`;
|
|
1574
1811
|
}
|
|
1575
1812
|
|
|
1576
|
-
function printDatasetTable(items) {
|
|
1813
|
+
function printDatasetTable(items, columnsValue) {
|
|
1814
|
+
if (columnsValue) {
|
|
1815
|
+
const columns = String(columnsValue)
|
|
1816
|
+
.split(",")
|
|
1817
|
+
.map((column) => column.trim())
|
|
1818
|
+
.filter(Boolean)
|
|
1819
|
+
.map((column) => [column, column]);
|
|
1820
|
+
printTable(items, columns);
|
|
1821
|
+
return;
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1577
1824
|
printTable(items.map(selectPublicSummary), [
|
|
1578
1825
|
["inn", "ИНН"],
|
|
1579
1826
|
["name", "Название"],
|