@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iola_adm/iola-cli",
3
- "version": "0.1.72",
3
+ "version": "0.1.73",
4
4
  "description": "CLI и AI-агент городского округа Йошкар-Ола.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/adm-iola/iola-cli#readme",
@@ -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 includeSchools = patterns.targetLayers.length === 0 || patterns.targetLayers.includes("schools");
6480
- const includeKindergartens = patterns.targetLayers.length === 0 || patterns.targetLayers.includes("kindergartens");
6481
- const schoolItems = includeSchools
6482
- ? findRelevantItems(normalizeItems(schools), queryTerms, patterns, "schools").slice(0, 8)
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: layers.data_layers || [],
6487
+ layers,
6490
6488
  query: {
6491
6489
  text: question,
6492
6490
  terms: queryTerms,
6493
6491
  patterns,
6494
6492
  },
6495
- schools: schoolItems.map(selectPublicSummary),
6496
- kindergartens: kindergartenItems.map(selectPublicSummary),
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