@iola_adm/iola-cli 0.1.60 → 0.1.61

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
@@ -89,6 +89,7 @@ iola gosuslugi connect
89
89
  iola gosuslugi whoami
90
90
  iola gosuslugi debt
91
91
  iola gosuslugi notifications --unread
92
+ iola gosuslugi keepalive
92
93
  ```
93
94
 
94
95
  Локальная модель через Ollama:
@@ -138,6 +139,7 @@ iola version --check
138
139
  - браузерный runtime через Playwright: чтение страниц, скриншоты, PDF, клики, ввод и eval;
139
140
  - личное локальное подключение Госуслуг через отдельный браузерный профиль на ПК пользователя;
140
141
  - read-only tools Госуслуг для агента: ФИО, дата рождения, задолженности и уведомления;
142
+ - keepalive-проверка сессии Госуслуг каждые 30 минут без обхода 2FA;
141
143
  - управляемые локальные файловые операции с режимами `locked`, `read-only`, `workspace-write`, `full-access`;
142
144
  - планы выполнения, traces, tasks, artifacts, snapshots и policy-профили;
143
145
  - экспорт отчетов в Excel/Word-совместимые файлы;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iola_adm/iola-cli",
3
- "version": "0.1.60",
3
+ "version": "0.1.61",
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
@@ -290,6 +290,7 @@ const SLASH_COMMANDS = [
290
290
  { command: "/gosuslugi connect", description: "открыть личный вход Госуслуг" },
291
291
  { command: "/gosuslugi debt", description: "задолженности Госуслуг" },
292
292
  { command: "/gosuslugi notifications", description: "уведомления Госуслуг" },
293
+ { command: "/gosuslugi keepalive", description: "проверка сессии каждые 30 минут" },
293
294
  { command: "/wiki", description: "ссылки на документацию" },
294
295
  { command: "/context list", description: "локальный контекст проекта" },
295
296
  { command: "/skills list", description: "skills" },
@@ -523,7 +524,7 @@ Usage:
523
524
  iola fork SESSION_ID [TEXT]
524
525
  iola features list|enable|disable
525
526
  iola settings list|get|validate|doctor|init
526
- iola gosuslugi terms|consent|status|connect|open|text|screenshot|whoami|debt|notifications|mark-read|logout|configure|login|userinfo
527
+ iola gosuslugi terms|consent|status|check|keepalive|install-keepalive|connect|open|text|screenshot|whoami|debt|notifications|mark-read|logout|configure|login|userinfo
527
528
  iola wiki [open|links]
528
529
  iola context list|show|init
529
530
  iola skills list|show|paths|enable|disable|bundles|bundle|doctor
@@ -2025,6 +2026,25 @@ async function handleDb(args) {
2025
2026
  return;
2026
2027
  }
2027
2028
 
2029
+ if (action === "check") {
2030
+ const result = await gosuslugiCheck(options);
2031
+ if (options.json) printJson(result);
2032
+ else printKeyValue(result);
2033
+ return;
2034
+ }
2035
+
2036
+ if (action === "keepalive") {
2037
+ await gosuslugiKeepalive(options);
2038
+ return;
2039
+ }
2040
+
2041
+ if (action === "install-keepalive") {
2042
+ const id = addCronJob("каждые 30 минут", "gosuslugi check --silent");
2043
+ console.log(`Keepalive-задача добавлена: ${id}`);
2044
+ console.log("Для выполнения запускайте периодически: iola cron tick");
2045
+ return;
2046
+ }
2047
+
2028
2048
  if (action === "reset") {
2029
2049
  const shouldReset = await confirm("Удалить локальную SQLite-БД iola.db? [y/N] ");
2030
2050
  if (!shouldReset) {
@@ -2286,6 +2306,25 @@ async function handleGosuslugi(args) {
2286
2306
  return;
2287
2307
  }
2288
2308
 
2309
+ if (action === "check") {
2310
+ const result = await gosuslugiCheck(options);
2311
+ if (options.json) printJson(result);
2312
+ else printKeyValue(result);
2313
+ return;
2314
+ }
2315
+
2316
+ if (action === "keepalive") {
2317
+ await gosuslugiKeepalive(options);
2318
+ return;
2319
+ }
2320
+
2321
+ if (action === "install-keepalive") {
2322
+ const id = addCronJob("каждые 30 минут", "gosuslugi check --silent");
2323
+ console.log(`Keepalive-задача добавлена: ${id}`);
2324
+ console.log("Для выполнения запускайте периодически: iola cron tick");
2325
+ return;
2326
+ }
2327
+
2289
2328
  if (action === "connect") {
2290
2329
  await gosuslugiBrowserConnect(options);
2291
2330
  return;
@@ -2389,7 +2428,7 @@ async function handleGosuslugi(args) {
2389
2428
  return;
2390
2429
  }
2391
2430
 
2392
- throw new Error("Команды gosuslugi: terms, consent, status, connect, open, text, screenshot, whoami, debt, notifications, mark-read, logout, configure, login, userinfo.");
2431
+ throw new Error("Команды gosuslugi: terms, consent, status, check, keepalive, install-keepalive, connect, open, text, screenshot, whoami, debt, notifications, mark-read, logout, configure, login, userinfo.");
2393
2432
  }
2394
2433
 
2395
2434
  function targetOrDefault(args, options = {}) {
@@ -5775,6 +5814,10 @@ function isCronDue(job) {
5775
5814
  if (normalized.includes("каждый час") || normalized.includes("hourly")) {
5776
5815
  return !lastRun || now.getTime() - lastRun.getTime() >= 60 * 60 * 1000;
5777
5816
  }
5817
+ const everyMinutes = normalized.match(/кажд(?:ые|ую)\s+(\d+)\s*(?:мин|минут)/u) || normalized.match(/every\s+(\d+)\s*(?:m|min|minutes)/u);
5818
+ if (everyMinutes) {
5819
+ return !lastRun || now.getTime() - lastRun.getTime() >= Number(everyMinutes[1]) * 60 * 1000;
5820
+ }
5778
5821
  if (normalized.includes("каждый день") || normalized.includes("daily")) {
5779
5822
  return !lastRun || now.toISOString().slice(0, 10) !== lastRun.toISOString().slice(0, 10);
5780
5823
  }
@@ -7150,7 +7193,7 @@ function parseOptions(args) {
7150
7193
 
7151
7194
  for (let index = 0; index < args.length; index += 1) {
7152
7195
  const arg = args[index];
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") {
7196
+ 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 === "--once" || 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") {
7154
7197
  result[arg.slice(2)] = true;
7155
7198
  } else if (arg === "--check" || arg === "--upgrade-node") {
7156
7199
  result.check = true;
@@ -7799,6 +7842,61 @@ async function gosuslugiMarkNotificationsRead(options = {}) {
7799
7842
  console.log("Команда отметки прочитанным выполнена. Проверьте статус: iola gosuslugi notifications --unread");
7800
7843
  }
7801
7844
 
7845
+ async function gosuslugiCheck(options = {}) {
7846
+ try {
7847
+ const result = await gosuslugiWhoami({ wait: options.wait || 2000 });
7848
+ return {
7849
+ status: "ok",
7850
+ authorized: "yes",
7851
+ fio: result.summary.fio,
7852
+ checkedAt: new Date().toISOString(),
7853
+ nextAction: "-",
7854
+ };
7855
+ } catch (error) {
7856
+ const message = error instanceof Error ? error.message : String(error);
7857
+ const result = {
7858
+ status: "needs-login",
7859
+ authorized: "unknown",
7860
+ checkedAt: new Date().toISOString(),
7861
+ nextAction: "iola gosuslugi connect",
7862
+ error: message,
7863
+ };
7864
+ if (!options.silent) {
7865
+ console.error("Сессия Госуслуг недоступна или требует повторный вход.");
7866
+ console.error("Запустите: iola gosuslugi connect");
7867
+ }
7868
+ return result;
7869
+ }
7870
+ }
7871
+
7872
+ async function gosuslugiKeepalive(options = {}) {
7873
+ const intervalMs = parseDurationMs(options.interval || "30m");
7874
+ const once = Boolean(options.once);
7875
+ console.log(`Gosuslugi keepalive запущен. Интервал: ${Math.round(intervalMs / 60000)} мин.`);
7876
+ console.log("Остановить: Ctrl+C");
7877
+ while (true) {
7878
+ const result = await gosuslugiCheck({ silent: true });
7879
+ const line = result.status === "ok"
7880
+ ? `[${result.checkedAt}] Госуслуги: сессия активна (${result.fio || "-"})`
7881
+ : `[${result.checkedAt}] Госуслуги: нужен повторный вход. Запустите: iola gosuslugi connect`;
7882
+ console.log(line);
7883
+ if (once) return;
7884
+ await sleep(intervalMs);
7885
+ }
7886
+ }
7887
+
7888
+ function parseDurationMs(value) {
7889
+ const text = String(value || "30m").trim().toLocaleLowerCase("ru-RU");
7890
+ const match = text.match(/^(\d+(?:[.,]\d+)?)(ms|s|m|h|мин|минут|час|часа|часов)?$/u);
7891
+ if (!match) throw new Error("Интервал задается как 30m, 1800s или 1h.");
7892
+ const amount = Number(match[1].replace(",", "."));
7893
+ const unit = match[2] || "m";
7894
+ if (unit === "ms") return Math.max(1000, amount);
7895
+ if (unit === "s") return Math.max(1000, amount * 1000);
7896
+ if (unit === "h" || unit.startsWith("час")) return Math.max(1000, amount * 60 * 60 * 1000);
7897
+ return Math.max(1000, amount * 60 * 1000);
7898
+ }
7899
+
7802
7900
  function printGosuslugiDebt(result) {
7803
7901
  printKeyValue({
7804
7902
  total: result.total,
@@ -34,3 +34,9 @@ iola cron delete 1
34
34
 
35
35
  `cron tick` проверяет задачи, которые пора выполнить. Его можно запускать вручную, через Windows Task Scheduler или другой планировщик.
36
36
 
37
+ Пример проверки сессии Госуслуг каждые 30 минут:
38
+
39
+ ```bash
40
+ iola gosuslugi install-keepalive
41
+ iola cron tick
42
+ ```
@@ -95,6 +95,9 @@ iola context list
95
95
  iola settings list
96
96
  iola settings validate
97
97
  iola gosuslugi status
98
+ iola gosuslugi check
99
+ iola gosuslugi keepalive
100
+ iola gosuslugi install-keepalive
98
101
  iola gosuslugi terms
99
102
  iola gosuslugi consent
100
103
  iola gosuslugi connect
@@ -78,10 +78,36 @@ AI-агент может использовать read-only tools Госуслу
78
78
 
79
79
  ```bash
80
80
  iola gosuslugi status
81
+ iola gosuslugi check
81
82
  ```
82
83
 
83
84
  Команда показывает, принято ли согласие, где лежит локальный профиль и когда он был создан.
84
85
 
86
+ ## Keepalive
87
+
88
+ Сессию Госуслуг нельзя сделать вечной: портал сам управляет сроком жизни входа и может попросить повторную двухфакторную аутентификацию. CLI может только мягко проверять сохраненный профиль.
89
+
90
+ Запуск проверки каждые 30 минут:
91
+
92
+ ```bash
93
+ iola gosuslugi keepalive
94
+ ```
95
+
96
+ Однократная проверка:
97
+
98
+ ```bash
99
+ iola gosuslugi keepalive --once
100
+ ```
101
+
102
+ Добавить локальную cron-задачу CLI:
103
+
104
+ ```bash
105
+ iola gosuslugi install-keepalive
106
+ iola cron tick
107
+ ```
108
+
109
+ `cron tick` нужно запускать системным планировщиком или вручную. Если сессия протухла, CLI попросит выполнить `iola gosuslugi connect`.
110
+
85
111
  ## Отключение
86
112
 
87
113
  Удалить локальное подключение: