@iola_adm/iola-cli 0.1.59 → 0.1.61
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/skills/gosuslugi/SKILL.md +16 -0
- package/src/cli.js +454 -8
- package/wiki/Daemon-RPC-/320/270-cron.md +6 -0
- package/wiki//320/232/320/276/320/274/320/260/320/275/320/264/321/213.md +7 -0
- package/wiki//320/237/320/276/320/264/320/272/320/273/321/216/321/207/320/265/320/275/320/270/320/265-/320/223/320/276/321/201/321/203/321/201/320/273/321/203/320/263.md +56 -0
package/README.md
CHANGED
|
@@ -86,6 +86,10 @@ iola review config
|
|
|
86
86
|
iola browser status
|
|
87
87
|
iola gosuslugi status
|
|
88
88
|
iola gosuslugi connect
|
|
89
|
+
iola gosuslugi whoami
|
|
90
|
+
iola gosuslugi debt
|
|
91
|
+
iola gosuslugi notifications --unread
|
|
92
|
+
iola gosuslugi keepalive
|
|
89
93
|
```
|
|
90
94
|
|
|
91
95
|
Локальная модель через Ollama:
|
|
@@ -134,6 +138,8 @@ iola version --check
|
|
|
134
138
|
- дополнительные stdio MCP-серверы можно добавить в `~/.iola/config.json` в раздел `mcp.servers`;
|
|
135
139
|
- браузерный runtime через Playwright: чтение страниц, скриншоты, PDF, клики, ввод и eval;
|
|
136
140
|
- личное локальное подключение Госуслуг через отдельный браузерный профиль на ПК пользователя;
|
|
141
|
+
- read-only tools Госуслуг для агента: ФИО, дата рождения, задолженности и уведомления;
|
|
142
|
+
- keepalive-проверка сессии Госуслуг каждые 30 минут без обхода 2FA;
|
|
137
143
|
- управляемые локальные файловые операции с режимами `locked`, `read-only`, `workspace-write`, `full-access`;
|
|
138
144
|
- планы выполнения, traces, tasks, artifacts, snapshots и policy-профили;
|
|
139
145
|
- экспорт отчетов в Excel/Word-совместимые файлы;
|
package/package.json
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gosuslugi
|
|
3
|
+
description: Работа с личным аккаунтом Госуслуг пользователя через локальный браузерный профиль.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Используй этот skill только когда пользователь явно спрашивает про свой личный аккаунт Госуслуг: ФИО, дату рождения, задолженности, штрафы, налоги, платежи, уведомления или госпочту.
|
|
7
|
+
|
|
8
|
+
Доступные read-only tools:
|
|
9
|
+
|
|
10
|
+
- `gosuslugi_whoami` - получить ФИО, дату рождения и краткие данные профиля.
|
|
11
|
+
- `gosuslugi_debt` - получить задолженности и суммы к оплате.
|
|
12
|
+
- `gosuslugi_notifications` - получить уведомления, включая новые непрочитанные.
|
|
13
|
+
|
|
14
|
+
Не выполняй юридически значимые действия автоматически. Оплата, отправка заявлений, изменение персональных данных, удаление учетной записи, согласия и отметка уведомлений прочитанными требуют отдельного явного подтверждения пользователя.
|
|
15
|
+
|
|
16
|
+
Если сохраненная сессия Госуслуг протухла или требуется повторная двухфакторная аутентификация, попроси пользователя выполнить `iola gosuslugi connect`.
|
package/src/cli.js
CHANGED
|
@@ -27,9 +27,10 @@ const LOCAL_CONFIG_FILE = path.join(PROJECT_IOLA_DIR, "local.json");
|
|
|
27
27
|
const BROWSER_RUNTIME_DIR = path.join(CONFIG_DIR, "browser-runtime");
|
|
28
28
|
const BROWSER_RUNTIME_PACKAGE = path.join(BROWSER_RUNTIME_DIR, "node_modules", "playwright", "package.json");
|
|
29
29
|
const GOSUSLUGI_BROWSER_PROFILE_DIR = path.join(CONFIG_DIR, "gosuslugi-browser-profile");
|
|
30
|
+
const GOSUSLUGI_BROWSER_LOCK_DIR = path.join(CONFIG_DIR, "gosuslugi-browser-profile.lock");
|
|
30
31
|
const GOSUSLUGI_DEFAULT_URL = "https://www.gosuslugi.ru/";
|
|
31
32
|
const INDEXABLE_EXTENSIONS = /\.(md|txt|csv|json|html|docx|xlsx|pptx|pdf)$/i;
|
|
32
|
-
const LOCAL_TOOLS = ["search_data", "get_card", "export_report", "file_read", "browser_open"];
|
|
33
|
+
const LOCAL_TOOLS = ["search_data", "get_card", "export_report", "file_read", "browser_open", "gosuslugi_whoami", "gosuslugi_debt", "gosuslugi_notifications"];
|
|
33
34
|
const LEGACY_LOCAL_TOOLS = ["search_local", "export_data", "run_report", "save_view"];
|
|
34
35
|
const FILE_TOOLS = ["files_tree", "files_read", "files_search", "files_write", "files_patch"];
|
|
35
36
|
const ALL_LOCAL_TOOLS = [...LOCAL_TOOLS, ...FILE_TOOLS];
|
|
@@ -180,6 +181,9 @@ const DEFAULT_AI_CONFIG = {
|
|
|
180
181
|
export_report: true,
|
|
181
182
|
file_read: false,
|
|
182
183
|
browser_open: true,
|
|
184
|
+
gosuslugi_whoami: true,
|
|
185
|
+
gosuslugi_debt: true,
|
|
186
|
+
gosuslugi_notifications: true,
|
|
183
187
|
files_tree: false,
|
|
184
188
|
files_read: false,
|
|
185
189
|
files_search: false,
|
|
@@ -210,7 +214,7 @@ const DEFAULT_AI_CONFIG = {
|
|
|
210
214
|
suggestions: true,
|
|
211
215
|
},
|
|
212
216
|
skills: {
|
|
213
|
-
enabled: ["open-data", "reports", "local-model", "local-files", "browser-agent"],
|
|
217
|
+
enabled: ["open-data", "reports", "local-model", "local-files", "browser-agent", "gosuslugi"],
|
|
214
218
|
},
|
|
215
219
|
daemon: {
|
|
216
220
|
host: "127.0.0.1",
|
|
@@ -284,6 +288,9 @@ const SLASH_COMMANDS = [
|
|
|
284
288
|
{ command: "/features list", description: "feature flags" },
|
|
285
289
|
{ command: "/gosuslugi status", description: "личное подключение Госуслуг" },
|
|
286
290
|
{ command: "/gosuslugi connect", description: "открыть личный вход Госуслуг" },
|
|
291
|
+
{ command: "/gosuslugi debt", description: "задолженности Госуслуг" },
|
|
292
|
+
{ command: "/gosuslugi notifications", description: "уведомления Госуслуг" },
|
|
293
|
+
{ command: "/gosuslugi keepalive", description: "проверка сессии каждые 30 минут" },
|
|
287
294
|
{ command: "/wiki", description: "ссылки на документацию" },
|
|
288
295
|
{ command: "/context list", description: "локальный контекст проекта" },
|
|
289
296
|
{ command: "/skills list", description: "skills" },
|
|
@@ -517,7 +524,7 @@ Usage:
|
|
|
517
524
|
iola fork SESSION_ID [TEXT]
|
|
518
525
|
iola features list|enable|disable
|
|
519
526
|
iola settings list|get|validate|doctor|init
|
|
520
|
-
iola gosuslugi terms|consent|status|connect|open|text|screenshot|logout|configure|login|userinfo
|
|
527
|
+
iola gosuslugi terms|consent|status|check|keepalive|install-keepalive|connect|open|text|screenshot|whoami|debt|notifications|mark-read|logout|configure|login|userinfo
|
|
521
528
|
iola wiki [open|links]
|
|
522
529
|
iola context list|show|init
|
|
523
530
|
iola skills list|show|paths|enable|disable|bundles|bundle|doctor
|
|
@@ -2019,6 +2026,25 @@ async function handleDb(args) {
|
|
|
2019
2026
|
return;
|
|
2020
2027
|
}
|
|
2021
2028
|
|
|
2029
|
+
if (action === "check") {
|
|
2030
|
+
const result = await gosuslugiCheck(options);
|
|
2031
|
+
if (options.json) printJson(result);
|
|
2032
|
+
else printKeyValue(result);
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
if (action === "keepalive") {
|
|
2037
|
+
await gosuslugiKeepalive(options);
|
|
2038
|
+
return;
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
if (action === "install-keepalive") {
|
|
2042
|
+
const id = addCronJob("каждые 30 минут", "gosuslugi check --silent");
|
|
2043
|
+
console.log(`Keepalive-задача добавлена: ${id}`);
|
|
2044
|
+
console.log("Для выполнения запускайте периодически: iola cron tick");
|
|
2045
|
+
return;
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2022
2048
|
if (action === "reset") {
|
|
2023
2049
|
const shouldReset = await confirm("Удалить локальную SQLite-БД iola.db? [y/N] ");
|
|
2024
2050
|
if (!shouldReset) {
|
|
@@ -2280,6 +2306,25 @@ async function handleGosuslugi(args) {
|
|
|
2280
2306
|
return;
|
|
2281
2307
|
}
|
|
2282
2308
|
|
|
2309
|
+
if (action === "check") {
|
|
2310
|
+
const result = await gosuslugiCheck(options);
|
|
2311
|
+
if (options.json) printJson(result);
|
|
2312
|
+
else printKeyValue(result);
|
|
2313
|
+
return;
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
if (action === "keepalive") {
|
|
2317
|
+
await gosuslugiKeepalive(options);
|
|
2318
|
+
return;
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
if (action === "install-keepalive") {
|
|
2322
|
+
const id = addCronJob("каждые 30 минут", "gosuslugi check --silent");
|
|
2323
|
+
console.log(`Keepalive-задача добавлена: ${id}`);
|
|
2324
|
+
console.log("Для выполнения запускайте периодически: iola cron tick");
|
|
2325
|
+
return;
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2283
2328
|
if (action === "connect") {
|
|
2284
2329
|
await gosuslugiBrowserConnect(options);
|
|
2285
2330
|
return;
|
|
@@ -2309,6 +2354,32 @@ async function handleGosuslugi(args) {
|
|
|
2309
2354
|
return;
|
|
2310
2355
|
}
|
|
2311
2356
|
|
|
2357
|
+
if (action === "whoami" || action === "profile") {
|
|
2358
|
+
const result = await gosuslugiWhoami(options);
|
|
2359
|
+
if (options.json) printJson(result);
|
|
2360
|
+
else printKeyValue(result.summary);
|
|
2361
|
+
return;
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
if (action === "debt" || action === "debts" || action === "payments") {
|
|
2365
|
+
const result = await gosuslugiDebt(options);
|
|
2366
|
+
if (options.json) printJson(result);
|
|
2367
|
+
else printGosuslugiDebt(result);
|
|
2368
|
+
return;
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
if (action === "notifications" || action === "notices") {
|
|
2372
|
+
const result = await gosuslugiNotifications(options);
|
|
2373
|
+
if (options.json) printJson(result);
|
|
2374
|
+
else printGosuslugiNotifications(result);
|
|
2375
|
+
return;
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
if (action === "mark-read") {
|
|
2379
|
+
await gosuslugiMarkNotificationsRead(options);
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2312
2383
|
if (action === "configure") {
|
|
2313
2384
|
const current = await loadConfig();
|
|
2314
2385
|
const next = {
|
|
@@ -2357,7 +2428,7 @@ async function handleGosuslugi(args) {
|
|
|
2357
2428
|
return;
|
|
2358
2429
|
}
|
|
2359
2430
|
|
|
2360
|
-
throw new Error("Команды gosuslugi: terms, consent, status, connect, open, text, screenshot, logout, configure, login, userinfo.");
|
|
2431
|
+
throw new Error("Команды gosuslugi: terms, consent, status, check, keepalive, install-keepalive, connect, open, text, screenshot, whoami, debt, notifications, mark-read, logout, configure, login, userinfo.");
|
|
2361
2432
|
}
|
|
2362
2433
|
|
|
2363
2434
|
function targetOrDefault(args, options = {}) {
|
|
@@ -5743,6 +5814,10 @@ function isCronDue(job) {
|
|
|
5743
5814
|
if (normalized.includes("каждый час") || normalized.includes("hourly")) {
|
|
5744
5815
|
return !lastRun || now.getTime() - lastRun.getTime() >= 60 * 60 * 1000;
|
|
5745
5816
|
}
|
|
5817
|
+
const everyMinutes = normalized.match(/кажд(?:ые|ую)\s+(\d+)\s*(?:мин|минут)/u) || normalized.match(/every\s+(\d+)\s*(?:m|min|minutes)/u);
|
|
5818
|
+
if (everyMinutes) {
|
|
5819
|
+
return !lastRun || now.getTime() - lastRun.getTime() >= Number(everyMinutes[1]) * 60 * 1000;
|
|
5820
|
+
}
|
|
5746
5821
|
if (normalized.includes("каждый день") || normalized.includes("daily")) {
|
|
5747
5822
|
return !lastRun || now.toISOString().slice(0, 10) !== lastRun.toISOString().slice(0, 10);
|
|
5748
5823
|
}
|
|
@@ -6010,6 +6085,12 @@ async function aiAsk(args, context = {}) {
|
|
|
6010
6085
|
throw new Error('Текст вопроса обязателен. Пример: iola ai ask "Какие школы есть на улице Петрова?"');
|
|
6011
6086
|
}
|
|
6012
6087
|
|
|
6088
|
+
if (!options.bare && isGosuslugiPersonalIntent(question)) {
|
|
6089
|
+
const answer = await answerGosuslugiQuestion(question, options);
|
|
6090
|
+
if (!options.quiet) console.log(answer);
|
|
6091
|
+
return answer;
|
|
6092
|
+
}
|
|
6093
|
+
|
|
6013
6094
|
const config = await loadConfig();
|
|
6014
6095
|
const providerConfig = await resolveUsableAiProfile(config, options);
|
|
6015
6096
|
if (providerConfig.provider === "codex") await assertPermission("codex");
|
|
@@ -6178,7 +6259,7 @@ async function buildLocalToolPlan(question, providerConfig, options) {
|
|
|
6178
6259
|
"Ты планировщик CLI iola. Верни только JSON.",
|
|
6179
6260
|
`Доступные tools: ${availableToolNames(options).join(", ")}.`,
|
|
6180
6261
|
"Схема: {\"steps\":[{\"tool\":\"search_data\",\"args\":{\"dataset\":\"schools|kindergartens|all\",\"query\":\"text\",\"limit\":10}}]}",
|
|
6181
|
-
"Минимальные tools: search_data {dataset,query,limit}, get_card {query}, export_report {name,format,output}, file_read {path}, browser_open {url}.",
|
|
6262
|
+
"Минимальные tools: search_data {dataset,query,limit}, get_card {query}, export_report {name,format,output}, file_read {path}, browser_open {url}, gosuslugi_whoami {}, gosuslugi_debt {}, gosuslugi_notifications {unread,limit}.",
|
|
6182
6263
|
"MCP tools доступны как mcp:SERVER:TOOL, например mcp:iola-local:search.",
|
|
6183
6264
|
"Для выгрузки CSV добавь export_report с format=csv и output, если пользователь назвал файл.",
|
|
6184
6265
|
`Вопрос: ${question}`,
|
|
@@ -6208,6 +6289,12 @@ function inferToolPlan(question, options = {}) {
|
|
|
6208
6289
|
const steps = [];
|
|
6209
6290
|
if (normalized.includes("без телефона")) {
|
|
6210
6291
|
steps.push({ tool: "export_report", args: { name: "missing-phones" } });
|
|
6292
|
+
} else if (/(уведомлен|сообщени|госпочт|непрочитан)/iu.test(normalized)) {
|
|
6293
|
+
steps.push({ tool: "gosuslugi_notifications", args: { unread: /непрочитан|нов/iu.test(normalized), limit: 15 } });
|
|
6294
|
+
} else if (/(задолж|долг|штраф|налог|к оплате|платеж|платёж)/iu.test(normalized)) {
|
|
6295
|
+
steps.push({ tool: "gosuslugi_debt", args: {} });
|
|
6296
|
+
} else if (/(фио|дата рождения|профиль|кто я)/iu.test(normalized) && /госуслуг/iu.test(normalized)) {
|
|
6297
|
+
steps.push({ tool: "gosuslugi_whoami", args: {} });
|
|
6211
6298
|
} else {
|
|
6212
6299
|
const query = normalized.match(/петрова|школ[а-яё ]*\d+|сад[а-яё ]*\d+|лицей[а-яё ]*\d+/iu)?.[0] || question;
|
|
6213
6300
|
steps.push({ tool: "search_data", args: { dataset, query, limit: 20 } });
|
|
@@ -6297,6 +6384,18 @@ async function executeToolPlan(plan, options = {}) {
|
|
|
6297
6384
|
const text = await runBrowserAutomation("text", { url: step.args?.url, waitMs: Number(step.args?.waitMs || 0), timeout: Number(step.args?.timeout || 30000), viewport: step.args?.viewport || "1366x768" });
|
|
6298
6385
|
current = [{ url: step.args?.url, text }];
|
|
6299
6386
|
outputs.push({ tool: step.tool, rows: 1 });
|
|
6387
|
+
} else if (step.tool === "gosuslugi_whoami") {
|
|
6388
|
+
const result = await gosuslugiWhoami(step.args || {});
|
|
6389
|
+
current = [result.summary];
|
|
6390
|
+
outputs.push({ tool: step.tool, rows: 1 });
|
|
6391
|
+
} else if (step.tool === "gosuslugi_debt") {
|
|
6392
|
+
const result = await gosuslugiDebt(step.args || {});
|
|
6393
|
+
current = [{ total: result.total, amount: result.amount, debts: result.debts }];
|
|
6394
|
+
outputs.push({ tool: step.tool, rows: result.debts.length });
|
|
6395
|
+
} else if (step.tool === "gosuslugi_notifications") {
|
|
6396
|
+
const result = await gosuslugiNotifications(step.args || {});
|
|
6397
|
+
current = [{ total: result.total, unread: result.unread, items: result.items }];
|
|
6398
|
+
outputs.push({ tool: step.tool, rows: result.items.length });
|
|
6300
6399
|
} else if (String(step.tool || "").startsWith("mcp:")) {
|
|
6301
6400
|
const result = await callConfiguredMcpTool(step.tool, step.args || {});
|
|
6302
6401
|
current = Array.isArray(result) ? result : [result];
|
|
@@ -7094,7 +7193,7 @@ function parseOptions(args) {
|
|
|
7094
7193
|
|
|
7095
7194
|
for (let index = 0; index < args.length; index += 1) {
|
|
7096
7195
|
const arg = args[index];
|
|
7097
|
-
if (arg === "--json" || arg === "--yes" || arg === "--silent" || arg === "--events" || arg === "--stream-json" || arg === "--stdio" || arg === "--system" || arg === "--headed" || arg === "--headless" || arg === "--no-history" || arg === "--summary" || arg === "--all" || arg === "--local" || arg === "--cache" || arg === "--tools" || arg === "--files" || arg === "--plan" || arg === "--trace" || arg === "--diff" || arg === "--stage" || arg === "--fts" || arg === "--bare" || arg === "--quiet" || arg === "--no-color" || arg === "--fail-on-empty" || arg === "--debug" || arg === "--fix" || arg === "--append") {
|
|
7196
|
+
if (arg === "--json" || arg === "--yes" || arg === "--silent" || arg === "--events" || arg === "--stream-json" || arg === "--stdio" || arg === "--system" || arg === "--headed" || arg === "--headless" || arg === "--no-history" || arg === "--summary" || arg === "--all" || arg === "--full" || arg === "--unread" || arg === "--once" || arg === "--local" || arg === "--cache" || arg === "--tools" || arg === "--files" || arg === "--plan" || arg === "--trace" || arg === "--diff" || arg === "--stage" || arg === "--fts" || arg === "--bare" || arg === "--quiet" || arg === "--no-color" || arg === "--fail-on-empty" || arg === "--debug" || arg === "--fix" || arg === "--append") {
|
|
7098
7197
|
result[arg.slice(2)] = true;
|
|
7099
7198
|
} else if (arg === "--check" || arg === "--upgrade-node") {
|
|
7100
7199
|
result.check = true;
|
|
@@ -7335,7 +7434,8 @@ async function buildSkillsText(config, question = "", options = {}) {
|
|
|
7335
7434
|
const chunks = [];
|
|
7336
7435
|
const selected = selectSkillsForPrompt(config, question, options);
|
|
7337
7436
|
for (const skill of listSkills(config)) {
|
|
7338
|
-
|
|
7437
|
+
const active = skill.enabled || (skill.name === "gosuslugi" && config.gosuslugi?.enabled);
|
|
7438
|
+
if (!active || !selected.has(skill.name)) continue;
|
|
7339
7439
|
const text = await readFile(skill.file, "utf8");
|
|
7340
7440
|
chunks.push(`## Skill: ${skill.name}\n${stripFrontmatter(text).trim()}`);
|
|
7341
7441
|
}
|
|
@@ -7351,6 +7451,7 @@ function selectSkillsForPrompt(config, question = "", options = {}) {
|
|
|
7351
7451
|
if (enabled.has("reports") && /(отчет|отчёт|выгруз|csv|xlsx|качество|провер)/iu.test(normalized)) selected.add("reports");
|
|
7352
7452
|
if (enabled.has("local-files") && (options.files || /(файл|папк|readme|документ|архив)/iu.test(normalized))) selected.add("local-files");
|
|
7353
7453
|
if (enabled.has("browser-agent") && /(браузер|сайт|страниц|url|https?:\/\/)/iu.test(normalized)) selected.add("browser-agent");
|
|
7454
|
+
if (enabled.has("gosuslugi") && /(госуслуг|задолж|долг|штраф|налог|к оплате|платеж|платёж|уведомлен|госпочт|фио|дата рождения)/iu.test(normalized)) selected.add("gosuslugi");
|
|
7354
7455
|
return selected;
|
|
7355
7456
|
}
|
|
7356
7457
|
|
|
@@ -7646,9 +7747,281 @@ async function gosuslugiBrowserScreenshot(url = GOSUSLUGI_DEFAULT_URL, outputFil
|
|
|
7646
7747
|
});
|
|
7647
7748
|
}
|
|
7648
7749
|
|
|
7750
|
+
async function gosuslugiWhoami(options = {}) {
|
|
7751
|
+
const data = await gosuslugiBrowserApiJson({
|
|
7752
|
+
pageUrl: "https://lk.gosuslugi.ru/settings/account",
|
|
7753
|
+
endpoint: "https://www.gosuslugi.ru/api/lk/v1/users/data",
|
|
7754
|
+
waitMs: Number(options.wait || 3000),
|
|
7755
|
+
});
|
|
7756
|
+
const person = data.person?.person || data.person || data;
|
|
7757
|
+
const summary = {
|
|
7758
|
+
fio: [data.lastName || person.lastName, data.firstName || person.firstName, data.middleName || person.middleName].filter(Boolean).join(" ") || data.formattedName || "-",
|
|
7759
|
+
birthDate: person.birthDate || data.birthDate || "-",
|
|
7760
|
+
status: data.assuranceLevel === "AL20" || person.trusted ? "Подтвержденная учетная запись" : data.assuranceLevel || "-",
|
|
7761
|
+
phone: options.full ? (data.personMobilePhone || data.mobile || "-") : maskPhone(data.personMobilePhone || data.mobile || ""),
|
|
7762
|
+
email: options.full ? (data.personEMail || data.personEmail || data.email || "-") : maskEmail(data.personEMail || data.personEmail || data.email || ""),
|
|
7763
|
+
snils: options.full ? (person.snils || data.personSnils || data.snils || "-") : maskDocument(person.snils || data.personSnils || data.snils || ""),
|
|
7764
|
+
inn: options.full ? (person.inn || data.personINN || data.inn || "-") : maskDocument(person.inn || data.personINN || data.inn || ""),
|
|
7765
|
+
};
|
|
7766
|
+
return {
|
|
7767
|
+
summary,
|
|
7768
|
+
raw: options.full ? redactGosuslugiSensitive(data, { keepPersonal: true }) : undefined,
|
|
7769
|
+
};
|
|
7770
|
+
}
|
|
7771
|
+
|
|
7772
|
+
async function gosuslugiDebt(options = {}) {
|
|
7773
|
+
const data = await gosuslugiBrowserApiJson({
|
|
7774
|
+
pageUrl: "https://www.gosuslugi.ru/pay/forPayment",
|
|
7775
|
+
endpoint: "https://www.gosuslugi.ru/api/pay/v2/informer/fetch",
|
|
7776
|
+
waitMs: Number(options.wait || 5000),
|
|
7777
|
+
});
|
|
7778
|
+
const groups = Array.isArray(data.groups) ? data.groups : [];
|
|
7779
|
+
const debts = groups.flatMap((group) => (group.bills || []).map((bill) => ({
|
|
7780
|
+
group: group.name || group.code || "-",
|
|
7781
|
+
caption: bill.caption || "-",
|
|
7782
|
+
amount: Number(bill.amount || 0),
|
|
7783
|
+
billDate: bill.billDate || "-",
|
|
7784
|
+
supplier: bill.supplierFullName || "-",
|
|
7785
|
+
document: bill.document?.typeName ? `${bill.document.typeName} ${bill.document.number || ""}`.trim() : "-",
|
|
7786
|
+
})));
|
|
7787
|
+
return {
|
|
7788
|
+
total: Number(data.summary?.total || debts.length || 0),
|
|
7789
|
+
amount: Number(data.summary?.amount || debts.reduce((sum, item) => sum + item.amount, 0)),
|
|
7790
|
+
groups: groups.map((group) => ({ name: group.name, code: group.code, total: group.summary?.total || 0, amount: group.summary?.amount || 0 })),
|
|
7791
|
+
debts,
|
|
7792
|
+
};
|
|
7793
|
+
}
|
|
7794
|
+
|
|
7795
|
+
async function gosuslugiNotifications(options = {}) {
|
|
7796
|
+
const types = "ORDER,EQUEUE,PAYMENT,GEPS,BIOMETRICS,ACCOUNT,ACCOUNT_CHILD,PROFILE,APPEAL,CLAIM,ELECTION_INFO,COMPLEX_ORDER,FEEDBACK,ORGANIZATION,BUSINESSMAN,ESIGNATURE,KND_APPEAL,LINKED_ACCOUNT,SIGN,GOSQR,INFO,PERMISSION,LICENSING,LICENSING_APPEAL,CONSTRUCTOR";
|
|
7797
|
+
const pageSize = Number(options.limit || 15);
|
|
7798
|
+
const unread = options.unread ? "true" : "false";
|
|
7799
|
+
const counters = await gosuslugiBrowserApiJson({
|
|
7800
|
+
pageUrl: `https://lk.gosuslugi.ru/notifications?type=${types}`,
|
|
7801
|
+
endpoint: `https://www.gosuslugi.ru/api/lk/v1/feeds/counters?types=${types},PARTNERS&isArchive=false`,
|
|
7802
|
+
waitMs: Number(options.wait || 3000),
|
|
7803
|
+
});
|
|
7804
|
+
const feed = await gosuslugiBrowserApiJson({
|
|
7805
|
+
pageUrl: `https://lk.gosuslugi.ru/notifications?type=${types}`,
|
|
7806
|
+
endpoint: `https://www.gosuslugi.ru/api/lk/v1/feeds/?unread=${unread}&isArchive=false&isHide=false&types=${types}&pageSize=${pageSize}&status=&startDate=&lastFeedId=&lastFeedDate=&q=`,
|
|
7807
|
+
waitMs: Number(options.wait || 3000),
|
|
7808
|
+
});
|
|
7809
|
+
const items = (feed.items || []).map((item) => ({
|
|
7810
|
+
id: item.id,
|
|
7811
|
+
unread: Boolean(item.unread),
|
|
7812
|
+
date: item.date || "-",
|
|
7813
|
+
type: item.feedType || "-",
|
|
7814
|
+
title: item.title || "-",
|
|
7815
|
+
subtitle: item.subTitle || "-",
|
|
7816
|
+
status: item.status || "-",
|
|
7817
|
+
summary: summarizeNotificationData(item.data),
|
|
7818
|
+
}));
|
|
7819
|
+
return {
|
|
7820
|
+
total: counters.total || feed.items?.length || 0,
|
|
7821
|
+
unread: counters.unread || items.filter((item) => item.unread).length,
|
|
7822
|
+
counters: counters.counter || [],
|
|
7823
|
+
hasMore: Boolean(feed.hasMore),
|
|
7824
|
+
items,
|
|
7825
|
+
};
|
|
7826
|
+
}
|
|
7827
|
+
|
|
7828
|
+
async function gosuslugiMarkNotificationsRead(options = {}) {
|
|
7829
|
+
await requireGosuslugiConsent();
|
|
7830
|
+
if (!options.yes) {
|
|
7831
|
+
const ok = await confirm("Отметить уведомления Госуслуг прочитанными? Это изменит состояние личного кабинета. [y/N] ");
|
|
7832
|
+
if (!ok) {
|
|
7833
|
+
console.log("Операция отменена.");
|
|
7834
|
+
return;
|
|
7835
|
+
}
|
|
7836
|
+
}
|
|
7837
|
+
await gosuslugiBrowserClickText({
|
|
7838
|
+
pageUrl: "https://lk.gosuslugi.ru/notifications?type=ORDER,EQUEUE,PAYMENT,GEPS,BIOMETRICS,ACCOUNT,ACCOUNT_CHILD,PROFILE,APPEAL,CLAIM,ELECTION_INFO,COMPLEX_ORDER,FEEDBACK,ORGANIZATION,BUSINESSMAN,ESIGNATURE,KND_APPEAL,LINKED_ACCOUNT,SIGN,GOSQR,INFO,PERMISSION,LICENSING,LICENSING_APPEAL,CONSTRUCTOR",
|
|
7839
|
+
text: "Прочитать все",
|
|
7840
|
+
waitMs: Number(options.wait || 5000),
|
|
7841
|
+
});
|
|
7842
|
+
console.log("Команда отметки прочитанным выполнена. Проверьте статус: iola gosuslugi notifications --unread");
|
|
7843
|
+
}
|
|
7844
|
+
|
|
7845
|
+
async function gosuslugiCheck(options = {}) {
|
|
7846
|
+
try {
|
|
7847
|
+
const result = await gosuslugiWhoami({ wait: options.wait || 2000 });
|
|
7848
|
+
return {
|
|
7849
|
+
status: "ok",
|
|
7850
|
+
authorized: "yes",
|
|
7851
|
+
fio: result.summary.fio,
|
|
7852
|
+
checkedAt: new Date().toISOString(),
|
|
7853
|
+
nextAction: "-",
|
|
7854
|
+
};
|
|
7855
|
+
} catch (error) {
|
|
7856
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7857
|
+
const result = {
|
|
7858
|
+
status: "needs-login",
|
|
7859
|
+
authorized: "unknown",
|
|
7860
|
+
checkedAt: new Date().toISOString(),
|
|
7861
|
+
nextAction: "iola gosuslugi connect",
|
|
7862
|
+
error: message,
|
|
7863
|
+
};
|
|
7864
|
+
if (!options.silent) {
|
|
7865
|
+
console.error("Сессия Госуслуг недоступна или требует повторный вход.");
|
|
7866
|
+
console.error("Запустите: iola gosuslugi connect");
|
|
7867
|
+
}
|
|
7868
|
+
return result;
|
|
7869
|
+
}
|
|
7870
|
+
}
|
|
7871
|
+
|
|
7872
|
+
async function gosuslugiKeepalive(options = {}) {
|
|
7873
|
+
const intervalMs = parseDurationMs(options.interval || "30m");
|
|
7874
|
+
const once = Boolean(options.once);
|
|
7875
|
+
console.log(`Gosuslugi keepalive запущен. Интервал: ${Math.round(intervalMs / 60000)} мин.`);
|
|
7876
|
+
console.log("Остановить: Ctrl+C");
|
|
7877
|
+
while (true) {
|
|
7878
|
+
const result = await gosuslugiCheck({ silent: true });
|
|
7879
|
+
const line = result.status === "ok"
|
|
7880
|
+
? `[${result.checkedAt}] Госуслуги: сессия активна (${result.fio || "-"})`
|
|
7881
|
+
: `[${result.checkedAt}] Госуслуги: нужен повторный вход. Запустите: iola gosuslugi connect`;
|
|
7882
|
+
console.log(line);
|
|
7883
|
+
if (once) return;
|
|
7884
|
+
await sleep(intervalMs);
|
|
7885
|
+
}
|
|
7886
|
+
}
|
|
7887
|
+
|
|
7888
|
+
function parseDurationMs(value) {
|
|
7889
|
+
const text = String(value || "30m").trim().toLocaleLowerCase("ru-RU");
|
|
7890
|
+
const match = text.match(/^(\d+(?:[.,]\d+)?)(ms|s|m|h|мин|минут|час|часа|часов)?$/u);
|
|
7891
|
+
if (!match) throw new Error("Интервал задается как 30m, 1800s или 1h.");
|
|
7892
|
+
const amount = Number(match[1].replace(",", "."));
|
|
7893
|
+
const unit = match[2] || "m";
|
|
7894
|
+
if (unit === "ms") return Math.max(1000, amount);
|
|
7895
|
+
if (unit === "s") return Math.max(1000, amount * 1000);
|
|
7896
|
+
if (unit === "h" || unit.startsWith("час")) return Math.max(1000, amount * 60 * 60 * 1000);
|
|
7897
|
+
return Math.max(1000, amount * 60 * 1000);
|
|
7898
|
+
}
|
|
7899
|
+
|
|
7900
|
+
function printGosuslugiDebt(result) {
|
|
7901
|
+
printKeyValue({
|
|
7902
|
+
total: result.total,
|
|
7903
|
+
amount: `${formatRub(result.amount)} Р`,
|
|
7904
|
+
});
|
|
7905
|
+
if (!result.debts.length) {
|
|
7906
|
+
console.log("Задолженности не найдены.");
|
|
7907
|
+
return;
|
|
7908
|
+
}
|
|
7909
|
+
printTable(result.debts.map((item) => ({
|
|
7910
|
+
group: item.group,
|
|
7911
|
+
amount: `${formatRub(item.amount)} Р`,
|
|
7912
|
+
date: item.billDate,
|
|
7913
|
+
caption: item.caption,
|
|
7914
|
+
})), [
|
|
7915
|
+
["group", "Группа"],
|
|
7916
|
+
["amount", "Сумма"],
|
|
7917
|
+
["date", "Дата"],
|
|
7918
|
+
["caption", "Описание"],
|
|
7919
|
+
]);
|
|
7920
|
+
}
|
|
7921
|
+
|
|
7922
|
+
function printGosuslugiNotifications(result) {
|
|
7923
|
+
printKeyValue({ total: result.total, unread: result.unread, hasMore: result.hasMore ? "yes" : "no" });
|
|
7924
|
+
printTable(result.items.map((item) => ({
|
|
7925
|
+
unread: item.unread ? "new" : "read",
|
|
7926
|
+
date: item.date,
|
|
7927
|
+
type: item.type,
|
|
7928
|
+
title: item.title,
|
|
7929
|
+
subtitle: item.subtitle,
|
|
7930
|
+
summary: item.summary,
|
|
7931
|
+
})), [
|
|
7932
|
+
["unread", "Статус"],
|
|
7933
|
+
["date", "Дата"],
|
|
7934
|
+
["type", "Тип"],
|
|
7935
|
+
["title", "Заголовок"],
|
|
7936
|
+
["subtitle", "Подзаголовок"],
|
|
7937
|
+
["summary", "Детали"],
|
|
7938
|
+
]);
|
|
7939
|
+
}
|
|
7940
|
+
|
|
7941
|
+
function summarizeNotificationData(data) {
|
|
7942
|
+
if (!data || typeof data !== "object") return "";
|
|
7943
|
+
const snippets = Array.isArray(data.snippets) ? data.snippets : [];
|
|
7944
|
+
if (snippets.length) {
|
|
7945
|
+
const first = snippets[0];
|
|
7946
|
+
return [first.orgName, first.address, first.date].filter(Boolean).join(" | ");
|
|
7947
|
+
}
|
|
7948
|
+
return [data.messageType, data.messageUuid, data.orderId, data.passCodeEpguCode].filter(Boolean).join(" | ");
|
|
7949
|
+
}
|
|
7950
|
+
|
|
7951
|
+
function formatRub(value) {
|
|
7952
|
+
return Number(value || 0).toLocaleString("ru-RU", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
7953
|
+
}
|
|
7954
|
+
|
|
7955
|
+
function isGosuslugiPersonalIntent(question) {
|
|
7956
|
+
const normalized = String(question || "").toLocaleLowerCase("ru-RU");
|
|
7957
|
+
return /(госуслуг|задолж|долг|штраф|налог|к оплате|платеж|платёж|уведомлен|госпочт|фио|дата рождения)/iu.test(normalized);
|
|
7958
|
+
}
|
|
7959
|
+
|
|
7960
|
+
async function answerGosuslugiQuestion(question, options = {}) {
|
|
7961
|
+
const normalized = String(question || "").toLocaleLowerCase("ru-RU");
|
|
7962
|
+
if (/(уведомлен|сообщени|госпочт|непрочитан)/iu.test(normalized)) {
|
|
7963
|
+
const result = await gosuslugiNotifications({ unread: /непрочитан|нов/iu.test(normalized), limit: options.limit || 10 });
|
|
7964
|
+
const lines = [`На Госуслугах: всего уведомлений ${result.total}, непрочитанных ${result.unread}.`];
|
|
7965
|
+
const items = result.items.slice(0, Number(options.limit || 5));
|
|
7966
|
+
if (items.length) {
|
|
7967
|
+
lines.push("");
|
|
7968
|
+
for (const item of items) {
|
|
7969
|
+
lines.push(`- ${item.unread ? "новое" : "прочитано"}: ${item.title} — ${item.subtitle} (${item.date})`);
|
|
7970
|
+
}
|
|
7971
|
+
}
|
|
7972
|
+
return lines.join("\n");
|
|
7973
|
+
}
|
|
7974
|
+
if (/(задолж|долг|штраф|налог|к оплате|платеж|платёж)/iu.test(normalized)) {
|
|
7975
|
+
const result = await gosuslugiDebt(options);
|
|
7976
|
+
if (!result.debts.length) return "На Госуслугах задолженности к оплате не найдены.";
|
|
7977
|
+
const lines = [`На Госуслугах найдено задолженностей: ${result.total}. Общая сумма: ${formatRub(result.amount)} Р.`];
|
|
7978
|
+
for (const item of result.debts) {
|
|
7979
|
+
lines.push(`- ${item.group}: ${formatRub(item.amount)} Р — ${item.caption}`);
|
|
7980
|
+
}
|
|
7981
|
+
return lines.join("\n");
|
|
7982
|
+
}
|
|
7983
|
+
const result = await gosuslugiWhoami(options);
|
|
7984
|
+
return [
|
|
7985
|
+
`ФИО: ${result.summary.fio}`,
|
|
7986
|
+
`Дата рождения: ${result.summary.birthDate}`,
|
|
7987
|
+
`Статус: ${result.summary.status}`,
|
|
7988
|
+
].join("\n");
|
|
7989
|
+
}
|
|
7990
|
+
|
|
7991
|
+
function maskPhone(value) {
|
|
7992
|
+
const text = String(value || "");
|
|
7993
|
+
return text.replace(/(\+?\d)([\d\s()-]{4,})(\d{2})$/u, "$1***$3") || "-";
|
|
7994
|
+
}
|
|
7995
|
+
|
|
7996
|
+
function maskEmail(value) {
|
|
7997
|
+
const text = String(value || "");
|
|
7998
|
+
const [name, domain] = text.split("@");
|
|
7999
|
+
if (!name || !domain) return text || "-";
|
|
8000
|
+
return `${name.slice(0, 2)}***@${domain}`;
|
|
8001
|
+
}
|
|
8002
|
+
|
|
8003
|
+
function maskDocument(value) {
|
|
8004
|
+
const digits = String(value || "").replace(/\D+/g, "");
|
|
8005
|
+
if (!digits) return "-";
|
|
8006
|
+
return `***${digits.slice(-4)}`;
|
|
8007
|
+
}
|
|
8008
|
+
|
|
8009
|
+
function redactGosuslugiSensitive(value, options = {}) {
|
|
8010
|
+
if (Array.isArray(value)) return value.map((item) => redactGosuslugiSensitive(item, options));
|
|
8011
|
+
if (!value || typeof value !== "object") return value;
|
|
8012
|
+
const result = {};
|
|
8013
|
+
for (const [key, item] of Object.entries(value)) {
|
|
8014
|
+
if (/token|cookie|session|password|secret|jwt|auth/i.test(key)) result[key] = "[redacted]";
|
|
8015
|
+
else if (!options.keepPersonal && /(snils|inn|passport|number|series|address|mobile|email|phone)/i.test(key)) result[key] = "[redacted]";
|
|
8016
|
+
else result[key] = redactGosuslugiSensitive(item, options);
|
|
8017
|
+
}
|
|
8018
|
+
return result;
|
|
8019
|
+
}
|
|
8020
|
+
|
|
7649
8021
|
async function runPersistentBrowserAutomation(action, params) {
|
|
7650
8022
|
await ensureBrowserRuntime();
|
|
7651
8023
|
await mkdir(params.userDataDir, { recursive: true });
|
|
8024
|
+
const releaseLock = params.userDataDir === GOSUSLUGI_BROWSER_PROFILE_DIR ? await acquireDirectoryLock(GOSUSLUGI_BROWSER_LOCK_DIR, 180000) : async () => {};
|
|
7652
8025
|
const scriptFile = path.join(BROWSER_RUNTIME_DIR, `iola-browser-profile-${Date.now()}-${Math.random().toString(16).slice(2)}.mjs`);
|
|
7653
8026
|
await writeFile(scriptFile, persistentBrowserAutomationScript(action, params), "utf8");
|
|
7654
8027
|
try {
|
|
@@ -7657,9 +8030,57 @@ async function runPersistentBrowserAutomation(action, params) {
|
|
|
7657
8030
|
return result.stdout?.trim() || "";
|
|
7658
8031
|
} finally {
|
|
7659
8032
|
await rm(scriptFile, { force: true }).catch(() => {});
|
|
8033
|
+
await releaseLock();
|
|
8034
|
+
}
|
|
8035
|
+
}
|
|
8036
|
+
|
|
8037
|
+
async function acquireDirectoryLock(lockDir, timeoutMs = 60000) {
|
|
8038
|
+
const started = Date.now();
|
|
8039
|
+
while (true) {
|
|
8040
|
+
try {
|
|
8041
|
+
await mkdir(lockDir, { recursive: false });
|
|
8042
|
+
await writeFile(path.join(lockDir, "owner.json"), JSON.stringify({ pid: process.pid, startedAt: new Date().toISOString() }, null, 2), "utf8").catch(() => {});
|
|
8043
|
+
return async () => {
|
|
8044
|
+
await rm(lockDir, { recursive: true, force: true }).catch(() => {});
|
|
8045
|
+
};
|
|
8046
|
+
} catch {
|
|
8047
|
+
if (Date.now() - started > timeoutMs) {
|
|
8048
|
+
throw new Error("Браузерный профиль Госуслуг занят другим процессом. Закройте окно Госуслуг или повторите команду позже.");
|
|
8049
|
+
}
|
|
8050
|
+
await sleep(1000);
|
|
8051
|
+
}
|
|
7660
8052
|
}
|
|
7661
8053
|
}
|
|
7662
8054
|
|
|
8055
|
+
async function gosuslugiBrowserApiJson(params) {
|
|
8056
|
+
await requireGosuslugiConsent();
|
|
8057
|
+
await ensureBrowserRuntimeForGosuslugi();
|
|
8058
|
+
const raw = await runPersistentBrowserAutomation("api-json", {
|
|
8059
|
+
pageUrl: params.pageUrl || GOSUSLUGI_DEFAULT_URL,
|
|
8060
|
+
endpoint: params.endpoint,
|
|
8061
|
+
userDataDir: GOSUSLUGI_BROWSER_PROFILE_DIR,
|
|
8062
|
+
headed: params.headed !== false,
|
|
8063
|
+
waitMs: Number(params.waitMs || 0),
|
|
8064
|
+
timeout: Number(params.timeout || 60000),
|
|
8065
|
+
viewport: params.viewport || "1366x768",
|
|
8066
|
+
});
|
|
8067
|
+
return JSON.parse(raw);
|
|
8068
|
+
}
|
|
8069
|
+
|
|
8070
|
+
async function gosuslugiBrowserClickText(params) {
|
|
8071
|
+
await requireGosuslugiConsent();
|
|
8072
|
+
await ensureBrowserRuntimeForGosuslugi();
|
|
8073
|
+
return runPersistentBrowserAutomation("click-text", {
|
|
8074
|
+
pageUrl: params.pageUrl || GOSUSLUGI_DEFAULT_URL,
|
|
8075
|
+
text: params.text,
|
|
8076
|
+
userDataDir: GOSUSLUGI_BROWSER_PROFILE_DIR,
|
|
8077
|
+
headed: true,
|
|
8078
|
+
waitMs: Number(params.waitMs || 3000),
|
|
8079
|
+
timeout: Number(params.timeout || 60000),
|
|
8080
|
+
viewport: params.viewport || "1366x768",
|
|
8081
|
+
});
|
|
8082
|
+
}
|
|
8083
|
+
|
|
7663
8084
|
function persistentBrowserAutomationScript(action, params) {
|
|
7664
8085
|
return `
|
|
7665
8086
|
import { chromium } from "playwright";
|
|
@@ -7673,7 +8094,7 @@ const context = await chromium.launchPersistentContext(params.userDataDir, {
|
|
|
7673
8094
|
context.setDefaultTimeout(params.timeout || 60000);
|
|
7674
8095
|
const page = context.pages()[0] || await context.newPage();
|
|
7675
8096
|
try {
|
|
7676
|
-
await page.goto(params.url, { waitUntil: "domcontentloaded", timeout: params.timeout || 60000 });
|
|
8097
|
+
await page.goto(params.url || params.pageUrl, { waitUntil: "domcontentloaded", timeout: params.timeout || 60000 });
|
|
7677
8098
|
if (params.waitMs) await page.waitForTimeout(params.waitMs);
|
|
7678
8099
|
if (action === "open") {
|
|
7679
8100
|
if (params.headed) {
|
|
@@ -7688,6 +8109,21 @@ try {
|
|
|
7688
8109
|
console.log((await page.locator("body").innerText()).trim());
|
|
7689
8110
|
} else if (action === "screenshot") {
|
|
7690
8111
|
await page.screenshot({ path: params.output, fullPage: true });
|
|
8112
|
+
} else if (action === "api-json") {
|
|
8113
|
+
const data = await page.evaluate(async (endpoint) => {
|
|
8114
|
+
const response = await fetch(endpoint, {
|
|
8115
|
+
credentials: "include",
|
|
8116
|
+
headers: { accept: "application/json" },
|
|
8117
|
+
});
|
|
8118
|
+
const text = await response.text();
|
|
8119
|
+
if (!response.ok) throw new Error(response.status + " " + response.statusText + ": " + text.slice(0, 500));
|
|
8120
|
+
return JSON.parse(text);
|
|
8121
|
+
}, params.endpoint);
|
|
8122
|
+
console.log(JSON.stringify(data));
|
|
8123
|
+
} else if (action === "click-text") {
|
|
8124
|
+
await page.getByText(params.text, { exact: true }).first().click();
|
|
8125
|
+
if (params.waitMs) await page.waitForTimeout(params.waitMs);
|
|
8126
|
+
console.log((await page.locator("body").innerText()).trim().slice(0, 4000));
|
|
7691
8127
|
}
|
|
7692
8128
|
} finally {
|
|
7693
8129
|
await context.close().catch(() => {});
|
|
@@ -7925,6 +8361,9 @@ function mcpTools() {
|
|
|
7925
8361
|
{ name: "report", description: "Запуск встроенного отчета.", inputSchema: schema({ name: { type: "string" }, format: { type: "string" }, output: { type: "string" } }) },
|
|
7926
8362
|
{ name: "browser.text", description: "Открыть страницу в headless Chromium и вернуть видимый текст.", inputSchema: schema({ url: { type: "string" }, waitMs: { type: "number" } }) },
|
|
7927
8363
|
{ name: "browser.screenshot", description: "Сделать скриншот страницы через Chromium.", inputSchema: schema({ url: { type: "string" }, output: { type: "string" }, waitMs: { type: "number" } }) },
|
|
8364
|
+
{ name: "gosuslugi.whoami", description: "Прочитать ФИО и дату рождения из личного профиля Госуслуг через локальный браузерный профиль.", inputSchema: schema({ full: { type: "boolean" } }) },
|
|
8365
|
+
{ name: "gosuslugi.debt", description: "Прочитать задолженности и платежи к оплате на Госуслугах.", inputSchema: schema() },
|
|
8366
|
+
{ name: "gosuslugi.notifications", description: "Прочитать уведомления Госуслуг.", inputSchema: schema({ unread: { type: "boolean" }, limit: { type: "number" } }) },
|
|
7928
8367
|
];
|
|
7929
8368
|
}
|
|
7930
8369
|
|
|
@@ -7962,6 +8401,9 @@ async function callMcpTool(name, args = {}) {
|
|
|
7962
8401
|
await runBrowserAutomation("screenshot", { url: args.url, output, waitMs: Number(args.waitMs || 0), timeout: Number(args.timeout || 30000), viewport: args.viewport || "1366x768" });
|
|
7963
8402
|
return { output };
|
|
7964
8403
|
}
|
|
8404
|
+
if (name === "gosuslugi.whoami") return gosuslugiWhoami(args);
|
|
8405
|
+
if (name === "gosuslugi.debt") return gosuslugiDebt(args);
|
|
8406
|
+
if (name === "gosuslugi.notifications") return gosuslugiNotifications(args);
|
|
7965
8407
|
return executeRpc(name, { ...args, _: [] });
|
|
7966
8408
|
}
|
|
7967
8409
|
|
|
@@ -9198,6 +9640,10 @@ function setConfigValue(config, key, value) {
|
|
|
9198
9640
|
current[parts.at(-1)] = value;
|
|
9199
9641
|
}
|
|
9200
9642
|
|
|
9643
|
+
function sleep(ms) {
|
|
9644
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9645
|
+
}
|
|
9646
|
+
|
|
9201
9647
|
async function loadSecrets() {
|
|
9202
9648
|
try {
|
|
9203
9649
|
return JSON.parse(await readFile(SECRETS_FILE, "utf8"));
|
|
@@ -34,3 +34,9 @@ iola cron delete 1
|
|
|
34
34
|
|
|
35
35
|
`cron tick` проверяет задачи, которые пора выполнить. Его можно запускать вручную, через Windows Task Scheduler или другой планировщик.
|
|
36
36
|
|
|
37
|
+
Пример проверки сессии Госуслуг каждые 30 минут:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
iola gosuslugi install-keepalive
|
|
41
|
+
iola cron tick
|
|
42
|
+
```
|
|
@@ -95,12 +95,19 @@ iola context list
|
|
|
95
95
|
iola settings list
|
|
96
96
|
iola settings validate
|
|
97
97
|
iola gosuslugi status
|
|
98
|
+
iola gosuslugi check
|
|
99
|
+
iola gosuslugi keepalive
|
|
100
|
+
iola gosuslugi install-keepalive
|
|
98
101
|
iola gosuslugi terms
|
|
99
102
|
iola gosuslugi consent
|
|
100
103
|
iola gosuslugi connect
|
|
101
104
|
iola gosuslugi open
|
|
102
105
|
iola gosuslugi text https://www.gosuslugi.ru/
|
|
103
106
|
iola gosuslugi screenshot https://www.gosuslugi.ru/ --output gosuslugi.png
|
|
107
|
+
iola gosuslugi whoami
|
|
108
|
+
iola gosuslugi debt
|
|
109
|
+
iola gosuslugi notifications --unread
|
|
110
|
+
iola gosuslugi mark-read
|
|
104
111
|
iola gosuslugi logout --all
|
|
105
112
|
iola gosuslugi configure --auth-url URL --token-url URL --client-id ID --scope openid
|
|
106
113
|
iola gosuslugi login
|
|
@@ -45,14 +45,69 @@ iola gosuslugi text https://www.gosuslugi.ru/
|
|
|
45
45
|
iola gosuslugi screenshot https://www.gosuslugi.ru/ --output gosuslugi.png
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
## Read-only данные
|
|
49
|
+
|
|
50
|
+
Краткие данные профиля:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
iola gosuslugi whoami
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Задолженности и платежи к оплате:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
iola gosuslugi debt
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Уведомления:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
iola gosuslugi notifications
|
|
66
|
+
iola gosuslugi notifications --unread
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Отметить уведомления прочитанными можно только отдельной командой с подтверждением:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
iola gosuslugi mark-read
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
AI-агент может использовать read-only tools Госуслуг, если пользователь спрашивает про ФИО, дату рождения, задолженности, штрафы, налоги, платежи или уведомления.
|
|
76
|
+
|
|
48
77
|
## Статус
|
|
49
78
|
|
|
50
79
|
```bash
|
|
51
80
|
iola gosuslugi status
|
|
81
|
+
iola gosuslugi check
|
|
52
82
|
```
|
|
53
83
|
|
|
54
84
|
Команда показывает, принято ли согласие, где лежит локальный профиль и когда он был создан.
|
|
55
85
|
|
|
86
|
+
## Keepalive
|
|
87
|
+
|
|
88
|
+
Сессию Госуслуг нельзя сделать вечной: портал сам управляет сроком жизни входа и может попросить повторную двухфакторную аутентификацию. CLI может только мягко проверять сохраненный профиль.
|
|
89
|
+
|
|
90
|
+
Запуск проверки каждые 30 минут:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
iola gosuslugi keepalive
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Однократная проверка:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
iola gosuslugi keepalive --once
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Добавить локальную cron-задачу CLI:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
iola gosuslugi install-keepalive
|
|
106
|
+
iola cron tick
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`cron tick` нужно запускать системным планировщиком или вручную. Если сессия протухла, CLI попросит выполнить `iola gosuslugi connect`.
|
|
110
|
+
|
|
56
111
|
## Отключение
|
|
57
112
|
|
|
58
113
|
Удалить локальное подключение:
|
|
@@ -68,6 +123,7 @@ iola gosuslugi logout --all
|
|
|
68
123
|
- CLI работает только с тем, что доступно пользователю в локальном браузере.
|
|
69
124
|
- CLI не извлекает cookies, session tokens или внутренние токены Госуслуг.
|
|
70
125
|
- Юридически значимые действия должны требовать отдельного подтверждения пользователя.
|
|
126
|
+
- Сессия браузерного профиля может протухнуть. Если Госуслуги попросят повторный вход или 2FA, выполните `iola gosuslugi connect`.
|
|
71
127
|
- Если Госуслуги попросят повторный вход, код или подтверждение, пользователь проходит его сам в открытом окне.
|
|
72
128
|
|
|
73
129
|
OAuth/OIDC-команды `configure`, `login`, `userinfo` оставлены для случая, если у пользователя есть официально зарегистрированное подключение ЕСИА.
|