@iola_adm/iola-cli 0.2.48 → 0.2.49

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
@@ -210,6 +210,7 @@ Yandex tools уже доступны: профиль Yandex ID, расширен
210
210
  Мой домофон:
211
211
 
212
212
  ```bash
213
+ iola ufanet
213
214
  iola ufanet setup
214
215
  iola ufanet intercoms
215
216
  iola ufanet open
@@ -223,7 +224,7 @@ iola dom_ru
223
224
  iola rostelecom
224
225
  ```
225
226
 
226
- Уфанет поддерживается как рабочий провайдер: список домофонов, открытие двери после подтверждения, история звонков, записи звонков, камеры/RTSP и уведомления о новых вызовах через опрос истории звонков, пока CLI открыт. Команда `открой домофон` открывает единственный домофон сразу; если домофонов несколько, CLI просит выбрать адрес цифрой. При уведомлении о вызове можно ответить `да`, `да открой` или `нет`. Дом.ру и Ростелеком добавлены как видимые направления `в разработке`.
227
+ `/ufanet` в интерактивном CLI открывает отдельное меню домофона с выбором действия цифрой. Уфанет поддерживается как рабочий провайдер: список домофонов, открытие двери после подтверждения, история звонков, записи звонков, камеры/RTSP и уведомления о новых вызовах через опрос истории звонков, пока CLI открыт. Команда `открой домофон` открывает единственный домофон сразу; если домофонов несколько, CLI просит выбрать адрес цифрой. При уведомлении о вызове можно ответить `да`, `да открой` или `нет`. Дом.ру и Ростелеком добавлены как видимые направления `в разработке`.
227
228
 
228
229
  Инструкция: [Мой домофон](https://github.com/adm-iola/iola-cli/wiki/Мой-домофон).
229
230
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iola_adm/iola-cli",
3
- "version": "0.2.48",
3
+ "version": "0.2.49",
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
@@ -1282,11 +1282,15 @@ async function startAgentRawInput() {
1282
1282
 
1283
1283
  async function handleAgentLine(line, state) {
1284
1284
  if (!line.startsWith("/")) {
1285
+ if (/^\d{1,2}$/u.test(line.trim()) && (state.pendingAction?.type === "ufanet_menu" || state.lastCommand?.command === "ufanet")) {
1286
+ state.pendingAction = state.pendingAction?.type === "ufanet_menu" ? state.pendingAction : buildUfanetMenuPendingAction();
1287
+ }
1285
1288
  const pendingAnswer = await handlePendingAgentAction(line, state);
1286
1289
  const answer = pendingAnswer || await aiAsk(state.rawMode ? [line, "--quiet"] : [line], { history: state.history, state });
1287
1290
  state.history.push({ role: "user", content: line });
1288
1291
  state.history.push({ role: "assistant", content: answer });
1289
1292
  if (state.rawMode) state.pendingOutput = answer;
1293
+ else if (pendingAnswer) printAiAnswer(pendingAnswer);
1290
1294
  return false;
1291
1295
  }
1292
1296
 
@@ -1308,6 +1312,10 @@ async function handleAgentLine(line, state) {
1308
1312
  }
1309
1313
 
1310
1314
  if (command === "help") {
1315
+ if (state.pendingAction?.type === "ufanet_menu") {
1316
+ await printUfanetMenu({ agentState: state, pending: true });
1317
+ return false;
1318
+ }
1311
1319
  printAgentHelp();
1312
1320
  return false;
1313
1321
  }
@@ -1942,6 +1950,21 @@ async function handlePendingAgentAction(line, state) {
1942
1950
  const result = await ufanetOpenIntercom(action.id, { confirm: true });
1943
1951
  return formatUfanetOpenResult(result, action);
1944
1952
  }
1953
+ if (action.type === "ufanet_menu") {
1954
+ const number = Number(text.match(/^\s*(\d{1,2})\s*$/u)?.[1] || 0);
1955
+ if (number === 0) {
1956
+ state.pendingAction = null;
1957
+ return "Выход из меню домофона.";
1958
+ }
1959
+ if (!number || number < 0 || number > action.items.length) {
1960
+ state.pendingAction = buildUfanetMenuPendingAction();
1961
+ return await formatUfanetMenu({ compact: true });
1962
+ }
1963
+ const item = action.items[number - 1];
1964
+ state.pendingAction = null;
1965
+ const answer = await executeUfanetMenuItem(item, state);
1966
+ return answer || "";
1967
+ }
1945
1968
  return "";
1946
1969
  }
1947
1970
 
@@ -3714,8 +3737,25 @@ async function handleUfanet(args = [], agentState = null) {
3714
3737
  const [action = process.stdin.isTTY ? "menu" : "status", target, ...rest] = args;
3715
3738
  const options = parseOptions(rest);
3716
3739
 
3717
- if (action === "menu" || action === "choose") {
3718
- await printUfanetMenu();
3740
+ if (action === "menu" || action === "choose" || action === "help") {
3741
+ await printUfanetMenu({ agentState, pending: action !== "help" });
3742
+ return;
3743
+ }
3744
+
3745
+ if (/^\d{1,2}$/u.test(String(action))) {
3746
+ const number = Number(action);
3747
+ const items = getUfanetMenuItems();
3748
+ if (number === 0) {
3749
+ console.log("Выход из меню домофона.");
3750
+ return;
3751
+ }
3752
+ const item = items[number - 1];
3753
+ if (!item) {
3754
+ console.log(formatUfanetMenu({ compact: true }));
3755
+ return;
3756
+ }
3757
+ const answer = await executeUfanetMenuItem(item, agentState);
3758
+ if (answer) console.log(answer);
3719
3759
  return;
3720
3760
  }
3721
3761
 
@@ -3804,28 +3844,88 @@ async function handleUfanet(args = [], agentState = null) {
3804
3844
  iola ufanet delete`);
3805
3845
  }
3806
3846
 
3807
- async function printUfanetMenu() {
3847
+ async function printUfanetMenu(options = {}) {
3848
+ if (options.agentState && options.pending) {
3849
+ options.agentState.pendingAction = buildUfanetMenuPendingAction();
3850
+ }
3851
+ console.log(await formatUfanetMenu());
3852
+ }
3853
+
3854
+ async function formatUfanetMenu(options = {}) {
3808
3855
  const status = await getUfanetStatus();
3809
- console.log("Мой домофон");
3810
- printTable([
3811
- { id: "ufanet", provider: "Уфанет", status: status.configured ? "готово" : "не настроено", command: "iola ufanet setup" },
3812
- { id: "domru", provider: "Дом.ру", status: "в разработке", command: "iola dom_ru" },
3813
- { id: "rostelecom", provider: "Ростелеком", status: "в разработке", command: "iola rostelecom" },
3814
- ], [["id", "ID"], ["provider", "Провайдер"], ["status", "Статус"], ["command", "Команда"]]);
3815
- console.log("");
3816
- console.log("Команды Уфанет:");
3817
- printTable([
3818
- { command: "/ufanet status", action: "статус подключения" },
3819
- { command: "/ufanet intercoms", action: "список доступных домофонов и их ID" },
3820
- { command: "/ufanet open ID", action: "открыть домофон по ID, только после подтверждения" },
3821
- { command: "/ufanet history", action: "история последних звонков" },
3822
- { command: "/ufanet links UUID", action: "ссылка/превью записи звонка по UUID" },
3823
- { command: "/ufanet cameras", action: "список камер и RTSP-ссылок, если доступны" },
3824
- { command: "/ufanet watch", action: "показывать новые вызовы, пока CLI открыт" },
3825
- { command: "/ufanet notifications on", action: "включить уведомления о вызовах в настройках" },
3826
- { command: "/ufanet notifications off", action: "выключить уведомления о вызовах" },
3827
- { command: "/ufanet delete", action: "удалить локальное подключение Уфанет" },
3828
- ], [["command", "Команда"], ["action", "Что делает"]]);
3856
+ const lines = [
3857
+ "Мой домофон",
3858
+ `Уфанет: ${status.configured ? "готово" : "не настроено"}; уведомления: ${status.notifications}.`,
3859
+ "Дом.ру: в разработке. Ростелеком: в разработке.",
3860
+ "",
3861
+ "Выберите действие:",
3862
+ ...getUfanetMenuItems().map((item) => `${item.number}. ${item.title}`),
3863
+ "0. Назад",
3864
+ "",
3865
+ "Введите цифру или команду вида `/ufanet 3`. Пока открыт этот раздел, `/help` показывает справку по домофону.",
3866
+ ];
3867
+ return options.compact ? lines.slice(4).join("\n") : lines.join("\n");
3868
+ }
3869
+
3870
+ function getUfanetMenuItems() {
3871
+ return [
3872
+ { number: 1, key: "open", title: "Открыть домофон" },
3873
+ { number: 2, key: "intercoms", title: "Показать мои домофоны" },
3874
+ { number: 3, key: "history", title: "История звонков" },
3875
+ { number: 4, key: "notifications-on", title: "Включить уведомления о вызовах" },
3876
+ { number: 5, key: "notifications-off", title: "Выключить уведомления" },
3877
+ { number: 6, key: "notifications-status", title: "Статус уведомлений" },
3878
+ { number: 7, key: "cameras", title: "Камеры" },
3879
+ { number: 8, key: "status", title: "Статус подключения" },
3880
+ { number: 9, key: "delete", title: "Удалить подключение Уфанет" },
3881
+ ];
3882
+ }
3883
+
3884
+ function buildUfanetMenuPendingAction() {
3885
+ return { type: "ufanet_menu", items: getUfanetMenuItems(), createdAt: new Date().toISOString() };
3886
+ }
3887
+
3888
+ async function executeUfanetMenuItem(item, agentState = null) {
3889
+ if (!item) return "";
3890
+ if (item.key === "open") {
3891
+ const result = await ufanetOpenSmart({ state: agentState });
3892
+ return formatUfanetSmartOpenResult(result);
3893
+ }
3894
+ if (item.key === "intercoms") {
3895
+ const rows = await ufanetGetIntercoms();
3896
+ if (!rows.length) return "Доступные домофоны Уфанет не найдены.";
3897
+ return ["Домофоны Уфанет:", ...rows.map((row, index) => `${index + 1}. ${row.address || row.name || `Домофон #${row.id}`} — ID ${row.id}`)].join("\n");
3898
+ }
3899
+ if (item.key === "history") {
3900
+ const rows = await ufanetGetCallHistory({ page: 1, pageSize: 10 });
3901
+ if (!rows.results?.length) return "В истории Уфанет звонков не найдено.";
3902
+ return ["История звонков Уфанет:", ...rows.results.map((row, index) => `${index + 1}. ${row.calledAt || "-"} — ${row.address || "-"}${row.uuid ? `, UUID ${row.uuid}` : ""}`)].join("\n");
3903
+ }
3904
+ if (item.key === "notifications-on") {
3905
+ await setUfanetNotifications(true);
3906
+ return "Уведомления Уфанет включены. При новом вызове в CLI можно ответить `да` или `нет`.";
3907
+ }
3908
+ if (item.key === "notifications-off") {
3909
+ await setUfanetNotifications(false);
3910
+ return "Уведомления Уфанет выключены.";
3911
+ }
3912
+ if (item.key === "notifications-status") {
3913
+ const status = await getUfanetStatus();
3914
+ return `Уведомления Уфанет: ${status.notifications}, интервал ${status.intervalSeconds} сек.`;
3915
+ }
3916
+ if (item.key === "cameras") {
3917
+ const rows = await ufanetGetCameras();
3918
+ if (!rows.length) return "Камеры Уфанет не найдены.";
3919
+ return ["Камеры Уфанет:", ...rows.slice(0, 10).map((row, index) => `${index + 1}. ${row.title || row.number || "камера"} — ${row.address || "-"}${row.rtspUrl ? `, ${row.rtspUrl}` : ""}`)].join("\n");
3920
+ }
3921
+ if (item.key === "status") {
3922
+ const status = await getUfanetStatus();
3923
+ return `Уфанет: ${status.configured ? "настроен" : "не настроен"}, ${status.enabled ? "включен" : "выключен"}, уведомления ${status.notifications}.`;
3924
+ }
3925
+ if (item.key === "delete") {
3926
+ return "Удаление подключения выполняется явной командой: `/ufanet delete`.";
3927
+ }
3928
+ return "";
3829
3929
  }
3830
3930
 
3831
3931
  async function setupUfanetConnector() {
@@ -91,6 +91,7 @@ iola yandex token delete
91
91
  Мой домофон:
92
92
 
93
93
  ```bash
94
+ iola ufanet
94
95
  iola ufanet setup
95
96
  iola ufanet status
96
97
  iola ufanet intercoms
@@ -7,6 +7,7 @@
7
7
  Рабочий провайдер:
8
8
 
9
9
  ```bash
10
+ iola ufanet
10
11
  iola ufanet setup
11
12
  iola ufanet status
12
13
  iola ufanet intercoms
@@ -22,6 +23,8 @@ iola ufanet notifications status
22
23
  iola ufanet delete
23
24
  ```
24
25
 
26
+ В интерактивном CLI команда `/ufanet` открывает отдельное меню домофона. В нем можно выбрать действие цифрой: открыть домофон, посмотреть список, историю, уведомления, камеры или статус.
27
+
25
28
  При настройке нужны:
26
29
 
27
30
  - номер договора Уфанет;