@iola_adm/iola-cli 0.2.46 → 0.2.47

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
@@ -215,11 +215,14 @@ iola ufanet intercoms
215
215
  iola ufanet open ID
216
216
  iola ufanet history
217
217
  iola ufanet cameras
218
+ iola ufanet watch
219
+ iola ufanet notifications on
220
+ iola ufanet notifications off
218
221
  iola dom_ru
219
222
  iola rostelecom
220
223
  ```
221
224
 
222
- Уфанет поддерживается как рабочий провайдер: список домофонов, открытие двери после подтверждения, история звонков, записи звонков и камеры/RTSP. Дом.ру и Ростелеком добавлены как видимые направления `в разработке`.
225
+ Уфанет поддерживается как рабочий провайдер: список домофонов, открытие двери после подтверждения, история звонков, записи звонков, камеры/RTSP и уведомления о новых вызовах через опрос истории звонков, пока CLI открыт. Дом.ру и Ростелеком добавлены как видимые направления `в разработке`.
223
226
 
224
227
  Инструкция: [Мой домофон](https://github.com/adm-iola/iola-cli/wiki/Мой-домофон).
225
228
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iola_adm/iola-cli",
3
- "version": "0.2.46",
3
+ "version": "0.2.47",
4
4
  "description": "CLI и AI-агент городского округа Йошкар-Ола.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/adm-iola/iola-cli#readme",
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  name: ufanet-intercom
3
- description: Мой домофон Уфанет: список домофонов, открытие двери после подтверждения, история звонков, записи звонков, камеры и RTSP.
3
+ description: Мой домофон Уфанет: список домофонов, открытие двери после подтверждения, история звонков, уведомления о новых вызовах, записи звонков, камеры и RTSP.
4
4
  ---
5
5
 
6
- Используй этот skill, когда пользователь явно просит работать с домофоном Уфанет: открыть домофон, показать доступные домофоны, посмотреть историю звонков, получить запись звонка, показать камеры или RTSP.
6
+ Используй этот skill, когда пользователь явно просит работать с домофоном Уфанет: открыть домофон, показать доступные домофоны, посмотреть историю звонков, включить/выключить уведомления о вызовах, получить запись звонка, показать камеры или RTSP.
7
7
 
8
8
  Не смешивай Уфанет с Яндекс-сервисами, городскими открытыми слоями и локальными файлами. Это отдельный личный сервис пользователя.
9
9
 
@@ -23,6 +23,8 @@ description: Мой домофон Уфанет: список домофонов
23
23
  - `iola ufanet intercoms` - список домофонов.
24
24
  - `iola ufanet open ID` - открыть домофон после подтверждения.
25
25
  - `iola ufanet history` - история звонков.
26
+ - `iola ufanet watch` - показывать новые вызовы, пока CLI открыт.
27
+ - `iola ufanet notifications on|off|status` - включить, выключить или проверить настройку уведомлений.
26
28
  - `iola ufanet links UUID` - ссылка на запись звонка.
27
29
  - `iola ufanet cameras` - камеры.
28
30
  - `iola ufanet delete` - удалить локальное подключение.
package/src/cli.js CHANGED
@@ -492,7 +492,7 @@ const DEFAULT_AI_CONFIG = {
492
492
  domophones: {
493
493
  activeProvider: "",
494
494
  providers: {
495
- ufanet: { enabled: false },
495
+ ufanet: { enabled: false, notifications: { enabled: false, intervalSeconds: 10, lastSeen: "" } },
496
496
  domru: { enabled: false, status: "backlog" },
497
497
  rostelecom: { enabled: false, status: "backlog" },
498
498
  },
@@ -600,6 +600,7 @@ const SLASH_COMMANDS = [
600
600
  { command: "/cloud status", description: "облачные диски" },
601
601
  { command: "/yandex", description: "выбор сервисов Yandex Connector" },
602
602
  { command: "/ufanet", description: "Мой домофон Уфанет" },
603
+ { command: "/ufanet watch", description: "уведомления о новых вызовах домофона" },
603
604
  { command: "/dom_ru", description: "Мой домофон Дом.ру (в разработке)" },
604
605
  { command: "/rostelecom", description: "Мой домофон Ростелеком (в разработке)" },
605
606
  { command: "/archive doctor", description: "архиватор" },
@@ -872,7 +873,7 @@ Usage:
872
873
  iola files status|mode|approvals|tree|read|search|write|patch
873
874
  iola cloud setup|status|ls|find|upload|download|share|save|backup
874
875
  iola yandex setup|menu|status|services|enable|disable|oauth-url|token
875
- iola ufanet setup|status|intercoms|open|history|links|cameras|delete
876
+ iola ufanet setup|status|intercoms|open|history|links|cameras|watch|notifications|delete
876
877
  iola dom_ru Мой домофон Дом.ру (в разработке)
877
878
  iola rostelecom Мой домофон Ростелеком (в разработке)
878
879
  iola archive doctor|list|test|extract|create|index
@@ -1614,6 +1615,10 @@ async function handleAgentLine(line, state) {
1614
1615
  files: ["files", args],
1615
1616
  archive: ["archive", args],
1616
1617
  yandex: ["yandex", args.length ? args : ["menu"]],
1618
+ ufanet: ["ufanet", args.length ? args : ["menu"]],
1619
+ dom_ru: ["dom_ru", args],
1620
+ domru: ["dom_ru", args],
1621
+ rostelecom: ["rostelecom", args],
1617
1622
  changes: ["changes", args],
1618
1623
  index: ["index", args],
1619
1624
  reports: ["reports", args],
@@ -3610,6 +3615,16 @@ async function handleUfanet(args = []) {
3610
3615
  return;
3611
3616
  }
3612
3617
 
3618
+ if (action === "watch" || action === "listen") {
3619
+ await watchUfanetCalls({ ...options, once: options.once, intervalSeconds: options.seconds || options.interval || target });
3620
+ return;
3621
+ }
3622
+
3623
+ if (action === "notifications" || action === "notify") {
3624
+ await handleUfanetNotifications([target, ...rest].filter(Boolean));
3625
+ return;
3626
+ }
3627
+
3613
3628
  if (action === "links" || action === "record" || action === "recording") {
3614
3629
  const uuid = target || options.uuid;
3615
3630
  if (!uuid) throw new Error("Укажите UUID звонка. Пример: iola ufanet links UUID");
@@ -3641,6 +3656,8 @@ async function handleUfanet(args = []) {
3641
3656
  iola ufanet history [--limit 10]
3642
3657
  iola ufanet links UUID
3643
3658
  iola ufanet cameras
3659
+ iola ufanet watch [--seconds 10]
3660
+ iola ufanet notifications on|off|status
3644
3661
  iola ufanet delete`);
3645
3662
  }
3646
3663
 
@@ -3652,6 +3669,20 @@ async function printUfanetMenu() {
3652
3669
  { id: "domru", provider: "Дом.ру", status: "в разработке", command: "iola dom_ru" },
3653
3670
  { id: "rostelecom", provider: "Ростелеком", status: "в разработке", command: "iola rostelecom" },
3654
3671
  ], [["id", "ID"], ["provider", "Провайдер"], ["status", "Статус"], ["command", "Команда"]]);
3672
+ console.log("");
3673
+ console.log("Команды Уфанет:");
3674
+ printTable([
3675
+ { command: "/ufanet status", action: "статус подключения" },
3676
+ { command: "/ufanet intercoms", action: "список доступных домофонов и их ID" },
3677
+ { command: "/ufanet open ID", action: "открыть домофон по ID, только после подтверждения" },
3678
+ { command: "/ufanet history", action: "история последних звонков" },
3679
+ { command: "/ufanet links UUID", action: "ссылка/превью записи звонка по UUID" },
3680
+ { command: "/ufanet cameras", action: "список камер и RTSP-ссылок, если доступны" },
3681
+ { command: "/ufanet watch", action: "показывать новые вызовы, пока CLI открыт" },
3682
+ { command: "/ufanet notifications on", action: "включить уведомления о вызовах в настройках" },
3683
+ { command: "/ufanet notifications off", action: "выключить уведомления о вызовах" },
3684
+ { command: "/ufanet delete", action: "удалить локальное подключение Уфанет" },
3685
+ ], [["command", "Команда"], ["action", "Что делает"]]);
3655
3686
  }
3656
3687
 
3657
3688
  async function setupUfanetConnector() {
@@ -3730,6 +3761,8 @@ async function printUfanetStatus(options = {}) {
3730
3761
  enabled: status.enabled ? "yes" : "no",
3731
3762
  contract: status.contract || "-",
3732
3763
  source: status.source || "-",
3764
+ notifications: status.notifications,
3765
+ intervalSeconds: status.intervalSeconds,
3733
3766
  });
3734
3767
  if (options.check) {
3735
3768
  if (!status.configured) {
@@ -3754,9 +3787,133 @@ async function getUfanetStatus() {
3754
3787
  enabled: Boolean(config.domophones?.providers?.ufanet?.enabled || (config.toolsets?.enabled || []).includes("ufanet")),
3755
3788
  contract: contract ? maskSecret(contract, 2) : "",
3756
3789
  source: contract && process.env.UFANET_CONTRACT ? "env" : contract ? "local" : "",
3790
+ notifications: config.domophones?.providers?.ufanet?.notifications?.enabled ? "on" : "off",
3791
+ intervalSeconds: config.domophones?.providers?.ufanet?.notifications?.intervalSeconds || 10,
3757
3792
  };
3758
3793
  }
3759
3794
 
3795
+ async function handleUfanetNotifications(args = []) {
3796
+ const [action = "status", ...rest] = args;
3797
+ const options = parseOptions(rest);
3798
+ const normalized = String(action || "status").toLocaleLowerCase("ru-RU");
3799
+ if (["on", "enable", "start", "вкл", "включить"].includes(normalized)) {
3800
+ const seconds = Number(options.seconds || options.interval || options.wait || 10);
3801
+ await setUfanetNotifications(true, { intervalSeconds: seconds });
3802
+ console.log(`Уведомления Уфанет включены. Интервал проверки: ${normalizeUfanetPollInterval(seconds)} сек.`);
3803
+ console.log("Чтобы получать события в текущей CLI-сессии, запустите: /ufanet watch");
3804
+ return;
3805
+ }
3806
+ if (["off", "disable", "stop", "выкл", "выключить"].includes(normalized)) {
3807
+ await setUfanetNotifications(false);
3808
+ console.log("Уведомления Уфанет выключены.");
3809
+ return;
3810
+ }
3811
+ const status = await getUfanetStatus();
3812
+ console.log(`Уведомления Уфанет: ${status.notifications}, интервал ${status.intervalSeconds} сек.`);
3813
+ console.log("Команды: /ufanet notifications on, /ufanet notifications off, /ufanet watch");
3814
+ }
3815
+
3816
+ async function setUfanetNotifications(enabled, options = {}) {
3817
+ const config = await loadConfig();
3818
+ const current = config.domophones?.providers?.ufanet || {};
3819
+ const currentNotifications = current.notifications || {};
3820
+ await saveConfig({
3821
+ domophones: {
3822
+ ...(config.domophones || {}),
3823
+ providers: {
3824
+ ...(config.domophones?.providers || {}),
3825
+ ufanet: {
3826
+ ...current,
3827
+ notifications: {
3828
+ ...currentNotifications,
3829
+ enabled: Boolean(enabled),
3830
+ intervalSeconds: normalizeUfanetPollInterval(options.intervalSeconds || currentNotifications.intervalSeconds || 10),
3831
+ },
3832
+ },
3833
+ },
3834
+ },
3835
+ });
3836
+ }
3837
+
3838
+ async function updateUfanetLastSeen(lastSeen) {
3839
+ if (!lastSeen) return;
3840
+ const config = await loadConfig();
3841
+ const current = config.domophones?.providers?.ufanet || {};
3842
+ const currentNotifications = current.notifications || {};
3843
+ await saveConfig({
3844
+ domophones: {
3845
+ ...(config.domophones || {}),
3846
+ providers: {
3847
+ ...(config.domophones?.providers || {}),
3848
+ ufanet: {
3849
+ ...current,
3850
+ notifications: {
3851
+ ...currentNotifications,
3852
+ lastSeen,
3853
+ },
3854
+ },
3855
+ },
3856
+ },
3857
+ });
3858
+ }
3859
+
3860
+ async function watchUfanetCalls(options = {}) {
3861
+ const config = await loadConfig();
3862
+ const notificationConfig = config.domophones?.providers?.ufanet?.notifications || {};
3863
+ const intervalSeconds = normalizeUfanetPollInterval(options.intervalSeconds || notificationConfig.intervalSeconds || 10);
3864
+ const pageSize = Number(options.limit || options["page-size"] || 10);
3865
+ let lastSeen = notificationConfig.lastSeen || "";
3866
+ const initial = await ufanetGetCallHistory({ page: 1, pageSize });
3867
+ const initialRows = initial.results || [];
3868
+ if (!lastSeen && initialRows[0]) {
3869
+ lastSeen = ufanetCallKey(initialRows[0]);
3870
+ await updateUfanetLastSeen(lastSeen);
3871
+ }
3872
+ if (options.once) {
3873
+ printTable(initialRows, [["uuid", "UUID"], ["calledAt", "Когда"], ["address", "Адрес"], ["porch", "Подъезд"], ["flat", "Кв"]]);
3874
+ return;
3875
+ }
3876
+ console.log(`Уфанет: слежу за новыми вызовами каждые ${intervalSeconds} сек. Остановить: Ctrl+C.`);
3877
+ while (true) {
3878
+ await sleep(intervalSeconds * 1000);
3879
+ const history = await ufanetGetCallHistory({ page: 1, pageSize }).catch((error) => {
3880
+ console.error(`Уфанет: ошибка проверки вызовов: ${error instanceof Error ? error.message : String(error)}`);
3881
+ return { results: [] };
3882
+ });
3883
+ const rows = history.results || [];
3884
+ const fresh = [];
3885
+ for (const row of rows) {
3886
+ const key = ufanetCallKey(row);
3887
+ if (!key || key === lastSeen) break;
3888
+ fresh.push(row);
3889
+ }
3890
+ if (fresh.length === 0) continue;
3891
+ for (const row of fresh.reverse()) {
3892
+ console.log("");
3893
+ console.log("Новый вызов домофона Уфанет:");
3894
+ printKeyValue({
3895
+ uuid: row.uuid || "-",
3896
+ calledAt: row.calledAt || "-",
3897
+ address: row.address || "-",
3898
+ porch: row.porch || "-",
3899
+ flat: row.flat || "-",
3900
+ });
3901
+ }
3902
+ lastSeen = ufanetCallKey(rows[0]) || lastSeen;
3903
+ await updateUfanetLastSeen(lastSeen);
3904
+ }
3905
+ }
3906
+
3907
+ function normalizeUfanetPollInterval(value) {
3908
+ const seconds = Number(value || 10);
3909
+ if (!Number.isFinite(seconds)) return 10;
3910
+ return Math.max(5, Math.min(300, Math.round(seconds)));
3911
+ }
3912
+
3913
+ function ufanetCallKey(row = {}) {
3914
+ return String(row.uuid || `${row.calledAt || ""}|${row.address || ""}|${row.porch || ""}|${row.flat || ""}`);
3915
+ }
3916
+
3760
3917
  async function executeUfanetTool(tool, args = {}) {
3761
3918
  if (tool === "ufanet_status") return getUfanetStatus();
3762
3919
  if (tool === "ufanet_intercoms") return ufanetGetIntercoms();
@@ -17316,7 +17473,7 @@ function parseOptions(args) {
17316
17473
  } else if (arg === "--check" || arg === "--upgrade-node") {
17317
17474
  result.check = true;
17318
17475
  result[arg.slice(2)] = true;
17319
- } else if (arg === "--limit" || arg === "--offset" || arg === "--search" || arg === "--replace" || arg === "--text" || arg === "--path" || arg === "--depth" || arg === "--max-bytes" || arg === "--query" || arg === "--where" || arg === "--columns" || arg === "--inn" || arg === "--model" || arg === "--provider" || arg === "--profile" || arg === "--name" || arg === "--source" || arg === "--command" || arg === "--prompt" || arg === "--description" || arg === "--instructions" || arg === "--allowed-tools" || arg === "--tool" || arg === "--uses" || arg === "--template" || arg === "--minutes" || arg === "--days" || arg === "--time" || arg === "--horizon" || arg === "--base-url" || arg === "--repo" || arg === "--model-dir" || arg === "--sandbox" || arg === "--approval" || arg === "--cwd" || arg === "--codex-profile" || arg === "--format" || arg === "--output" || arg === "--schema" || arg === "--session" || arg === "--temperature" || arg === "--config" || arg === "--dataset" || arg === "--save" || arg === "--reasoning" || arg === "--agent" || arg === "--scope" || arg === "--selector" || arg === "--url" || arg === "--timeout" || arg === "--wait" || arg === "--viewport" || arg === "--press" || arg === "--script" || arg === "--auth-url" || arg === "--token-url" || arg === "--userinfo-url" || arg === "--client-id" || arg === "--client-secret" || arg === "--redirect-url" || arg === "--redirect-host" || arg === "--redirect-port" || arg === "--redirect-path" || arg === "--debug-file" || arg === "--from" || arg === "--to" || arg === "--radius" || arg === "--address" || arg === "--token" || arg === "--app" || arg === "--tariff" || arg === "--class" || arg === "--level" || arg === "--ref" || arg === "--lang" || arg === "--id" || arg === "--uuid" || arg === "--intercom" || arg === "--page-size") {
17476
+ } else if (arg === "--limit" || arg === "--offset" || arg === "--search" || arg === "--replace" || arg === "--text" || arg === "--path" || arg === "--depth" || arg === "--max-bytes" || arg === "--query" || arg === "--where" || arg === "--columns" || arg === "--inn" || arg === "--model" || arg === "--provider" || arg === "--profile" || arg === "--name" || arg === "--source" || arg === "--command" || arg === "--prompt" || arg === "--description" || arg === "--instructions" || arg === "--allowed-tools" || arg === "--tool" || arg === "--uses" || arg === "--template" || arg === "--minutes" || arg === "--days" || arg === "--time" || arg === "--horizon" || arg === "--base-url" || arg === "--repo" || arg === "--model-dir" || arg === "--sandbox" || arg === "--approval" || arg === "--cwd" || arg === "--codex-profile" || arg === "--format" || arg === "--output" || arg === "--schema" || arg === "--session" || arg === "--temperature" || arg === "--config" || arg === "--dataset" || arg === "--save" || arg === "--reasoning" || arg === "--agent" || arg === "--scope" || arg === "--selector" || arg === "--url" || arg === "--timeout" || arg === "--wait" || arg === "--viewport" || arg === "--press" || arg === "--script" || arg === "--auth-url" || arg === "--token-url" || arg === "--userinfo-url" || arg === "--client-id" || arg === "--client-secret" || arg === "--redirect-url" || arg === "--redirect-host" || arg === "--redirect-port" || arg === "--redirect-path" || arg === "--debug-file" || arg === "--from" || arg === "--to" || arg === "--radius" || arg === "--address" || arg === "--token" || arg === "--app" || arg === "--tariff" || arg === "--class" || arg === "--level" || arg === "--ref" || arg === "--lang" || arg === "--id" || arg === "--uuid" || arg === "--intercom" || arg === "--page-size" || arg === "--seconds" || arg === "--interval") {
17320
17477
  result[arg.slice(2)] = args[index + 1];
17321
17478
  index += 1;
17322
17479
  } else {
@@ -10,7 +10,7 @@ Skills не подмешиваются в каждый запрос целико
10
10
  - `browser-agent` - когда запрос связан с сайтом, URL, страницей, скриншотом или браузером;
11
11
  - `local-model` - инструкции для локальных компактных моделей и tool-планирования;
12
12
  - `yandex-services` - когда запрос связан с Yandex Connector или Yandex Cloud Connector: Яндекс Диск, Почта, Календарь, Контакты, Телемост, Yandex ID, геокодер, YandexGPT или Яндекс Go deeplink;
13
- - `ufanet-intercom` - когда запрос связан с домофоном Уфанет: открыть домофон, история звонков, камеры, записи;
13
+ - `ufanet-intercom` - когда запрос связан с домофоном Уфанет: открыть домофон, история звонков, уведомления о вызовах, камеры, записи;
14
14
  - `user-skills` - когда пользователь просит создать, включить, выключить или удалить собственный skill.
15
15
 
16
16
  Обычный диалог вроде `привет` не получает инструкции про слои, отчеты, файлы и браузер.
@@ -103,4 +103,6 @@ Toolset `ufanet` включает tools для личного домофона
103
103
  - `ufanet_call_links` - ссылка на запись звонка по UUID;
104
104
  - `ufanet_cameras` - камеры и RTSP-ссылки.
105
105
 
106
+ Уведомления о новых вызовах доступны командой `iola ufanet watch`; включение/выключение настройки - `iola ufanet notifications on|off`.
107
+
106
108
  Открытие домофона требует явного подтверждения `confirm=true`. Договор, пароль и JWT хранятся локально и не выводятся в ответах.
@@ -98,6 +98,9 @@ iola ufanet open ID
98
98
  iola ufanet history --limit 10
99
99
  iola ufanet links UUID
100
100
  iola ufanet cameras
101
+ iola ufanet watch --seconds 10
102
+ iola ufanet notifications on
103
+ iola ufanet notifications off
101
104
  iola ufanet delete
102
105
  iola dom_ru
103
106
  iola rostelecom
@@ -14,6 +14,10 @@ iola ufanet history
14
14
  iola ufanet cameras
15
15
  iola ufanet links UUID
16
16
  iola ufanet open ID
17
+ iola ufanet watch
18
+ iola ufanet notifications on
19
+ iola ufanet notifications off
20
+ iola ufanet notifications status
17
21
  iola ufanet delete
18
22
  ```
19
23
 
@@ -43,10 +47,20 @@ UFANET_PASSWORD
43
47
  - получить ссылку на запись звонка по UUID;
44
48
  - показать камеры;
45
49
  - показать RTSP-ссылки камер;
50
+ - получать уведомления о новых вызовах, пока CLI открыт;
51
+ - включить или выключить настройку уведомлений;
46
52
  - удалить локальное подключение.
47
53
 
48
54
  Открытие двери всегда требует явного подтверждения пользователя.
49
55
 
56
+ Уведомления сейчас работают через периодическую проверку истории звонков Уфанета:
57
+
58
+ ```bash
59
+ iola ufanet watch --seconds 10
60
+ ```
61
+
62
+ Остановка режима наблюдения: `Ctrl+C`. Команда `notifications on` сохраняет настройку, а `watch` запускает получение событий в текущей CLI-сессии.
63
+
50
64
  ## Дом.ру
51
65
 
52
66
  Команда-заготовка:
@@ -429,6 +429,9 @@ iola ufanet intercoms
429
429
  iola ufanet open ID
430
430
  iola ufanet history
431
431
  iola ufanet cameras
432
+ iola ufanet watch
433
+ iola ufanet notifications on
434
+ iola ufanet notifications off
432
435
  ```
433
436
 
434
437
  ### Дом.ру и Ростелеком