@iola_adm/iola-cli 0.2.46 → 0.2.48
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 +5 -1
- package/package.json +1 -1
- package/skills/ufanet-intercom/SKILL.md +8 -4
- package/src/cli.js +425 -11
- package/wiki/Skills-/320/270-toolsets.md +3 -1
- package/wiki//320/232/320/276/320/274/320/260/320/275/320/264/321/213.md +4 -0
- package/wiki//320/234/320/276/320/271-/320/264/320/276/320/274/320/276/321/204/320/276/320/275.md +18 -1
- package/wiki//320/241/320/272/320/270/320/273/320/273/321/213-/320/264/320/273/321/217-/320/266/320/270/321/202/320/265/320/273/320/265/320/271.md +3 -0
package/README.md
CHANGED
|
@@ -212,14 +212,18 @@ Yandex tools уже доступны: профиль Yandex ID, расширен
|
|
|
212
212
|
```bash
|
|
213
213
|
iola ufanet setup
|
|
214
214
|
iola ufanet intercoms
|
|
215
|
+
iola ufanet open
|
|
215
216
|
iola ufanet open ID
|
|
216
217
|
iola ufanet history
|
|
217
218
|
iola ufanet cameras
|
|
219
|
+
iola ufanet watch
|
|
220
|
+
iola ufanet notifications on
|
|
221
|
+
iola ufanet notifications off
|
|
218
222
|
iola dom_ru
|
|
219
223
|
iola rostelecom
|
|
220
224
|
```
|
|
221
225
|
|
|
222
|
-
Уфанет поддерживается как рабочий провайдер: список домофонов, открытие двери после подтверждения, история звонков, записи
|
|
226
|
+
Уфанет поддерживается как рабочий провайдер: список домофонов, открытие двери после подтверждения, история звонков, записи звонков, камеры/RTSP и уведомления о новых вызовах через опрос истории звонков, пока CLI открыт. Команда `открой домофон` открывает единственный домофон сразу; если домофонов несколько, CLI просит выбрать адрес цифрой. При уведомлении о вызове можно ответить `да`, `да открой` или `нет`. Дом.ру и Ростелеком добавлены как видимые направления `в разработке`.
|
|
223
227
|
|
|
224
228
|
Инструкция: [Мой домофон](https://github.com/adm-iola/iola-cli/wiki/Мой-домофон).
|
|
225
229
|
|
package/package.json
CHANGED
|
@@ -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
|
|
|
@@ -11,7 +11,7 @@ description: Мой домофон Уфанет: список домофонов
|
|
|
11
11
|
|
|
12
12
|
- `ufanet_status` - проверить, подключен ли Уфанет.
|
|
13
13
|
- `ufanet_intercoms` - показать доступные домофоны пользователя.
|
|
14
|
-
- `ufanet_open_intercom` - открыть домофон по ID
|
|
14
|
+
- `ufanet_open_intercom` - открыть домофон по ID; если ID не указан, CLI сам получает список домофонов.
|
|
15
15
|
- `ufanet_call_history` - показать историю звонков домофона.
|
|
16
16
|
- `ufanet_call_links` - получить ссылку на запись/превью звонка по UUID.
|
|
17
17
|
- `ufanet_cameras` - показать доступные камеры и RTSP-ссылки.
|
|
@@ -22,7 +22,10 @@ description: Мой домофон Уфанет: список домофонов
|
|
|
22
22
|
- `iola ufanet status` - проверить подключение.
|
|
23
23
|
- `iola ufanet intercoms` - список домофонов.
|
|
24
24
|
- `iola ufanet open ID` - открыть домофон после подтверждения.
|
|
25
|
+
- `iola ufanet open` - если домофон один, открыть его; если несколько, попросить выбрать цифрой.
|
|
25
26
|
- `iola ufanet history` - история звонков.
|
|
27
|
+
- `iola ufanet watch` - показывать новые вызовы, пока CLI открыт.
|
|
28
|
+
- `iola ufanet notifications on|off|status` - включить, выключить или проверить настройку уведомлений.
|
|
26
29
|
- `iola ufanet links UUID` - ссылка на запись звонка.
|
|
27
30
|
- `iola ufanet cameras` - камеры.
|
|
28
31
|
- `iola ufanet delete` - удалить локальное подключение.
|
|
@@ -30,7 +33,8 @@ description: Мой домофон Уфанет: список домофонов
|
|
|
30
33
|
Безопасность:
|
|
31
34
|
|
|
32
35
|
- Для `ufanet_open_intercom` всегда нужен явный запрос пользователя и `confirm=true`.
|
|
33
|
-
- Если пользователь просит "открой домофон",
|
|
36
|
+
- Если пользователь просит "открой домофон", а ID не указан, CLI должен сам получить список домофонов. Если домофон один - открыть его. Если домофонов несколько - показать адреса с цифрами и ждать выбор цифрой; выбранная цифра считается подтверждением.
|
|
37
|
+
- Если включены уведомления и пришел вызов, в сообщении должен быть вопрос "Открыть?". Ответы `да`, `да открой`, `открой`, `ок` открывают сопоставленный домофон. Ответы `нет`, `отмена` отменяют.
|
|
34
38
|
- Не открывай домофон по косвенному намерению вроде "кто там", "звонят", "посмотри".
|
|
35
39
|
- Не выводи договор, пароль, JWT-токен и другие секреты.
|
|
36
40
|
- Если Уфанет не подключен, скажи запустить `/master` и выбрать `Мой домофон Уфанет`, либо команду `iola ufanet setup`.
|
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
|
|
@@ -1186,7 +1187,7 @@ async function startAgentReadline() {
|
|
|
1186
1187
|
}
|
|
1187
1188
|
|
|
1188
1189
|
async function startAgentRawInput() {
|
|
1189
|
-
const state = { history: [], buffer: "", selected: 0, slashOffset: 0, slashOpen: false, running: false, renderedInputLines: 0, renderedLines: 0, rawMode: true, pendingOutput: "", aiStatus: null, statusBar: false, statusRows: 0 };
|
|
1190
|
+
const state = { history: [], buffer: "", selected: 0, slashOffset: 0, slashOpen: false, running: false, renderedInputLines: 0, renderedLines: 0, rawMode: true, pendingOutput: "", pendingAction: null, aiStatus: null, statusBar: false, statusRows: 0 };
|
|
1190
1191
|
const wasRaw = input.isRaw;
|
|
1191
1192
|
activateRawInput(input);
|
|
1192
1193
|
setupAgentStatusBar(state);
|
|
@@ -1194,6 +1195,7 @@ async function startAgentRawInput() {
|
|
|
1194
1195
|
await refreshAgentAiStatus(state);
|
|
1195
1196
|
const render = () => renderAgentInput(state);
|
|
1196
1197
|
render();
|
|
1198
|
+
const stopUfanetNotifications = setupAgentUfanetNotifications(state, render);
|
|
1197
1199
|
|
|
1198
1200
|
try {
|
|
1199
1201
|
while (true) {
|
|
@@ -1270,6 +1272,7 @@ async function startAgentRawInput() {
|
|
|
1270
1272
|
}
|
|
1271
1273
|
}
|
|
1272
1274
|
} finally {
|
|
1275
|
+
stopUfanetNotifications();
|
|
1273
1276
|
clearAgentInputArea(state);
|
|
1274
1277
|
finishAgentTerminalLine(state);
|
|
1275
1278
|
if (!wasRaw) input.setRawMode(false);
|
|
@@ -1279,7 +1282,8 @@ async function startAgentRawInput() {
|
|
|
1279
1282
|
|
|
1280
1283
|
async function handleAgentLine(line, state) {
|
|
1281
1284
|
if (!line.startsWith("/")) {
|
|
1282
|
-
const
|
|
1285
|
+
const pendingAnswer = await handlePendingAgentAction(line, state);
|
|
1286
|
+
const answer = pendingAnswer || await aiAsk(state.rawMode ? [line, "--quiet"] : [line], { history: state.history, state });
|
|
1283
1287
|
state.history.push({ role: "user", content: line });
|
|
1284
1288
|
state.history.push({ role: "assistant", content: answer });
|
|
1285
1289
|
if (state.rawMode) state.pendingOutput = answer;
|
|
@@ -1599,6 +1603,21 @@ async function handleAgentLine(line, state) {
|
|
|
1599
1603
|
return false;
|
|
1600
1604
|
}
|
|
1601
1605
|
|
|
1606
|
+
if (command === "ufanet") {
|
|
1607
|
+
await handleUfanet(args.length ? args : ["menu"], state);
|
|
1608
|
+
return false;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
if (command === "dom_ru" || command === "domru") {
|
|
1612
|
+
await handleDomRu(args);
|
|
1613
|
+
return false;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
if (command === "rostelecom") {
|
|
1617
|
+
await handleRostelecom(args);
|
|
1618
|
+
return false;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1602
1621
|
const mapped = {
|
|
1603
1622
|
health: ["health", args],
|
|
1604
1623
|
doctor: ["doctor", args],
|
|
@@ -1896,6 +1915,131 @@ function flushPendingAgentOutput(state) {
|
|
|
1896
1915
|
printAiAnswer(text);
|
|
1897
1916
|
}
|
|
1898
1917
|
|
|
1918
|
+
async function handlePendingAgentAction(line, state) {
|
|
1919
|
+
const action = state?.pendingAction;
|
|
1920
|
+
if (!action) return "";
|
|
1921
|
+
const text = String(line || "").trim();
|
|
1922
|
+
const normalized = text.toLocaleLowerCase("ru-RU");
|
|
1923
|
+
if (/^(нет|не|отмена|cancel|no|n)$/iu.test(normalized)) {
|
|
1924
|
+
state.pendingAction = null;
|
|
1925
|
+
return "Действие отменено.";
|
|
1926
|
+
}
|
|
1927
|
+
if (action.type === "ufanet_select_open") {
|
|
1928
|
+
const number = Number(text.match(/\d+/u)?.[0] || 0);
|
|
1929
|
+
if (!number || number < 1 || number > action.choices.length) {
|
|
1930
|
+
return formatUfanetChoicePrompt(action.choices);
|
|
1931
|
+
}
|
|
1932
|
+
const choice = action.choices[number - 1];
|
|
1933
|
+
state.pendingAction = null;
|
|
1934
|
+
const result = await ufanetOpenIntercom(choice.id, { confirm: true });
|
|
1935
|
+
return formatUfanetOpenResult(result, choice);
|
|
1936
|
+
}
|
|
1937
|
+
if (action.type === "ufanet_confirm_open") {
|
|
1938
|
+
if (!/^(да|д|yes|y|ok|ок|открой|да\s+открой|открывай|пусти)/iu.test(normalized)) {
|
|
1939
|
+
return "Ответьте `да`, чтобы открыть домофон, или `нет`, чтобы отменить.";
|
|
1940
|
+
}
|
|
1941
|
+
state.pendingAction = null;
|
|
1942
|
+
const result = await ufanetOpenIntercom(action.id, { confirm: true });
|
|
1943
|
+
return formatUfanetOpenResult(result, action);
|
|
1944
|
+
}
|
|
1945
|
+
return "";
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
function setupAgentUfanetNotifications(state, render) {
|
|
1949
|
+
if (!input.isTTY) return () => {};
|
|
1950
|
+
let stopped = false;
|
|
1951
|
+
let timer = null;
|
|
1952
|
+
let busy = false;
|
|
1953
|
+
const schedule = (seconds = 10) => {
|
|
1954
|
+
if (stopped) return;
|
|
1955
|
+
timer = setTimeout(loop, normalizeUfanetPollInterval(seconds) * 1000);
|
|
1956
|
+
};
|
|
1957
|
+
const loop = async () => {
|
|
1958
|
+
if (stopped || busy) {
|
|
1959
|
+
schedule(10);
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1962
|
+
busy = true;
|
|
1963
|
+
let nextInterval = 10;
|
|
1964
|
+
try {
|
|
1965
|
+
const config = await loadConfig();
|
|
1966
|
+
const notificationConfig = config.domophones?.providers?.ufanet?.notifications || {};
|
|
1967
|
+
nextInterval = normalizeUfanetPollInterval(notificationConfig.intervalSeconds || 10);
|
|
1968
|
+
if (notificationConfig.enabled) {
|
|
1969
|
+
const history = await ufanetGetCallHistory({ page: 1, pageSize: 10 });
|
|
1970
|
+
const rows = history.results || [];
|
|
1971
|
+
let lastSeen = notificationConfig.lastSeen || "";
|
|
1972
|
+
if (!lastSeen && rows[0]) {
|
|
1973
|
+
await updateUfanetLastSeen(ufanetCallKey(rows[0]));
|
|
1974
|
+
} else {
|
|
1975
|
+
const fresh = [];
|
|
1976
|
+
for (const row of rows) {
|
|
1977
|
+
const key = ufanetCallKey(row);
|
|
1978
|
+
if (!key || key === lastSeen) break;
|
|
1979
|
+
fresh.push(row);
|
|
1980
|
+
}
|
|
1981
|
+
if (fresh.length > 0) {
|
|
1982
|
+
for (const row of fresh.reverse()) {
|
|
1983
|
+
await announceUfanetCallInAgent(row, state, render);
|
|
1984
|
+
}
|
|
1985
|
+
await updateUfanetLastSeen(ufanetCallKey(rows[0]) || lastSeen);
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
} catch (error) {
|
|
1990
|
+
if (process.env.IOLA_DEBUG === "1") {
|
|
1991
|
+
printAgentAsyncMessage(state, render, `Уфанет: ошибка проверки вызовов: ${error instanceof Error ? error.message : String(error)}`);
|
|
1992
|
+
}
|
|
1993
|
+
} finally {
|
|
1994
|
+
busy = false;
|
|
1995
|
+
schedule(nextInterval);
|
|
1996
|
+
}
|
|
1997
|
+
};
|
|
1998
|
+
schedule(2);
|
|
1999
|
+
return () => {
|
|
2000
|
+
stopped = true;
|
|
2001
|
+
if (timer) clearTimeout(timer);
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
async function announceUfanetCallInAgent(row, state, render) {
|
|
2006
|
+
const match = await resolveUfanetIntercomForCall(row).catch(() => null);
|
|
2007
|
+
if (match?.id) {
|
|
2008
|
+
state.pendingAction = {
|
|
2009
|
+
type: "ufanet_confirm_open",
|
|
2010
|
+
id: match.id,
|
|
2011
|
+
address: match.address || row.address || "",
|
|
2012
|
+
name: match.name || "",
|
|
2013
|
+
createdAt: new Date().toISOString(),
|
|
2014
|
+
};
|
|
2015
|
+
printAgentAsyncMessage(state, render, [
|
|
2016
|
+
"Новый вызов домофона Уфанет:",
|
|
2017
|
+
`Адрес: ${row.address || match.address || "-"}`,
|
|
2018
|
+
`Время: ${row.calledAt || "-"}`,
|
|
2019
|
+
"Открыть? Ответьте `да` или `нет`.",
|
|
2020
|
+
].join("\n"));
|
|
2021
|
+
return;
|
|
2022
|
+
}
|
|
2023
|
+
const intercoms = await ufanetGetIntercoms().catch(() => []);
|
|
2024
|
+
const choices = intercoms.map((item) => ({ id: item.id, name: item.name, address: item.address }));
|
|
2025
|
+
state.pendingAction = choices.length
|
|
2026
|
+
? { type: "ufanet_select_open", choices, createdAt: new Date().toISOString() }
|
|
2027
|
+
: null;
|
|
2028
|
+
printAgentAsyncMessage(state, render, [
|
|
2029
|
+
"Новый вызов домофона Уфанет:",
|
|
2030
|
+
`Адрес: ${row.address || "-"}`,
|
|
2031
|
+
`Время: ${row.calledAt || "-"}`,
|
|
2032
|
+
choices.length ? "Открыть? Выберите домофон номером:" : "Не смог сопоставить вызов с домофоном.",
|
|
2033
|
+
choices.length ? formatUfanetChoicePrompt(choices) : "",
|
|
2034
|
+
].filter(Boolean).join("\n"));
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
function printAgentAsyncMessage(state, render, text) {
|
|
2038
|
+
clearAgentInputArea(state);
|
|
2039
|
+
output.write(`${text}\n`);
|
|
2040
|
+
render();
|
|
2041
|
+
}
|
|
2042
|
+
|
|
1899
2043
|
function colorSlashSelection(row) {
|
|
1900
2044
|
if (!output.isTTY || process.env.NO_COLOR === "1") return row;
|
|
1901
2045
|
return `\x1b[38;5;213m${row}\x1b[0m`;
|
|
@@ -3566,7 +3710,7 @@ async function handleRostelecom() {
|
|
|
3566
3710
|
console.log("Пока доступна заготовка пункта в городских сервисах. API/авторизация будут добавлены после исследования провайдера.");
|
|
3567
3711
|
}
|
|
3568
3712
|
|
|
3569
|
-
async function handleUfanet(args = []) {
|
|
3713
|
+
async function handleUfanet(args = [], agentState = null) {
|
|
3570
3714
|
const [action = process.stdin.isTTY ? "menu" : "status", target, ...rest] = args;
|
|
3571
3715
|
const options = parseOptions(rest);
|
|
3572
3716
|
|
|
@@ -3593,7 +3737,11 @@ async function handleUfanet(args = []) {
|
|
|
3593
3737
|
|
|
3594
3738
|
if (action === "open") {
|
|
3595
3739
|
const intercomId = target || options.id || options.intercom;
|
|
3596
|
-
if (!intercomId)
|
|
3740
|
+
if (!intercomId) {
|
|
3741
|
+
const result = await ufanetOpenSmart({ state: agentState });
|
|
3742
|
+
console.log(formatUfanetSmartOpenResult(result));
|
|
3743
|
+
return;
|
|
3744
|
+
}
|
|
3597
3745
|
const ok = options.yes || options.confirm || await confirm(`Открыть домофон Уфанет #${intercomId}? [y/N] `);
|
|
3598
3746
|
if (!ok) {
|
|
3599
3747
|
console.log("Открытие отменено.");
|
|
@@ -3610,6 +3758,16 @@ async function handleUfanet(args = []) {
|
|
|
3610
3758
|
return;
|
|
3611
3759
|
}
|
|
3612
3760
|
|
|
3761
|
+
if (action === "watch" || action === "listen") {
|
|
3762
|
+
await watchUfanetCalls({ ...options, once: options.once, intervalSeconds: options.seconds || options.interval || target });
|
|
3763
|
+
return;
|
|
3764
|
+
}
|
|
3765
|
+
|
|
3766
|
+
if (action === "notifications" || action === "notify") {
|
|
3767
|
+
await handleUfanetNotifications([target, ...rest].filter(Boolean));
|
|
3768
|
+
return;
|
|
3769
|
+
}
|
|
3770
|
+
|
|
3613
3771
|
if (action === "links" || action === "record" || action === "recording") {
|
|
3614
3772
|
const uuid = target || options.uuid;
|
|
3615
3773
|
if (!uuid) throw new Error("Укажите UUID звонка. Пример: iola ufanet links UUID");
|
|
@@ -3641,6 +3799,8 @@ async function handleUfanet(args = []) {
|
|
|
3641
3799
|
iola ufanet history [--limit 10]
|
|
3642
3800
|
iola ufanet links UUID
|
|
3643
3801
|
iola ufanet cameras
|
|
3802
|
+
iola ufanet watch [--seconds 10]
|
|
3803
|
+
iola ufanet notifications on|off|status
|
|
3644
3804
|
iola ufanet delete`);
|
|
3645
3805
|
}
|
|
3646
3806
|
|
|
@@ -3652,6 +3812,20 @@ async function printUfanetMenu() {
|
|
|
3652
3812
|
{ id: "domru", provider: "Дом.ру", status: "в разработке", command: "iola dom_ru" },
|
|
3653
3813
|
{ id: "rostelecom", provider: "Ростелеком", status: "в разработке", command: "iola rostelecom" },
|
|
3654
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", "Что делает"]]);
|
|
3655
3829
|
}
|
|
3656
3830
|
|
|
3657
3831
|
async function setupUfanetConnector() {
|
|
@@ -3730,6 +3904,8 @@ async function printUfanetStatus(options = {}) {
|
|
|
3730
3904
|
enabled: status.enabled ? "yes" : "no",
|
|
3731
3905
|
contract: status.contract || "-",
|
|
3732
3906
|
source: status.source || "-",
|
|
3907
|
+
notifications: status.notifications,
|
|
3908
|
+
intervalSeconds: status.intervalSeconds,
|
|
3733
3909
|
});
|
|
3734
3910
|
if (options.check) {
|
|
3735
3911
|
if (!status.configured) {
|
|
@@ -3754,13 +3930,139 @@ async function getUfanetStatus() {
|
|
|
3754
3930
|
enabled: Boolean(config.domophones?.providers?.ufanet?.enabled || (config.toolsets?.enabled || []).includes("ufanet")),
|
|
3755
3931
|
contract: contract ? maskSecret(contract, 2) : "",
|
|
3756
3932
|
source: contract && process.env.UFANET_CONTRACT ? "env" : contract ? "local" : "",
|
|
3933
|
+
notifications: config.domophones?.providers?.ufanet?.notifications?.enabled ? "on" : "off",
|
|
3934
|
+
intervalSeconds: config.domophones?.providers?.ufanet?.notifications?.intervalSeconds || 10,
|
|
3757
3935
|
};
|
|
3758
3936
|
}
|
|
3759
3937
|
|
|
3938
|
+
async function handleUfanetNotifications(args = []) {
|
|
3939
|
+
const [action = "status", ...rest] = args;
|
|
3940
|
+
const options = parseOptions(rest);
|
|
3941
|
+
const normalized = String(action || "status").toLocaleLowerCase("ru-RU");
|
|
3942
|
+
if (["on", "enable", "start", "вкл", "включить"].includes(normalized)) {
|
|
3943
|
+
const seconds = Number(options.seconds || options.interval || options.wait || 10);
|
|
3944
|
+
await setUfanetNotifications(true, { intervalSeconds: seconds });
|
|
3945
|
+
console.log(`Уведомления Уфанет включены. Интервал проверки: ${normalizeUfanetPollInterval(seconds)} сек.`);
|
|
3946
|
+
console.log("Чтобы получать события в текущей CLI-сессии, запустите: /ufanet watch");
|
|
3947
|
+
return;
|
|
3948
|
+
}
|
|
3949
|
+
if (["off", "disable", "stop", "выкл", "выключить"].includes(normalized)) {
|
|
3950
|
+
await setUfanetNotifications(false);
|
|
3951
|
+
console.log("Уведомления Уфанет выключены.");
|
|
3952
|
+
return;
|
|
3953
|
+
}
|
|
3954
|
+
const status = await getUfanetStatus();
|
|
3955
|
+
console.log(`Уведомления Уфанет: ${status.notifications}, интервал ${status.intervalSeconds} сек.`);
|
|
3956
|
+
console.log("Команды: /ufanet notifications on, /ufanet notifications off, /ufanet watch");
|
|
3957
|
+
}
|
|
3958
|
+
|
|
3959
|
+
async function setUfanetNotifications(enabled, options = {}) {
|
|
3960
|
+
const config = await loadConfig();
|
|
3961
|
+
const current = config.domophones?.providers?.ufanet || {};
|
|
3962
|
+
const currentNotifications = current.notifications || {};
|
|
3963
|
+
await saveConfig({
|
|
3964
|
+
domophones: {
|
|
3965
|
+
...(config.domophones || {}),
|
|
3966
|
+
providers: {
|
|
3967
|
+
...(config.domophones?.providers || {}),
|
|
3968
|
+
ufanet: {
|
|
3969
|
+
...current,
|
|
3970
|
+
notifications: {
|
|
3971
|
+
...currentNotifications,
|
|
3972
|
+
enabled: Boolean(enabled),
|
|
3973
|
+
intervalSeconds: normalizeUfanetPollInterval(options.intervalSeconds || currentNotifications.intervalSeconds || 10),
|
|
3974
|
+
},
|
|
3975
|
+
},
|
|
3976
|
+
},
|
|
3977
|
+
},
|
|
3978
|
+
});
|
|
3979
|
+
}
|
|
3980
|
+
|
|
3981
|
+
async function updateUfanetLastSeen(lastSeen) {
|
|
3982
|
+
if (!lastSeen) return;
|
|
3983
|
+
const config = await loadConfig();
|
|
3984
|
+
const current = config.domophones?.providers?.ufanet || {};
|
|
3985
|
+
const currentNotifications = current.notifications || {};
|
|
3986
|
+
await saveConfig({
|
|
3987
|
+
domophones: {
|
|
3988
|
+
...(config.domophones || {}),
|
|
3989
|
+
providers: {
|
|
3990
|
+
...(config.domophones?.providers || {}),
|
|
3991
|
+
ufanet: {
|
|
3992
|
+
...current,
|
|
3993
|
+
notifications: {
|
|
3994
|
+
...currentNotifications,
|
|
3995
|
+
lastSeen,
|
|
3996
|
+
},
|
|
3997
|
+
},
|
|
3998
|
+
},
|
|
3999
|
+
},
|
|
4000
|
+
});
|
|
4001
|
+
}
|
|
4002
|
+
|
|
4003
|
+
async function watchUfanetCalls(options = {}) {
|
|
4004
|
+
const config = await loadConfig();
|
|
4005
|
+
const notificationConfig = config.domophones?.providers?.ufanet?.notifications || {};
|
|
4006
|
+
const intervalSeconds = normalizeUfanetPollInterval(options.intervalSeconds || notificationConfig.intervalSeconds || 10);
|
|
4007
|
+
const pageSize = Number(options.limit || options["page-size"] || 10);
|
|
4008
|
+
let lastSeen = notificationConfig.lastSeen || "";
|
|
4009
|
+
const initial = await ufanetGetCallHistory({ page: 1, pageSize });
|
|
4010
|
+
const initialRows = initial.results || [];
|
|
4011
|
+
if (!lastSeen && initialRows[0]) {
|
|
4012
|
+
lastSeen = ufanetCallKey(initialRows[0]);
|
|
4013
|
+
await updateUfanetLastSeen(lastSeen);
|
|
4014
|
+
}
|
|
4015
|
+
if (options.once) {
|
|
4016
|
+
printTable(initialRows, [["uuid", "UUID"], ["calledAt", "Когда"], ["address", "Адрес"], ["porch", "Подъезд"], ["flat", "Кв"]]);
|
|
4017
|
+
return;
|
|
4018
|
+
}
|
|
4019
|
+
console.log(`Уфанет: слежу за новыми вызовами каждые ${intervalSeconds} сек. Остановить: Ctrl+C.`);
|
|
4020
|
+
while (true) {
|
|
4021
|
+
await sleep(intervalSeconds * 1000);
|
|
4022
|
+
const history = await ufanetGetCallHistory({ page: 1, pageSize }).catch((error) => {
|
|
4023
|
+
console.error(`Уфанет: ошибка проверки вызовов: ${error instanceof Error ? error.message : String(error)}`);
|
|
4024
|
+
return { results: [] };
|
|
4025
|
+
});
|
|
4026
|
+
const rows = history.results || [];
|
|
4027
|
+
const fresh = [];
|
|
4028
|
+
for (const row of rows) {
|
|
4029
|
+
const key = ufanetCallKey(row);
|
|
4030
|
+
if (!key || key === lastSeen) break;
|
|
4031
|
+
fresh.push(row);
|
|
4032
|
+
}
|
|
4033
|
+
if (fresh.length === 0) continue;
|
|
4034
|
+
for (const row of fresh.reverse()) {
|
|
4035
|
+
console.log("");
|
|
4036
|
+
console.log("Новый вызов домофона Уфанет:");
|
|
4037
|
+
printKeyValue({
|
|
4038
|
+
uuid: row.uuid || "-",
|
|
4039
|
+
calledAt: row.calledAt || "-",
|
|
4040
|
+
address: row.address || "-",
|
|
4041
|
+
porch: row.porch || "-",
|
|
4042
|
+
flat: row.flat || "-",
|
|
4043
|
+
});
|
|
4044
|
+
}
|
|
4045
|
+
lastSeen = ufanetCallKey(rows[0]) || lastSeen;
|
|
4046
|
+
await updateUfanetLastSeen(lastSeen);
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
|
|
4050
|
+
function normalizeUfanetPollInterval(value) {
|
|
4051
|
+
const seconds = Number(value || 10);
|
|
4052
|
+
if (!Number.isFinite(seconds)) return 10;
|
|
4053
|
+
return Math.max(5, Math.min(300, Math.round(seconds)));
|
|
4054
|
+
}
|
|
4055
|
+
|
|
4056
|
+
function ufanetCallKey(row = {}) {
|
|
4057
|
+
return String(row.uuid || `${row.calledAt || ""}|${row.address || ""}|${row.porch || ""}|${row.flat || ""}`);
|
|
4058
|
+
}
|
|
4059
|
+
|
|
3760
4060
|
async function executeUfanetTool(tool, args = {}) {
|
|
3761
4061
|
if (tool === "ufanet_status") return getUfanetStatus();
|
|
3762
4062
|
if (tool === "ufanet_intercoms") return ufanetGetIntercoms();
|
|
3763
|
-
if (tool === "ufanet_open_intercom") return
|
|
4063
|
+
if (tool === "ufanet_open_intercom") return args.id || args.intercomId || args.intercom_id
|
|
4064
|
+
? ufanetOpenIntercom(args.id || args.intercomId || args.intercom_id, args)
|
|
4065
|
+
: ufanetOpenSmart({ ...args, state: args.state });
|
|
3764
4066
|
if (tool === "ufanet_call_history") return ufanetGetCallHistory({ page: args.page || 1, pageSize: args.pageSize || args.page_size || args.limit || 10 });
|
|
3765
4067
|
if (tool === "ufanet_call_links") return ufanetGetCallLinks(args.uuid || args.id);
|
|
3766
4068
|
if (tool === "ufanet_cameras") return ufanetGetCameras();
|
|
@@ -3854,6 +4156,59 @@ async function ufanetOpenIntercom(intercomId, options = {}) {
|
|
|
3854
4156
|
return { provider: "ufanet", status: payload?.result ? "opened" : "not-opened", id: Number(intercomId), result: Boolean(payload?.result) };
|
|
3855
4157
|
}
|
|
3856
4158
|
|
|
4159
|
+
async function ufanetOpenSmart(options = {}) {
|
|
4160
|
+
const intercomId = options.id || options.intercomId || options.intercom_id;
|
|
4161
|
+
if (intercomId) {
|
|
4162
|
+
const result = await ufanetOpenIntercom(intercomId, { confirm: true });
|
|
4163
|
+
return { type: "opened", result, choice: { id: intercomId } };
|
|
4164
|
+
}
|
|
4165
|
+
const intercoms = await ufanetGetIntercoms();
|
|
4166
|
+
if (intercoms.length === 0) return { type: "empty" };
|
|
4167
|
+
const choices = intercoms.map((item) => ({ id: item.id, name: item.name, address: item.address }));
|
|
4168
|
+
if (intercoms.length === 1) {
|
|
4169
|
+
const result = await ufanetOpenIntercom(intercoms[0].id, { confirm: true });
|
|
4170
|
+
return { type: "opened", result, choice: choices[0] };
|
|
4171
|
+
}
|
|
4172
|
+
if (options.state) {
|
|
4173
|
+
options.state.pendingAction = { type: "ufanet_select_open", choices, createdAt: new Date().toISOString() };
|
|
4174
|
+
}
|
|
4175
|
+
return { type: "select", choices };
|
|
4176
|
+
}
|
|
4177
|
+
|
|
4178
|
+
function formatUfanetSmartOpenResult(value) {
|
|
4179
|
+
if (value.type === "empty") return "Уфанет подключен, но доступных домофонов не найдено.";
|
|
4180
|
+
if (value.type === "select") return formatUfanetChoicePrompt(value.choices);
|
|
4181
|
+
if (value.type === "opened") return formatUfanetOpenResult(value.result, value.choice);
|
|
4182
|
+
return "Не удалось выполнить действие с домофоном.";
|
|
4183
|
+
}
|
|
4184
|
+
|
|
4185
|
+
function formatUfanetChoicePrompt(choices = []) {
|
|
4186
|
+
return [
|
|
4187
|
+
"Найдено несколько домофонов. Какой открыть? Ответьте цифрой:",
|
|
4188
|
+
...choices.map((item, index) => `${index + 1}. ${item.address || item.name || `Домофон #${item.id}`} — ID ${item.id}`),
|
|
4189
|
+
].join("\n");
|
|
4190
|
+
}
|
|
4191
|
+
|
|
4192
|
+
function formatUfanetOpenResult(result, choice = {}) {
|
|
4193
|
+
const label = choice.address || choice.name || `ID ${result.id}`;
|
|
4194
|
+
return result.result
|
|
4195
|
+
? `Домофон открыт: ${label}.`
|
|
4196
|
+
: `Уфанет не подтвердил открытие домофона: ${label}.`;
|
|
4197
|
+
}
|
|
4198
|
+
|
|
4199
|
+
async function resolveUfanetIntercomForCall(row = {}) {
|
|
4200
|
+
const address = normalizeGeoText(row.address || "");
|
|
4201
|
+
if (!address) return null;
|
|
4202
|
+
const intercoms = await ufanetGetIntercoms();
|
|
4203
|
+
const matches = intercoms.filter((item) => {
|
|
4204
|
+
const itemAddress = normalizeGeoText(item.address || item.name || "");
|
|
4205
|
+
return itemAddress && (itemAddress.includes(address) || address.includes(itemAddress));
|
|
4206
|
+
});
|
|
4207
|
+
if (matches.length === 1) return matches[0];
|
|
4208
|
+
if (matches.length > 1) return null;
|
|
4209
|
+
return intercoms.length === 1 ? intercoms[0] : null;
|
|
4210
|
+
}
|
|
4211
|
+
|
|
3857
4212
|
async function ufanetGetCallHistory(options = {}) {
|
|
3858
4213
|
const page = Math.max(1, Number(options.page || 1));
|
|
3859
4214
|
const pageSize = Math.max(1, Math.min(100, Number(options.pageSize || 10)));
|
|
@@ -12758,6 +13113,7 @@ async function setupIolaLocal(args) {
|
|
|
12758
13113
|
|
|
12759
13114
|
async function aiAsk(args, context = {}) {
|
|
12760
13115
|
const options = parseOptions(args);
|
|
13116
|
+
if (context.state) options.state = context.state;
|
|
12761
13117
|
const question = options._.join(" ").trim();
|
|
12762
13118
|
|
|
12763
13119
|
if (!question) {
|
|
@@ -12807,6 +13163,20 @@ async function aiAsk(args, context = {}) {
|
|
|
12807
13163
|
if (!options.quiet) console.log(userSkillAnswer);
|
|
12808
13164
|
return userSkillAnswer;
|
|
12809
13165
|
}
|
|
13166
|
+
const ufanetAnswer = await buildUfanetDirectAnswer(question, context);
|
|
13167
|
+
if (ufanetAnswer) {
|
|
13168
|
+
if (historyEnabled) {
|
|
13169
|
+
recordAskHistory({ question, answer: ufanetAnswer, providerConfig, dataContext, error: "", sessionId });
|
|
13170
|
+
appendSessionExchange(sessionId, question, ufanetAnswer, dataContext, "");
|
|
13171
|
+
}
|
|
13172
|
+
emitEvent(options, "answer", { length: ufanetAnswer.length, sessionId, direct: true, ufanet: true });
|
|
13173
|
+
if (options.output) {
|
|
13174
|
+
await assertPermission("writeFiles");
|
|
13175
|
+
await writeFile(options.output, ufanetAnswer, "utf8");
|
|
13176
|
+
}
|
|
13177
|
+
if (!options.quiet) console.log(ufanetAnswer);
|
|
13178
|
+
return ufanetAnswer;
|
|
13179
|
+
}
|
|
12810
13180
|
if (/(контакт|адресн)/iu.test(question) && !isExplicitYandexDiskPathDelete(question)) {
|
|
12811
13181
|
const yandexContactAnswer = await buildYandexDirectAnswer(question, context.history || history);
|
|
12812
13182
|
if (yandexContactAnswer) {
|
|
@@ -14404,6 +14774,48 @@ function cleanupCloudSaveText(question) {
|
|
|
14404
14774
|
.trim();
|
|
14405
14775
|
}
|
|
14406
14776
|
|
|
14777
|
+
async function buildUfanetDirectAnswer(question, context = {}) {
|
|
14778
|
+
const normalized = String(question || "").toLocaleLowerCase("ru-RU");
|
|
14779
|
+
if (!/(домофон|уфанет|ufanet|дверь|подъезд)/iu.test(normalized)) return "";
|
|
14780
|
+
if (/(дом\.?ру|dom\.?ru)/iu.test(normalized)) return "Мой домофон Дом.ру пока в разработке. Сейчас реализован Уфанет: /ufanet.";
|
|
14781
|
+
if (/(ростелеком|rostelecom)/iu.test(normalized)) return "Мой домофон Ростелеком пока в разработке. Сейчас реализован Уфанет: /ufanet.";
|
|
14782
|
+
if (/(уведом|оповещ|сообщ).{0,40}(включ|получ|on|вкл)/iu.test(normalized) || /(включ|получ).{0,40}(уведом|оповещ|сообщ).{0,40}(домофон)/iu.test(normalized)) {
|
|
14783
|
+
await setUfanetNotifications(true, { intervalSeconds: extractSecondsFromText(question) || 10 });
|
|
14784
|
+
return "Уведомления Уфанет включены. Когда в CLI придет новый вызов, можно ответить `да`, чтобы открыть, или `нет`, чтобы отменить.";
|
|
14785
|
+
}
|
|
14786
|
+
if (/(уведом|оповещ|сообщ).{0,40}(выключ|отключ|off|выкл)/iu.test(normalized) || /(выключ|отключ).{0,40}(уведом|оповещ|сообщ).{0,40}(домофон)/iu.test(normalized)) {
|
|
14787
|
+
await setUfanetNotifications(false);
|
|
14788
|
+
return "Уведомления Уфанет выключены.";
|
|
14789
|
+
}
|
|
14790
|
+
if (/(статус|подключ|аккаунт|договор)/iu.test(normalized)) {
|
|
14791
|
+
const status = await getUfanetStatus();
|
|
14792
|
+
return `Уфанет: ${status.configured ? "настроен" : "не настроен"}, уведомления ${status.notifications}.`;
|
|
14793
|
+
}
|
|
14794
|
+
if (/(открой|открыть|открывай|пусти|впусти|двер)/iu.test(normalized)) {
|
|
14795
|
+
const id = extractUfanetIntercomId(question);
|
|
14796
|
+
const result = await ufanetOpenSmart({ id, state: context.state });
|
|
14797
|
+
return formatUfanetSmartOpenResult(result);
|
|
14798
|
+
}
|
|
14799
|
+
if (/(истори|звонк|кто звонил|последн)/iu.test(normalized)) {
|
|
14800
|
+
const rows = await ufanetGetCallHistory({ page: 1, pageSize: 10 });
|
|
14801
|
+
if (!rows.results?.length) return "В истории Уфанет звонков не найдено.";
|
|
14802
|
+
return ["История звонков Уфанет:", ...rows.results.map((row, index) => `${index + 1}. ${row.calledAt || "-"} — ${row.address || "-"}${row.uuid ? `, UUID ${row.uuid}` : ""}`)].join("\n");
|
|
14803
|
+
}
|
|
14804
|
+
if (/(камер|rtsp|видео)/iu.test(normalized)) {
|
|
14805
|
+
const rows = await ufanetGetCameras();
|
|
14806
|
+
if (!rows.length) return "Камеры Уфанет не найдены.";
|
|
14807
|
+
return ["Камеры Уфанет:", ...rows.slice(0, 10).map((row, index) => `${index + 1}. ${row.title || row.number || "камера"} — ${row.address || "-"}${row.rtspUrl ? `, ${row.rtspUrl}` : ""}`)].join("\n");
|
|
14808
|
+
}
|
|
14809
|
+
const intercoms = await ufanetGetIntercoms();
|
|
14810
|
+
if (!intercoms.length) return "Доступные домофоны Уфанет не найдены.";
|
|
14811
|
+
return ["Доступные домофоны Уфанет:", ...intercoms.map((item, index) => `${index + 1}. ${item.address || item.name || `Домофон #${item.id}`} — ID ${item.id}`)].join("\n");
|
|
14812
|
+
}
|
|
14813
|
+
|
|
14814
|
+
function extractSecondsFromText(text) {
|
|
14815
|
+
const match = String(text || "").match(/(\d{1,3})\s*(?:сек|seconds|s)\b/iu);
|
|
14816
|
+
return match ? Number(match[1]) : 0;
|
|
14817
|
+
}
|
|
14818
|
+
|
|
14407
14819
|
function detectDirectDataFields(normalizedQuestion) {
|
|
14408
14820
|
const fields = [];
|
|
14409
14821
|
if (/(директ|руководител|заведующ|кто возглавляет)/iu.test(normalizedQuestion)) fields.push("head");
|
|
@@ -15022,8 +15434,7 @@ function inferToolPlan(question, options = {}) {
|
|
|
15022
15434
|
if (/(ростелеком|rostelecom)/iu.test(normalized)) return { directAnswer: "Мой домофон Ростелеком пока в разработке. Сейчас реализован Уфанет: /ufanet." };
|
|
15023
15435
|
if (/(открой|открыть|пусти|впусти|двер)/iu.test(normalized)) {
|
|
15024
15436
|
const id = extractUfanetIntercomId(question);
|
|
15025
|
-
|
|
15026
|
-
return { steps: [{ tool: "ufanet_open_intercom", args: { id, confirm: true } }] };
|
|
15437
|
+
return { steps: [{ tool: "ufanet_open_intercom", args: { ...(id ? { id } : {}), confirm: true } }] };
|
|
15027
15438
|
}
|
|
15028
15439
|
if (/(истори|звонк|кто\s+звонил|последн)/iu.test(normalized) && !/(ссылк|запис|видео)/iu.test(normalized)) {
|
|
15029
15440
|
return { steps: [{ tool: "ufanet_call_history", args: { limit: 10 } }] };
|
|
@@ -15530,7 +15941,7 @@ async function executeToolPlan(plan, options = {}) {
|
|
|
15530
15941
|
outputs.push({ tool: step.tool, rows: current.length });
|
|
15531
15942
|
} else if (UFANET_TOOLS.includes(step.tool)) {
|
|
15532
15943
|
await assertPermission("externalApi");
|
|
15533
|
-
const result = await executeUfanetTool(step.tool, step.args || {});
|
|
15944
|
+
const result = await executeUfanetTool(step.tool, { ...(step.args || {}), state: options.state });
|
|
15534
15945
|
current = Array.isArray(result) ? result : [result];
|
|
15535
15946
|
outputs.push({ tool: step.tool, rows: current.length });
|
|
15536
15947
|
} else if (USER_SKILL_TOOLS.includes(step.tool)) {
|
|
@@ -15688,6 +16099,9 @@ function formatToolResult(result, options) {
|
|
|
15688
16099
|
return `${name}: ${row.field} = ${row.value ?? "не указано"}`;
|
|
15689
16100
|
}
|
|
15690
16101
|
if (row.date && row.time) return `Сегодня ${row.date}, ${row.time}.`;
|
|
16102
|
+
if (row.type === "select" && Array.isArray(row.choices)) return formatUfanetChoicePrompt(row.choices);
|
|
16103
|
+
if (row.type === "empty") return "Уфанет подключен, но доступных домофонов не найдено.";
|
|
16104
|
+
if (row.type === "opened" && row.result) return formatUfanetOpenResult(row.result, row.choice || {});
|
|
15691
16105
|
if (row.provider === "ufanet" && (row.status === "opened" || row.status === "not-opened")) return `Уфанет: домофон #${row.id} ${row.status === "opened" ? "открыт" : "не открылся"}.`;
|
|
15692
16106
|
if (row.provider === "ufanet" && row.uuid && (row.url || row.preview)) return `Уфанет: запись звонка ${row.uuid}\nСсылка: ${row.url || "-"}\nПревью: ${row.preview || "-"}`;
|
|
15693
16107
|
if (row.rtspUrl) return `Камера Уфанет ${row.title || row.number}: ${row.address || "-"}\nRTSP: ${row.rtspUrl}`;
|
|
@@ -17316,7 +17730,7 @@ function parseOptions(args) {
|
|
|
17316
17730
|
} else if (arg === "--check" || arg === "--upgrade-node") {
|
|
17317
17731
|
result.check = true;
|
|
17318
17732
|
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") {
|
|
17733
|
+
} 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
17734
|
result[arg.slice(2)] = args[index + 1];
|
|
17321
17735
|
index += 1;
|
|
17322
17736
|
} 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 хранятся локально и не выводятся в ответах.
|
|
@@ -94,10 +94,14 @@ iola yandex token delete
|
|
|
94
94
|
iola ufanet setup
|
|
95
95
|
iola ufanet status
|
|
96
96
|
iola ufanet intercoms
|
|
97
|
+
iola ufanet open
|
|
97
98
|
iola ufanet open ID
|
|
98
99
|
iola ufanet history --limit 10
|
|
99
100
|
iola ufanet links UUID
|
|
100
101
|
iola ufanet cameras
|
|
102
|
+
iola ufanet watch --seconds 10
|
|
103
|
+
iola ufanet notifications on
|
|
104
|
+
iola ufanet notifications off
|
|
101
105
|
iola ufanet delete
|
|
102
106
|
iola dom_ru
|
|
103
107
|
iola rostelecom
|
package/wiki//320/234/320/276/320/271-/320/264/320/276/320/274/320/276/321/204/320/276/320/275.md
CHANGED
|
@@ -13,7 +13,12 @@ iola ufanet intercoms
|
|
|
13
13
|
iola ufanet history
|
|
14
14
|
iola ufanet cameras
|
|
15
15
|
iola ufanet links UUID
|
|
16
|
+
iola ufanet open
|
|
16
17
|
iola ufanet open ID
|
|
18
|
+
iola ufanet watch
|
|
19
|
+
iola ufanet notifications on
|
|
20
|
+
iola ufanet notifications off
|
|
21
|
+
iola ufanet notifications status
|
|
17
22
|
iola ufanet delete
|
|
18
23
|
```
|
|
19
24
|
|
|
@@ -38,14 +43,26 @@ UFANET_PASSWORD
|
|
|
38
43
|
Доступные сценарии:
|
|
39
44
|
|
|
40
45
|
- показать доступные домофоны;
|
|
46
|
+
- открыть единственный домофон простой фразой `открой домофон`;
|
|
47
|
+
- если домофонов несколько, показать адреса с цифрами и открыть выбранный номер;
|
|
41
48
|
- открыть домофон по ID;
|
|
42
49
|
- показать историю звонков;
|
|
43
50
|
- получить ссылку на запись звонка по UUID;
|
|
44
51
|
- показать камеры;
|
|
45
52
|
- показать RTSP-ссылки камер;
|
|
53
|
+
- получать уведомления о новых вызовах, пока CLI открыт;
|
|
54
|
+
- включить или выключить настройку уведомлений;
|
|
46
55
|
- удалить локальное подключение.
|
|
47
56
|
|
|
48
|
-
Открытие двери всегда требует явного
|
|
57
|
+
Открытие двери всегда требует явного действия пользователя. Если домофон один, фраза `открой домофон` считается таким действием. Если домофонов несколько, CLI выводит адреса с цифрами, а выбранная цифра считается подтверждением.
|
|
58
|
+
|
|
59
|
+
Уведомления сейчас работают через периодическую проверку истории звонков Уфанета:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
iola ufanet watch --seconds 10
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Остановка режима наблюдения: `Ctrl+C`. Команда `notifications on` сохраняет настройку. В обычном интерактивном CLI при включенных уведомлениях новый вызов выводится с вопросом `Открыть?`; ответы `да`, `да открой`, `открой`, `ок` открывают сопоставленный домофон, `нет` или `отмена` отменяют.
|
|
49
66
|
|
|
50
67
|
## Дом.ру
|
|
51
68
|
|