@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 CHANGED
@@ -40,8 +40,7 @@ npx -y @iola_adm/iola-cli
40
40
  Повторный запуск мастера:
41
41
 
42
42
  ```bash
43
- iola wizard
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.57",
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 || true",
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-local",
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|configure|status|login|logout|userinfo
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.setRawMode(true);
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
- if (stream.isTTY) {
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-local",
2262
+ mode: config.gosuslugi?.mode || "personal-browser",
2256
2263
  enabled: config.gosuslugi?.enabled ? "yes" : "no",
2257
- configured: isGosuslugiConfigured(config) ? "yes" : "no",
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, configure, status, login, logout, userinfo.");
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
- console.log("Параметры подключения можно указать командой: iola gosuslugi configure --auth-url URL --token-url URL --client-id ID --scope openid");
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
- errors.push("gosuslugi включен, но authUrl/tokenUrl/clientId не заполнены");
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: { requiredWhenEnabled: ["authUrl", "tokenUrl", "clientId"], optional: ["userinfoUrl", "clientSecret", "scope", "redirectHost", "redirectPort", "redirectPath"] },
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
- Сценарий рассчитан именно на пользователя, который ставит CLI на свой ПК и подключает свою учетную запись. Ключи организации или администрации в публичный пакет не вшиваются.
5
+ Это не официальный API ЕСИА и не встраивание ключей организации. Пользователь сам открывает окно браузера, сам вводит логин, пароль и код подтверждения. CLI сохраняет только локальный браузерный профиль в домашней папке пользователя.
6
6
 
7
- ## Согласие пользователя
8
-
9
- Перед входом CLI показывает текст согласия в терминале. В wiki он не дублируется, чтобы пользователь видел актуальную формулировку именно в установленной версии CLI.
10
-
11
- Посмотреть текст:
7
+ ## Первый вход
12
8
 
13
9
  ```bash
14
- iola gosuslugi terms
10
+ iola gosuslugi connect
15
11
  ```
16
12
 
17
- Принять заранее:
13
+ Что происходит:
18
14
 
19
- ```bash
20
- iola gosuslugi consent
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
- Для работы нужны параметры пользовательского OAuth/OIDC-подключения:
24
+ ```text
25
+ ~/.iola/gosuslugi-browser-profile
26
+ ```
26
27
 
27
- - authorization endpoint;
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 configure \
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
- CLI покажет redirect URI:
36
+ Прочитать видимый текст страницы:
48
37
 
49
- ```text
50
- http://127.0.0.1:18791/gosuslugi/callback
38
+ ```bash
39
+ iola gosuslugi text https://www.gosuslugi.ru/
51
40
  ```
52
41
 
53
- Этот URI должен быть разрешен в настройках подключения.
54
-
55
- ## Вход
42
+ Сделать скриншот:
56
43
 
57
44
  ```bash
58
- iola gosuslugi login
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
- Без параметров пользовательского подключения команда `login` не сможет завершить подключение. Все ключи, токены и настройки хранятся только локально у пользователя.
68
+ - CLI работает только с тем, что доступно пользователю в локальном браузере.
69
+ - CLI не извлекает cookies, session tokens или внутренние токены Госуслуг.
70
+ - Юридически значимые действия должны требовать отдельного подтверждения пользователя.
71
+ - Если Госуслуги попросят повторный вход, код или подтверждение, пользователь проходит его сам в открытом окне.
72
+
73
+ OAuth/OIDC-команды `configure`, `login`, `userinfo` оставлены для случая, если у пользователя есть официально зарегистрированное подключение ЕСИА.