@iola_adm/iola-cli 0.1.57 → 0.1.59
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 -4
- package/package.json +2 -2
- package/src/cli.js +201 -16
- package/wiki//320/232/320/276/320/274/320/260/320/275/320/264/321/213.md +5 -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 +37 -53
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,7 @@ iola trajectory last
|
|
|
86
85
|
iola review config
|
|
87
86
|
iola browser status
|
|
88
87
|
iola gosuslugi status
|
|
88
|
+
iola gosuslugi connect
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
Локальная модель через Ollama:
|
|
@@ -133,14 +133,14 @@ iola version --check
|
|
|
133
133
|
- MCP-мост для локальной модели: встроенный `iola-local` доступен как `mcp:iola-local:TOOL`;
|
|
134
134
|
- дополнительные stdio MCP-серверы можно добавить в `~/.iola/config.json` в раздел `mcp.servers`;
|
|
135
135
|
- браузерный runtime через Playwright: чтение страниц, скриншоты, PDF, клики, ввод и eval;
|
|
136
|
-
- личное локальное подключение Госуслуг
|
|
136
|
+
- личное локальное подключение Госуслуг через отдельный браузерный профиль на ПК пользователя;
|
|
137
137
|
- управляемые локальные файловые операции с режимами `locked`, `read-only`, `workspace-write`, `full-access`;
|
|
138
138
|
- планы выполнения, traces, tasks, artifacts, snapshots и policy-профили;
|
|
139
139
|
- экспорт отчетов в Excel/Word-совместимые файлы;
|
|
140
140
|
- staged changes, импорт локальных CSV/JSON, индекс локальных документов, report packs, plugins и локальный MCP endpoint;
|
|
141
141
|
- чтение и индексирование `.docx`, `.xlsx`, `.pptx`, `.pdf`, `.md`, `.txt`, `.csv`, `.json`, `.html`;
|
|
142
142
|
- работа с архивами через 7-Zip: `.zip`, `.7z`, `.rar`, `.tar`, `.gz`, `.tgz`, `.bz2`, `.xz` и другие;
|
|
143
|
-
- расширенный `iola onboard` с установкой 7-Zip, Ollama, Codex CLI и настройкой выбранных компонентов;
|
|
143
|
+
- расширенный `iola onboard` с установкой 7-Zip, браузерного runtime, Ollama, Codex CLI и настройкой выбранных компонентов;
|
|
144
144
|
- cron-задачи, локальный daemon, web dashboard и RPC для автоматизаций;
|
|
145
145
|
- контекстные файлы `IOLA.md` и `.iola/context.md`;
|
|
146
146
|
- интеграция с публичным 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.59",
|
|
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
|
},
|
package/src/cli.js
CHANGED
|
@@ -26,6 +26,8 @@ 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_DEFAULT_URL = "https://www.gosuslugi.ru/";
|
|
29
31
|
const INDEXABLE_EXTENSIONS = /\.(md|txt|csv|json|html|docx|xlsx|pptx|pdf)$/i;
|
|
30
32
|
const LOCAL_TOOLS = ["search_data", "get_card", "export_report", "file_read", "browser_open"];
|
|
31
33
|
const LEGACY_LOCAL_TOOLS = ["search_local", "export_data", "run_report", "save_view"];
|
|
@@ -130,7 +132,7 @@ const DEFAULT_AI_CONFIG = {
|
|
|
130
132
|
},
|
|
131
133
|
gosuslugi: {
|
|
132
134
|
enabled: false,
|
|
133
|
-
mode: "personal-
|
|
135
|
+
mode: "personal-browser",
|
|
134
136
|
authUrl: "",
|
|
135
137
|
tokenUrl: "",
|
|
136
138
|
userinfoUrl: "",
|
|
@@ -281,6 +283,7 @@ const SLASH_COMMANDS = [
|
|
|
281
283
|
{ command: "/resume SESSION_ID", description: "продолжить сессию" },
|
|
282
284
|
{ command: "/features list", description: "feature flags" },
|
|
283
285
|
{ command: "/gosuslugi status", description: "личное подключение Госуслуг" },
|
|
286
|
+
{ command: "/gosuslugi connect", description: "открыть личный вход Госуслуг" },
|
|
284
287
|
{ command: "/wiki", description: "ссылки на документацию" },
|
|
285
288
|
{ command: "/context list", description: "локальный контекст проекта" },
|
|
286
289
|
{ command: "/skills list", description: "skills" },
|
|
@@ -514,7 +517,7 @@ Usage:
|
|
|
514
517
|
iola fork SESSION_ID [TEXT]
|
|
515
518
|
iola features list|enable|disable
|
|
516
519
|
iola settings list|get|validate|doctor|init
|
|
517
|
-
iola gosuslugi terms|consent|
|
|
520
|
+
iola gosuslugi terms|consent|status|connect|open|text|screenshot|logout|configure|login|userinfo
|
|
518
521
|
iola wiki [open|links]
|
|
519
522
|
iola context list|show|init
|
|
520
523
|
iola skills list|show|paths|enable|disable|bundles|bundle|doctor
|
|
@@ -794,10 +797,8 @@ async function startAgentReadline() {
|
|
|
794
797
|
|
|
795
798
|
async function startAgentRawInput() {
|
|
796
799
|
const state = { history: [], buffer: "", selected: 0, slashOpen: false, running: false, renderedInputLines: 0, rawMode: true, pendingOutput: "" };
|
|
797
|
-
emitKeypressEvents(input);
|
|
798
800
|
const wasRaw = input.isRaw;
|
|
799
|
-
input
|
|
800
|
-
input.resume();
|
|
801
|
+
activateRawInput(input);
|
|
801
802
|
|
|
802
803
|
const render = () => renderAgentInput(state);
|
|
803
804
|
render();
|
|
@@ -1376,14 +1377,19 @@ function startActivityIndicator(label = "работаю") {
|
|
|
1376
1377
|
function suspendRawInputForCommand(stream) {
|
|
1377
1378
|
if (!stream.isTTY || !stream.isRaw) return () => {};
|
|
1378
1379
|
stream.setRawMode(false);
|
|
1380
|
+
stream.pause();
|
|
1379
1381
|
return () => {
|
|
1380
|
-
|
|
1381
|
-
stream.setRawMode(true);
|
|
1382
|
-
stream.resume();
|
|
1383
|
-
}
|
|
1382
|
+
activateRawInput(stream);
|
|
1384
1383
|
};
|
|
1385
1384
|
}
|
|
1386
1385
|
|
|
1386
|
+
function activateRawInput(stream) {
|
|
1387
|
+
if (!stream.isTTY) return;
|
|
1388
|
+
emitKeypressEvents(stream);
|
|
1389
|
+
stream.setRawMode(true);
|
|
1390
|
+
stream.resume();
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1387
1393
|
function flushPendingAgentOutput(state) {
|
|
1388
1394
|
const text = state.pendingOutput;
|
|
1389
1395
|
state.pendingOutput = "";
|
|
@@ -2250,11 +2256,16 @@ async function handleGosuslugi(args) {
|
|
|
2250
2256
|
const config = await loadConfig();
|
|
2251
2257
|
const secrets = await loadSecrets();
|
|
2252
2258
|
const tokens = secrets.gosuslugi?.tokens || null;
|
|
2259
|
+
const browserSession = secrets.gosuslugiBrowser || null;
|
|
2253
2260
|
const consent = secrets.gosuslugiConsent || null;
|
|
2254
2261
|
printKeyValue({
|
|
2255
|
-
mode: config.gosuslugi?.mode || "personal-
|
|
2262
|
+
mode: config.gosuslugi?.mode || "personal-browser",
|
|
2256
2263
|
enabled: config.gosuslugi?.enabled ? "yes" : "no",
|
|
2257
|
-
|
|
2264
|
+
browserProfile: GOSUSLUGI_BROWSER_PROFILE_DIR,
|
|
2265
|
+
browserProfileExists: existsSync(GOSUSLUGI_BROWSER_PROFILE_DIR) ? "yes" : "no",
|
|
2266
|
+
browserConnected: browserSession?.connectedAt ? "yes" : "unknown",
|
|
2267
|
+
browserConnectedAt: browserSession?.connectedAt || "-",
|
|
2268
|
+
oauthConfigured: isGosuslugiConfigured(config) ? "yes" : "no",
|
|
2258
2269
|
consent: consent?.version === GOSUSLUGI_CONSENT_VERSION ? "accepted" : "not accepted",
|
|
2259
2270
|
consentAt: consent?.acceptedAt || "-",
|
|
2260
2271
|
clientId: config.gosuslugi?.clientId ? maskSecret(config.gosuslugi.clientId) : "-",
|
|
@@ -2269,6 +2280,35 @@ async function handleGosuslugi(args) {
|
|
|
2269
2280
|
return;
|
|
2270
2281
|
}
|
|
2271
2282
|
|
|
2283
|
+
if (action === "connect") {
|
|
2284
|
+
await gosuslugiBrowserConnect(options);
|
|
2285
|
+
return;
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
if (action === "open") {
|
|
2289
|
+
await gosuslugiBrowserOpen(targetOrDefault(rest, options), options);
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
if (action === "text") {
|
|
2294
|
+
const result = await gosuslugiBrowserReadText(targetOrDefault(rest, options), options);
|
|
2295
|
+
if (options.output) {
|
|
2296
|
+
await writeFile(path.resolve(options.output), result, "utf8");
|
|
2297
|
+
console.log(`Файл сохранен: ${path.resolve(options.output)}`);
|
|
2298
|
+
} else {
|
|
2299
|
+
console.log(result);
|
|
2300
|
+
}
|
|
2301
|
+
return;
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
if (action === "screenshot") {
|
|
2305
|
+
const outputFile = path.resolve(options.output || "gosuslugi-page.png");
|
|
2306
|
+
await gosuslugiBrowserScreenshot(targetOrDefault(rest, options), outputFile, options);
|
|
2307
|
+
saveArtifact("gosuslugi-screenshot", targetOrDefault(rest, options), outputFile, { url: targetOrDefault(rest, options) });
|
|
2308
|
+
console.log(`Файл сохранен: ${outputFile}`);
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2272
2312
|
if (action === "configure") {
|
|
2273
2313
|
const current = await loadConfig();
|
|
2274
2314
|
const next = {
|
|
@@ -2300,7 +2340,12 @@ async function handleGosuslugi(args) {
|
|
|
2300
2340
|
if (action === "logout") {
|
|
2301
2341
|
const secrets = await loadSecrets();
|
|
2302
2342
|
delete secrets.gosuslugi;
|
|
2343
|
+
delete secrets.gosuslugiBrowser;
|
|
2303
2344
|
await saveSecrets(secrets);
|
|
2345
|
+
if (options.profile || options.all) {
|
|
2346
|
+
await rm(GOSUSLUGI_BROWSER_PROFILE_DIR, { recursive: true, force: true }).catch(() => {});
|
|
2347
|
+
console.log("Локальный браузерный профиль Госуслуг удален.");
|
|
2348
|
+
}
|
|
2304
2349
|
console.log("Локальное подключение Госуслуг удалено.");
|
|
2305
2350
|
return;
|
|
2306
2351
|
}
|
|
@@ -2312,7 +2357,11 @@ async function handleGosuslugi(args) {
|
|
|
2312
2357
|
return;
|
|
2313
2358
|
}
|
|
2314
2359
|
|
|
2315
|
-
throw new Error("Команды gosuslugi: terms, consent,
|
|
2360
|
+
throw new Error("Команды gosuslugi: terms, consent, status, connect, open, text, screenshot, logout, configure, login, userinfo.");
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
function targetOrDefault(args, options = {}) {
|
|
2364
|
+
return options.url || args.find((item) => !item.startsWith("--")) || GOSUSLUGI_DEFAULT_URL;
|
|
2316
2365
|
}
|
|
2317
2366
|
|
|
2318
2367
|
async function handleWiki(args) {
|
|
@@ -3342,6 +3391,10 @@ async function ensureGosuslugiConsent(options = {}) {
|
|
|
3342
3391
|
await acceptGosuslugiConsent(options);
|
|
3343
3392
|
}
|
|
3344
3393
|
|
|
3394
|
+
async function requireGosuslugiConsent() {
|
|
3395
|
+
await ensureGosuslugiConsent();
|
|
3396
|
+
}
|
|
3397
|
+
|
|
3345
3398
|
function waitForOAuthCallback(settings, expectedState, timeoutMs) {
|
|
3346
3399
|
const host = settings.redirectHost || "127.0.0.1";
|
|
3347
3400
|
const port = Number(settings.redirectPort || 18791);
|
|
@@ -6980,9 +7033,14 @@ async function onboard(args = []) {
|
|
|
6980
7033
|
else await installBrowserRuntime();
|
|
6981
7034
|
}
|
|
6982
7035
|
if (components.includes("gosuslugi")) {
|
|
6983
|
-
await handleGosuslugi(["terms"]);
|
|
6984
7036
|
if (process.stdin.isTTY) await handleGosuslugi(["consent"]);
|
|
6985
|
-
|
|
7037
|
+
else await handleGosuslugi(["terms"]);
|
|
7038
|
+
await ensureBrowserRuntimeForGosuslugi();
|
|
7039
|
+
if (process.stdin.isTTY && await confirm("Открыть Госуслуги для входа сейчас? [Y/n] ")) {
|
|
7040
|
+
await gosuslugiBrowserConnect({ yes: true });
|
|
7041
|
+
} else {
|
|
7042
|
+
console.log("Подключить личные Госуслуги позже: iola gosuslugi connect");
|
|
7043
|
+
}
|
|
6986
7044
|
}
|
|
6987
7045
|
if (components.includes("index")) {
|
|
6988
7046
|
await setFilesMode("read-only", await loadConfig());
|
|
@@ -7478,6 +7536,10 @@ async function getBrowserStatus() {
|
|
|
7478
7536
|
}
|
|
7479
7537
|
|
|
7480
7538
|
async function installBrowserRuntime() {
|
|
7539
|
+
if (existsSync(BROWSER_RUNTIME_PACKAGE)) {
|
|
7540
|
+
console.log(`Browser runtime уже установлен: ${BROWSER_RUNTIME_DIR}`);
|
|
7541
|
+
return;
|
|
7542
|
+
}
|
|
7481
7543
|
await mkdir(BROWSER_RUNTIME_DIR, { recursive: true });
|
|
7482
7544
|
const packageFile = path.join(BROWSER_RUNTIME_DIR, "package.json");
|
|
7483
7545
|
if (!existsSync(packageFile)) {
|
|
@@ -7512,6 +7574,127 @@ async function runBrowserAutomation(action, params) {
|
|
|
7512
7574
|
}
|
|
7513
7575
|
}
|
|
7514
7576
|
|
|
7577
|
+
async function ensureBrowserRuntimeForGosuslugi() {
|
|
7578
|
+
if (existsSync(BROWSER_RUNTIME_PACKAGE)) return;
|
|
7579
|
+
console.log("Browser runtime не установлен. Устанавливаю Playwright/Chromium для локального браузерного профиля.");
|
|
7580
|
+
await installBrowserRuntime();
|
|
7581
|
+
}
|
|
7582
|
+
|
|
7583
|
+
async function gosuslugiBrowserConnect(options = {}) {
|
|
7584
|
+
await ensureGosuslugiConsent({ yes: options.yes });
|
|
7585
|
+
await ensureBrowserRuntimeForGosuslugi();
|
|
7586
|
+
await saveConfig({ gosuslugi: { ...(await loadConfig()).gosuslugi, enabled: true, mode: "personal-browser" } });
|
|
7587
|
+
const url = options.url || GOSUSLUGI_DEFAULT_URL;
|
|
7588
|
+
console.log(`Открываю Госуслуги в отдельном локальном профиле: ${GOSUSLUGI_BROWSER_PROFILE_DIR}`);
|
|
7589
|
+
console.log("Авторизуйтесь в открывшемся окне. Когда закончите, закройте окно браузера.");
|
|
7590
|
+
await runPersistentBrowserAutomation("open", {
|
|
7591
|
+
url,
|
|
7592
|
+
userDataDir: GOSUSLUGI_BROWSER_PROFILE_DIR,
|
|
7593
|
+
headed: true,
|
|
7594
|
+
waitMs: Number(options.wait || 0),
|
|
7595
|
+
timeout: Number(options.timeout || 120000),
|
|
7596
|
+
viewport: options.viewport || "1366x768",
|
|
7597
|
+
});
|
|
7598
|
+
const secrets = await loadSecrets();
|
|
7599
|
+
secrets.gosuslugiBrowser = {
|
|
7600
|
+
mode: "personal-browser",
|
|
7601
|
+
profileDir: GOSUSLUGI_BROWSER_PROFILE_DIR,
|
|
7602
|
+
connectedAt: new Date().toISOString(),
|
|
7603
|
+
lastUrl: url,
|
|
7604
|
+
};
|
|
7605
|
+
await saveSecrets(secrets);
|
|
7606
|
+
console.log("Локальный браузерный профиль Госуслуг сохранен.");
|
|
7607
|
+
}
|
|
7608
|
+
|
|
7609
|
+
async function gosuslugiBrowserOpen(url = GOSUSLUGI_DEFAULT_URL, options = {}) {
|
|
7610
|
+
await requireGosuslugiConsent();
|
|
7611
|
+
await ensureBrowserRuntimeForGosuslugi();
|
|
7612
|
+
await runPersistentBrowserAutomation("open", {
|
|
7613
|
+
url,
|
|
7614
|
+
userDataDir: GOSUSLUGI_BROWSER_PROFILE_DIR,
|
|
7615
|
+
headed: true,
|
|
7616
|
+
waitMs: Number(options.wait || 0),
|
|
7617
|
+
timeout: Number(options.timeout || 120000),
|
|
7618
|
+
viewport: options.viewport || "1366x768",
|
|
7619
|
+
});
|
|
7620
|
+
}
|
|
7621
|
+
|
|
7622
|
+
async function gosuslugiBrowserReadText(url = GOSUSLUGI_DEFAULT_URL, options = {}) {
|
|
7623
|
+
await requireGosuslugiConsent();
|
|
7624
|
+
await ensureBrowserRuntimeForGosuslugi();
|
|
7625
|
+
return runPersistentBrowserAutomation("text", {
|
|
7626
|
+
url,
|
|
7627
|
+
userDataDir: GOSUSLUGI_BROWSER_PROFILE_DIR,
|
|
7628
|
+
headed: Boolean(options.headed),
|
|
7629
|
+
waitMs: Number(options.wait || 3000),
|
|
7630
|
+
timeout: Number(options.timeout || 60000),
|
|
7631
|
+
viewport: options.viewport || "1366x768",
|
|
7632
|
+
});
|
|
7633
|
+
}
|
|
7634
|
+
|
|
7635
|
+
async function gosuslugiBrowserScreenshot(url = GOSUSLUGI_DEFAULT_URL, outputFile, options = {}) {
|
|
7636
|
+
await requireGosuslugiConsent();
|
|
7637
|
+
await ensureBrowserRuntimeForGosuslugi();
|
|
7638
|
+
await runPersistentBrowserAutomation("screenshot", {
|
|
7639
|
+
url,
|
|
7640
|
+
output: outputFile,
|
|
7641
|
+
userDataDir: GOSUSLUGI_BROWSER_PROFILE_DIR,
|
|
7642
|
+
headed: Boolean(options.headed),
|
|
7643
|
+
waitMs: Number(options.wait || 3000),
|
|
7644
|
+
timeout: Number(options.timeout || 60000),
|
|
7645
|
+
viewport: options.viewport || "1366x768",
|
|
7646
|
+
});
|
|
7647
|
+
}
|
|
7648
|
+
|
|
7649
|
+
async function runPersistentBrowserAutomation(action, params) {
|
|
7650
|
+
await ensureBrowserRuntime();
|
|
7651
|
+
await mkdir(params.userDataDir, { recursive: true });
|
|
7652
|
+
const scriptFile = path.join(BROWSER_RUNTIME_DIR, `iola-browser-profile-${Date.now()}-${Math.random().toString(16).slice(2)}.mjs`);
|
|
7653
|
+
await writeFile(scriptFile, persistentBrowserAutomationScript(action, params), "utf8");
|
|
7654
|
+
try {
|
|
7655
|
+
const options = action === "open" ? { cwd: BROWSER_RUNTIME_DIR, inherit: true } : { cwd: BROWSER_RUNTIME_DIR };
|
|
7656
|
+
const result = await runCommand(process.execPath, [scriptFile], options);
|
|
7657
|
+
return result.stdout?.trim() || "";
|
|
7658
|
+
} finally {
|
|
7659
|
+
await rm(scriptFile, { force: true }).catch(() => {});
|
|
7660
|
+
}
|
|
7661
|
+
}
|
|
7662
|
+
|
|
7663
|
+
function persistentBrowserAutomationScript(action, params) {
|
|
7664
|
+
return `
|
|
7665
|
+
import { chromium } from "playwright";
|
|
7666
|
+
const action = ${JSON.stringify(action)};
|
|
7667
|
+
const params = ${JSON.stringify(params)};
|
|
7668
|
+
const [width, height] = String(params.viewport || "1366x768").split("x").map(Number);
|
|
7669
|
+
const context = await chromium.launchPersistentContext(params.userDataDir, {
|
|
7670
|
+
headless: !params.headed,
|
|
7671
|
+
viewport: { width: width || 1366, height: height || 768 },
|
|
7672
|
+
});
|
|
7673
|
+
context.setDefaultTimeout(params.timeout || 60000);
|
|
7674
|
+
const page = context.pages()[0] || await context.newPage();
|
|
7675
|
+
try {
|
|
7676
|
+
await page.goto(params.url, { waitUntil: "domcontentloaded", timeout: params.timeout || 60000 });
|
|
7677
|
+
if (params.waitMs) await page.waitForTimeout(params.waitMs);
|
|
7678
|
+
if (action === "open") {
|
|
7679
|
+
if (params.headed) {
|
|
7680
|
+
page.on("close", async () => {
|
|
7681
|
+
await context.close().catch(() => {});
|
|
7682
|
+
});
|
|
7683
|
+
while (!page.isClosed()) {
|
|
7684
|
+
await page.waitForTimeout(1000).catch(() => {});
|
|
7685
|
+
}
|
|
7686
|
+
}
|
|
7687
|
+
} else if (action === "text") {
|
|
7688
|
+
console.log((await page.locator("body").innerText()).trim());
|
|
7689
|
+
} else if (action === "screenshot") {
|
|
7690
|
+
await page.screenshot({ path: params.output, fullPage: true });
|
|
7691
|
+
}
|
|
7692
|
+
} finally {
|
|
7693
|
+
await context.close().catch(() => {});
|
|
7694
|
+
}
|
|
7695
|
+
`;
|
|
7696
|
+
}
|
|
7697
|
+
|
|
7515
7698
|
function browserAutomationScript(action, params) {
|
|
7516
7699
|
return `
|
|
7517
7700
|
import { chromium } from "playwright";
|
|
@@ -8944,7 +9127,9 @@ function validateConfig(config) {
|
|
|
8944
9127
|
if (!TOOLSETS[toolset]) errors.push(`toolsets.enabled содержит неизвестный toolset: ${toolset}`);
|
|
8945
9128
|
}
|
|
8946
9129
|
if (config.gosuslugi?.enabled && !isGosuslugiConfigured(config)) {
|
|
8947
|
-
|
|
9130
|
+
if ((config.gosuslugi?.mode || "personal-browser") !== "personal-browser") {
|
|
9131
|
+
errors.push("gosuslugi включен в OAuth/OIDC-режиме, но authUrl/tokenUrl/clientId не заполнены");
|
|
9132
|
+
}
|
|
8948
9133
|
}
|
|
8949
9134
|
return errors;
|
|
8950
9135
|
}
|
|
@@ -8955,7 +9140,7 @@ function configSchema() {
|
|
|
8955
9140
|
required: ["api", "ai"],
|
|
8956
9141
|
properties: {
|
|
8957
9142
|
api: { required: ["baseUrl", "mcpBaseUrl"] },
|
|
8958
|
-
gosuslugi: {
|
|
9143
|
+
gosuslugi: { modes: ["personal-browser", "personal-local"], browserProfile: GOSUSLUGI_BROWSER_PROFILE_DIR, oauthRequiredWhenEnabled: ["authUrl", "tokenUrl", "clientId"], optional: ["userinfoUrl", "clientSecret", "scope", "redirectHost", "redirectPort", "redirectPath"] },
|
|
8959
9144
|
ai: { required: ["activeProfile", "profiles"], providers: ["ollama", "openai", "openrouter", "codex"] },
|
|
8960
9145
|
permissions: { localTools: ALL_LOCAL_TOOLS, runtime: ["readFiles", "writeFiles", "editFiles", "deleteFiles", "sync", "externalApi", "externalAi", "codex"] },
|
|
8961
9146
|
toolsets: { available: Object.keys(TOOLSETS) },
|
|
@@ -97,6 +97,11 @@ 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 logout --all
|
|
100
105
|
iola gosuslugi configure --auth-url URL --token-url URL --client-id ID --scope openid
|
|
101
106
|
iola gosuslugi login
|
|
102
107
|
iola gosuslugi userinfo --json
|
|
@@ -1,89 +1,73 @@
|
|
|
1
1
|
# Личное подключение Госуслуг
|
|
2
2
|
|
|
3
|
-
`iola-cli`
|
|
3
|
+
`iola-cli` подключает личный аккаунт Госуслуг через отдельный локальный браузерный профиль на компьютере пользователя.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Это не официальный API ЕСИА и не встраивание ключей организации. Пользователь сам открывает окно браузера, сам вводит логин, пароль и код подтверждения. CLI сохраняет только локальный браузерный профиль в домашней папке пользователя.
|
|
6
6
|
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
Перед входом CLI показывает текст согласия в терминале. В wiki он не дублируется, чтобы пользователь видел актуальную формулировку именно в установленной версии CLI.
|
|
10
|
-
|
|
11
|
-
Посмотреть текст:
|
|
7
|
+
## Первый вход
|
|
12
8
|
|
|
13
9
|
```bash
|
|
14
|
-
iola gosuslugi
|
|
10
|
+
iola gosuslugi connect
|
|
15
11
|
```
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
Что происходит:
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
1. CLI показывает согласие пользователя.
|
|
16
|
+
2. Устанавливает browser runtime, если он еще не установлен.
|
|
17
|
+
3. Открывает Госуслуги в отдельном профиле Chromium.
|
|
18
|
+
4. Пользователь сам проходит вход.
|
|
19
|
+
5. После завершения пользователь закрывает окно браузера.
|
|
20
|
+
6. CLI сохраняет локальное состояние профиля.
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
Профиль хранится в:
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
```text
|
|
25
|
+
~/.iola/gosuslugi-browser-profile
|
|
26
|
+
```
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
- token endpoint;
|
|
29
|
-
- client ID;
|
|
30
|
-
- разрешенный redirect URI;
|
|
31
|
-
- scope;
|
|
32
|
-
- optional client secret, если он выдан именно пользователю или локальному приложению;
|
|
33
|
-
- optional userinfo endpoint.
|
|
28
|
+
## Работа с открытым профилем
|
|
34
29
|
|
|
35
|
-
|
|
30
|
+
Открыть Госуслуги:
|
|
36
31
|
|
|
37
32
|
```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
|
|
33
|
+
iola gosuslugi open
|
|
45
34
|
```
|
|
46
35
|
|
|
47
|
-
|
|
36
|
+
Прочитать видимый текст страницы:
|
|
48
37
|
|
|
49
|
-
```
|
|
50
|
-
|
|
38
|
+
```bash
|
|
39
|
+
iola gosuslugi text https://www.gosuslugi.ru/
|
|
51
40
|
```
|
|
52
41
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
## Вход
|
|
42
|
+
Сделать скриншот:
|
|
56
43
|
|
|
57
44
|
```bash
|
|
58
|
-
iola gosuslugi
|
|
45
|
+
iola gosuslugi screenshot https://www.gosuslugi.ru/ --output gosuslugi.png
|
|
59
46
|
```
|
|
60
47
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
1. CLI запускает локальный callback server на `127.0.0.1`.
|
|
64
|
-
2. Показывается и сохраняется согласие пользователя.
|
|
65
|
-
3. Открывается экран входа Госуслуг.
|
|
66
|
-
4. Пользователь сам вводит логин, пароль, SMS/2FA.
|
|
67
|
-
5. Госуслуги возвращают `authorization code` на локальный callback.
|
|
68
|
-
6. CLI обменивает code на токены и сохраняет их локально.
|
|
69
|
-
|
|
70
|
-
Токены хранятся в `~/.iola/secrets.json` на компьютере пользователя.
|
|
71
|
-
|
|
72
|
-
## Проверка
|
|
48
|
+
## Статус
|
|
73
49
|
|
|
74
50
|
```bash
|
|
75
51
|
iola gosuslugi status
|
|
76
|
-
iola gosuslugi userinfo --json
|
|
77
52
|
```
|
|
78
53
|
|
|
79
|
-
|
|
54
|
+
Команда показывает, принято ли согласие, где лежит локальный профиль и когда он был создан.
|
|
55
|
+
|
|
56
|
+
## Отключение
|
|
57
|
+
|
|
58
|
+
Удалить локальное подключение:
|
|
80
59
|
|
|
81
60
|
```bash
|
|
82
|
-
iola gosuslugi logout
|
|
61
|
+
iola gosuslugi logout --all
|
|
83
62
|
```
|
|
84
63
|
|
|
85
|
-
|
|
64
|
+
`--all` удаляет сохраненный браузерный профиль.
|
|
86
65
|
|
|
87
66
|
## Ограничения
|
|
88
67
|
|
|
89
|
-
|
|
68
|
+
- CLI работает только с тем, что доступно пользователю в локальном браузере.
|
|
69
|
+
- CLI не извлекает cookies, session tokens или внутренние токены Госуслуг.
|
|
70
|
+
- Юридически значимые действия должны требовать отдельного подтверждения пользователя.
|
|
71
|
+
- Если Госуслуги попросят повторный вход, код или подтверждение, пользователь проходит его сам в открытом окне.
|
|
72
|
+
|
|
73
|
+
OAuth/OIDC-команды `configure`, `login`, `userinfo` оставлены для случая, если у пользователя есть официально зарегистрированное подключение ЕСИА.
|