@iola_adm/iola-cli 0.1.58 → 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 +8 -4
- package/package.json +2 -2
- package/skills/gosuslugi/SKILL.md +16 -0
- package/src/cli.js +543 -13
- package/wiki//320/232/320/276/320/274/320/260/320/275/320/264/321/213.md +9 -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 +60 -46
package/README.md
CHANGED
|
@@ -40,8 +40,7 @@ npx -y @iola_adm/iola-cli
|
|
|
40
40
|
Повторный запуск мастера:
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
|
-
iola
|
|
44
|
-
iola setup wizard
|
|
43
|
+
iola master
|
|
45
44
|
```
|
|
46
45
|
|
|
47
46
|
Мастер обновляет только выбранные разделы и не сбрасывает остальные настройки.
|
|
@@ -86,6 +85,10 @@ iola trajectory last
|
|
|
86
85
|
iola review config
|
|
87
86
|
iola browser status
|
|
88
87
|
iola gosuslugi status
|
|
88
|
+
iola gosuslugi connect
|
|
89
|
+
iola gosuslugi whoami
|
|
90
|
+
iola gosuslugi debt
|
|
91
|
+
iola gosuslugi notifications --unread
|
|
89
92
|
```
|
|
90
93
|
|
|
91
94
|
Локальная модель через Ollama:
|
|
@@ -133,14 +136,15 @@ iola version --check
|
|
|
133
136
|
- MCP-мост для локальной модели: встроенный `iola-local` доступен как `mcp:iola-local:TOOL`;
|
|
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-совместимые файлы;
|
|
140
144
|
- staged changes, импорт локальных CSV/JSON, индекс локальных документов, report packs, plugins и локальный MCP endpoint;
|
|
141
145
|
- чтение и индексирование `.docx`, `.xlsx`, `.pptx`, `.pdf`, `.md`, `.txt`, `.csv`, `.json`, `.html`;
|
|
142
146
|
- работа с архивами через 7-Zip: `.zip`, `.7z`, `.rar`, `.tar`, `.gz`, `.tgz`, `.bz2`, `.xz` и другие;
|
|
143
|
-
- расширенный `iola onboard` с установкой 7-Zip, Ollama, Codex CLI и настройкой выбранных компонентов;
|
|
147
|
+
- расширенный `iola onboard` с установкой 7-Zip, браузерного runtime, Ollama, Codex CLI и настройкой выбранных компонентов;
|
|
144
148
|
- cron-задачи, локальный daemon, web dashboard и RPC для автоматизаций;
|
|
145
149
|
- контекстные файлы `IOLA.md` и `.iola/context.md`;
|
|
146
150
|
- интеграция с публичным MCP-сервером Йошкар-Олы.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iola_adm/iola-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.60",
|
|
4
4
|
"description": "CLI и AI-агент городского округа Йошкар-Ола.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/adm-iola/iola-cli#readme",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"iola": "bin/iola.js"
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
|
-
"postinstall": "node --no-warnings bin/iola.js db init --silent
|
|
19
|
+
"postinstall": "node --no-warnings bin/iola.js db init --silent && node --no-warnings bin/iola.js browser install",
|
|
20
20
|
"start": "node --no-warnings bin/iola.js",
|
|
21
21
|
"test": "node --no-warnings --check bin/iola.js && node --no-warnings --check src/cli.js"
|
|
22
22
|
},
|
|
@@ -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
|
@@ -26,8 +26,11 @@ const PROJECT_CONFIG_FILE = path.join(PROJECT_IOLA_DIR, "config.json");
|
|
|
26
26
|
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
|
+
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");
|
|
31
|
+
const GOSUSLUGI_DEFAULT_URL = "https://www.gosuslugi.ru/";
|
|
29
32
|
const INDEXABLE_EXTENSIONS = /\.(md|txt|csv|json|html|docx|xlsx|pptx|pdf)$/i;
|
|
30
|
-
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"];
|
|
31
34
|
const LEGACY_LOCAL_TOOLS = ["search_local", "export_data", "run_report", "save_view"];
|
|
32
35
|
const FILE_TOOLS = ["files_tree", "files_read", "files_search", "files_write", "files_patch"];
|
|
33
36
|
const ALL_LOCAL_TOOLS = [...LOCAL_TOOLS, ...FILE_TOOLS];
|
|
@@ -130,7 +133,7 @@ const DEFAULT_AI_CONFIG = {
|
|
|
130
133
|
},
|
|
131
134
|
gosuslugi: {
|
|
132
135
|
enabled: false,
|
|
133
|
-
mode: "personal-
|
|
136
|
+
mode: "personal-browser",
|
|
134
137
|
authUrl: "",
|
|
135
138
|
tokenUrl: "",
|
|
136
139
|
userinfoUrl: "",
|
|
@@ -178,6 +181,9 @@ const DEFAULT_AI_CONFIG = {
|
|
|
178
181
|
export_report: true,
|
|
179
182
|
file_read: false,
|
|
180
183
|
browser_open: true,
|
|
184
|
+
gosuslugi_whoami: true,
|
|
185
|
+
gosuslugi_debt: true,
|
|
186
|
+
gosuslugi_notifications: true,
|
|
181
187
|
files_tree: false,
|
|
182
188
|
files_read: false,
|
|
183
189
|
files_search: false,
|
|
@@ -208,7 +214,7 @@ const DEFAULT_AI_CONFIG = {
|
|
|
208
214
|
suggestions: true,
|
|
209
215
|
},
|
|
210
216
|
skills: {
|
|
211
|
-
enabled: ["open-data", "reports", "local-model", "local-files", "browser-agent"],
|
|
217
|
+
enabled: ["open-data", "reports", "local-model", "local-files", "browser-agent", "gosuslugi"],
|
|
212
218
|
},
|
|
213
219
|
daemon: {
|
|
214
220
|
host: "127.0.0.1",
|
|
@@ -281,6 +287,9 @@ const SLASH_COMMANDS = [
|
|
|
281
287
|
{ command: "/resume SESSION_ID", description: "продолжить сессию" },
|
|
282
288
|
{ command: "/features list", description: "feature flags" },
|
|
283
289
|
{ command: "/gosuslugi status", description: "личное подключение Госуслуг" },
|
|
290
|
+
{ command: "/gosuslugi connect", description: "открыть личный вход Госуслуг" },
|
|
291
|
+
{ command: "/gosuslugi debt", description: "задолженности Госуслуг" },
|
|
292
|
+
{ command: "/gosuslugi notifications", description: "уведомления Госуслуг" },
|
|
284
293
|
{ command: "/wiki", description: "ссылки на документацию" },
|
|
285
294
|
{ command: "/context list", description: "локальный контекст проекта" },
|
|
286
295
|
{ command: "/skills list", description: "skills" },
|
|
@@ -514,7 +523,7 @@ Usage:
|
|
|
514
523
|
iola fork SESSION_ID [TEXT]
|
|
515
524
|
iola features list|enable|disable
|
|
516
525
|
iola settings list|get|validate|doctor|init
|
|
517
|
-
iola gosuslugi terms|consent|
|
|
526
|
+
iola gosuslugi terms|consent|status|connect|open|text|screenshot|whoami|debt|notifications|mark-read|logout|configure|login|userinfo
|
|
518
527
|
iola wiki [open|links]
|
|
519
528
|
iola context list|show|init
|
|
520
529
|
iola skills list|show|paths|enable|disable|bundles|bundle|doctor
|
|
@@ -2253,11 +2262,16 @@ async function handleGosuslugi(args) {
|
|
|
2253
2262
|
const config = await loadConfig();
|
|
2254
2263
|
const secrets = await loadSecrets();
|
|
2255
2264
|
const tokens = secrets.gosuslugi?.tokens || null;
|
|
2265
|
+
const browserSession = secrets.gosuslugiBrowser || null;
|
|
2256
2266
|
const consent = secrets.gosuslugiConsent || null;
|
|
2257
2267
|
printKeyValue({
|
|
2258
|
-
mode: config.gosuslugi?.mode || "personal-
|
|
2268
|
+
mode: config.gosuslugi?.mode || "personal-browser",
|
|
2259
2269
|
enabled: config.gosuslugi?.enabled ? "yes" : "no",
|
|
2260
|
-
|
|
2270
|
+
browserProfile: GOSUSLUGI_BROWSER_PROFILE_DIR,
|
|
2271
|
+
browserProfileExists: existsSync(GOSUSLUGI_BROWSER_PROFILE_DIR) ? "yes" : "no",
|
|
2272
|
+
browserConnected: browserSession?.connectedAt ? "yes" : "unknown",
|
|
2273
|
+
browserConnectedAt: browserSession?.connectedAt || "-",
|
|
2274
|
+
oauthConfigured: isGosuslugiConfigured(config) ? "yes" : "no",
|
|
2261
2275
|
consent: consent?.version === GOSUSLUGI_CONSENT_VERSION ? "accepted" : "not accepted",
|
|
2262
2276
|
consentAt: consent?.acceptedAt || "-",
|
|
2263
2277
|
clientId: config.gosuslugi?.clientId ? maskSecret(config.gosuslugi.clientId) : "-",
|
|
@@ -2272,6 +2286,61 @@ async function handleGosuslugi(args) {
|
|
|
2272
2286
|
return;
|
|
2273
2287
|
}
|
|
2274
2288
|
|
|
2289
|
+
if (action === "connect") {
|
|
2290
|
+
await gosuslugiBrowserConnect(options);
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
if (action === "open") {
|
|
2295
|
+
await gosuslugiBrowserOpen(targetOrDefault(rest, options), options);
|
|
2296
|
+
return;
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
if (action === "text") {
|
|
2300
|
+
const result = await gosuslugiBrowserReadText(targetOrDefault(rest, options), options);
|
|
2301
|
+
if (options.output) {
|
|
2302
|
+
await writeFile(path.resolve(options.output), result, "utf8");
|
|
2303
|
+
console.log(`Файл сохранен: ${path.resolve(options.output)}`);
|
|
2304
|
+
} else {
|
|
2305
|
+
console.log(result);
|
|
2306
|
+
}
|
|
2307
|
+
return;
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
if (action === "screenshot") {
|
|
2311
|
+
const outputFile = path.resolve(options.output || "gosuslugi-page.png");
|
|
2312
|
+
await gosuslugiBrowserScreenshot(targetOrDefault(rest, options), outputFile, options);
|
|
2313
|
+
saveArtifact("gosuslugi-screenshot", targetOrDefault(rest, options), outputFile, { url: targetOrDefault(rest, options) });
|
|
2314
|
+
console.log(`Файл сохранен: ${outputFile}`);
|
|
2315
|
+
return;
|
|
2316
|
+
}
|
|
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
|
+
|
|
2275
2344
|
if (action === "configure") {
|
|
2276
2345
|
const current = await loadConfig();
|
|
2277
2346
|
const next = {
|
|
@@ -2303,7 +2372,12 @@ async function handleGosuslugi(args) {
|
|
|
2303
2372
|
if (action === "logout") {
|
|
2304
2373
|
const secrets = await loadSecrets();
|
|
2305
2374
|
delete secrets.gosuslugi;
|
|
2375
|
+
delete secrets.gosuslugiBrowser;
|
|
2306
2376
|
await saveSecrets(secrets);
|
|
2377
|
+
if (options.profile || options.all) {
|
|
2378
|
+
await rm(GOSUSLUGI_BROWSER_PROFILE_DIR, { recursive: true, force: true }).catch(() => {});
|
|
2379
|
+
console.log("Локальный браузерный профиль Госуслуг удален.");
|
|
2380
|
+
}
|
|
2307
2381
|
console.log("Локальное подключение Госуслуг удалено.");
|
|
2308
2382
|
return;
|
|
2309
2383
|
}
|
|
@@ -2315,7 +2389,11 @@ async function handleGosuslugi(args) {
|
|
|
2315
2389
|
return;
|
|
2316
2390
|
}
|
|
2317
2391
|
|
|
2318
|
-
throw new Error("Команды gosuslugi: terms, consent,
|
|
2392
|
+
throw new Error("Команды gosuslugi: terms, consent, status, connect, open, text, screenshot, whoami, debt, notifications, mark-read, logout, configure, login, userinfo.");
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
function targetOrDefault(args, options = {}) {
|
|
2396
|
+
return options.url || args.find((item) => !item.startsWith("--")) || GOSUSLUGI_DEFAULT_URL;
|
|
2319
2397
|
}
|
|
2320
2398
|
|
|
2321
2399
|
async function handleWiki(args) {
|
|
@@ -3345,6 +3423,10 @@ async function ensureGosuslugiConsent(options = {}) {
|
|
|
3345
3423
|
await acceptGosuslugiConsent(options);
|
|
3346
3424
|
}
|
|
3347
3425
|
|
|
3426
|
+
async function requireGosuslugiConsent() {
|
|
3427
|
+
await ensureGosuslugiConsent();
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3348
3430
|
function waitForOAuthCallback(settings, expectedState, timeoutMs) {
|
|
3349
3431
|
const host = settings.redirectHost || "127.0.0.1";
|
|
3350
3432
|
const port = Number(settings.redirectPort || 18791);
|
|
@@ -5960,6 +6042,12 @@ async function aiAsk(args, context = {}) {
|
|
|
5960
6042
|
throw new Error('Текст вопроса обязателен. Пример: iola ai ask "Какие школы есть на улице Петрова?"');
|
|
5961
6043
|
}
|
|
5962
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
|
+
|
|
5963
6051
|
const config = await loadConfig();
|
|
5964
6052
|
const providerConfig = await resolveUsableAiProfile(config, options);
|
|
5965
6053
|
if (providerConfig.provider === "codex") await assertPermission("codex");
|
|
@@ -6128,7 +6216,7 @@ async function buildLocalToolPlan(question, providerConfig, options) {
|
|
|
6128
6216
|
"Ты планировщик CLI iola. Верни только JSON.",
|
|
6129
6217
|
`Доступные tools: ${availableToolNames(options).join(", ")}.`,
|
|
6130
6218
|
"Схема: {\"steps\":[{\"tool\":\"search_data\",\"args\":{\"dataset\":\"schools|kindergartens|all\",\"query\":\"text\",\"limit\":10}}]}",
|
|
6131
|
-
"Минимальные 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}.",
|
|
6132
6220
|
"MCP tools доступны как mcp:SERVER:TOOL, например mcp:iola-local:search.",
|
|
6133
6221
|
"Для выгрузки CSV добавь export_report с format=csv и output, если пользователь назвал файл.",
|
|
6134
6222
|
`Вопрос: ${question}`,
|
|
@@ -6158,6 +6246,12 @@ function inferToolPlan(question, options = {}) {
|
|
|
6158
6246
|
const steps = [];
|
|
6159
6247
|
if (normalized.includes("без телефона")) {
|
|
6160
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: {} });
|
|
6161
6255
|
} else {
|
|
6162
6256
|
const query = normalized.match(/петрова|школ[а-яё ]*\d+|сад[а-яё ]*\d+|лицей[а-яё ]*\d+/iu)?.[0] || question;
|
|
6163
6257
|
steps.push({ tool: "search_data", args: { dataset, query, limit: 20 } });
|
|
@@ -6247,6 +6341,18 @@ async function executeToolPlan(plan, options = {}) {
|
|
|
6247
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" });
|
|
6248
6342
|
current = [{ url: step.args?.url, text }];
|
|
6249
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 });
|
|
6250
6356
|
} else if (String(step.tool || "").startsWith("mcp:")) {
|
|
6251
6357
|
const result = await callConfiguredMcpTool(step.tool, step.args || {});
|
|
6252
6358
|
current = Array.isArray(result) ? result : [result];
|
|
@@ -6985,7 +7091,12 @@ async function onboard(args = []) {
|
|
|
6985
7091
|
if (components.includes("gosuslugi")) {
|
|
6986
7092
|
if (process.stdin.isTTY) await handleGosuslugi(["consent"]);
|
|
6987
7093
|
else await handleGosuslugi(["terms"]);
|
|
6988
|
-
|
|
7094
|
+
await ensureBrowserRuntimeForGosuslugi();
|
|
7095
|
+
if (process.stdin.isTTY && await confirm("Открыть Госуслуги для входа сейчас? [Y/n] ")) {
|
|
7096
|
+
await gosuslugiBrowserConnect({ yes: true });
|
|
7097
|
+
} else {
|
|
7098
|
+
console.log("Подключить личные Госуслуги позже: iola gosuslugi connect");
|
|
7099
|
+
}
|
|
6989
7100
|
}
|
|
6990
7101
|
if (components.includes("index")) {
|
|
6991
7102
|
await setFilesMode("read-only", await loadConfig());
|
|
@@ -7039,7 +7150,7 @@ function parseOptions(args) {
|
|
|
7039
7150
|
|
|
7040
7151
|
for (let index = 0; index < args.length; index += 1) {
|
|
7041
7152
|
const arg = args[index];
|
|
7042
|
-
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") {
|
|
7043
7154
|
result[arg.slice(2)] = true;
|
|
7044
7155
|
} else if (arg === "--check" || arg === "--upgrade-node") {
|
|
7045
7156
|
result.check = true;
|
|
@@ -7280,7 +7391,8 @@ async function buildSkillsText(config, question = "", options = {}) {
|
|
|
7280
7391
|
const chunks = [];
|
|
7281
7392
|
const selected = selectSkillsForPrompt(config, question, options);
|
|
7282
7393
|
for (const skill of listSkills(config)) {
|
|
7283
|
-
|
|
7394
|
+
const active = skill.enabled || (skill.name === "gosuslugi" && config.gosuslugi?.enabled);
|
|
7395
|
+
if (!active || !selected.has(skill.name)) continue;
|
|
7284
7396
|
const text = await readFile(skill.file, "utf8");
|
|
7285
7397
|
chunks.push(`## Skill: ${skill.name}\n${stripFrontmatter(text).trim()}`);
|
|
7286
7398
|
}
|
|
@@ -7296,6 +7408,7 @@ function selectSkillsForPrompt(config, question = "", options = {}) {
|
|
|
7296
7408
|
if (enabled.has("reports") && /(отчет|отчёт|выгруз|csv|xlsx|качество|провер)/iu.test(normalized)) selected.add("reports");
|
|
7297
7409
|
if (enabled.has("local-files") && (options.files || /(файл|папк|readme|документ|архив)/iu.test(normalized))) selected.add("local-files");
|
|
7298
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");
|
|
7299
7412
|
return selected;
|
|
7300
7413
|
}
|
|
7301
7414
|
|
|
@@ -7481,6 +7594,10 @@ async function getBrowserStatus() {
|
|
|
7481
7594
|
}
|
|
7482
7595
|
|
|
7483
7596
|
async function installBrowserRuntime() {
|
|
7597
|
+
if (existsSync(BROWSER_RUNTIME_PACKAGE)) {
|
|
7598
|
+
console.log(`Browser runtime уже установлен: ${BROWSER_RUNTIME_DIR}`);
|
|
7599
|
+
return;
|
|
7600
|
+
}
|
|
7484
7601
|
await mkdir(BROWSER_RUNTIME_DIR, { recursive: true });
|
|
7485
7602
|
const packageFile = path.join(BROWSER_RUNTIME_DIR, "package.json");
|
|
7486
7603
|
if (!existsSync(packageFile)) {
|
|
@@ -7515,6 +7632,407 @@ async function runBrowserAutomation(action, params) {
|
|
|
7515
7632
|
}
|
|
7516
7633
|
}
|
|
7517
7634
|
|
|
7635
|
+
async function ensureBrowserRuntimeForGosuslugi() {
|
|
7636
|
+
if (existsSync(BROWSER_RUNTIME_PACKAGE)) return;
|
|
7637
|
+
console.log("Browser runtime не установлен. Устанавливаю Playwright/Chromium для локального браузерного профиля.");
|
|
7638
|
+
await installBrowserRuntime();
|
|
7639
|
+
}
|
|
7640
|
+
|
|
7641
|
+
async function gosuslugiBrowserConnect(options = {}) {
|
|
7642
|
+
await ensureGosuslugiConsent({ yes: options.yes });
|
|
7643
|
+
await ensureBrowserRuntimeForGosuslugi();
|
|
7644
|
+
await saveConfig({ gosuslugi: { ...(await loadConfig()).gosuslugi, enabled: true, mode: "personal-browser" } });
|
|
7645
|
+
const url = options.url || GOSUSLUGI_DEFAULT_URL;
|
|
7646
|
+
console.log(`Открываю Госуслуги в отдельном локальном профиле: ${GOSUSLUGI_BROWSER_PROFILE_DIR}`);
|
|
7647
|
+
console.log("Авторизуйтесь в открывшемся окне. Когда закончите, закройте окно браузера.");
|
|
7648
|
+
await runPersistentBrowserAutomation("open", {
|
|
7649
|
+
url,
|
|
7650
|
+
userDataDir: GOSUSLUGI_BROWSER_PROFILE_DIR,
|
|
7651
|
+
headed: true,
|
|
7652
|
+
waitMs: Number(options.wait || 0),
|
|
7653
|
+
timeout: Number(options.timeout || 120000),
|
|
7654
|
+
viewport: options.viewport || "1366x768",
|
|
7655
|
+
});
|
|
7656
|
+
const secrets = await loadSecrets();
|
|
7657
|
+
secrets.gosuslugiBrowser = {
|
|
7658
|
+
mode: "personal-browser",
|
|
7659
|
+
profileDir: GOSUSLUGI_BROWSER_PROFILE_DIR,
|
|
7660
|
+
connectedAt: new Date().toISOString(),
|
|
7661
|
+
lastUrl: url,
|
|
7662
|
+
};
|
|
7663
|
+
await saveSecrets(secrets);
|
|
7664
|
+
console.log("Локальный браузерный профиль Госуслуг сохранен.");
|
|
7665
|
+
}
|
|
7666
|
+
|
|
7667
|
+
async function gosuslugiBrowserOpen(url = GOSUSLUGI_DEFAULT_URL, options = {}) {
|
|
7668
|
+
await requireGosuslugiConsent();
|
|
7669
|
+
await ensureBrowserRuntimeForGosuslugi();
|
|
7670
|
+
await runPersistentBrowserAutomation("open", {
|
|
7671
|
+
url,
|
|
7672
|
+
userDataDir: GOSUSLUGI_BROWSER_PROFILE_DIR,
|
|
7673
|
+
headed: true,
|
|
7674
|
+
waitMs: Number(options.wait || 0),
|
|
7675
|
+
timeout: Number(options.timeout || 120000),
|
|
7676
|
+
viewport: options.viewport || "1366x768",
|
|
7677
|
+
});
|
|
7678
|
+
}
|
|
7679
|
+
|
|
7680
|
+
async function gosuslugiBrowserReadText(url = GOSUSLUGI_DEFAULT_URL, options = {}) {
|
|
7681
|
+
await requireGosuslugiConsent();
|
|
7682
|
+
await ensureBrowserRuntimeForGosuslugi();
|
|
7683
|
+
return runPersistentBrowserAutomation("text", {
|
|
7684
|
+
url,
|
|
7685
|
+
userDataDir: GOSUSLUGI_BROWSER_PROFILE_DIR,
|
|
7686
|
+
headed: Boolean(options.headed),
|
|
7687
|
+
waitMs: Number(options.wait || 3000),
|
|
7688
|
+
timeout: Number(options.timeout || 60000),
|
|
7689
|
+
viewport: options.viewport || "1366x768",
|
|
7690
|
+
});
|
|
7691
|
+
}
|
|
7692
|
+
|
|
7693
|
+
async function gosuslugiBrowserScreenshot(url = GOSUSLUGI_DEFAULT_URL, outputFile, options = {}) {
|
|
7694
|
+
await requireGosuslugiConsent();
|
|
7695
|
+
await ensureBrowserRuntimeForGosuslugi();
|
|
7696
|
+
await runPersistentBrowserAutomation("screenshot", {
|
|
7697
|
+
url,
|
|
7698
|
+
output: outputFile,
|
|
7699
|
+
userDataDir: GOSUSLUGI_BROWSER_PROFILE_DIR,
|
|
7700
|
+
headed: Boolean(options.headed),
|
|
7701
|
+
waitMs: Number(options.wait || 3000),
|
|
7702
|
+
timeout: Number(options.timeout || 60000),
|
|
7703
|
+
viewport: options.viewport || "1366x768",
|
|
7704
|
+
});
|
|
7705
|
+
}
|
|
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
|
+
|
|
7923
|
+
async function runPersistentBrowserAutomation(action, params) {
|
|
7924
|
+
await ensureBrowserRuntime();
|
|
7925
|
+
await mkdir(params.userDataDir, { recursive: true });
|
|
7926
|
+
const releaseLock = params.userDataDir === GOSUSLUGI_BROWSER_PROFILE_DIR ? await acquireDirectoryLock(GOSUSLUGI_BROWSER_LOCK_DIR, 180000) : async () => {};
|
|
7927
|
+
const scriptFile = path.join(BROWSER_RUNTIME_DIR, `iola-browser-profile-${Date.now()}-${Math.random().toString(16).slice(2)}.mjs`);
|
|
7928
|
+
await writeFile(scriptFile, persistentBrowserAutomationScript(action, params), "utf8");
|
|
7929
|
+
try {
|
|
7930
|
+
const options = action === "open" ? { cwd: BROWSER_RUNTIME_DIR, inherit: true } : { cwd: BROWSER_RUNTIME_DIR };
|
|
7931
|
+
const result = await runCommand(process.execPath, [scriptFile], options);
|
|
7932
|
+
return result.stdout?.trim() || "";
|
|
7933
|
+
} finally {
|
|
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
|
+
}
|
|
7954
|
+
}
|
|
7955
|
+
}
|
|
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
|
+
|
|
7986
|
+
function persistentBrowserAutomationScript(action, params) {
|
|
7987
|
+
return `
|
|
7988
|
+
import { chromium } from "playwright";
|
|
7989
|
+
const action = ${JSON.stringify(action)};
|
|
7990
|
+
const params = ${JSON.stringify(params)};
|
|
7991
|
+
const [width, height] = String(params.viewport || "1366x768").split("x").map(Number);
|
|
7992
|
+
const context = await chromium.launchPersistentContext(params.userDataDir, {
|
|
7993
|
+
headless: !params.headed,
|
|
7994
|
+
viewport: { width: width || 1366, height: height || 768 },
|
|
7995
|
+
});
|
|
7996
|
+
context.setDefaultTimeout(params.timeout || 60000);
|
|
7997
|
+
const page = context.pages()[0] || await context.newPage();
|
|
7998
|
+
try {
|
|
7999
|
+
await page.goto(params.url || params.pageUrl, { waitUntil: "domcontentloaded", timeout: params.timeout || 60000 });
|
|
8000
|
+
if (params.waitMs) await page.waitForTimeout(params.waitMs);
|
|
8001
|
+
if (action === "open") {
|
|
8002
|
+
if (params.headed) {
|
|
8003
|
+
page.on("close", async () => {
|
|
8004
|
+
await context.close().catch(() => {});
|
|
8005
|
+
});
|
|
8006
|
+
while (!page.isClosed()) {
|
|
8007
|
+
await page.waitForTimeout(1000).catch(() => {});
|
|
8008
|
+
}
|
|
8009
|
+
}
|
|
8010
|
+
} else if (action === "text") {
|
|
8011
|
+
console.log((await page.locator("body").innerText()).trim());
|
|
8012
|
+
} else if (action === "screenshot") {
|
|
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));
|
|
8029
|
+
}
|
|
8030
|
+
} finally {
|
|
8031
|
+
await context.close().catch(() => {});
|
|
8032
|
+
}
|
|
8033
|
+
`;
|
|
8034
|
+
}
|
|
8035
|
+
|
|
7518
8036
|
function browserAutomationScript(action, params) {
|
|
7519
8037
|
return `
|
|
7520
8038
|
import { chromium } from "playwright";
|
|
@@ -7745,6 +8263,9 @@ function mcpTools() {
|
|
|
7745
8263
|
{ name: "report", description: "Запуск встроенного отчета.", inputSchema: schema({ name: { type: "string" }, format: { type: "string" }, output: { type: "string" } }) },
|
|
7746
8264
|
{ name: "browser.text", description: "Открыть страницу в headless Chromium и вернуть видимый текст.", inputSchema: schema({ url: { type: "string" }, waitMs: { type: "number" } }) },
|
|
7747
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" } }) },
|
|
7748
8269
|
];
|
|
7749
8270
|
}
|
|
7750
8271
|
|
|
@@ -7782,6 +8303,9 @@ async function callMcpTool(name, args = {}) {
|
|
|
7782
8303
|
await runBrowserAutomation("screenshot", { url: args.url, output, waitMs: Number(args.waitMs || 0), timeout: Number(args.timeout || 30000), viewport: args.viewport || "1366x768" });
|
|
7783
8304
|
return { output };
|
|
7784
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);
|
|
7785
8309
|
return executeRpc(name, { ...args, _: [] });
|
|
7786
8310
|
}
|
|
7787
8311
|
|
|
@@ -8947,7 +9471,9 @@ function validateConfig(config) {
|
|
|
8947
9471
|
if (!TOOLSETS[toolset]) errors.push(`toolsets.enabled содержит неизвестный toolset: ${toolset}`);
|
|
8948
9472
|
}
|
|
8949
9473
|
if (config.gosuslugi?.enabled && !isGosuslugiConfigured(config)) {
|
|
8950
|
-
|
|
9474
|
+
if ((config.gosuslugi?.mode || "personal-browser") !== "personal-browser") {
|
|
9475
|
+
errors.push("gosuslugi включен в OAuth/OIDC-режиме, но authUrl/tokenUrl/clientId не заполнены");
|
|
9476
|
+
}
|
|
8951
9477
|
}
|
|
8952
9478
|
return errors;
|
|
8953
9479
|
}
|
|
@@ -8958,7 +9484,7 @@ function configSchema() {
|
|
|
8958
9484
|
required: ["api", "ai"],
|
|
8959
9485
|
properties: {
|
|
8960
9486
|
api: { required: ["baseUrl", "mcpBaseUrl"] },
|
|
8961
|
-
gosuslugi: {
|
|
9487
|
+
gosuslugi: { modes: ["personal-browser", "personal-local"], browserProfile: GOSUSLUGI_BROWSER_PROFILE_DIR, oauthRequiredWhenEnabled: ["authUrl", "tokenUrl", "clientId"], optional: ["userinfoUrl", "clientSecret", "scope", "redirectHost", "redirectPort", "redirectPath"] },
|
|
8962
9488
|
ai: { required: ["activeProfile", "profiles"], providers: ["ollama", "openai", "openrouter", "codex"] },
|
|
8963
9489
|
permissions: { localTools: ALL_LOCAL_TOOLS, runtime: ["readFiles", "writeFiles", "editFiles", "deleteFiles", "sync", "externalApi", "externalAi", "codex"] },
|
|
8964
9490
|
toolsets: { available: Object.keys(TOOLSETS) },
|
|
@@ -9016,6 +9542,10 @@ function setConfigValue(config, key, value) {
|
|
|
9016
9542
|
current[parts.at(-1)] = value;
|
|
9017
9543
|
}
|
|
9018
9544
|
|
|
9545
|
+
function sleep(ms) {
|
|
9546
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9547
|
+
}
|
|
9548
|
+
|
|
9019
9549
|
async function loadSecrets() {
|
|
9020
9550
|
try {
|
|
9021
9551
|
return JSON.parse(await readFile(SECRETS_FILE, "utf8"));
|
|
@@ -97,6 +97,15 @@ iola settings validate
|
|
|
97
97
|
iola gosuslugi status
|
|
98
98
|
iola gosuslugi terms
|
|
99
99
|
iola gosuslugi consent
|
|
100
|
+
iola gosuslugi connect
|
|
101
|
+
iola gosuslugi open
|
|
102
|
+
iola gosuslugi text https://www.gosuslugi.ru/
|
|
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
|
|
108
|
+
iola gosuslugi logout --all
|
|
100
109
|
iola gosuslugi configure --auth-url URL --token-url URL --client-id ID --scope openid
|
|
101
110
|
iola gosuslugi login
|
|
102
111
|
iola gosuslugi userinfo --json
|
|
@@ -1,89 +1,103 @@
|
|
|
1
1
|
# Личное подключение Госуслуг
|
|
2
2
|
|
|
3
|
-
`iola-cli`
|
|
3
|
+
`iola-cli` подключает личный аккаунт Госуслуг через отдельный локальный браузерный профиль на компьютере пользователя.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Это не официальный API ЕСИА и не встраивание ключей организации. Пользователь сам открывает окно браузера, сам вводит логин, пароль и код подтверждения. CLI сохраняет только локальный браузерный профиль в домашней папке пользователя.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Первый вход
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
```bash
|
|
10
|
+
iola gosuslugi connect
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Что происходит:
|
|
14
|
+
|
|
15
|
+
1. CLI показывает согласие пользователя.
|
|
16
|
+
2. Устанавливает browser runtime, если он еще не установлен.
|
|
17
|
+
3. Открывает Госуслуги в отдельном профиле Chromium.
|
|
18
|
+
4. Пользователь сам проходит вход.
|
|
19
|
+
5. После завершения пользователь закрывает окно браузера.
|
|
20
|
+
6. CLI сохраняет локальное состояние профиля.
|
|
10
21
|
|
|
11
|
-
|
|
22
|
+
Профиль хранится в:
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
~/.iola/gosuslugi-browser-profile
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Работа с открытым профилем
|
|
29
|
+
|
|
30
|
+
Открыть Госуслуги:
|
|
12
31
|
|
|
13
32
|
```bash
|
|
14
|
-
iola gosuslugi
|
|
33
|
+
iola gosuslugi open
|
|
15
34
|
```
|
|
16
35
|
|
|
17
|
-
|
|
36
|
+
Прочитать видимый текст страницы:
|
|
18
37
|
|
|
19
38
|
```bash
|
|
20
|
-
iola gosuslugi
|
|
39
|
+
iola gosuslugi text https://www.gosuslugi.ru/
|
|
21
40
|
```
|
|
22
41
|
|
|
23
|
-
|
|
42
|
+
Сделать скриншот:
|
|
24
43
|
|
|
25
|
-
|
|
44
|
+
```bash
|
|
45
|
+
iola gosuslugi screenshot https://www.gosuslugi.ru/ --output gosuslugi.png
|
|
46
|
+
```
|
|
26
47
|
|
|
27
|
-
-
|
|
28
|
-
- token endpoint;
|
|
29
|
-
- client ID;
|
|
30
|
-
- разрешенный redirect URI;
|
|
31
|
-
- scope;
|
|
32
|
-
- optional client secret, если он выдан именно пользователю или локальному приложению;
|
|
33
|
-
- optional userinfo endpoint.
|
|
48
|
+
## Read-only данные
|
|
34
49
|
|
|
35
|
-
|
|
50
|
+
Краткие данные профиля:
|
|
36
51
|
|
|
37
52
|
```bash
|
|
38
|
-
iola gosuslugi
|
|
39
|
-
--auth-url "https://..." \
|
|
40
|
-
--token-url "https://..." \
|
|
41
|
-
--userinfo-url "https://..." \
|
|
42
|
-
--client-id "CLIENT_ID" \
|
|
43
|
-
--scope "openid" \
|
|
44
|
-
--redirect-port 18791
|
|
53
|
+
iola gosuslugi whoami
|
|
45
54
|
```
|
|
46
55
|
|
|
47
|
-
|
|
56
|
+
Задолженности и платежи к оплате:
|
|
48
57
|
|
|
49
|
-
```
|
|
50
|
-
|
|
58
|
+
```bash
|
|
59
|
+
iola gosuslugi debt
|
|
51
60
|
```
|
|
52
61
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
## Вход
|
|
62
|
+
Уведомления:
|
|
56
63
|
|
|
57
64
|
```bash
|
|
58
|
-
iola gosuslugi
|
|
65
|
+
iola gosuslugi notifications
|
|
66
|
+
iola gosuslugi notifications --unread
|
|
59
67
|
```
|
|
60
68
|
|
|
61
|
-
|
|
69
|
+
Отметить уведомления прочитанными можно только отдельной командой с подтверждением:
|
|
62
70
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
4. Пользователь сам вводит логин, пароль, SMS/2FA.
|
|
67
|
-
5. Госуслуги возвращают `authorization code` на локальный callback.
|
|
68
|
-
6. CLI обменивает code на токены и сохраняет их локально.
|
|
71
|
+
```bash
|
|
72
|
+
iola gosuslugi mark-read
|
|
73
|
+
```
|
|
69
74
|
|
|
70
|
-
|
|
75
|
+
AI-агент может использовать read-only tools Госуслуг, если пользователь спрашивает про ФИО, дату рождения, задолженности, штрафы, налоги, платежи или уведомления.
|
|
71
76
|
|
|
72
|
-
##
|
|
77
|
+
## Статус
|
|
73
78
|
|
|
74
79
|
```bash
|
|
75
80
|
iola gosuslugi status
|
|
76
|
-
iola gosuslugi userinfo --json
|
|
77
81
|
```
|
|
78
82
|
|
|
79
|
-
|
|
83
|
+
Команда показывает, принято ли согласие, где лежит локальный профиль и когда он был создан.
|
|
84
|
+
|
|
85
|
+
## Отключение
|
|
86
|
+
|
|
87
|
+
Удалить локальное подключение:
|
|
80
88
|
|
|
81
89
|
```bash
|
|
82
|
-
iola gosuslugi logout
|
|
90
|
+
iola gosuslugi logout --all
|
|
83
91
|
```
|
|
84
92
|
|
|
85
|
-
|
|
93
|
+
`--all` удаляет сохраненный браузерный профиль.
|
|
86
94
|
|
|
87
95
|
## Ограничения
|
|
88
96
|
|
|
89
|
-
|
|
97
|
+
- CLI работает только с тем, что доступно пользователю в локальном браузере.
|
|
98
|
+
- CLI не извлекает cookies, session tokens или внутренние токены Госуслуг.
|
|
99
|
+
- Юридически значимые действия должны требовать отдельного подтверждения пользователя.
|
|
100
|
+
- Сессия браузерного профиля может протухнуть. Если Госуслуги попросят повторный вход или 2FA, выполните `iola gosuslugi connect`.
|
|
101
|
+
- Если Госуслуги попросят повторный вход, код или подтверждение, пользователь проходит его сам в открытом окне.
|
|
102
|
+
|
|
103
|
+
OAuth/OIDC-команды `configure`, `login`, `userinfo` оставлены для случая, если у пользователя есть официально зарегистрированное подключение ЕСИА.
|