@iola_adm/iola-cli 0.1.60 → 0.1.62

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,8 @@ iola gosuslugi connect
89
89
  iola gosuslugi whoami
90
90
  iola gosuslugi debt
91
91
  iola gosuslugi notifications --unread
92
+ iola gosuslugi keepalive
93
+ iola gosuslugi install-keepalive
92
94
  ```
93
95
 
94
96
  Локальная модель через Ollama:
@@ -138,6 +140,7 @@ iola version --check
138
140
  - браузерный runtime через Playwright: чтение страниц, скриншоты, PDF, клики, ввод и eval;
139
141
  - личное локальное подключение Госуслуг через отдельный браузерный профиль на ПК пользователя;
140
142
  - read-only tools Госуслуг для агента: ФИО, дата рождения, задолженности и уведомления;
143
+ - keepalive-проверка сессии Госуслуг каждые 30 минут через Windows Task Scheduler без висящего окна терминала;
141
144
  - управляемые локальные файловые операции с режимами `locked`, `read-only`, `workspace-write`, `full-access`;
142
145
  - планы выполнения, traces, tasks, artifacts, snapshots и policy-профили;
143
146
  - экспорт отчетов в 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.62",
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|keepalive-status|uninstall-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
@@ -2286,6 +2287,33 @@ async function handleGosuslugi(args) {
2286
2287
  return;
2287
2288
  }
2288
2289
 
2290
+ if (action === "check") {
2291
+ const result = await gosuslugiCheck(options);
2292
+ if (options.json) printJson(result);
2293
+ else printKeyValue(result);
2294
+ return;
2295
+ }
2296
+
2297
+ if (action === "keepalive") {
2298
+ await gosuslugiKeepalive(options);
2299
+ return;
2300
+ }
2301
+
2302
+ if (action === "install-keepalive") {
2303
+ await installGosuslugiKeepaliveTask(options);
2304
+ return;
2305
+ }
2306
+
2307
+ if (action === "uninstall-keepalive") {
2308
+ await uninstallGosuslugiKeepaliveTask(options);
2309
+ return;
2310
+ }
2311
+
2312
+ if (action === "keepalive-status") {
2313
+ await printGosuslugiKeepaliveTaskStatus(options);
2314
+ return;
2315
+ }
2316
+
2289
2317
  if (action === "connect") {
2290
2318
  await gosuslugiBrowserConnect(options);
2291
2319
  return;
@@ -2389,7 +2417,7 @@ async function handleGosuslugi(args) {
2389
2417
  return;
2390
2418
  }
2391
2419
 
2392
- throw new Error("Команды gosuslugi: terms, consent, status, connect, open, text, screenshot, whoami, debt, notifications, mark-read, logout, configure, login, userinfo.");
2420
+ throw new Error("Команды gosuslugi: terms, consent, status, check, keepalive, install-keepalive, keepalive-status, uninstall-keepalive, connect, open, text, screenshot, whoami, debt, notifications, mark-read, logout, configure, login, userinfo.");
2393
2421
  }
2394
2422
 
2395
2423
  function targetOrDefault(args, options = {}) {
@@ -5775,6 +5803,10 @@ function isCronDue(job) {
5775
5803
  if (normalized.includes("каждый час") || normalized.includes("hourly")) {
5776
5804
  return !lastRun || now.getTime() - lastRun.getTime() >= 60 * 60 * 1000;
5777
5805
  }
5806
+ const everyMinutes = normalized.match(/кажд(?:ые|ую)\s+(\d+)\s*(?:мин|минут)/u) || normalized.match(/every\s+(\d+)\s*(?:m|min|minutes)/u);
5807
+ if (everyMinutes) {
5808
+ return !lastRun || now.getTime() - lastRun.getTime() >= Number(everyMinutes[1]) * 60 * 1000;
5809
+ }
5778
5810
  if (normalized.includes("каждый день") || normalized.includes("daily")) {
5779
5811
  return !lastRun || now.toISOString().slice(0, 10) !== lastRun.toISOString().slice(0, 10);
5780
5812
  }
@@ -7150,7 +7182,7 @@ function parseOptions(args) {
7150
7182
 
7151
7183
  for (let index = 0; index < args.length; index += 1) {
7152
7184
  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") {
7185
+ 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
7186
  result[arg.slice(2)] = true;
7155
7187
  } else if (arg === "--check" || arg === "--upgrade-node") {
7156
7188
  result.check = true;
@@ -7799,6 +7831,133 @@ async function gosuslugiMarkNotificationsRead(options = {}) {
7799
7831
  console.log("Команда отметки прочитанным выполнена. Проверьте статус: iola gosuslugi notifications --unread");
7800
7832
  }
7801
7833
 
7834
+ async function gosuslugiCheck(options = {}) {
7835
+ try {
7836
+ const result = await gosuslugiWhoami({ wait: options.wait || 2000 });
7837
+ return {
7838
+ status: "ok",
7839
+ authorized: "yes",
7840
+ fio: result.summary.fio,
7841
+ checkedAt: new Date().toISOString(),
7842
+ nextAction: "-",
7843
+ };
7844
+ } catch (error) {
7845
+ const message = error instanceof Error ? error.message : String(error);
7846
+ const result = {
7847
+ status: "needs-login",
7848
+ authorized: "unknown",
7849
+ checkedAt: new Date().toISOString(),
7850
+ nextAction: "iola gosuslugi connect",
7851
+ error: message,
7852
+ };
7853
+ if (!options.silent) {
7854
+ console.error("Сессия Госуслуг недоступна или требует повторный вход.");
7855
+ console.error("Запустите: iola gosuslugi connect");
7856
+ }
7857
+ return result;
7858
+ }
7859
+ }
7860
+
7861
+ async function gosuslugiKeepalive(options = {}) {
7862
+ const intervalMs = parseDurationMs(options.interval || "30m");
7863
+ const once = Boolean(options.once);
7864
+ console.log(`Gosuslugi keepalive запущен. Интервал: ${Math.round(intervalMs / 60000)} мин.`);
7865
+ console.log("Остановить: Ctrl+C");
7866
+ while (true) {
7867
+ const result = await gosuslugiCheck({ silent: true });
7868
+ const line = result.status === "ok"
7869
+ ? `[${result.checkedAt}] Госуслуги: сессия активна (${result.fio || "-"})`
7870
+ : `[${result.checkedAt}] Госуслуги: нужен повторный вход. Запустите: iola gosuslugi connect`;
7871
+ console.log(line);
7872
+ if (once) return;
7873
+ await sleep(intervalMs);
7874
+ }
7875
+ }
7876
+
7877
+ function gosuslugiKeepaliveTaskName() {
7878
+ return "iola-gosuslugi-keepalive";
7879
+ }
7880
+
7881
+ function gosuslugiKeepaliveLogFile() {
7882
+ return path.join(CONFIG_DIR, "gosuslugi-keepalive.log");
7883
+ }
7884
+
7885
+ function cliEntrypointFile() {
7886
+ return path.resolve(__dirname, "..", "bin", "iola.js");
7887
+ }
7888
+
7889
+ async function installGosuslugiKeepaliveTask(options = {}) {
7890
+ const intervalMinutes = Math.max(1, Math.round(parseDurationMs(options.interval || "30m") / 60000));
7891
+ if (process.platform === "win32") {
7892
+ await installWindowsGosuslugiKeepaliveTask(intervalMinutes);
7893
+ return;
7894
+ }
7895
+ const id = addCronJob(`каждые ${intervalMinutes} минут`, "gosuslugi check --silent");
7896
+ console.log(`Локальная cron-задача добавлена: ${id}`);
7897
+ console.log("Для автоматического выполнения настройте системный планировщик на запуск: iola cron tick");
7898
+ }
7899
+
7900
+ async function installWindowsGosuslugiKeepaliveTask(intervalMinutes) {
7901
+ await mkdir(CONFIG_DIR, { recursive: true });
7902
+ const taskName = gosuslugiKeepaliveTaskName();
7903
+ const logFile = gosuslugiKeepaliveLogFile();
7904
+ const script = path.join(CONFIG_DIR, "gosuslugi-keepalive-task.cmd");
7905
+ const command = `"${process.execPath}" --no-warnings "${cliEntrypointFile()}" gosuslugi check --silent >> "${logFile}" 2>&1`;
7906
+ await writeFile(script, `@echo off\r\n${command}\r\n`, "utf8");
7907
+ await runCommand("schtasks.exe", [
7908
+ "/Create",
7909
+ "/TN", taskName,
7910
+ "/SC", "MINUTE",
7911
+ "/MO", String(intervalMinutes),
7912
+ "/TR", script,
7913
+ "/F",
7914
+ ]);
7915
+ console.log(`Windows Task Scheduler задача создана: ${taskName}`);
7916
+ console.log(`Интервал: ${intervalMinutes} мин.`);
7917
+ console.log(`Лог: ${logFile}`);
7918
+ console.log("Проверить: iola gosuslugi keepalive-status");
7919
+ }
7920
+
7921
+ async function uninstallGosuslugiKeepaliveTask() {
7922
+ if (process.platform === "win32") {
7923
+ await runCommand("schtasks.exe", ["/Delete", "/TN", gosuslugiKeepaliveTaskName(), "/F"]).catch(() => {});
7924
+ console.log(`Windows Task Scheduler задача удалена: ${gosuslugiKeepaliveTaskName()}`);
7925
+ return;
7926
+ }
7927
+ console.log("Для не-Windows удалите локальную cron-задачу вручную: iola cron list, затем iola cron delete ID.");
7928
+ }
7929
+
7930
+ async function printGosuslugiKeepaliveTaskStatus(options = {}) {
7931
+ if (process.platform === "win32") {
7932
+ try {
7933
+ const { stdout } = await runCommand("schtasks.exe", ["/Query", "/TN", gosuslugiKeepaliveTaskName(), "/FO", "LIST"]);
7934
+ console.log(stdout.trim());
7935
+ } catch {
7936
+ console.log(`Задача не найдена: ${gosuslugiKeepaliveTaskName()}`);
7937
+ }
7938
+ if (existsSync(gosuslugiKeepaliveLogFile())) {
7939
+ console.log("");
7940
+ console.log(`Лог: ${gosuslugiKeepaliveLogFile()}`);
7941
+ }
7942
+ return;
7943
+ }
7944
+ const rows = listCronJobs().filter((job) => String(job.command).includes("gosuslugi check"));
7945
+ if (options.json) printJson(rows);
7946
+ else printTable(rows, [["id", "ID"], ["enabled", "Вкл"], ["schedule_text", "Расписание"], ["command", "Команда"], ["last_run_at", "Последний запуск"]]);
7947
+ }
7948
+
7949
+ function parseDurationMs(value) {
7950
+ const text = String(value || "30m").trim().toLocaleLowerCase("ru-RU");
7951
+ const match = text.match(/^(\d+(?:[.,]\d+)?)(ms|s|m|h|мин|минут|час|часа|часов)?$/u);
7952
+ if (!match) throw new Error("Интервал задается как 30m, 1800s или 1h.");
7953
+ const amount = Number(match[1].replace(",", "."));
7954
+ const unit = match[2] || "m";
7955
+ if (unit === "ms") return Math.max(1000, amount);
7956
+ if (unit === "s") return Math.max(1000, amount * 1000);
7957
+ if (unit === "h" || unit.startsWith("час")) return Math.max(1000, amount * 60 * 60 * 1000);
7958
+ return Math.max(1000, amount * 60 * 1000);
7959
+ }
7960
+
7802
7961
  function printGosuslugiDebt(result) {
7803
7962
  printKeyValue({
7804
7963
  total: result.total,
@@ -34,3 +34,9 @@ iola cron delete 1
34
34
 
35
35
  `cron tick` проверяет задачи, которые пора выполнить. Его можно запускать вручную, через Windows Task Scheduler или другой планировщик.
36
36
 
37
+ Для Госуслуг на Windows лучше использовать готовую системную задачу без висящего окна терминала:
38
+
39
+ ```bash
40
+ iola gosuslugi install-keepalive
41
+ iola gosuslugi keepalive-status
42
+ ```
@@ -95,6 +95,11 @@ 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
101
+ iola gosuslugi keepalive-status
102
+ iola gosuslugi uninstall-keepalive
98
103
  iola gosuslugi terms
99
104
  iola gosuslugi consent
100
105
  iola gosuslugi connect
@@ -78,10 +78,44 @@ 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
+ Полностью автоматический режим через системный планировщик Windows:
103
+
104
+ ```bash
105
+ iola gosuslugi install-keepalive
106
+ iola gosuslugi keepalive-status
107
+ ```
108
+
109
+ На Windows создается задача Task Scheduler `iola-gosuslugi-keepalive`, которая каждые 30 минут выполняет `iola gosuslugi check --silent` без постоянно открытого окна терминала.
110
+
111
+ Удалить задачу:
112
+
113
+ ```bash
114
+ iola gosuslugi uninstall-keepalive
115
+ ```
116
+
117
+ Если сессия протухла, CLI запишет это в лог и при следующей ручной команде попросит выполнить `iola gosuslugi connect`.
118
+
85
119
  ## Отключение
86
120
 
87
121
  Удалить локальное подключение: