@iola_adm/iola-cli 0.1.72 → 0.1.73
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/package.json +1 -1
- package/skills/education/SKILL.md +22 -0
- package/src/cli.js +73 -19
package/package.json
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: education
|
|
3
|
+
description: Вопросы по образовательным учреждениям Йошкар-Олы: школы, детские сады, руководители, контакты, адреса и лицензии.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Используй MCP tools слоя данных, а не прямую догадку модели.
|
|
7
|
+
|
|
8
|
+
Основные слои:
|
|
9
|
+
|
|
10
|
+
- `schools` — муниципальные школы, лицеи и гимназии.
|
|
11
|
+
- `kindergartens` — муниципальные детские сады.
|
|
12
|
+
|
|
13
|
+
Для поиска используй:
|
|
14
|
+
|
|
15
|
+
- `mcp:iola-local:layer.list` — список доступных слоев.
|
|
16
|
+
- `mcp:iola-local:layer.schema` — схема слоя.
|
|
17
|
+
- `mcp:iola-local:layer.query` — поиск по слою.
|
|
18
|
+
- `mcp:iola-local:layer.get` — точная карточка записи.
|
|
19
|
+
|
|
20
|
+
Если вопрос можно ответить точным совпадением по руководителю, ИНН, названию или адресу, отвечай только по найденной записи и указывай источник: слой, название и ИНН.
|
|
21
|
+
|
|
22
|
+
Если совпадений нет или их несколько, прямо скажи, что данных недостаточно или нужно уточнение. Не выдумывай школы, детские сады, руководителей, адреса, телефоны, лицензии и сайты.
|
package/src/cli.js
CHANGED
|
@@ -180,7 +180,7 @@ const DEFAULT_AI_CONFIG = {
|
|
|
180
180
|
suggestions: true,
|
|
181
181
|
},
|
|
182
182
|
skills: {
|
|
183
|
-
enabled: ["open-data", "reports", "local-model", "local-files", "browser-agent"],
|
|
183
|
+
enabled: ["education", "open-data", "reports", "local-model", "local-files", "browser-agent"],
|
|
184
184
|
},
|
|
185
185
|
daemon: {
|
|
186
186
|
host: "127.0.0.1",
|
|
@@ -236,11 +236,19 @@ const AGENTS = {
|
|
|
236
236
|
const DATASETS = {
|
|
237
237
|
schools: {
|
|
238
238
|
title: "Школы",
|
|
239
|
+
category: "Образование",
|
|
239
240
|
endpoint: "schools",
|
|
241
|
+
aliases: ["школ", "лицей", "гимнази"],
|
|
242
|
+
searchFields: ["name", "address", "head", "inn"],
|
|
243
|
+
personFields: ["head"],
|
|
240
244
|
},
|
|
241
245
|
kindergartens: {
|
|
242
246
|
title: "Детские сады",
|
|
247
|
+
category: "Образование",
|
|
243
248
|
endpoint: "kindergartens",
|
|
249
|
+
aliases: ["сад", "детсад", "детский сад", "доу", "мбдоу"],
|
|
250
|
+
searchFields: ["name", "address", "head", "inn"],
|
|
251
|
+
personFields: ["head"],
|
|
244
252
|
},
|
|
245
253
|
};
|
|
246
254
|
const SLASH_COMMANDS = [
|
|
@@ -6467,36 +6475,32 @@ function emitEvent(options, type, data) {
|
|
|
6467
6475
|
|
|
6468
6476
|
async function buildDataContext(question) {
|
|
6469
6477
|
await assertPermission("externalApi");
|
|
6470
|
-
const apiBaseUrl = await getApiBaseUrl();
|
|
6471
|
-
const mcpBaseUrl = await getMcpBaseUrl();
|
|
6472
|
-
const [layers, schools, kindergartens] = await Promise.all([
|
|
6473
|
-
fetchJson(`${mcpBaseUrl}/mcp-version`),
|
|
6474
|
-
fetchAllApiItems(`${apiBaseUrl}/schools`),
|
|
6475
|
-
fetchAllApiItems(`${apiBaseUrl}/kindergartens`),
|
|
6476
|
-
]);
|
|
6477
6478
|
const queryTerms = extractSearchTerms(question);
|
|
6478
6479
|
const patterns = extractStructuredPatterns(question);
|
|
6479
|
-
const
|
|
6480
|
-
const
|
|
6481
|
-
const
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
const kindergartenItems = includeKindergartens
|
|
6485
|
-
? findRelevantItems(normalizeItems(kindergartens), queryTerms, patterns, "kindergartens").slice(0, 8)
|
|
6486
|
-
: [];
|
|
6480
|
+
const layers = await callMcpTool("layer.list", { category: "Образование" });
|
|
6481
|
+
const targetLayerIds = resolveTargetLayerIds(patterns);
|
|
6482
|
+
const layerResults = await Promise.all(targetLayerIds.map((layer) =>
|
|
6483
|
+
callMcpTool("layer.query", { layer, query: question, terms: queryTerms, patterns, limit: 8 })));
|
|
6484
|
+
const layerMap = Object.fromEntries(layerResults.map((result) => [result.layer, result.items || []]));
|
|
6487
6485
|
|
|
6488
6486
|
return {
|
|
6489
|
-
layers
|
|
6487
|
+
layers,
|
|
6490
6488
|
query: {
|
|
6491
6489
|
text: question,
|
|
6492
6490
|
terms: queryTerms,
|
|
6493
6491
|
patterns,
|
|
6494
6492
|
},
|
|
6495
|
-
schools:
|
|
6496
|
-
kindergartens:
|
|
6493
|
+
schools: layerMap.schools || [],
|
|
6494
|
+
kindergartens: layerMap.kindergartens || [],
|
|
6497
6495
|
};
|
|
6498
6496
|
}
|
|
6499
6497
|
|
|
6498
|
+
function resolveTargetLayerIds(patterns = {}) {
|
|
6499
|
+
const knownLayers = Object.keys(DATASETS);
|
|
6500
|
+
if (patterns.targetLayers?.length) return patterns.targetLayers.filter((layer) => DATASETS[layer]);
|
|
6501
|
+
return knownLayers;
|
|
6502
|
+
}
|
|
6503
|
+
|
|
6500
6504
|
async function fetchAllApiItems(endpoint, limit = 500, maxItems = 5000) {
|
|
6501
6505
|
const all = [];
|
|
6502
6506
|
for (let offset = 0; offset < maxItems; offset += limit) {
|
|
@@ -6509,6 +6513,36 @@ async function fetchAllApiItems(endpoint, limit = 500, maxItems = 5000) {
|
|
|
6509
6513
|
return all;
|
|
6510
6514
|
}
|
|
6511
6515
|
|
|
6516
|
+
async function queryLayer(layer, args = {}) {
|
|
6517
|
+
const meta = DATASETS[layer];
|
|
6518
|
+
if (!meta) throw new Error(`Неизвестный слой: ${layer}`);
|
|
6519
|
+
const endpoint = `${await getApiBaseUrl()}/${meta.endpoint}`;
|
|
6520
|
+
const items = await fetchAllApiItems(endpoint);
|
|
6521
|
+
const terms = args.terms || extractSearchTerms(args.query || "");
|
|
6522
|
+
const patterns = args.patterns || extractStructuredPatterns(args.query || "");
|
|
6523
|
+
const limit = Number(args.limit || 20);
|
|
6524
|
+
return {
|
|
6525
|
+
layer,
|
|
6526
|
+
schema: layerSchema(layer),
|
|
6527
|
+
items: findRelevantItems(normalizeItems(items), terms, patterns, layer).slice(0, limit).map(selectPublicSummary),
|
|
6528
|
+
};
|
|
6529
|
+
}
|
|
6530
|
+
|
|
6531
|
+
function layerSchema(layer) {
|
|
6532
|
+
const meta = DATASETS[layer];
|
|
6533
|
+
if (!meta) throw new Error(`Неизвестный слой: ${layer}`);
|
|
6534
|
+
return {
|
|
6535
|
+
id: layer,
|
|
6536
|
+
title: meta.title,
|
|
6537
|
+
category: meta.category,
|
|
6538
|
+
endpoint: meta.endpoint,
|
|
6539
|
+
aliases: meta.aliases || [],
|
|
6540
|
+
searchFields: meta.searchFields || [],
|
|
6541
|
+
personFields: meta.personFields || [],
|
|
6542
|
+
sourceFields: ["layer", "name", "inn"],
|
|
6543
|
+
};
|
|
6544
|
+
}
|
|
6545
|
+
|
|
6512
6546
|
function emptyDataContext(question) {
|
|
6513
6547
|
return {
|
|
6514
6548
|
enabled: false,
|
|
@@ -7843,6 +7877,10 @@ function mcpTools() {
|
|
|
7843
7877
|
const schema = (properties = {}) => ({ type: "object", properties, additionalProperties: false });
|
|
7844
7878
|
return [
|
|
7845
7879
|
{ name: "status", description: "Статус локальной БД, sync и активного AI-профиля.", inputSchema: schema() },
|
|
7880
|
+
{ name: "layer.list", description: "Список слоев данных и их схем.", inputSchema: schema({ category: { type: "string" } }) },
|
|
7881
|
+
{ name: "layer.schema", description: "Схема слоя данных.", inputSchema: schema({ layer: { type: "string" } }) },
|
|
7882
|
+
{ name: "layer.query", description: "Поиск по слою данных через общий retrieval.", inputSchema: schema({ layer: { type: "string" }, query: { type: "string" }, terms: { type: "array" }, patterns: { type: "object" }, limit: { type: "number" } }) },
|
|
7883
|
+
{ name: "layer.get", description: "Получить запись слоя по ИНН или названию.", inputSchema: schema({ layer: { type: "string" }, query: { type: "string" }, inn: { type: "string" } }) },
|
|
7846
7884
|
{ name: "search", description: "Поиск по локальным открытым данным Йошкар-Олы.", inputSchema: schema({ query: { type: "string" }, dataset: { type: "string" }, limit: { type: "number" } }) },
|
|
7847
7885
|
{ name: "card", description: "Карточка объекта по названию или ИНН.", inputSchema: schema({ query: { type: "string" } }) },
|
|
7848
7886
|
{ name: "quality", description: "Проверки качества данных.", inputSchema: schema({ scope: { type: "string" } }) },
|
|
@@ -7860,6 +7898,7 @@ function mcpTools() {
|
|
|
7860
7898
|
function mcpResources() {
|
|
7861
7899
|
return [
|
|
7862
7900
|
{ uri: "iola://status", name: "Статус CLI", mimeType: "application/json" },
|
|
7901
|
+
{ uri: "iola://layers", name: "Слои данных", mimeType: "application/json" },
|
|
7863
7902
|
{ uri: "iola://sync", name: "Статус синхронизации", mimeType: "application/json" },
|
|
7864
7903
|
{ uri: "iola://settings", name: "Эффективные настройки", mimeType: "application/json" },
|
|
7865
7904
|
{ uri: "iola://skills", name: "Skills", mimeType: "application/json" },
|
|
@@ -7877,6 +7916,17 @@ function mcpPrompts() {
|
|
|
7877
7916
|
}
|
|
7878
7917
|
|
|
7879
7918
|
async function callMcpTool(name, args = {}) {
|
|
7919
|
+
if (name === "layer.list") {
|
|
7920
|
+
return Object.entries(DATASETS)
|
|
7921
|
+
.map(([id, meta]) => layerSchema(id))
|
|
7922
|
+
.filter((layer) => !args.category || layer.category === args.category);
|
|
7923
|
+
}
|
|
7924
|
+
if (name === "layer.schema") return layerSchema(args.layer);
|
|
7925
|
+
if (name === "layer.query") return queryLayer(args.layer, args);
|
|
7926
|
+
if (name === "layer.get") {
|
|
7927
|
+
const result = await queryLayer(args.layer, { query: args.inn || args.query || "", terms: [args.inn || args.query || ""], limit: 1 });
|
|
7928
|
+
return result.items[0] || null;
|
|
7929
|
+
}
|
|
7880
7930
|
if (name === "index.search") return searchDocs(args.query || "", Number(args.limit || 20));
|
|
7881
7931
|
if (name === "report") {
|
|
7882
7932
|
const output = args.output || `${args.name || "education-contacts"}.${args.format || "xlsx"}`;
|
|
@@ -7896,6 +7946,7 @@ async function callMcpTool(name, args = {}) {
|
|
|
7896
7946
|
|
|
7897
7947
|
async function readMcpResource(uri) {
|
|
7898
7948
|
if (uri === "iola://status") return JSON.stringify({ db: getDbStatus(), sync: getSyncStatus() }, null, 2);
|
|
7949
|
+
if (uri === "iola://layers") return JSON.stringify(Object.fromEntries(Object.keys(DATASETS).map((id) => [id, layerSchema(id)])), null, 2);
|
|
7899
7950
|
if (uri === "iola://sync") return JSON.stringify(getSyncStatus(), null, 2);
|
|
7900
7951
|
if (uri === "iola://settings") return JSON.stringify(await loadConfig(), null, 2);
|
|
7901
7952
|
if (uri === "iola://skills") return JSON.stringify(listSkills(await loadConfig()), null, 2);
|
|
@@ -9044,6 +9095,9 @@ function sanitizeConfig(config) {
|
|
|
9044
9095
|
}
|
|
9045
9096
|
}
|
|
9046
9097
|
}
|
|
9098
|
+
if (Array.isArray(next.skills?.enabled) && next.skills.enabled.includes("open-data") && !next.skills.enabled.includes("education")) {
|
|
9099
|
+
next.skills.enabled = ["education", ...next.skills.enabled];
|
|
9100
|
+
}
|
|
9047
9101
|
return next;
|
|
9048
9102
|
}
|
|
9049
9103
|
|