@iola_adm/iola-cli 0.1.1 → 0.1.2
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 +6 -0
- package/package.json +1 -1
- package/src/cli.js +193 -12
package/README.md
CHANGED
|
@@ -25,14 +25,20 @@ iola help
|
|
|
25
25
|
## Команды
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
+
iola health
|
|
28
29
|
iola layers
|
|
29
30
|
iola schools --limit 10
|
|
31
|
+
iola schools get --inn 1215067180
|
|
30
32
|
iola kindergartens --search "29"
|
|
33
|
+
iola kindergartens get --inn 1215077421 --json
|
|
31
34
|
iola search "лицей"
|
|
32
35
|
iola mcp-info
|
|
33
36
|
iola setup codex
|
|
34
37
|
```
|
|
35
38
|
|
|
39
|
+
По умолчанию команды выводят компактную таблицу. Для полного ответа API
|
|
40
|
+
используйте `--json`.
|
|
41
|
+
|
|
36
42
|
## Назначение
|
|
37
43
|
|
|
38
44
|
Первый релиз CLI дает прямой терминальный доступ к открытым данным и командам
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ const MCP_BASE_URL = process.env.IOLA_MCP_BASE_URL || "https://apiiola.yasg.ru";
|
|
|
4
4
|
const COMMANDS = new Map([
|
|
5
5
|
["help", showHelp],
|
|
6
6
|
["version", showVersion],
|
|
7
|
+
["health", checkHealth],
|
|
7
8
|
["layers", listLayers],
|
|
8
9
|
["schools", listSchools],
|
|
9
10
|
["kindergartens", listKindergartens],
|
|
@@ -27,11 +28,14 @@ async function showHelp() {
|
|
|
27
28
|
console.log(`iola - CLI для открытых данных городского округа "Город Йошкар-Ола"
|
|
28
29
|
|
|
29
30
|
Usage:
|
|
30
|
-
iola
|
|
31
|
-
iola
|
|
32
|
-
iola
|
|
33
|
-
iola
|
|
34
|
-
iola
|
|
31
|
+
iola health [--json]
|
|
32
|
+
iola layers [--json]
|
|
33
|
+
iola schools [--limit 10] [--search TEXT] [--json]
|
|
34
|
+
iola schools get --inn INN [--json]
|
|
35
|
+
iola kindergartens [--limit 10] [--search TEXT] [--json]
|
|
36
|
+
iola kindergartens get --inn INN [--json]
|
|
37
|
+
iola search TEXT [--limit 5] [--json]
|
|
38
|
+
iola mcp-info [--json]
|
|
35
39
|
iola setup codex
|
|
36
40
|
iola version
|
|
37
41
|
|
|
@@ -46,14 +50,57 @@ async function showVersion() {
|
|
|
46
50
|
console.log(packageJson.default.version);
|
|
47
51
|
}
|
|
48
52
|
|
|
49
|
-
async function
|
|
53
|
+
async function checkHealth(args) {
|
|
54
|
+
const options = parseOptions(args);
|
|
55
|
+
const health = await fetchJson(`${MCP_BASE_URL}/mcp-health`);
|
|
56
|
+
|
|
57
|
+
if (options.json) {
|
|
58
|
+
printJson(health);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
printKeyValue({
|
|
63
|
+
status: health.status,
|
|
64
|
+
server_version: health.server_version,
|
|
65
|
+
skill_version: health.skill_version,
|
|
66
|
+
mcp_endpoint: health.mcp_endpoint,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function listLayers(args) {
|
|
71
|
+
const options = parseOptions(args);
|
|
50
72
|
const info = await fetchJson(`${MCP_BASE_URL}/mcp-version`);
|
|
51
|
-
|
|
73
|
+
|
|
74
|
+
if (options.json) {
|
|
75
|
+
printJson(info.data_layers);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
printTable(info.data_layers, [
|
|
80
|
+
["id", "ID"],
|
|
81
|
+
["name", "Название"],
|
|
82
|
+
["category", "Категория"],
|
|
83
|
+
["status", "Статус"],
|
|
84
|
+
]);
|
|
52
85
|
}
|
|
53
86
|
|
|
54
|
-
async function showMcpInfo() {
|
|
87
|
+
async function showMcpInfo(args) {
|
|
88
|
+
const options = parseOptions(args);
|
|
55
89
|
const info = await fetchJson(`${MCP_BASE_URL}/mcp-version`);
|
|
56
|
-
|
|
90
|
+
|
|
91
|
+
if (options.json) {
|
|
92
|
+
printJson(info);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
printKeyValue({
|
|
97
|
+
server_name: info.server_name,
|
|
98
|
+
server_version: info.server_version,
|
|
99
|
+
skill_version: info.skill_version,
|
|
100
|
+
npm_package: info.npm_package,
|
|
101
|
+
mcp_endpoint: info.mcp_endpoint,
|
|
102
|
+
layers: info.data_layers.map((layer) => layer.id).join(", "),
|
|
103
|
+
});
|
|
57
104
|
}
|
|
58
105
|
|
|
59
106
|
async function listSchools(args) {
|
|
@@ -66,6 +113,12 @@ async function listKindergartens(args) {
|
|
|
66
113
|
|
|
67
114
|
async function listDataset(dataset, args) {
|
|
68
115
|
const options = parseOptions(args);
|
|
116
|
+
|
|
117
|
+
if (options._[0] === "get") {
|
|
118
|
+
await getDatasetItem(dataset, options);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
69
122
|
const params = new URLSearchParams();
|
|
70
123
|
params.set("limit", options.limit || "20");
|
|
71
124
|
params.set("offset", options.offset || "0");
|
|
@@ -73,7 +126,34 @@ async function listDataset(dataset, args) {
|
|
|
73
126
|
const data = await fetchJson(`${API_BASE_URL}/${dataset}?${params}`);
|
|
74
127
|
const items = normalizeItems(data);
|
|
75
128
|
const filtered = options.search ? filterItems(items, options.search) : items;
|
|
76
|
-
|
|
129
|
+
const limited = filtered.slice(0, Number(options.limit || 20));
|
|
130
|
+
|
|
131
|
+
if (options.json) {
|
|
132
|
+
printJson(limited);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
printDatasetTable(limited);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function getDatasetItem(dataset, options) {
|
|
140
|
+
if (!options.inn) {
|
|
141
|
+
throw new Error(`INN is required. Example: iola ${dataset} get --inn 1215067180`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const data = await fetchJson(`${API_BASE_URL}/${dataset}?limit=500&offset=0`);
|
|
145
|
+
const item = normalizeItems(data).find((entry) => String(entry.inn) === String(options.inn));
|
|
146
|
+
|
|
147
|
+
if (!item) {
|
|
148
|
+
throw new Error(`Record was not found in ${dataset}: inn=${options.inn}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (options.json) {
|
|
152
|
+
printJson(item);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
printKeyValue(selectPublicSummary(item));
|
|
77
157
|
}
|
|
78
158
|
|
|
79
159
|
async function searchAll(args) {
|
|
@@ -95,7 +175,16 @@ async function searchAll(args) {
|
|
|
95
175
|
kindergartens: filterItems(normalizeItems(kindergartens), query).slice(0, limit),
|
|
96
176
|
};
|
|
97
177
|
|
|
98
|
-
|
|
178
|
+
if (options.json) {
|
|
179
|
+
printJson(result);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
console.log("Школы");
|
|
184
|
+
printDatasetTable(result.schools);
|
|
185
|
+
console.log("");
|
|
186
|
+
console.log("Детские сады");
|
|
187
|
+
printDatasetTable(result.kindergartens);
|
|
99
188
|
}
|
|
100
189
|
|
|
101
190
|
async function setupClient(args) {
|
|
@@ -115,7 +204,9 @@ function parseOptions(args) {
|
|
|
115
204
|
|
|
116
205
|
for (let index = 0; index < args.length; index += 1) {
|
|
117
206
|
const arg = args[index];
|
|
118
|
-
if (arg === "--
|
|
207
|
+
if (arg === "--json") {
|
|
208
|
+
result.json = true;
|
|
209
|
+
} else if (arg === "--limit" || arg === "--offset" || arg === "--search" || arg === "--inn") {
|
|
119
210
|
result[arg.slice(2)] = args[index + 1];
|
|
120
211
|
index += 1;
|
|
121
212
|
} else {
|
|
@@ -147,6 +238,20 @@ function normalizeItems(payload) {
|
|
|
147
238
|
return [];
|
|
148
239
|
}
|
|
149
240
|
|
|
241
|
+
function selectPublicSummary(item) {
|
|
242
|
+
return {
|
|
243
|
+
inn: item.inn,
|
|
244
|
+
name: item.fns_short_name || item.fns_full_name,
|
|
245
|
+
address: item.address || item.legal_address,
|
|
246
|
+
phone: item.phone,
|
|
247
|
+
email: item.email,
|
|
248
|
+
website: item.website,
|
|
249
|
+
head: item.fns_head_name,
|
|
250
|
+
license_number: item.license_number,
|
|
251
|
+
license_status: item.license_status,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
150
255
|
async function fetchJson(url) {
|
|
151
256
|
const response = await fetch(url, {
|
|
152
257
|
headers: {
|
|
@@ -165,3 +270,79 @@ async function fetchJson(url) {
|
|
|
165
270
|
function printJson(value) {
|
|
166
271
|
console.log(JSON.stringify(value, null, 2));
|
|
167
272
|
}
|
|
273
|
+
|
|
274
|
+
function printDatasetTable(items) {
|
|
275
|
+
printTable(items.map(selectPublicSummary), [
|
|
276
|
+
["inn", "ИНН"],
|
|
277
|
+
["name", "Название"],
|
|
278
|
+
["address", "Адрес"],
|
|
279
|
+
["phone", "Телефон"],
|
|
280
|
+
]);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function printKeyValue(value) {
|
|
284
|
+
const rows = Object.entries(value).map(([key, raw]) => ({
|
|
285
|
+
key,
|
|
286
|
+
value: raw == null || raw === "" ? "-" : String(raw),
|
|
287
|
+
}));
|
|
288
|
+
|
|
289
|
+
printTable(rows, [
|
|
290
|
+
["key", "Поле"],
|
|
291
|
+
["value", "Значение"],
|
|
292
|
+
]);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function printTable(rows, columns) {
|
|
296
|
+
if (rows.length === 0) {
|
|
297
|
+
console.log("Нет данных.");
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const normalized = rows.map((row) =>
|
|
302
|
+
Object.fromEntries(
|
|
303
|
+
columns.map(([key]) => [key, formatCell(row[key])]),
|
|
304
|
+
),
|
|
305
|
+
);
|
|
306
|
+
const widths = columns.map(([key, title]) =>
|
|
307
|
+
Math.min(
|
|
308
|
+
Math.max(
|
|
309
|
+
visibleLength(title),
|
|
310
|
+
...normalized.map((row) => visibleLength(row[key])),
|
|
311
|
+
),
|
|
312
|
+
52,
|
|
313
|
+
),
|
|
314
|
+
);
|
|
315
|
+
const header = columns.map(([, title], index) => padCell(title, widths[index])).join(" ");
|
|
316
|
+
const divider = widths.map((width) => "-".repeat(width)).join(" ");
|
|
317
|
+
|
|
318
|
+
console.log(header);
|
|
319
|
+
console.log(divider);
|
|
320
|
+
|
|
321
|
+
for (const row of normalized) {
|
|
322
|
+
console.log(columns.map(([key], index) => padCell(truncateCell(row[key], widths[index]), widths[index])).join(" "));
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function formatCell(value) {
|
|
327
|
+
if (value == null || value === "") {
|
|
328
|
+
return "-";
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return String(value).replace(/\s+/g, " ").trim();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function truncateCell(value, width) {
|
|
335
|
+
if (visibleLength(value) <= width) {
|
|
336
|
+
return value;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return `${value.slice(0, Math.max(0, width - 1))}…`;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function padCell(value, width) {
|
|
343
|
+
return value + " ".repeat(Math.max(0, width - visibleLength(value)));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function visibleLength(value) {
|
|
347
|
+
return String(value).length;
|
|
348
|
+
}
|