@iola_adm/iola-cli 0.1.59 → 0.1.60
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 +4 -0
- package/package.json +1 -1
- package/skills/gosuslugi/SKILL.md +16 -0
- package/src/cli.js +356 -8
- package/wiki//320/232/320/276/320/274/320/260/320/275/320/264/321/213.md +4 -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 +30 -0
package/README.md
CHANGED
|
@@ -86,6 +86,9 @@ 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
|
|
89
92
|
```
|
|
90
93
|
|
|
91
94
|
Локальная модель через Ollama:
|
|
@@ -134,6 +137,7 @@ iola version --check
|
|
|
134
137
|
- дополнительные stdio MCP-серверы можно добавить в `~/.iola/config.json` в раздел `mcp.servers`;
|
|
135
138
|
- браузерный runtime через Playwright: чтение страниц, скриншоты, PDF, клики, ввод и eval;
|
|
136
139
|
- личное локальное подключение Госуслуг через отдельный браузерный профиль на ПК пользователя;
|
|
140
|
+
- read-only tools Госуслуг для агента: ФИО, дата рождения, задолженности и уведомления;
|
|
137
141
|
- управляемые локальные файловые операции с режимами `locked`, `read-only`, `workspace-write`, `full-access`;
|
|
138
142
|
- планы выполнения, traces, tasks, artifacts, snapshots и policy-профили;
|
|
139
143
|
- экспорт отчетов в 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,8 @@ 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: "уведомления Госуслуг" },
|
|
287
293
|
{ command: "/wiki", description: "ссылки на документацию" },
|
|
288
294
|
{ command: "/context list", description: "локальный контекст проекта" },
|
|
289
295
|
{ command: "/skills list", description: "skills" },
|
|
@@ -517,7 +523,7 @@ Usage:
|
|
|
517
523
|
iola fork SESSION_ID [TEXT]
|
|
518
524
|
iola features list|enable|disable
|
|
519
525
|
iola settings list|get|validate|doctor|init
|
|
520
|
-
iola gosuslugi terms|consent|status|connect|open|text|screenshot|logout|configure|login|userinfo
|
|
526
|
+
iola gosuslugi terms|consent|status|connect|open|text|screenshot|whoami|debt|notifications|mark-read|logout|configure|login|userinfo
|
|
521
527
|
iola wiki [open|links]
|
|
522
528
|
iola context list|show|init
|
|
523
529
|
iola skills list|show|paths|enable|disable|bundles|bundle|doctor
|
|
@@ -2309,6 +2315,32 @@ async function handleGosuslugi(args) {
|
|
|
2309
2315
|
return;
|
|
2310
2316
|
}
|
|
2311
2317
|
|
|
2318
|
+
if (action === "whoami" || action === "profile") {
|
|
2319
|
+
const result = await gosuslugiWhoami(options);
|
|
2320
|
+
if (options.json) printJson(result);
|
|
2321
|
+
else printKeyValue(result.summary);
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
if (action === "debt" || action === "debts" || action === "payments") {
|
|
2326
|
+
const result = await gosuslugiDebt(options);
|
|
2327
|
+
if (options.json) printJson(result);
|
|
2328
|
+
else printGosuslugiDebt(result);
|
|
2329
|
+
return;
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
if (action === "notifications" || action === "notices") {
|
|
2333
|
+
const result = await gosuslugiNotifications(options);
|
|
2334
|
+
if (options.json) printJson(result);
|
|
2335
|
+
else printGosuslugiNotifications(result);
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
if (action === "mark-read") {
|
|
2340
|
+
await gosuslugiMarkNotificationsRead(options);
|
|
2341
|
+
return;
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2312
2344
|
if (action === "configure") {
|
|
2313
2345
|
const current = await loadConfig();
|
|
2314
2346
|
const next = {
|
|
@@ -2357,7 +2389,7 @@ async function handleGosuslugi(args) {
|
|
|
2357
2389
|
return;
|
|
2358
2390
|
}
|
|
2359
2391
|
|
|
2360
|
-
throw new Error("Команды gosuslugi: terms, consent, status, connect, open, text, screenshot, logout, configure, login, userinfo.");
|
|
2392
|
+
throw new Error("Команды gosuslugi: terms, consent, status, connect, open, text, screenshot, whoami, debt, notifications, mark-read, logout, configure, login, userinfo.");
|
|
2361
2393
|
}
|
|
2362
2394
|
|
|
2363
2395
|
function targetOrDefault(args, options = {}) {
|
|
@@ -6010,6 +6042,12 @@ async function aiAsk(args, context = {}) {
|
|
|
6010
6042
|
throw new Error('Текст вопроса обязателен. Пример: iola ai ask "Какие школы есть на улице Петрова?"');
|
|
6011
6043
|
}
|
|
6012
6044
|
|
|
6045
|
+
if (!options.bare && isGosuslugiPersonalIntent(question)) {
|
|
6046
|
+
const answer = await answerGosuslugiQuestion(question, options);
|
|
6047
|
+
if (!options.quiet) console.log(answer);
|
|
6048
|
+
return answer;
|
|
6049
|
+
}
|
|
6050
|
+
|
|
6013
6051
|
const config = await loadConfig();
|
|
6014
6052
|
const providerConfig = await resolveUsableAiProfile(config, options);
|
|
6015
6053
|
if (providerConfig.provider === "codex") await assertPermission("codex");
|
|
@@ -6178,7 +6216,7 @@ async function buildLocalToolPlan(question, providerConfig, options) {
|
|
|
6178
6216
|
"Ты планировщик CLI iola. Верни только JSON.",
|
|
6179
6217
|
`Доступные tools: ${availableToolNames(options).join(", ")}.`,
|
|
6180
6218
|
"Схема: {\"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}.",
|
|
6219
|
+
"Минимальные 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
6220
|
"MCP tools доступны как mcp:SERVER:TOOL, например mcp:iola-local:search.",
|
|
6183
6221
|
"Для выгрузки CSV добавь export_report с format=csv и output, если пользователь назвал файл.",
|
|
6184
6222
|
`Вопрос: ${question}`,
|
|
@@ -6208,6 +6246,12 @@ function inferToolPlan(question, options = {}) {
|
|
|
6208
6246
|
const steps = [];
|
|
6209
6247
|
if (normalized.includes("без телефона")) {
|
|
6210
6248
|
steps.push({ tool: "export_report", args: { name: "missing-phones" } });
|
|
6249
|
+
} else if (/(уведомлен|сообщени|госпочт|непрочитан)/iu.test(normalized)) {
|
|
6250
|
+
steps.push({ tool: "gosuslugi_notifications", args: { unread: /непрочитан|нов/iu.test(normalized), limit: 15 } });
|
|
6251
|
+
} else if (/(задолж|долг|штраф|налог|к оплате|платеж|платёж)/iu.test(normalized)) {
|
|
6252
|
+
steps.push({ tool: "gosuslugi_debt", args: {} });
|
|
6253
|
+
} else if (/(фио|дата рождения|профиль|кто я)/iu.test(normalized) && /госуслуг/iu.test(normalized)) {
|
|
6254
|
+
steps.push({ tool: "gosuslugi_whoami", args: {} });
|
|
6211
6255
|
} else {
|
|
6212
6256
|
const query = normalized.match(/петрова|школ[а-яё ]*\d+|сад[а-яё ]*\d+|лицей[а-яё ]*\d+/iu)?.[0] || question;
|
|
6213
6257
|
steps.push({ tool: "search_data", args: { dataset, query, limit: 20 } });
|
|
@@ -6297,6 +6341,18 @@ async function executeToolPlan(plan, options = {}) {
|
|
|
6297
6341
|
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
6342
|
current = [{ url: step.args?.url, text }];
|
|
6299
6343
|
outputs.push({ tool: step.tool, rows: 1 });
|
|
6344
|
+
} else if (step.tool === "gosuslugi_whoami") {
|
|
6345
|
+
const result = await gosuslugiWhoami(step.args || {});
|
|
6346
|
+
current = [result.summary];
|
|
6347
|
+
outputs.push({ tool: step.tool, rows: 1 });
|
|
6348
|
+
} else if (step.tool === "gosuslugi_debt") {
|
|
6349
|
+
const result = await gosuslugiDebt(step.args || {});
|
|
6350
|
+
current = [{ total: result.total, amount: result.amount, debts: result.debts }];
|
|
6351
|
+
outputs.push({ tool: step.tool, rows: result.debts.length });
|
|
6352
|
+
} else if (step.tool === "gosuslugi_notifications") {
|
|
6353
|
+
const result = await gosuslugiNotifications(step.args || {});
|
|
6354
|
+
current = [{ total: result.total, unread: result.unread, items: result.items }];
|
|
6355
|
+
outputs.push({ tool: step.tool, rows: result.items.length });
|
|
6300
6356
|
} else if (String(step.tool || "").startsWith("mcp:")) {
|
|
6301
6357
|
const result = await callConfiguredMcpTool(step.tool, step.args || {});
|
|
6302
6358
|
current = Array.isArray(result) ? result : [result];
|
|
@@ -7094,7 +7150,7 @@ function parseOptions(args) {
|
|
|
7094
7150
|
|
|
7095
7151
|
for (let index = 0; index < args.length; index += 1) {
|
|
7096
7152
|
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") {
|
|
7153
|
+
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 === "--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
7154
|
result[arg.slice(2)] = true;
|
|
7099
7155
|
} else if (arg === "--check" || arg === "--upgrade-node") {
|
|
7100
7156
|
result.check = true;
|
|
@@ -7335,7 +7391,8 @@ async function buildSkillsText(config, question = "", options = {}) {
|
|
|
7335
7391
|
const chunks = [];
|
|
7336
7392
|
const selected = selectSkillsForPrompt(config, question, options);
|
|
7337
7393
|
for (const skill of listSkills(config)) {
|
|
7338
|
-
|
|
7394
|
+
const active = skill.enabled || (skill.name === "gosuslugi" && config.gosuslugi?.enabled);
|
|
7395
|
+
if (!active || !selected.has(skill.name)) continue;
|
|
7339
7396
|
const text = await readFile(skill.file, "utf8");
|
|
7340
7397
|
chunks.push(`## Skill: ${skill.name}\n${stripFrontmatter(text).trim()}`);
|
|
7341
7398
|
}
|
|
@@ -7351,6 +7408,7 @@ function selectSkillsForPrompt(config, question = "", options = {}) {
|
|
|
7351
7408
|
if (enabled.has("reports") && /(отчет|отчёт|выгруз|csv|xlsx|качество|провер)/iu.test(normalized)) selected.add("reports");
|
|
7352
7409
|
if (enabled.has("local-files") && (options.files || /(файл|папк|readme|документ|архив)/iu.test(normalized))) selected.add("local-files");
|
|
7353
7410
|
if (enabled.has("browser-agent") && /(браузер|сайт|страниц|url|https?:\/\/)/iu.test(normalized)) selected.add("browser-agent");
|
|
7411
|
+
if (enabled.has("gosuslugi") && /(госуслуг|задолж|долг|штраф|налог|к оплате|платеж|платёж|уведомлен|госпочт|фио|дата рождения)/iu.test(normalized)) selected.add("gosuslugi");
|
|
7354
7412
|
return selected;
|
|
7355
7413
|
}
|
|
7356
7414
|
|
|
@@ -7646,9 +7704,226 @@ async function gosuslugiBrowserScreenshot(url = GOSUSLUGI_DEFAULT_URL, outputFil
|
|
|
7646
7704
|
});
|
|
7647
7705
|
}
|
|
7648
7706
|
|
|
7707
|
+
async function gosuslugiWhoami(options = {}) {
|
|
7708
|
+
const data = await gosuslugiBrowserApiJson({
|
|
7709
|
+
pageUrl: "https://lk.gosuslugi.ru/settings/account",
|
|
7710
|
+
endpoint: "https://www.gosuslugi.ru/api/lk/v1/users/data",
|
|
7711
|
+
waitMs: Number(options.wait || 3000),
|
|
7712
|
+
});
|
|
7713
|
+
const person = data.person?.person || data.person || data;
|
|
7714
|
+
const summary = {
|
|
7715
|
+
fio: [data.lastName || person.lastName, data.firstName || person.firstName, data.middleName || person.middleName].filter(Boolean).join(" ") || data.formattedName || "-",
|
|
7716
|
+
birthDate: person.birthDate || data.birthDate || "-",
|
|
7717
|
+
status: data.assuranceLevel === "AL20" || person.trusted ? "Подтвержденная учетная запись" : data.assuranceLevel || "-",
|
|
7718
|
+
phone: options.full ? (data.personMobilePhone || data.mobile || "-") : maskPhone(data.personMobilePhone || data.mobile || ""),
|
|
7719
|
+
email: options.full ? (data.personEMail || data.personEmail || data.email || "-") : maskEmail(data.personEMail || data.personEmail || data.email || ""),
|
|
7720
|
+
snils: options.full ? (person.snils || data.personSnils || data.snils || "-") : maskDocument(person.snils || data.personSnils || data.snils || ""),
|
|
7721
|
+
inn: options.full ? (person.inn || data.personINN || data.inn || "-") : maskDocument(person.inn || data.personINN || data.inn || ""),
|
|
7722
|
+
};
|
|
7723
|
+
return {
|
|
7724
|
+
summary,
|
|
7725
|
+
raw: options.full ? redactGosuslugiSensitive(data, { keepPersonal: true }) : undefined,
|
|
7726
|
+
};
|
|
7727
|
+
}
|
|
7728
|
+
|
|
7729
|
+
async function gosuslugiDebt(options = {}) {
|
|
7730
|
+
const data = await gosuslugiBrowserApiJson({
|
|
7731
|
+
pageUrl: "https://www.gosuslugi.ru/pay/forPayment",
|
|
7732
|
+
endpoint: "https://www.gosuslugi.ru/api/pay/v2/informer/fetch",
|
|
7733
|
+
waitMs: Number(options.wait || 5000),
|
|
7734
|
+
});
|
|
7735
|
+
const groups = Array.isArray(data.groups) ? data.groups : [];
|
|
7736
|
+
const debts = groups.flatMap((group) => (group.bills || []).map((bill) => ({
|
|
7737
|
+
group: group.name || group.code || "-",
|
|
7738
|
+
caption: bill.caption || "-",
|
|
7739
|
+
amount: Number(bill.amount || 0),
|
|
7740
|
+
billDate: bill.billDate || "-",
|
|
7741
|
+
supplier: bill.supplierFullName || "-",
|
|
7742
|
+
document: bill.document?.typeName ? `${bill.document.typeName} ${bill.document.number || ""}`.trim() : "-",
|
|
7743
|
+
})));
|
|
7744
|
+
return {
|
|
7745
|
+
total: Number(data.summary?.total || debts.length || 0),
|
|
7746
|
+
amount: Number(data.summary?.amount || debts.reduce((sum, item) => sum + item.amount, 0)),
|
|
7747
|
+
groups: groups.map((group) => ({ name: group.name, code: group.code, total: group.summary?.total || 0, amount: group.summary?.amount || 0 })),
|
|
7748
|
+
debts,
|
|
7749
|
+
};
|
|
7750
|
+
}
|
|
7751
|
+
|
|
7752
|
+
async function gosuslugiNotifications(options = {}) {
|
|
7753
|
+
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";
|
|
7754
|
+
const pageSize = Number(options.limit || 15);
|
|
7755
|
+
const unread = options.unread ? "true" : "false";
|
|
7756
|
+
const counters = await gosuslugiBrowserApiJson({
|
|
7757
|
+
pageUrl: `https://lk.gosuslugi.ru/notifications?type=${types}`,
|
|
7758
|
+
endpoint: `https://www.gosuslugi.ru/api/lk/v1/feeds/counters?types=${types},PARTNERS&isArchive=false`,
|
|
7759
|
+
waitMs: Number(options.wait || 3000),
|
|
7760
|
+
});
|
|
7761
|
+
const feed = await gosuslugiBrowserApiJson({
|
|
7762
|
+
pageUrl: `https://lk.gosuslugi.ru/notifications?type=${types}`,
|
|
7763
|
+
endpoint: `https://www.gosuslugi.ru/api/lk/v1/feeds/?unread=${unread}&isArchive=false&isHide=false&types=${types}&pageSize=${pageSize}&status=&startDate=&lastFeedId=&lastFeedDate=&q=`,
|
|
7764
|
+
waitMs: Number(options.wait || 3000),
|
|
7765
|
+
});
|
|
7766
|
+
const items = (feed.items || []).map((item) => ({
|
|
7767
|
+
id: item.id,
|
|
7768
|
+
unread: Boolean(item.unread),
|
|
7769
|
+
date: item.date || "-",
|
|
7770
|
+
type: item.feedType || "-",
|
|
7771
|
+
title: item.title || "-",
|
|
7772
|
+
subtitle: item.subTitle || "-",
|
|
7773
|
+
status: item.status || "-",
|
|
7774
|
+
summary: summarizeNotificationData(item.data),
|
|
7775
|
+
}));
|
|
7776
|
+
return {
|
|
7777
|
+
total: counters.total || feed.items?.length || 0,
|
|
7778
|
+
unread: counters.unread || items.filter((item) => item.unread).length,
|
|
7779
|
+
counters: counters.counter || [],
|
|
7780
|
+
hasMore: Boolean(feed.hasMore),
|
|
7781
|
+
items,
|
|
7782
|
+
};
|
|
7783
|
+
}
|
|
7784
|
+
|
|
7785
|
+
async function gosuslugiMarkNotificationsRead(options = {}) {
|
|
7786
|
+
await requireGosuslugiConsent();
|
|
7787
|
+
if (!options.yes) {
|
|
7788
|
+
const ok = await confirm("Отметить уведомления Госуслуг прочитанными? Это изменит состояние личного кабинета. [y/N] ");
|
|
7789
|
+
if (!ok) {
|
|
7790
|
+
console.log("Операция отменена.");
|
|
7791
|
+
return;
|
|
7792
|
+
}
|
|
7793
|
+
}
|
|
7794
|
+
await gosuslugiBrowserClickText({
|
|
7795
|
+
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",
|
|
7796
|
+
text: "Прочитать все",
|
|
7797
|
+
waitMs: Number(options.wait || 5000),
|
|
7798
|
+
});
|
|
7799
|
+
console.log("Команда отметки прочитанным выполнена. Проверьте статус: iola gosuslugi notifications --unread");
|
|
7800
|
+
}
|
|
7801
|
+
|
|
7802
|
+
function printGosuslugiDebt(result) {
|
|
7803
|
+
printKeyValue({
|
|
7804
|
+
total: result.total,
|
|
7805
|
+
amount: `${formatRub(result.amount)} Р`,
|
|
7806
|
+
});
|
|
7807
|
+
if (!result.debts.length) {
|
|
7808
|
+
console.log("Задолженности не найдены.");
|
|
7809
|
+
return;
|
|
7810
|
+
}
|
|
7811
|
+
printTable(result.debts.map((item) => ({
|
|
7812
|
+
group: item.group,
|
|
7813
|
+
amount: `${formatRub(item.amount)} Р`,
|
|
7814
|
+
date: item.billDate,
|
|
7815
|
+
caption: item.caption,
|
|
7816
|
+
})), [
|
|
7817
|
+
["group", "Группа"],
|
|
7818
|
+
["amount", "Сумма"],
|
|
7819
|
+
["date", "Дата"],
|
|
7820
|
+
["caption", "Описание"],
|
|
7821
|
+
]);
|
|
7822
|
+
}
|
|
7823
|
+
|
|
7824
|
+
function printGosuslugiNotifications(result) {
|
|
7825
|
+
printKeyValue({ total: result.total, unread: result.unread, hasMore: result.hasMore ? "yes" : "no" });
|
|
7826
|
+
printTable(result.items.map((item) => ({
|
|
7827
|
+
unread: item.unread ? "new" : "read",
|
|
7828
|
+
date: item.date,
|
|
7829
|
+
type: item.type,
|
|
7830
|
+
title: item.title,
|
|
7831
|
+
subtitle: item.subtitle,
|
|
7832
|
+
summary: item.summary,
|
|
7833
|
+
})), [
|
|
7834
|
+
["unread", "Статус"],
|
|
7835
|
+
["date", "Дата"],
|
|
7836
|
+
["type", "Тип"],
|
|
7837
|
+
["title", "Заголовок"],
|
|
7838
|
+
["subtitle", "Подзаголовок"],
|
|
7839
|
+
["summary", "Детали"],
|
|
7840
|
+
]);
|
|
7841
|
+
}
|
|
7842
|
+
|
|
7843
|
+
function summarizeNotificationData(data) {
|
|
7844
|
+
if (!data || typeof data !== "object") return "";
|
|
7845
|
+
const snippets = Array.isArray(data.snippets) ? data.snippets : [];
|
|
7846
|
+
if (snippets.length) {
|
|
7847
|
+
const first = snippets[0];
|
|
7848
|
+
return [first.orgName, first.address, first.date].filter(Boolean).join(" | ");
|
|
7849
|
+
}
|
|
7850
|
+
return [data.messageType, data.messageUuid, data.orderId, data.passCodeEpguCode].filter(Boolean).join(" | ");
|
|
7851
|
+
}
|
|
7852
|
+
|
|
7853
|
+
function formatRub(value) {
|
|
7854
|
+
return Number(value || 0).toLocaleString("ru-RU", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
7855
|
+
}
|
|
7856
|
+
|
|
7857
|
+
function isGosuslugiPersonalIntent(question) {
|
|
7858
|
+
const normalized = String(question || "").toLocaleLowerCase("ru-RU");
|
|
7859
|
+
return /(госуслуг|задолж|долг|штраф|налог|к оплате|платеж|платёж|уведомлен|госпочт|фио|дата рождения)/iu.test(normalized);
|
|
7860
|
+
}
|
|
7861
|
+
|
|
7862
|
+
async function answerGosuslugiQuestion(question, options = {}) {
|
|
7863
|
+
const normalized = String(question || "").toLocaleLowerCase("ru-RU");
|
|
7864
|
+
if (/(уведомлен|сообщени|госпочт|непрочитан)/iu.test(normalized)) {
|
|
7865
|
+
const result = await gosuslugiNotifications({ unread: /непрочитан|нов/iu.test(normalized), limit: options.limit || 10 });
|
|
7866
|
+
const lines = [`На Госуслугах: всего уведомлений ${result.total}, непрочитанных ${result.unread}.`];
|
|
7867
|
+
const items = result.items.slice(0, Number(options.limit || 5));
|
|
7868
|
+
if (items.length) {
|
|
7869
|
+
lines.push("");
|
|
7870
|
+
for (const item of items) {
|
|
7871
|
+
lines.push(`- ${item.unread ? "новое" : "прочитано"}: ${item.title} — ${item.subtitle} (${item.date})`);
|
|
7872
|
+
}
|
|
7873
|
+
}
|
|
7874
|
+
return lines.join("\n");
|
|
7875
|
+
}
|
|
7876
|
+
if (/(задолж|долг|штраф|налог|к оплате|платеж|платёж)/iu.test(normalized)) {
|
|
7877
|
+
const result = await gosuslugiDebt(options);
|
|
7878
|
+
if (!result.debts.length) return "На Госуслугах задолженности к оплате не найдены.";
|
|
7879
|
+
const lines = [`На Госуслугах найдено задолженностей: ${result.total}. Общая сумма: ${formatRub(result.amount)} Р.`];
|
|
7880
|
+
for (const item of result.debts) {
|
|
7881
|
+
lines.push(`- ${item.group}: ${formatRub(item.amount)} Р — ${item.caption}`);
|
|
7882
|
+
}
|
|
7883
|
+
return lines.join("\n");
|
|
7884
|
+
}
|
|
7885
|
+
const result = await gosuslugiWhoami(options);
|
|
7886
|
+
return [
|
|
7887
|
+
`ФИО: ${result.summary.fio}`,
|
|
7888
|
+
`Дата рождения: ${result.summary.birthDate}`,
|
|
7889
|
+
`Статус: ${result.summary.status}`,
|
|
7890
|
+
].join("\n");
|
|
7891
|
+
}
|
|
7892
|
+
|
|
7893
|
+
function maskPhone(value) {
|
|
7894
|
+
const text = String(value || "");
|
|
7895
|
+
return text.replace(/(\+?\d)([\d\s()-]{4,})(\d{2})$/u, "$1***$3") || "-";
|
|
7896
|
+
}
|
|
7897
|
+
|
|
7898
|
+
function maskEmail(value) {
|
|
7899
|
+
const text = String(value || "");
|
|
7900
|
+
const [name, domain] = text.split("@");
|
|
7901
|
+
if (!name || !domain) return text || "-";
|
|
7902
|
+
return `${name.slice(0, 2)}***@${domain}`;
|
|
7903
|
+
}
|
|
7904
|
+
|
|
7905
|
+
function maskDocument(value) {
|
|
7906
|
+
const digits = String(value || "").replace(/\D+/g, "");
|
|
7907
|
+
if (!digits) return "-";
|
|
7908
|
+
return `***${digits.slice(-4)}`;
|
|
7909
|
+
}
|
|
7910
|
+
|
|
7911
|
+
function redactGosuslugiSensitive(value, options = {}) {
|
|
7912
|
+
if (Array.isArray(value)) return value.map((item) => redactGosuslugiSensitive(item, options));
|
|
7913
|
+
if (!value || typeof value !== "object") return value;
|
|
7914
|
+
const result = {};
|
|
7915
|
+
for (const [key, item] of Object.entries(value)) {
|
|
7916
|
+
if (/token|cookie|session|password|secret|jwt|auth/i.test(key)) result[key] = "[redacted]";
|
|
7917
|
+
else if (!options.keepPersonal && /(snils|inn|passport|number|series|address|mobile|email|phone)/i.test(key)) result[key] = "[redacted]";
|
|
7918
|
+
else result[key] = redactGosuslugiSensitive(item, options);
|
|
7919
|
+
}
|
|
7920
|
+
return result;
|
|
7921
|
+
}
|
|
7922
|
+
|
|
7649
7923
|
async function runPersistentBrowserAutomation(action, params) {
|
|
7650
7924
|
await ensureBrowserRuntime();
|
|
7651
7925
|
await mkdir(params.userDataDir, { recursive: true });
|
|
7926
|
+
const releaseLock = params.userDataDir === GOSUSLUGI_BROWSER_PROFILE_DIR ? await acquireDirectoryLock(GOSUSLUGI_BROWSER_LOCK_DIR, 180000) : async () => {};
|
|
7652
7927
|
const scriptFile = path.join(BROWSER_RUNTIME_DIR, `iola-browser-profile-${Date.now()}-${Math.random().toString(16).slice(2)}.mjs`);
|
|
7653
7928
|
await writeFile(scriptFile, persistentBrowserAutomationScript(action, params), "utf8");
|
|
7654
7929
|
try {
|
|
@@ -7657,9 +7932,57 @@ async function runPersistentBrowserAutomation(action, params) {
|
|
|
7657
7932
|
return result.stdout?.trim() || "";
|
|
7658
7933
|
} finally {
|
|
7659
7934
|
await rm(scriptFile, { force: true }).catch(() => {});
|
|
7935
|
+
await releaseLock();
|
|
7936
|
+
}
|
|
7937
|
+
}
|
|
7938
|
+
|
|
7939
|
+
async function acquireDirectoryLock(lockDir, timeoutMs = 60000) {
|
|
7940
|
+
const started = Date.now();
|
|
7941
|
+
while (true) {
|
|
7942
|
+
try {
|
|
7943
|
+
await mkdir(lockDir, { recursive: false });
|
|
7944
|
+
await writeFile(path.join(lockDir, "owner.json"), JSON.stringify({ pid: process.pid, startedAt: new Date().toISOString() }, null, 2), "utf8").catch(() => {});
|
|
7945
|
+
return async () => {
|
|
7946
|
+
await rm(lockDir, { recursive: true, force: true }).catch(() => {});
|
|
7947
|
+
};
|
|
7948
|
+
} catch {
|
|
7949
|
+
if (Date.now() - started > timeoutMs) {
|
|
7950
|
+
throw new Error("Браузерный профиль Госуслуг занят другим процессом. Закройте окно Госуслуг или повторите команду позже.");
|
|
7951
|
+
}
|
|
7952
|
+
await sleep(1000);
|
|
7953
|
+
}
|
|
7660
7954
|
}
|
|
7661
7955
|
}
|
|
7662
7956
|
|
|
7957
|
+
async function gosuslugiBrowserApiJson(params) {
|
|
7958
|
+
await requireGosuslugiConsent();
|
|
7959
|
+
await ensureBrowserRuntimeForGosuslugi();
|
|
7960
|
+
const raw = await runPersistentBrowserAutomation("api-json", {
|
|
7961
|
+
pageUrl: params.pageUrl || GOSUSLUGI_DEFAULT_URL,
|
|
7962
|
+
endpoint: params.endpoint,
|
|
7963
|
+
userDataDir: GOSUSLUGI_BROWSER_PROFILE_DIR,
|
|
7964
|
+
headed: params.headed !== false,
|
|
7965
|
+
waitMs: Number(params.waitMs || 0),
|
|
7966
|
+
timeout: Number(params.timeout || 60000),
|
|
7967
|
+
viewport: params.viewport || "1366x768",
|
|
7968
|
+
});
|
|
7969
|
+
return JSON.parse(raw);
|
|
7970
|
+
}
|
|
7971
|
+
|
|
7972
|
+
async function gosuslugiBrowserClickText(params) {
|
|
7973
|
+
await requireGosuslugiConsent();
|
|
7974
|
+
await ensureBrowserRuntimeForGosuslugi();
|
|
7975
|
+
return runPersistentBrowserAutomation("click-text", {
|
|
7976
|
+
pageUrl: params.pageUrl || GOSUSLUGI_DEFAULT_URL,
|
|
7977
|
+
text: params.text,
|
|
7978
|
+
userDataDir: GOSUSLUGI_BROWSER_PROFILE_DIR,
|
|
7979
|
+
headed: true,
|
|
7980
|
+
waitMs: Number(params.waitMs || 3000),
|
|
7981
|
+
timeout: Number(params.timeout || 60000),
|
|
7982
|
+
viewport: params.viewport || "1366x768",
|
|
7983
|
+
});
|
|
7984
|
+
}
|
|
7985
|
+
|
|
7663
7986
|
function persistentBrowserAutomationScript(action, params) {
|
|
7664
7987
|
return `
|
|
7665
7988
|
import { chromium } from "playwright";
|
|
@@ -7673,7 +7996,7 @@ const context = await chromium.launchPersistentContext(params.userDataDir, {
|
|
|
7673
7996
|
context.setDefaultTimeout(params.timeout || 60000);
|
|
7674
7997
|
const page = context.pages()[0] || await context.newPage();
|
|
7675
7998
|
try {
|
|
7676
|
-
await page.goto(params.url, { waitUntil: "domcontentloaded", timeout: params.timeout || 60000 });
|
|
7999
|
+
await page.goto(params.url || params.pageUrl, { waitUntil: "domcontentloaded", timeout: params.timeout || 60000 });
|
|
7677
8000
|
if (params.waitMs) await page.waitForTimeout(params.waitMs);
|
|
7678
8001
|
if (action === "open") {
|
|
7679
8002
|
if (params.headed) {
|
|
@@ -7688,6 +8011,21 @@ try {
|
|
|
7688
8011
|
console.log((await page.locator("body").innerText()).trim());
|
|
7689
8012
|
} else if (action === "screenshot") {
|
|
7690
8013
|
await page.screenshot({ path: params.output, fullPage: true });
|
|
8014
|
+
} else if (action === "api-json") {
|
|
8015
|
+
const data = await page.evaluate(async (endpoint) => {
|
|
8016
|
+
const response = await fetch(endpoint, {
|
|
8017
|
+
credentials: "include",
|
|
8018
|
+
headers: { accept: "application/json" },
|
|
8019
|
+
});
|
|
8020
|
+
const text = await response.text();
|
|
8021
|
+
if (!response.ok) throw new Error(response.status + " " + response.statusText + ": " + text.slice(0, 500));
|
|
8022
|
+
return JSON.parse(text);
|
|
8023
|
+
}, params.endpoint);
|
|
8024
|
+
console.log(JSON.stringify(data));
|
|
8025
|
+
} else if (action === "click-text") {
|
|
8026
|
+
await page.getByText(params.text, { exact: true }).first().click();
|
|
8027
|
+
if (params.waitMs) await page.waitForTimeout(params.waitMs);
|
|
8028
|
+
console.log((await page.locator("body").innerText()).trim().slice(0, 4000));
|
|
7691
8029
|
}
|
|
7692
8030
|
} finally {
|
|
7693
8031
|
await context.close().catch(() => {});
|
|
@@ -7925,6 +8263,9 @@ function mcpTools() {
|
|
|
7925
8263
|
{ name: "report", description: "Запуск встроенного отчета.", inputSchema: schema({ name: { type: "string" }, format: { type: "string" }, output: { type: "string" } }) },
|
|
7926
8264
|
{ name: "browser.text", description: "Открыть страницу в headless Chromium и вернуть видимый текст.", inputSchema: schema({ url: { type: "string" }, waitMs: { type: "number" } }) },
|
|
7927
8265
|
{ name: "browser.screenshot", description: "Сделать скриншот страницы через Chromium.", inputSchema: schema({ url: { type: "string" }, output: { type: "string" }, waitMs: { type: "number" } }) },
|
|
8266
|
+
{ name: "gosuslugi.whoami", description: "Прочитать ФИО и дату рождения из личного профиля Госуслуг через локальный браузерный профиль.", inputSchema: schema({ full: { type: "boolean" } }) },
|
|
8267
|
+
{ name: "gosuslugi.debt", description: "Прочитать задолженности и платежи к оплате на Госуслугах.", inputSchema: schema() },
|
|
8268
|
+
{ name: "gosuslugi.notifications", description: "Прочитать уведомления Госуслуг.", inputSchema: schema({ unread: { type: "boolean" }, limit: { type: "number" } }) },
|
|
7928
8269
|
];
|
|
7929
8270
|
}
|
|
7930
8271
|
|
|
@@ -7962,6 +8303,9 @@ async function callMcpTool(name, args = {}) {
|
|
|
7962
8303
|
await runBrowserAutomation("screenshot", { url: args.url, output, waitMs: Number(args.waitMs || 0), timeout: Number(args.timeout || 30000), viewport: args.viewport || "1366x768" });
|
|
7963
8304
|
return { output };
|
|
7964
8305
|
}
|
|
8306
|
+
if (name === "gosuslugi.whoami") return gosuslugiWhoami(args);
|
|
8307
|
+
if (name === "gosuslugi.debt") return gosuslugiDebt(args);
|
|
8308
|
+
if (name === "gosuslugi.notifications") return gosuslugiNotifications(args);
|
|
7965
8309
|
return executeRpc(name, { ...args, _: [] });
|
|
7966
8310
|
}
|
|
7967
8311
|
|
|
@@ -9198,6 +9542,10 @@ function setConfigValue(config, key, value) {
|
|
|
9198
9542
|
current[parts.at(-1)] = value;
|
|
9199
9543
|
}
|
|
9200
9544
|
|
|
9545
|
+
function sleep(ms) {
|
|
9546
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9547
|
+
}
|
|
9548
|
+
|
|
9201
9549
|
async function loadSecrets() {
|
|
9202
9550
|
try {
|
|
9203
9551
|
return JSON.parse(await readFile(SECRETS_FILE, "utf8"));
|
|
@@ -101,6 +101,10 @@ iola gosuslugi connect
|
|
|
101
101
|
iola gosuslugi open
|
|
102
102
|
iola gosuslugi text https://www.gosuslugi.ru/
|
|
103
103
|
iola gosuslugi screenshot https://www.gosuslugi.ru/ --output gosuslugi.png
|
|
104
|
+
iola gosuslugi whoami
|
|
105
|
+
iola gosuslugi debt
|
|
106
|
+
iola gosuslugi notifications --unread
|
|
107
|
+
iola gosuslugi mark-read
|
|
104
108
|
iola gosuslugi logout --all
|
|
105
109
|
iola gosuslugi configure --auth-url URL --token-url URL --client-id ID --scope openid
|
|
106
110
|
iola gosuslugi login
|
|
@@ -45,6 +45,35 @@ 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
|
|
@@ -68,6 +97,7 @@ iola gosuslugi logout --all
|
|
|
68
97
|
- CLI работает только с тем, что доступно пользователю в локальном браузере.
|
|
69
98
|
- CLI не извлекает cookies, session tokens или внутренние токены Госуслуг.
|
|
70
99
|
- Юридически значимые действия должны требовать отдельного подтверждения пользователя.
|
|
100
|
+
- Сессия браузерного профиля может протухнуть. Если Госуслуги попросят повторный вход или 2FA, выполните `iola gosuslugi connect`.
|
|
71
101
|
- Если Госуслуги попросят повторный вход, код или подтверждение, пользователь проходит его сам в открытом окне.
|
|
72
102
|
|
|
73
103
|
OAuth/OIDC-команды `configure`, `login`, `userinfo` оставлены для случая, если у пользователя есть официально зарегистрированное подключение ЕСИА.
|