@iola_adm/iola-cli 0.1.32 → 0.1.34

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
@@ -26,16 +26,17 @@ npm --version
26
26
 
27
27
  ```bash
28
28
  npm install -g @iola_adm/iola-cli
29
- iola init
30
- iola --help
29
+ iola
31
30
  ```
32
31
 
33
32
  Без глобальной установки:
34
33
 
35
34
  ```bash
36
- npx -y @iola_adm/iola-cli init
35
+ npx -y @iola_adm/iola-cli
37
36
  ```
38
37
 
38
+ При первом запуске `iola` открывает мастер настройки, затем запускает интерактивный агент. Короткая справка: `iola help`, полный список команд: `iola commands`.
39
+
39
40
  Основные команды:
40
41
 
41
42
  ```bash
@@ -121,7 +122,7 @@ iola version --check
121
122
  - subagents, skill bundles, layered settings, usage/budget accounting и trajectory export;
122
123
  - полноценный локальный MCP server по stdio/http: tools, resources и prompts;
123
124
  - браузерный runtime через Playwright: чтение страниц, скриншоты, PDF, клики, ввод и eval;
124
- - локальный OAuth/OIDC-каркас для подключения Госуслуг/ЕСИА через официальный redirect flow;
125
+ - личное локальное подключение Госуслуг с явным согласием пользователя и хранением доступа только на его ПК;
125
126
  - управляемые локальные файловые операции с режимами `locked`, `read-only`, `workspace-write`, `full-access`;
126
127
  - планы выполнения, traces, tasks, artifacts, snapshots и policy-профили;
127
128
  - экспорт отчетов в Excel/Word-совместимые файлы;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iola_adm/iola-cli",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "description": "CLI и AI-агент для работы с открытыми данными городского округа Йошкар-Ола.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/adm-iola/iola-cli#readme",
package/src/cli.js CHANGED
@@ -31,6 +31,20 @@ const FILE_TOOLS = ["files_tree", "files_read", "files_search", "files_write", "
31
31
  const ALL_LOCAL_TOOLS = [...LOCAL_TOOLS, ...FILE_TOOLS];
32
32
  const HOOK_EVENTS = ["SessionStart", "BeforeTool", "AfterTool", "PreToolUse", "PostToolUse", "OnError", "AfterSync", "BeforeExport", "SessionEnd"];
33
33
  const DAEMON_PORT = Number(process.env.IOLA_DAEMON_PORT || 18790);
34
+ const GOSUSLUGI_CONSENT_VERSION = "2026-05-26-personal-local-v1";
35
+ const GOSUSLUGI_CONSENT_TEXT = `Подключение личных Госуслуг
36
+
37
+ Вы подключаете личную учетную запись Госуслуг к локальному CLI-агенту iola-cli на этом компьютере.
38
+
39
+ Нажимая "Да", вы подтверждаете, что:
40
+ - используете собственную учетную запись Госуслуг;
41
+ - понимаете, что все действия, выполненные через CLI-агента после подключения, считаются действиями владельца этой учетной записи;
42
+ - разрешаете iola-cli локально сохранить данные доступа, необходимые для повторного входа или выполнения запросов от вашего имени;
43
+ - понимаете, что данные доступа хранятся только на этом компьютере в локальном хранилище пользователя и не передаются разработчикам CLI, администрации города или третьим лицам;
44
+ - обязуетесь не подключать чужие учетные записи и не передавать локальные файлы доступа другим лицам;
45
+ - понимаете, что перед юридически значимыми действиями, отправкой заявлений, оплатой, подписанием или изменением персональных данных CLI должен запросить отдельное подтверждение.
46
+
47
+ Продолжить подключение?`;
34
48
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
35
49
  const BUILTIN_SKILLS_DIR = path.resolve(__dirname, "..", "skills");
36
50
  const USER_SKILLS_DIR = path.join(CONFIG_DIR, "skills");
@@ -112,6 +126,7 @@ const DEFAULT_AI_CONFIG = {
112
126
  },
113
127
  gosuslugi: {
114
128
  enabled: false,
129
+ mode: "personal-local",
115
130
  authUrl: "",
116
131
  tokenUrl: "",
117
132
  userinfoUrl: "",
@@ -265,6 +280,7 @@ const BANNER = `\x1b[38;5;45m┌────────────────
265
280
 
266
281
  const COMMANDS = new Map([
267
282
  ["help", showHelp],
283
+ ["commands", showCommands],
268
284
  ["version", showVersion],
269
285
  ["update", checkUpdate],
270
286
  ["doctor", doctor],
@@ -340,7 +356,12 @@ const COMMANDS = new Map([
340
356
  ]);
341
357
 
342
358
  export async function main(argv) {
343
- if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
359
+ if (argv.length === 0) {
360
+ await runDefaultCli();
361
+ return;
362
+ }
363
+
364
+ if (argv[0] === "--help" || argv[0] === "-h") {
344
365
  await showHelp();
345
366
  return;
346
367
  }
@@ -383,6 +404,35 @@ export async function main(argv) {
383
404
  }
384
405
 
385
406
  async function showHelp() {
407
+ showBanner();
408
+ console.log(`iola - локальный CLI и AI-агент для данных городского округа "Город Йошкар-Ола"
409
+
410
+ Запуск:
411
+ iola открыть интерактивный агент
412
+ iola onboard мастер настройки
413
+ iola ask "найди школу 29" задать вопрос
414
+ iola search "Петрова" поиск по открытым данным
415
+
416
+ Основные разделы:
417
+ iola agent интерактивный режим
418
+ iola ai setup настройка AI-профиля
419
+ iola browser status браузерный runtime
420
+ iola gosuslugi status личное подключение Госуслуг
421
+ iola mcp status MCP-подключение
422
+ iola doctor диагностика
423
+ iola wiki документация
424
+
425
+ Справка:
426
+ iola help короткая справка
427
+ iola commands полный список команд
428
+ iola version версия
429
+
430
+ Requirements:
431
+ Node.js >= ${MIN_NODE_VERSION}
432
+ `);
433
+ }
434
+
435
+ async function showCommands() {
386
436
  showBanner();
387
437
  console.log(`iola - CLI для открытых данных городского округа "Город Йошкар-Ола"
388
438
 
@@ -402,7 +452,7 @@ Usage:
402
452
  iola fork SESSION_ID [TEXT]
403
453
  iola features list|enable|disable
404
454
  iola settings list|get|validate|doctor|init
405
- iola gosuslugi configure|status|login|logout|userinfo
455
+ iola gosuslugi terms|consent|configure|status|login|logout|userinfo
406
456
  iola wiki [open|links]
407
457
  iola context list|show|init
408
458
  iola skills list|show|paths|enable|disable|bundles|bundle|doctor
@@ -494,6 +544,26 @@ Requirements:
494
544
  `);
495
545
  }
496
546
 
547
+ async function runDefaultCli() {
548
+ const nodeStatus = getNodeRequirementStatus();
549
+ if (!nodeStatus.ok) {
550
+ throw new Error(`Нужен Node.js ${MIN_NODE_VERSION} или новее. Сейчас: ${nodeStatus.current}. Запустите: iola init --upgrade-node`);
551
+ }
552
+
553
+ initDatabase();
554
+ if (!isFirstRunCompleted()) {
555
+ showBanner();
556
+ console.log("Первый запуск iola-cli. Сейчас откроется мастер настройки.");
557
+ console.log("После мастера запустится интерактивный агент.");
558
+ console.log("");
559
+ await onboard([]);
560
+ markFirstRunCompleted();
561
+ console.log("");
562
+ }
563
+
564
+ await startAgent([]);
565
+ }
566
+
497
567
  async function startAgent() {
498
568
  showBanner();
499
569
  console.log("Интерактивный режим. Введите /help для списка команд, /exit для выхода.");
@@ -1726,13 +1796,27 @@ async function handleGosuslugi(args) {
1726
1796
  const [action = "status", ...rest] = args;
1727
1797
  const options = parseOptions(rest);
1728
1798
 
1799
+ if (action === "terms") {
1800
+ console.log(GOSUSLUGI_CONSENT_TEXT);
1801
+ return;
1802
+ }
1803
+
1804
+ if (action === "consent") {
1805
+ await acceptGosuslugiConsent(options);
1806
+ return;
1807
+ }
1808
+
1729
1809
  if (action === "status") {
1730
1810
  const config = await loadConfig();
1731
1811
  const secrets = await loadSecrets();
1732
1812
  const tokens = secrets.gosuslugi?.tokens || null;
1813
+ const consent = secrets.gosuslugiConsent || null;
1733
1814
  printKeyValue({
1815
+ mode: config.gosuslugi?.mode || "personal-local",
1734
1816
  enabled: config.gosuslugi?.enabled ? "yes" : "no",
1735
1817
  configured: isGosuslugiConfigured(config) ? "yes" : "no",
1818
+ consent: consent?.version === GOSUSLUGI_CONSENT_VERSION ? "accepted" : "not accepted",
1819
+ consentAt: consent?.acceptedAt || "-",
1736
1820
  clientId: config.gosuslugi?.clientId ? maskSecret(config.gosuslugi.clientId) : "-",
1737
1821
  authUrl: config.gosuslugi?.authUrl || "-",
1738
1822
  tokenUrl: config.gosuslugi?.tokenUrl || "-",
@@ -1750,6 +1834,7 @@ async function handleGosuslugi(args) {
1750
1834
  const next = {
1751
1835
  ...(current.gosuslugi || {}),
1752
1836
  enabled: true,
1837
+ mode: "personal-local",
1753
1838
  authUrl: options["auth-url"] || current.gosuslugi?.authUrl || "",
1754
1839
  tokenUrl: options["token-url"] || current.gosuslugi?.tokenUrl || "",
1755
1840
  userinfoUrl: options["userinfo-url"] || current.gosuslugi?.userinfoUrl || "",
@@ -1761,7 +1846,7 @@ async function handleGosuslugi(args) {
1761
1846
  redirectPath: options["redirect-path"] || current.gosuslugi?.redirectPath || "/gosuslugi/callback",
1762
1847
  };
1763
1848
  await saveConfig({ gosuslugi: next });
1764
- console.log("Настройки подключения к Госуслугам сохранены.");
1849
+ console.log("Настройки личного локального подключения Госуслуг сохранены.");
1765
1850
  console.log(`Redirect URI: ${gosuslugiRedirectUri({ gosuslugi: next })}`);
1766
1851
  return;
1767
1852
  }
@@ -1787,7 +1872,7 @@ async function handleGosuslugi(args) {
1787
1872
  return;
1788
1873
  }
1789
1874
 
1790
- throw new Error("Команды gosuslugi: configure, status, login, logout, userinfo.");
1875
+ throw new Error("Команды gosuslugi: terms, consent, configure, status, login, logout, userinfo.");
1791
1876
  }
1792
1877
 
1793
1878
  async function handleWiki(args) {
@@ -2734,8 +2819,9 @@ async function openUrl(url) {
2734
2819
  async function gosuslugiLogin(options = {}) {
2735
2820
  const config = await loadConfig();
2736
2821
  if (!isGosuslugiConfigured(config)) {
2737
- throw new Error("Подключение не настроено. Пример: iola gosuslugi configure --auth-url URL --token-url URL --client-id ID --scope openid");
2822
+ throw new Error("Личное подключение не настроено. Пример: iola gosuslugi configure --auth-url URL --token-url URL --client-id ID --scope openid");
2738
2823
  }
2824
+ await ensureGosuslugiConsent(options);
2739
2825
 
2740
2826
  const state = randomUrlSafe(24);
2741
2827
  const codeVerifier = randomUrlSafe(64);
@@ -2751,8 +2837,8 @@ async function gosuslugiLogin(options = {}) {
2751
2837
  authUrl.searchParams.set("code_challenge", codeChallenge);
2752
2838
  authUrl.searchParams.set("code_challenge_method", "S256");
2753
2839
 
2754
- console.log("Открываю официальный экран входа Госуслуг/ЕСИА в браузере.");
2755
- console.log("После входа CLI примет callback на локальном адресе и сохранит токены только на этом компьютере.");
2840
+ console.log("Открываю экран входа Госуслуг в браузере для личного локального подключения.");
2841
+ console.log("После входа CLI примет callback на локальном адресе и сохранит данные доступа только на этом компьютере.");
2756
2842
  await openUrl(authUrl.toString());
2757
2843
  const params = await callback;
2758
2844
  if (params.error) throw new Error(`Госуслуги вернули ошибку: ${params.error} ${params.error_description || ""}`.trim());
@@ -2781,6 +2867,31 @@ async function gosuslugiLogin(options = {}) {
2781
2867
  };
2782
2868
  }
2783
2869
 
2870
+ async function acceptGosuslugiConsent(options = {}) {
2871
+ console.log(GOSUSLUGI_CONSENT_TEXT);
2872
+ if (!options.yes) {
2873
+ const accepted = await confirm("Да, подключить личные Госуслуги к локальному iola-cli? [y/N] ");
2874
+ if (!accepted) {
2875
+ throw new Error("Подключение Госуслуг отменено пользователем.");
2876
+ }
2877
+ }
2878
+ const secrets = await loadSecrets();
2879
+ secrets.gosuslugiConsent = {
2880
+ version: GOSUSLUGI_CONSENT_VERSION,
2881
+ acceptedAt: new Date().toISOString(),
2882
+ user: os.userInfo().username,
2883
+ host: os.hostname(),
2884
+ };
2885
+ await saveSecrets(secrets);
2886
+ console.log("Согласие сохранено локально.");
2887
+ }
2888
+
2889
+ async function ensureGosuslugiConsent(options = {}) {
2890
+ const secrets = await loadSecrets();
2891
+ if (secrets.gosuslugiConsent?.version === GOSUSLUGI_CONSENT_VERSION) return;
2892
+ await acceptGosuslugiConsent(options);
2893
+ }
2894
+
2784
2895
  function waitForOAuthCallback(settings, expectedState, timeoutMs) {
2785
2896
  const host = settings.redirectHost || "127.0.0.1";
2786
2897
  const port = Number(settings.redirectPort || 18791);
@@ -2826,6 +2937,7 @@ async function exchangeGosuslugiCode(config, { code, codeVerifier, redirectUri }
2826
2937
  body.set("redirect_uri", redirectUri);
2827
2938
  body.set("client_id", config.gosuslugi.clientId);
2828
2939
  body.set("code_verifier", codeVerifier);
2940
+ body.set("client_mode", config.gosuslugi.mode || "personal-local");
2829
2941
  if (config.gosuslugi.clientSecret) body.set("client_secret", config.gosuslugi.clientSecret);
2830
2942
 
2831
2943
  const response = await fetch(config.gosuslugi.tokenUrl, {
@@ -4364,6 +4476,34 @@ function getDbStatus() {
4364
4476
  }
4365
4477
  }
4366
4478
 
4479
+ function getMetaValue(key) {
4480
+ initDatabase();
4481
+ const db = openDatabase();
4482
+ try {
4483
+ return db.prepare("SELECT value FROM meta WHERE key = ?").get(key)?.value || null;
4484
+ } finally {
4485
+ db.close();
4486
+ }
4487
+ }
4488
+
4489
+ function setMetaValue(key, value) {
4490
+ initDatabase();
4491
+ const db = openDatabase();
4492
+ try {
4493
+ db.prepare("INSERT INTO meta(key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(key, String(value));
4494
+ } finally {
4495
+ db.close();
4496
+ }
4497
+ }
4498
+
4499
+ function isFirstRunCompleted() {
4500
+ return getMetaValue("first_run_completed") === "1";
4501
+ }
4502
+
4503
+ function markFirstRunCompleted() {
4504
+ setMetaValue("first_run_completed", "1");
4505
+ }
4506
+
4367
4507
  function recordAskHistory({ question, answer, providerConfig, dataContext, error, sessionId }) {
4368
4508
  try {
4369
4509
  initDatabase();
@@ -6191,7 +6331,14 @@ async function onboard(args = []) {
6191
6331
  initDatabase();
6192
6332
  await handleConfig(["validate"]);
6193
6333
  await doctor(["--summary"]);
6194
- await ensureArchiveTool({ install: true });
6334
+ if (options.full || options.install) {
6335
+ await ensureArchiveTool({ install: true });
6336
+ } else {
6337
+ const archiveTool = await findCommand(["7z", "7zz", "7za"], ["--help"]);
6338
+ if (!archiveTool) {
6339
+ console.log("7-Zip не найден. Для архивов можно позже запустить: iola archive doctor");
6340
+ }
6341
+ }
6195
6342
 
6196
6343
  const components = options.yes ? ["workspace", "policy", "ollama", "openai", "openrouter", "codex", "codex-mcp", "index"] : await chooseOnboardComponents();
6197
6344
  if (components.includes("workspace")) await handleWorkspace(["init"]);
@@ -6217,6 +6364,7 @@ async function onboard(args = []) {
6217
6364
  await setFilesMode("read-only", await loadConfig());
6218
6365
  console.log("Индекс документов можно запустить командой: iola index folder ./docs");
6219
6366
  }
6367
+ markFirstRunCompleted();
6220
6368
  console.log("Onboard завершен.");
6221
6369
  }
6222
6370
 
@@ -4,6 +4,8 @@
4
4
 
5
5
  ```bash
6
6
  iola --help
7
+ iola help
8
+ iola commands
7
9
  iola init
8
10
  iola doctor
9
11
  iola doctor --fix
@@ -93,6 +95,8 @@ iola context list
93
95
  iola settings list
94
96
  iola settings validate
95
97
  iola gosuslugi status
98
+ iola gosuslugi terms
99
+ iola gosuslugi consent
96
100
  iola gosuslugi configure --auth-url URL --token-url URL --client-id ID --scope openid
97
101
  iola gosuslugi login
98
102
  iola gosuslugi userinfo --json
@@ -3,16 +3,33 @@
3
3
  Первый запуск:
4
4
 
5
5
  ```bash
6
- iola init
6
+ iola
7
7
  ```
8
8
 
9
- Команда проверит:
9
+ При первом запуске команда откроет мастер настройки, а затем запустит интерактивный агент.
10
+
11
+ Мастер проверит:
10
12
 
11
13
  - Node.js и npm;
12
14
  - локальную SQLite-БД;
13
15
  - доступность публичного API/MCP;
14
- - Ollama и подходящую локальную модель;
15
- - обновления npm-пакета.
16
+ - обновления npm-пакета;
17
+ - выбранные компоненты: workspace, policy, локальную модель, API-ключи, Codex, MCP, индекс документов.
18
+
19
+ Обычный последующий запуск:
20
+
21
+ ```bash
22
+ iola
23
+ ```
24
+
25
+ Команда сразу откроет интерактивный CLI-агент.
26
+
27
+ Справка:
28
+
29
+ ```bash
30
+ iola help
31
+ iola commands
32
+ ```
16
33
 
17
34
  Диагностика:
18
35
 
@@ -1,19 +1,47 @@
1
- # Подключение Госуслуг
1
+ # Личное подключение Госуслуг
2
2
 
3
- `iola-cli` поддерживает локальный OAuth/OIDC-каркас для подключения учетной записи через официальный flow ЕСИА/Госуслуг.
3
+ `iola-cli` поддерживает локальное подключение личной учетной записи Госуслуг на компьютере пользователя.
4
4
 
5
- Важно: CLI не извлекает cookies, токены или session storage из браузера. Пользователь входит на официальном экране ЕСИА, после чего ЕСИА возвращает `authorization code` на локальный `redirect_uri`, а CLI обменивает его на токены через `token endpoint`.
5
+ Сценарий рассчитан именно на пользователя, который ставит CLI на свой ПК и подключает свою учетную запись. Ключи организации или администрации в публичный пакет не вшиваются.
6
+
7
+ ## Согласие пользователя
8
+
9
+ Перед входом CLI показывает текст согласия:
10
+
11
+ ```text
12
+ Вы подключаете личную учетную запись Госуслуг к локальному CLI-агенту iola-cli на этом компьютере.
13
+
14
+ Нажимая "Да", вы подтверждаете, что:
15
+ - используете собственную учетную запись Госуслуг;
16
+ - понимаете, что все действия, выполненные через CLI-агента после подключения, считаются действиями владельца этой учетной записи;
17
+ - разрешаете iola-cli локально сохранить данные доступа, необходимые для повторного входа или выполнения запросов от вашего имени;
18
+ - понимаете, что данные доступа хранятся только на этом компьютере в локальном хранилище пользователя и не передаются разработчикам CLI, администрации города или третьим лицам;
19
+ - обязуетесь не подключать чужие учетные записи и не передавать локальные файлы доступа другим лицам;
20
+ - понимаете, что перед юридически значимыми действиями, отправкой заявлений, оплатой, подписанием или изменением персональных данных CLI должен запросить отдельное подтверждение.
21
+ ```
22
+
23
+ Посмотреть текст:
24
+
25
+ ```bash
26
+ iola gosuslugi terms
27
+ ```
28
+
29
+ Принять заранее:
30
+
31
+ ```bash
32
+ iola gosuslugi consent
33
+ ```
6
34
 
7
35
  ## Настройка
8
36
 
9
- Для работы нужны параметры официально подключенной информационной системы:
37
+ Для работы нужны параметры пользовательского OAuth/OIDC-подключения:
10
38
 
11
39
  - authorization endpoint;
12
40
  - token endpoint;
13
41
  - client ID;
14
42
  - разрешенный redirect URI;
15
43
  - scope;
16
- - при необходимости client secret или иной официальный способ подписи/аутентификации клиента;
44
+ - optional client secret, если он выдан именно пользователю или локальному приложению;
17
45
  - optional userinfo endpoint.
18
46
 
19
47
  Команда настройки:
@@ -34,7 +62,7 @@ CLI покажет redirect URI:
34
62
  http://127.0.0.1:18791/gosuslugi/callback
35
63
  ```
36
64
 
37
- Этот URI должен быть разрешен в настройках подключенной информационной системы.
65
+ Этот URI должен быть разрешен в настройках подключения.
38
66
 
39
67
  ## Вход
40
68
 
@@ -45,10 +73,11 @@ iola gosuslugi login
45
73
  Что происходит:
46
74
 
47
75
  1. CLI запускает локальный callback server на `127.0.0.1`.
48
- 2. Открывается официальный экран входа ЕСИА/Госуслуг.
49
- 3. Пользователь сам вводит логин, пароль, SMS/2FA.
50
- 4. ЕСИА возвращает `authorization code` на локальный callback.
51
- 5. CLI обменивает code на токены и сохраняет их локально.
76
+ 2. Показывается и сохраняется согласие пользователя.
77
+ 3. Открывается экран входа Госуслуг.
78
+ 4. Пользователь сам вводит логин, пароль, SMS/2FA.
79
+ 5. Госуслуги возвращают `authorization code` на локальный callback.
80
+ 6. CLI обменивает code на токены и сохраняет их локально.
52
81
 
53
82
  Токены хранятся в `~/.iola/secrets.json` на компьютере пользователя.
54
83
 
@@ -69,5 +98,4 @@ iola gosuslugi logout
69
98
 
70
99
  ## Ограничения
71
100
 
72
- Без официальных параметров ЕСИА команда `login` не сможет завершить подключение. Это сделано намеренно: CLI реализует легальный redirect flow, а не копирование живой браузерной сессии.
73
-
101
+ Без параметров пользовательского подключения команда `login` не сможет завершить подключение. Все ключи, токены и настройки хранятся только локально у пользователя.