@iola_adm/iola-cli 0.2.45 → 0.2.46
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 +18 -0
- package/package.json +1 -1
- package/skills/ufanet-intercom/SKILL.md +41 -0
- package/src/cli.js +466 -23
- package/wiki/Home.md +2 -0
- package/wiki/Skills-/320/270-toolsets.md +14 -0
- package/wiki/Yandex-Cloud-Connector.md +1 -1
- package/wiki//320/232/320/276/320/274/320/260/320/275/320/264/321/213.md +15 -0
- package/wiki//320/234/320/260/321/201/321/202/320/265/321/200-/320/275/320/260/321/201/321/202/321/200/320/276/320/271/320/272/320/270.md +35 -16
- package/wiki//320/234/320/276/320/271-/320/264/320/276/320/274/320/276/321/204/320/276/320/275.md +68 -0
- 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 +37 -0
package/README.md
CHANGED
|
@@ -207,6 +207,22 @@ Yandex tools уже доступны: профиль Yandex ID, расширен
|
|
|
207
207
|
|
|
208
208
|
Инструкция: [Yandex Connector](https://github.com/adm-iola/iola-cli/wiki/Yandex-Connector).
|
|
209
209
|
|
|
210
|
+
Мой домофон:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
iola ufanet setup
|
|
214
|
+
iola ufanet intercoms
|
|
215
|
+
iola ufanet open ID
|
|
216
|
+
iola ufanet history
|
|
217
|
+
iola ufanet cameras
|
|
218
|
+
iola dom_ru
|
|
219
|
+
iola rostelecom
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Уфанет поддерживается как рабочий провайдер: список домофонов, открытие двери после подтверждения, история звонков, записи звонков и камеры/RTSP. Дом.ру и Ростелеком добавлены как видимые направления `в разработке`.
|
|
223
|
+
|
|
224
|
+
Инструкция: [Мой домофон](https://github.com/adm-iola/iola-cli/wiki/Мой-домофон).
|
|
225
|
+
|
|
210
226
|
Зарубежные API-ключи:
|
|
211
227
|
|
|
212
228
|
- OpenAI Platform: регистрация `https://platform.openai.com/`, ключи `https://platform.openai.com/api-keys`;
|
|
@@ -236,6 +252,7 @@ iola version --check
|
|
|
236
252
|
- [Yandex Geocoder API key](https://github.com/adm-iola/iola-cli/wiki/Yandex-Geocoder-API-key)
|
|
237
253
|
- [Yandex Cloud Connector](https://github.com/adm-iola/iola-cli/wiki/Yandex-Cloud-Connector)
|
|
238
254
|
- [Yandex Connector](https://github.com/adm-iola/iola-cli/wiki/Yandex-Connector)
|
|
255
|
+
- [Мой домофон](https://github.com/adm-iola/iola-cli/wiki/Мой-домофон)
|
|
239
256
|
- [Облачные диски](https://github.com/adm-iola/iola-cli/wiki/Облачные-диски)
|
|
240
257
|
- [Скиллы для жителей](https://github.com/adm-iola/iola-cli/wiki/Скиллы-для-жителей)
|
|
241
258
|
- [Локальный инструментальный агент](https://github.com/adm-iola/iola-cli/wiki/Локальный-инструментальный-агент)
|
|
@@ -259,6 +276,7 @@ iola version --check
|
|
|
259
276
|
- AI-профили для IOLA local, Ollama, YandexGPT, GigaChat, OpenAI, OpenRouter и Codex CLI;
|
|
260
277
|
- Yandex Connector: единая точка подключения пользовательских сервисов Яндекса с локальным хранением OAuth-токенов;
|
|
261
278
|
- Yandex Cloud Connector: геокодер, YandexGPT и deeplink маршрута Яндекс Go;
|
|
279
|
+
- Мой домофон Уфанет: домофоны, история звонков, записи, камеры и открытие двери после подтверждения;
|
|
262
280
|
- локальный tool-agent для модели IOLA с tools открытых данных, файлов, браузера и сервисов Яндекса;
|
|
263
281
|
- ленивые skills, toolsets, permissions, memory, hooks и готовые agents;
|
|
264
282
|
- личные облачные диски: Яндекс Диск и Облако Mail.ru для сохранения отчетов, backup и документов;
|
package/package.json
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ufanet-intercom
|
|
3
|
+
description: Мой домофон Уфанет: список домофонов, открытие двери после подтверждения, история звонков, записи звонков, камеры и RTSP.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Используй этот skill, когда пользователь явно просит работать с домофоном Уфанет: открыть домофон, показать доступные домофоны, посмотреть историю звонков, получить запись звонка, показать камеры или RTSP.
|
|
7
|
+
|
|
8
|
+
Не смешивай Уфанет с Яндекс-сервисами, городскими открытыми слоями и локальными файлами. Это отдельный личный сервис пользователя.
|
|
9
|
+
|
|
10
|
+
Доступные tools:
|
|
11
|
+
|
|
12
|
+
- `ufanet_status` - проверить, подключен ли Уфанет.
|
|
13
|
+
- `ufanet_intercoms` - показать доступные домофоны пользователя.
|
|
14
|
+
- `ufanet_open_intercom` - открыть домофон по ID.
|
|
15
|
+
- `ufanet_call_history` - показать историю звонков домофона.
|
|
16
|
+
- `ufanet_call_links` - получить ссылку на запись/превью звонка по UUID.
|
|
17
|
+
- `ufanet_cameras` - показать доступные камеры и RTSP-ссылки.
|
|
18
|
+
|
|
19
|
+
Команды CLI:
|
|
20
|
+
|
|
21
|
+
- `iola ufanet setup` - подключить Уфанет.
|
|
22
|
+
- `iola ufanet status` - проверить подключение.
|
|
23
|
+
- `iola ufanet intercoms` - список домофонов.
|
|
24
|
+
- `iola ufanet open ID` - открыть домофон после подтверждения.
|
|
25
|
+
- `iola ufanet history` - история звонков.
|
|
26
|
+
- `iola ufanet links UUID` - ссылка на запись звонка.
|
|
27
|
+
- `iola ufanet cameras` - камеры.
|
|
28
|
+
- `iola ufanet delete` - удалить локальное подключение.
|
|
29
|
+
|
|
30
|
+
Безопасность:
|
|
31
|
+
|
|
32
|
+
- Для `ufanet_open_intercom` всегда нужен явный запрос пользователя и `confirm=true`.
|
|
33
|
+
- Если пользователь просит "открой домофон", но ID домофона неизвестен, сначала покажи список домофонов или попроси выбрать ID.
|
|
34
|
+
- Не открывай домофон по косвенному намерению вроде "кто там", "звонят", "посмотри".
|
|
35
|
+
- Не выводи договор, пароль, JWT-токен и другие секреты.
|
|
36
|
+
- Если Уфанет не подключен, скажи запустить `/master` и выбрать `Мой домофон Уфанет`, либо команду `iola ufanet setup`.
|
|
37
|
+
|
|
38
|
+
Дом.ру и Ростелеком:
|
|
39
|
+
|
|
40
|
+
- `/dom_ru` и `/rostelecom` пока являются заготовками "в разработке".
|
|
41
|
+
- Не обещай работу этих провайдеров, пока их API не реализован.
|
package/src/cli.js
CHANGED
|
@@ -152,6 +152,7 @@ const LOCAL_TOOLS = ["search_data", "search_entities", "resolve_entity_field", "
|
|
|
152
152
|
const LEGACY_LOCAL_TOOLS = ["search_local", "export_data", "run_report", "save_view"];
|
|
153
153
|
const FILE_TOOLS = ["files_tree", "files_read", "files_search", "files_write", "files_patch"];
|
|
154
154
|
const USER_SKILL_TOOLS = ["user_skill_create", "user_skill_update", "user_skill_enable", "user_skill_disable", "user_skill_delete", "user_skill_list", "user_skill_templates", "user_skill_validate", "user_skill_preview"];
|
|
155
|
+
const UFANET_TOOLS = ["ufanet_status", "ufanet_intercoms", "ufanet_open_intercom", "ufanet_call_history", "ufanet_call_links", "ufanet_cameras"];
|
|
155
156
|
const YANDEX_TOOLS = [
|
|
156
157
|
"yandex_identity_me",
|
|
157
158
|
"yandex_disk_info",
|
|
@@ -251,7 +252,7 @@ const YANDEX_TOOLS = [
|
|
|
251
252
|
"yandex_cloud_status",
|
|
252
253
|
"yandex_go_deeplink",
|
|
253
254
|
];
|
|
254
|
-
const ALL_LOCAL_TOOLS = [...LOCAL_TOOLS, ...FILE_TOOLS, ...YANDEX_TOOLS, ...USER_SKILL_TOOLS];
|
|
255
|
+
const ALL_LOCAL_TOOLS = [...LOCAL_TOOLS, ...FILE_TOOLS, ...YANDEX_TOOLS, ...UFANET_TOOLS, ...USER_SKILL_TOOLS];
|
|
255
256
|
const ALL_TOOL_ALIASES = [...ALL_LOCAL_TOOLS, ...LEGACY_LOCAL_TOOLS];
|
|
256
257
|
const HOOK_EVENTS = ["SessionStart", "BeforeTool", "AfterTool", "PreToolUse", "PostToolUse", "OnError", "AfterSync", "BeforeExport", "SessionEnd"];
|
|
257
258
|
const DAEMON_PORT = Number(process.env.IOLA_DAEMON_PORT || 18790);
|
|
@@ -283,6 +284,13 @@ const TOOLSETS = {
|
|
|
283
284
|
localTools: Object.fromEntries(YANDEX_TOOLS.map((tool) => [tool, true])),
|
|
284
285
|
},
|
|
285
286
|
},
|
|
287
|
+
ufanet: {
|
|
288
|
+
description: "Мой домофон Уфанет: домофоны, история звонков, камеры и открытие двери после подтверждения.",
|
|
289
|
+
permissions: {
|
|
290
|
+
externalApi: true,
|
|
291
|
+
localTools: Object.fromEntries(UFANET_TOOLS.map((tool) => [tool, true])),
|
|
292
|
+
},
|
|
293
|
+
},
|
|
286
294
|
"local-files-read": {
|
|
287
295
|
description: "Чтение файлов, дерево папок и поиск внутри workspace.",
|
|
288
296
|
permissions: { readFiles: true, localTools: { files_tree: true, files_read: true, files_search: true } },
|
|
@@ -436,6 +444,12 @@ const DEFAULT_AI_CONFIG = {
|
|
|
436
444
|
yandex_mail_search: true,
|
|
437
445
|
yandex_mail_read: true,
|
|
438
446
|
yandex_mail_send: false,
|
|
447
|
+
ufanet_status: true,
|
|
448
|
+
ufanet_intercoms: true,
|
|
449
|
+
ufanet_open_intercom: false,
|
|
450
|
+
ufanet_call_history: true,
|
|
451
|
+
ufanet_call_links: true,
|
|
452
|
+
ufanet_cameras: true,
|
|
439
453
|
yandex_calendar_status: true,
|
|
440
454
|
yandex_calendar_create_event: false,
|
|
441
455
|
yandex_calendar_list: true,
|
|
@@ -475,6 +489,14 @@ const DEFAULT_AI_CONFIG = {
|
|
|
475
489
|
skills: {
|
|
476
490
|
enabled: ["education", "open-data", "geo", "personal-docs", "reports", "local-model", "local-files", "browser-agent", "yandex-services", "user-skills"],
|
|
477
491
|
},
|
|
492
|
+
domophones: {
|
|
493
|
+
activeProvider: "",
|
|
494
|
+
providers: {
|
|
495
|
+
ufanet: { enabled: false },
|
|
496
|
+
domru: { enabled: false, status: "backlog" },
|
|
497
|
+
rostelecom: { enabled: false, status: "backlog" },
|
|
498
|
+
},
|
|
499
|
+
},
|
|
478
500
|
cloud: {
|
|
479
501
|
activeProvider: "",
|
|
480
502
|
providers: {
|
|
@@ -577,6 +599,9 @@ const SLASH_COMMANDS = [
|
|
|
577
599
|
{ command: "/files status", description: "локальные файловые операции" },
|
|
578
600
|
{ command: "/cloud status", description: "облачные диски" },
|
|
579
601
|
{ command: "/yandex", description: "выбор сервисов Yandex Connector" },
|
|
602
|
+
{ command: "/ufanet", description: "Мой домофон Уфанет" },
|
|
603
|
+
{ command: "/dom_ru", description: "Мой домофон Дом.ру (в разработке)" },
|
|
604
|
+
{ command: "/rostelecom", description: "Мой домофон Ростелеком (в разработке)" },
|
|
580
605
|
{ command: "/archive doctor", description: "архиватор" },
|
|
581
606
|
{ command: "/changes list", description: "подготовленные изменения" },
|
|
582
607
|
{ command: "/index status", description: "индекс документов" },
|
|
@@ -651,6 +676,10 @@ const COMMANDS = new Map([
|
|
|
651
676
|
["files", handleFiles],
|
|
652
677
|
["cloud", handleCloud],
|
|
653
678
|
["yandex", handleYandex],
|
|
679
|
+
["ufanet", handleUfanet],
|
|
680
|
+
["dom_ru", handleDomRu],
|
|
681
|
+
["domru", handleDomRu],
|
|
682
|
+
["rostelecom", handleRostelecom],
|
|
654
683
|
["archive", handleArchive],
|
|
655
684
|
["changes", handleChanges],
|
|
656
685
|
["import", handleImport],
|
|
@@ -801,6 +830,7 @@ async function showHelp() {
|
|
|
801
830
|
iola browser status браузерный runtime
|
|
802
831
|
iola cloud status облачные диски
|
|
803
832
|
iola yandex status Yandex Connector
|
|
833
|
+
iola ufanet status Мой домофон Уфанет
|
|
804
834
|
iola mcp status MCP-подключение
|
|
805
835
|
iola doctor диагностика
|
|
806
836
|
iola wiki документация
|
|
@@ -842,6 +872,9 @@ Usage:
|
|
|
842
872
|
iola files status|mode|approvals|tree|read|search|write|patch
|
|
843
873
|
iola cloud setup|status|ls|find|upload|download|share|save|backup
|
|
844
874
|
iola yandex setup|menu|status|services|enable|disable|oauth-url|token
|
|
875
|
+
iola ufanet setup|status|intercoms|open|history|links|cameras|delete
|
|
876
|
+
iola dom_ru Мой домофон Дом.ру (в разработке)
|
|
877
|
+
iola rostelecom Мой домофон Ростелеком (в разработке)
|
|
845
878
|
iola archive doctor|list|test|extract|create|index
|
|
846
879
|
iola changes list|show|apply|discard
|
|
847
880
|
iola import file|folder
|
|
@@ -3523,6 +3556,344 @@ async function handleYandex(args) {
|
|
|
3523
3556
|
iola yandex backlog`);
|
|
3524
3557
|
}
|
|
3525
3558
|
|
|
3559
|
+
async function handleDomRu() {
|
|
3560
|
+
console.log("Мой домофон Дом.ру: в разработке.");
|
|
3561
|
+
console.log("Пока доступна заготовка пункта в городских сервисах. API/авторизация будут добавлены после исследования провайдера.");
|
|
3562
|
+
}
|
|
3563
|
+
|
|
3564
|
+
async function handleRostelecom() {
|
|
3565
|
+
console.log("Мой домофон Ростелеком: в разработке.");
|
|
3566
|
+
console.log("Пока доступна заготовка пункта в городских сервисах. API/авторизация будут добавлены после исследования провайдера.");
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3569
|
+
async function handleUfanet(args = []) {
|
|
3570
|
+
const [action = process.stdin.isTTY ? "menu" : "status", target, ...rest] = args;
|
|
3571
|
+
const options = parseOptions(rest);
|
|
3572
|
+
|
|
3573
|
+
if (action === "menu" || action === "choose") {
|
|
3574
|
+
await printUfanetMenu();
|
|
3575
|
+
return;
|
|
3576
|
+
}
|
|
3577
|
+
|
|
3578
|
+
if (action === "setup" || action === "connect" || action === "onboard") {
|
|
3579
|
+
await setupUfanetConnector();
|
|
3580
|
+
return;
|
|
3581
|
+
}
|
|
3582
|
+
|
|
3583
|
+
if (action === "status" || action === "doctor" || action === "check") {
|
|
3584
|
+
await printUfanetStatus({ check: action !== "status" || options.check });
|
|
3585
|
+
return;
|
|
3586
|
+
}
|
|
3587
|
+
|
|
3588
|
+
if (action === "intercoms" || action === "list" || action === "ls") {
|
|
3589
|
+
const rows = await ufanetGetIntercoms();
|
|
3590
|
+
printTable(rows, [["id", "ID"], ["name", "Название"], ["address", "Адрес"], ["role", "Роль"], ["blocked", "Блок"]]);
|
|
3591
|
+
return;
|
|
3592
|
+
}
|
|
3593
|
+
|
|
3594
|
+
if (action === "open") {
|
|
3595
|
+
const intercomId = target || options.id || options.intercom;
|
|
3596
|
+
if (!intercomId) throw new Error("Укажите ID домофона. Пример: iola ufanet open 123");
|
|
3597
|
+
const ok = options.yes || options.confirm || await confirm(`Открыть домофон Уфанет #${intercomId}? [y/N] `);
|
|
3598
|
+
if (!ok) {
|
|
3599
|
+
console.log("Открытие отменено.");
|
|
3600
|
+
return;
|
|
3601
|
+
}
|
|
3602
|
+
printKeyValue(await ufanetOpenIntercom(intercomId, { confirm: true }));
|
|
3603
|
+
return;
|
|
3604
|
+
}
|
|
3605
|
+
|
|
3606
|
+
if (action === "history" || action === "calls") {
|
|
3607
|
+
const rows = await ufanetGetCallHistory({ page: target || options.page || 1, pageSize: options.limit || options["page-size"] || 10 });
|
|
3608
|
+
printTable(rows.results || [], [["uuid", "UUID"], ["calledAt", "Когда"], ["address", "Адрес"], ["porch", "Подъезд"], ["flat", "Кв"]]);
|
|
3609
|
+
if (rows.count !== undefined) console.log(`Всего: ${rows.count}`);
|
|
3610
|
+
return;
|
|
3611
|
+
}
|
|
3612
|
+
|
|
3613
|
+
if (action === "links" || action === "record" || action === "recording") {
|
|
3614
|
+
const uuid = target || options.uuid;
|
|
3615
|
+
if (!uuid) throw new Error("Укажите UUID звонка. Пример: iola ufanet links UUID");
|
|
3616
|
+
printKeyValue(await ufanetGetCallLinks(uuid));
|
|
3617
|
+
return;
|
|
3618
|
+
}
|
|
3619
|
+
|
|
3620
|
+
if (action === "cameras" || action === "camera") {
|
|
3621
|
+
const rows = await ufanetGetCameras();
|
|
3622
|
+
printTable(rows, [["number", "Номер"], ["title", "Название"], ["address", "Адрес"], ["type", "Тип"], ["rtspUrl", "RTSP"]]);
|
|
3623
|
+
return;
|
|
3624
|
+
}
|
|
3625
|
+
|
|
3626
|
+
if (action === "delete" || action === "disconnect" || action === "remove") {
|
|
3627
|
+
const ok = !process.stdin.isTTY || await askYesNo("Удалить локальные данные подключения Уфанет? [y/N] ", false);
|
|
3628
|
+
if (!ok) {
|
|
3629
|
+
console.log("Удаление отменено.");
|
|
3630
|
+
return;
|
|
3631
|
+
}
|
|
3632
|
+
await deleteUfanetConnector();
|
|
3633
|
+
return;
|
|
3634
|
+
}
|
|
3635
|
+
|
|
3636
|
+
throw new Error(`Команды ufanet:
|
|
3637
|
+
iola ufanet setup
|
|
3638
|
+
iola ufanet status|doctor
|
|
3639
|
+
iola ufanet intercoms
|
|
3640
|
+
iola ufanet open ID
|
|
3641
|
+
iola ufanet history [--limit 10]
|
|
3642
|
+
iola ufanet links UUID
|
|
3643
|
+
iola ufanet cameras
|
|
3644
|
+
iola ufanet delete`);
|
|
3645
|
+
}
|
|
3646
|
+
|
|
3647
|
+
async function printUfanetMenu() {
|
|
3648
|
+
const status = await getUfanetStatus();
|
|
3649
|
+
console.log("Мой домофон");
|
|
3650
|
+
printTable([
|
|
3651
|
+
{ id: "ufanet", provider: "Уфанет", status: status.configured ? "готово" : "не настроено", command: "iola ufanet setup" },
|
|
3652
|
+
{ id: "domru", provider: "Дом.ру", status: "в разработке", command: "iola dom_ru" },
|
|
3653
|
+
{ id: "rostelecom", provider: "Ростелеком", status: "в разработке", command: "iola rostelecom" },
|
|
3654
|
+
], [["id", "ID"], ["provider", "Провайдер"], ["status", "Статус"], ["command", "Команда"]]);
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3657
|
+
async function setupUfanetConnector() {
|
|
3658
|
+
console.log("Мой домофон Уфанет.");
|
|
3659
|
+
console.log("Нужны номер договора и пароль от сервиса Уфанет. Они сохраняются только локально в ~/.iola/secrets.json.");
|
|
3660
|
+
if (!process.stdin.isTTY) {
|
|
3661
|
+
console.log("Интерактивный ввод недоступен. Используйте env UFANET_CONTRACT и UFANET_PASSWORD или запустите iola ufanet setup в терминале.");
|
|
3662
|
+
return;
|
|
3663
|
+
}
|
|
3664
|
+
const secrets = await loadSecrets();
|
|
3665
|
+
const currentContract = secrets.ufanet?.contract || "";
|
|
3666
|
+
const contract = (await askText(`Номер договора${currentContract ? " [Enter - оставить]" : ""}: `)).trim() || currentContract;
|
|
3667
|
+
const password = (await askText(`Пароль Уфанет${secrets.ufanet?.password ? " [Enter - оставить]" : ""}: `)).trim() || secrets.ufanet?.password || "";
|
|
3668
|
+
if (!contract || !password) throw new Error("Для Уфанет нужны номер договора и пароль.");
|
|
3669
|
+
await saveUfanetConnectorSecrets({ contract, password });
|
|
3670
|
+
await enableUfanetConnector();
|
|
3671
|
+
console.log(`Уфанет сохранен локально: ${SECRETS_FILE}`);
|
|
3672
|
+
await printUfanetStatus({ check: true });
|
|
3673
|
+
}
|
|
3674
|
+
|
|
3675
|
+
async function saveUfanetConnectorSecrets({ contract, password }) {
|
|
3676
|
+
const secrets = await loadSecrets();
|
|
3677
|
+
secrets.ufanet = {
|
|
3678
|
+
...(secrets.ufanet || {}),
|
|
3679
|
+
contract,
|
|
3680
|
+
password,
|
|
3681
|
+
updatedAt: new Date().toISOString(),
|
|
3682
|
+
};
|
|
3683
|
+
await saveSecrets(secrets);
|
|
3684
|
+
}
|
|
3685
|
+
|
|
3686
|
+
async function enableUfanetConnector() {
|
|
3687
|
+
const config = await loadConfig();
|
|
3688
|
+
await saveConfig({
|
|
3689
|
+
domophones: {
|
|
3690
|
+
...(config.domophones || {}),
|
|
3691
|
+
activeProvider: "ufanet",
|
|
3692
|
+
providers: {
|
|
3693
|
+
...(config.domophones?.providers || {}),
|
|
3694
|
+
ufanet: { ...(config.domophones?.providers?.ufanet || {}), enabled: true, updatedAt: new Date().toISOString() },
|
|
3695
|
+
domru: { ...(config.domophones?.providers?.domru || {}), enabled: false, status: "backlog" },
|
|
3696
|
+
rostelecom: { ...(config.domophones?.providers?.rostelecom || {}), enabled: false, status: "backlog" },
|
|
3697
|
+
},
|
|
3698
|
+
},
|
|
3699
|
+
toolsets: { ...(config.toolsets || {}), enabled: [...new Set([...(config.toolsets?.enabled || []), "ufanet"])] },
|
|
3700
|
+
skills: { ...(config.skills || {}), enabled: [...new Set([...(config.skills?.enabled || []), "ufanet-intercom"])] },
|
|
3701
|
+
});
|
|
3702
|
+
}
|
|
3703
|
+
|
|
3704
|
+
async function deleteUfanetConnector() {
|
|
3705
|
+
const secrets = await loadSecrets();
|
|
3706
|
+
delete secrets.ufanet;
|
|
3707
|
+
await saveSecrets(secrets);
|
|
3708
|
+
const config = await loadConfig();
|
|
3709
|
+
const enabledToolsets = (config.toolsets?.enabled || []).filter((item) => item !== "ufanet");
|
|
3710
|
+
const enabledSkills = (config.skills?.enabled || []).filter((item) => item !== "ufanet-intercom");
|
|
3711
|
+
await saveConfig({
|
|
3712
|
+
domophones: {
|
|
3713
|
+
...(config.domophones || {}),
|
|
3714
|
+
activeProvider: config.domophones?.activeProvider === "ufanet" ? "" : config.domophones?.activeProvider || "",
|
|
3715
|
+
providers: {
|
|
3716
|
+
...(config.domophones?.providers || {}),
|
|
3717
|
+
ufanet: { ...(config.domophones?.providers?.ufanet || {}), enabled: false },
|
|
3718
|
+
},
|
|
3719
|
+
},
|
|
3720
|
+
toolsets: { ...(config.toolsets || {}), enabled: enabledToolsets },
|
|
3721
|
+
skills: { ...(config.skills || {}), enabled: enabledSkills },
|
|
3722
|
+
});
|
|
3723
|
+
console.log("Подключение Уфанет удалено локально.");
|
|
3724
|
+
}
|
|
3725
|
+
|
|
3726
|
+
async function printUfanetStatus(options = {}) {
|
|
3727
|
+
const status = await getUfanetStatus();
|
|
3728
|
+
printKeyValue({
|
|
3729
|
+
configured: status.configured ? "yes" : "no",
|
|
3730
|
+
enabled: status.enabled ? "yes" : "no",
|
|
3731
|
+
contract: status.contract || "-",
|
|
3732
|
+
source: status.source || "-",
|
|
3733
|
+
});
|
|
3734
|
+
if (options.check) {
|
|
3735
|
+
if (!status.configured) {
|
|
3736
|
+
console.log("Уфанет: не настроен. Запустите: iola ufanet setup");
|
|
3737
|
+
return;
|
|
3738
|
+
}
|
|
3739
|
+
try {
|
|
3740
|
+
const rows = await ufanetGetIntercoms();
|
|
3741
|
+
console.log(`Уфанет: ok, домофонов: ${rows.length}`);
|
|
3742
|
+
} catch (error) {
|
|
3743
|
+
console.log(`Уфанет: ошибка проверки: ${error instanceof Error ? error.message : String(error)}`);
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3747
|
+
|
|
3748
|
+
async function getUfanetStatus() {
|
|
3749
|
+
const [config, secrets] = await Promise.all([loadConfig(), loadSecrets()]);
|
|
3750
|
+
const contract = process.env.UFANET_CONTRACT || secrets.ufanet?.contract || "";
|
|
3751
|
+
const password = process.env.UFANET_PASSWORD || secrets.ufanet?.password || "";
|
|
3752
|
+
return {
|
|
3753
|
+
configured: Boolean(contract && password),
|
|
3754
|
+
enabled: Boolean(config.domophones?.providers?.ufanet?.enabled || (config.toolsets?.enabled || []).includes("ufanet")),
|
|
3755
|
+
contract: contract ? maskSecret(contract, 2) : "",
|
|
3756
|
+
source: contract && process.env.UFANET_CONTRACT ? "env" : contract ? "local" : "",
|
|
3757
|
+
};
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3760
|
+
async function executeUfanetTool(tool, args = {}) {
|
|
3761
|
+
if (tool === "ufanet_status") return getUfanetStatus();
|
|
3762
|
+
if (tool === "ufanet_intercoms") return ufanetGetIntercoms();
|
|
3763
|
+
if (tool === "ufanet_open_intercom") return ufanetOpenIntercom(args.id || args.intercomId || args.intercom_id, args);
|
|
3764
|
+
if (tool === "ufanet_call_history") return ufanetGetCallHistory({ page: args.page || 1, pageSize: args.pageSize || args.page_size || args.limit || 10 });
|
|
3765
|
+
if (tool === "ufanet_call_links") return ufanetGetCallLinks(args.uuid || args.id);
|
|
3766
|
+
if (tool === "ufanet_cameras") return ufanetGetCameras();
|
|
3767
|
+
throw new Error(`Ufanet tool неизвестен: ${tool}`);
|
|
3768
|
+
}
|
|
3769
|
+
|
|
3770
|
+
async function ufanetCredentials() {
|
|
3771
|
+
const secrets = await loadSecrets();
|
|
3772
|
+
const contract = process.env.UFANET_CONTRACT || secrets.ufanet?.contract || "";
|
|
3773
|
+
const password = process.env.UFANET_PASSWORD || secrets.ufanet?.password || "";
|
|
3774
|
+
if (!contract || !password) throw new Error("Уфанет не подключен. Запустите: iola ufanet setup");
|
|
3775
|
+
return { contract, password, token: secrets.ufanet?.token || "", tokenUpdatedAt: secrets.ufanet?.tokenUpdatedAt || "" };
|
|
3776
|
+
}
|
|
3777
|
+
|
|
3778
|
+
async function ufanetEnsureToken(options = {}) {
|
|
3779
|
+
const credentials = await ufanetCredentials();
|
|
3780
|
+
if (credentials.token && !options.force) return credentials.token;
|
|
3781
|
+
const response = await fetch("https://dom.ufanet.ru/api/v1/auth/auth_by_contract/", {
|
|
3782
|
+
method: "POST",
|
|
3783
|
+
headers: { accept: "application/json", "content-type": "application/json" },
|
|
3784
|
+
body: JSON.stringify({ contract: credentials.contract, password: credentials.password }),
|
|
3785
|
+
signal: AbortSignal.timeout(30000),
|
|
3786
|
+
});
|
|
3787
|
+
const payload = await parseJsonResponse(response, "Уфанет авторизация");
|
|
3788
|
+
const token = payload?.token?.refresh || payload?.token?.access || payload?.refresh || payload?.access || "";
|
|
3789
|
+
if (!token) throw new Error("Уфанет не вернул JWT token.");
|
|
3790
|
+
if (!process.env.UFANET_CONTRACT && !process.env.UFANET_PASSWORD) {
|
|
3791
|
+
const secrets = await loadSecrets();
|
|
3792
|
+
secrets.ufanet = { ...(secrets.ufanet || {}), token, tokenUpdatedAt: new Date().toISOString() };
|
|
3793
|
+
await saveSecrets(secrets);
|
|
3794
|
+
}
|
|
3795
|
+
return token;
|
|
3796
|
+
}
|
|
3797
|
+
|
|
3798
|
+
async function ufanetRequest(method, apiPath, options = {}) {
|
|
3799
|
+
let token = await ufanetEnsureToken();
|
|
3800
|
+
let response = await fetch(new URL(apiPath, "https://dom.ufanet.ru/"), {
|
|
3801
|
+
method,
|
|
3802
|
+
headers: { accept: "application/json", "content-type": "application/json", Authorization: `JWT ${token}` },
|
|
3803
|
+
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
3804
|
+
signal: AbortSignal.timeout(Number(options.timeout || 30000)),
|
|
3805
|
+
});
|
|
3806
|
+
if (response.status === 401) {
|
|
3807
|
+
token = await ufanetEnsureToken({ force: true });
|
|
3808
|
+
response = await fetch(new URL(apiPath, "https://dom.ufanet.ru/"), {
|
|
3809
|
+
method,
|
|
3810
|
+
headers: { accept: "application/json", "content-type": "application/json", Authorization: `JWT ${token}` },
|
|
3811
|
+
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
3812
|
+
signal: AbortSignal.timeout(Number(options.timeout || 30000)),
|
|
3813
|
+
});
|
|
3814
|
+
}
|
|
3815
|
+
return parseJsonResponse(response, `Уфанет ${apiPath}`);
|
|
3816
|
+
}
|
|
3817
|
+
|
|
3818
|
+
async function parseJsonResponse(response, label) {
|
|
3819
|
+
const text = await response.text().catch(() => "");
|
|
3820
|
+
let payload = null;
|
|
3821
|
+
if (text) {
|
|
3822
|
+
try {
|
|
3823
|
+
payload = JSON.parse(text);
|
|
3824
|
+
} catch {
|
|
3825
|
+
payload = text;
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3828
|
+
if (!response.ok) {
|
|
3829
|
+
const message = typeof payload === "string" ? payload.slice(0, 300) : JSON.stringify(payload).slice(0, 300);
|
|
3830
|
+
throw new Error(`${label}: ${response.status} ${response.statusText}${message ? ` ${message}` : ""}`);
|
|
3831
|
+
}
|
|
3832
|
+
return payload;
|
|
3833
|
+
}
|
|
3834
|
+
|
|
3835
|
+
async function ufanetGetIntercoms() {
|
|
3836
|
+
const payload = await ufanetRequest("GET", "api/v0/skud/shared/");
|
|
3837
|
+
return normalizeItems(payload).map((item) => ({
|
|
3838
|
+
id: item.id,
|
|
3839
|
+
name: item.custom_name || item.string_view || `Домофон ${item.id}`,
|
|
3840
|
+
address: item.string_view || "",
|
|
3841
|
+
role: item.role?.name || "",
|
|
3842
|
+
camera: item.camera || item.cctv_number || "",
|
|
3843
|
+
blocked: item.is_blocked ? "yes" : "no",
|
|
3844
|
+
disableButton: Boolean(item.disable_button),
|
|
3845
|
+
inactivityReason: item.inactivity_reason || "",
|
|
3846
|
+
raw: item,
|
|
3847
|
+
}));
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
async function ufanetOpenIntercom(intercomId, options = {}) {
|
|
3851
|
+
if (!options.confirm) throw new Error("Для открытия домофона нужен аргумент confirm=true.");
|
|
3852
|
+
if (!intercomId) throw new Error("ID домофона обязателен.");
|
|
3853
|
+
const payload = await ufanetRequest("GET", `api/v0/skud/shared/${encodeURIComponent(intercomId)}/open/`, { timeout: 30000 });
|
|
3854
|
+
return { provider: "ufanet", status: payload?.result ? "opened" : "not-opened", id: Number(intercomId), result: Boolean(payload?.result) };
|
|
3855
|
+
}
|
|
3856
|
+
|
|
3857
|
+
async function ufanetGetCallHistory(options = {}) {
|
|
3858
|
+
const page = Math.max(1, Number(options.page || 1));
|
|
3859
|
+
const pageSize = Math.max(1, Math.min(100, Number(options.pageSize || 10)));
|
|
3860
|
+
const url = `api/v1/skuds/call-history/?page=${encodeURIComponent(page)}&page_size=${encodeURIComponent(pageSize)}`;
|
|
3861
|
+
const payload = await ufanetRequest("GET", url, { timeout: 30000 });
|
|
3862
|
+
return {
|
|
3863
|
+
count: payload?.count || 0,
|
|
3864
|
+
next: payload?.next || "",
|
|
3865
|
+
previous: payload?.previous || "",
|
|
3866
|
+
results: normalizeItems(payload?.results || []).map((item) => ({
|
|
3867
|
+
uuid: item.uuid,
|
|
3868
|
+
calledAt: item.called_at || item.calledAt || "",
|
|
3869
|
+
address: item.address || "",
|
|
3870
|
+
porch: item.porch || "",
|
|
3871
|
+
flat: item.flat || "",
|
|
3872
|
+
cameraNumber: item.camera_number || "",
|
|
3873
|
+
timezone: item.timezone || "",
|
|
3874
|
+
})),
|
|
3875
|
+
};
|
|
3876
|
+
}
|
|
3877
|
+
|
|
3878
|
+
async function ufanetGetCallLinks(uuid) {
|
|
3879
|
+
if (!uuid) throw new Error("UUID звонка обязателен.");
|
|
3880
|
+
const payload = await ufanetRequest("POST", "api/v1/cctv/history/", { body: { uuid: String(uuid) }, timeout: 30000 });
|
|
3881
|
+
return { provider: "ufanet", uuid: String(uuid), url: payload?.url || "", preview: payload?.preview || "" };
|
|
3882
|
+
}
|
|
3883
|
+
|
|
3884
|
+
async function ufanetGetCameras() {
|
|
3885
|
+
const payload = await ufanetRequest("GET", "api/v1/cctv", { timeout: 30000 });
|
|
3886
|
+
return normalizeItems(payload).map((item) => ({
|
|
3887
|
+
number: item.number || "",
|
|
3888
|
+
title: item.title || "",
|
|
3889
|
+
address: item.address || "",
|
|
3890
|
+
latitude: item.latitude,
|
|
3891
|
+
longitude: item.longitude,
|
|
3892
|
+
type: item.type || "",
|
|
3893
|
+
rtspUrl: item.servers?.domain && item.number && item.token_l ? `rtsp://${item.servers.domain}/${item.number}?token=${item.token_l}` : "",
|
|
3894
|
+
}));
|
|
3895
|
+
}
|
|
3896
|
+
|
|
3526
3897
|
function printYandexServices(options = {}) {
|
|
3527
3898
|
const rows = Object.entries(YANDEX_CONNECTOR_SERVICES)
|
|
3528
3899
|
.filter(([, service]) => !options.status || service.status === options.status)
|
|
@@ -14545,7 +14916,8 @@ async function buildLocalToolPlan(question, providerConfig, options) {
|
|
|
14545
14916
|
"Схема: {\"steps\":[{\"tool\":\"search_data\",\"args\":{\"dataset\":\"schools|kindergartens|all\",\"query\":\"text\",\"limit\":10}}]}",
|
|
14546
14917
|
"Минимальные tools: search_data {dataset,query,limit}, get_card {query}, export_report {name,format,output}, file_read {path}, browser_open {url}.",
|
|
14547
14918
|
"Yandex tools: yandex_identity_me {}, yandex_disk_info {}, yandex_disk_ls {path}, yandex_disk_mkdir {path}, yandex_disk_find {query,path}, yandex_disk_stat {path}, yandex_disk_exists {path}, yandex_disk_read_text {path}, yandex_disk_save_text {path,text}, yandex_disk_upload {localPath,remotePath}, yandex_disk_download {remotePath,outputPath}, yandex_disk_move {from,to,confirm}, yandex_disk_copy {from,to,confirm}, yandex_disk_rename {path,name,confirm}, yandex_disk_share {path,confirm}, yandex_disk_share_qr {path,confirm}, yandex_disk_share_email {path,to,contact,subject,text,confirm}, yandex_disk_package_share_email {sourcePath,targetFolder,to,contact,mode,confirm}, yandex_disk_unshare {path}, yandex_disk_delete {path,confirm}, yandex_disk_trash_list {}, yandex_disk_restore {path,confirm}, yandex_disk_empty_trash {confirm}, yandex_mail_folders {}, yandex_mail_list {mailbox,limit,unread}, yandex_mail_search {mailbox,query}, yandex_mail_read {mailbox,uid}, yandex_mail_mark {mailbox,uid,seen}, yandex_mail_send {to,subject,text,confirm}, yandex_mail_reply {uid,text,confirm}, yandex_mail_forward {uid,to,confirm}, yandex_mail_save_to_disk {uid,path}, yandex_mail_city_context {uid}, yandex_mail_map_addresses {uid}, yandex_mail_create_task {uid,title}, yandex_mail_meeting_pack {uid,start,end,send,confirm}, yandex_calendar_calendars {}, yandex_calendar_list {start,end}, yandex_calendar_search {query,start,end}, yandex_calendar_get {query}, yandex_calendar_create_event {title,start,end,location,attendees,reminders,confirm}, yandex_calendar_update {query,title,start,end,location,description,reminders,confirm}, yandex_calendar_move {query,start,end,confirm}, yandex_calendar_delete {query,confirm}, yandex_docs_list {path}, yandex_docs_find {query}, yandex_docs_create_text {title,text,format,confirm}, yandex_docs_read {path|query}, yandex_docs_share {path|query,confirm}, yandex_docs_rename {path|query,name,confirm}, yandex_docs_delete {path|query,confirm}, yandex_contacts_list {limit}, yandex_contacts_search {query}, yandex_contacts_get {query}, yandex_contacts_create {name,email,phone,address,note,confirm}, yandex_contacts_update {query,email,phone,address,note,birthday,org,title,confirm}, yandex_contacts_delete {query,confirm}, yandex_contacts_export_csv {}, yandex_contacts_find_incomplete {}, yandex_contacts_find_duplicates {}, yandex_contacts_backup_to_disk {format,confirm}, yandex_contact_send_mail {contact,subject,text,confirm}, yandex_contact_send_disk_link_qr {contact,path,confirm}, yandex_contact_create_disk_folder {contact,confirm}, yandex_contact_create_calendar_event {contact,start,end,title,confirm}, yandex_contact_create_telemost_event {contact,start,end,title,confirm}, yandex_contact_full_pack {contact,start,end,send,confirm}, yandex_cloud_status {}, yandex_go_deeplink {from,to,tariff}, yandex_daily_digest {save,email}, yandex_calendar_reminders_tick {}, yandex_disk_maintenance_tick {}.",
|
|
14548
|
-
"
|
|
14919
|
+
"Ufanet tools: ufanet_status {}, ufanet_intercoms {}, ufanet_open_intercom {id,confirm}, ufanet_call_history {page,limit}, ufanet_call_links {uuid}, ufanet_cameras {}.",
|
|
14920
|
+
"Опасные Yandex/Ufanet tools используй только при явной просьбе пользователя и с confirm=true: yandex_disk_share, yandex_disk_share_qr, yandex_disk_share_email, yandex_disk_package_share_email, yandex_disk_delete, yandex_disk_move, yandex_disk_copy, yandex_disk_rename, yandex_disk_restore, yandex_disk_empty_trash, yandex_mail_send, yandex_mail_reply, yandex_mail_forward, yandex_mail_delete, yandex_mail_create_calendar_event, yandex_mail_sender_to_contact, yandex_mail_meeting_pack, yandex_contacts_create, yandex_contacts_update, yandex_contacts_delete, yandex_contacts_add_email, yandex_contacts_add_phone, yandex_contacts_add_address, yandex_contacts_backup_to_disk, yandex_contact_send_mail, yandex_contact_send_disk_link_qr, yandex_contact_create_disk_folder, yandex_contact_create_calendar_event, yandex_contact_create_telemost_event, yandex_contact_full_pack, yandex_calendar_create_event, yandex_calendar_update, yandex_calendar_move, yandex_calendar_delete, yandex_calendar_add_reminder, yandex_docs_create_text, yandex_docs_share, yandex_docs_rename, yandex_docs_delete, yandex_telemost_create_event, ufanet_open_intercom.",
|
|
14549
14921
|
"User skill tools: user_skill_create {name,description,instructions,tools,template,enable,confirm}, user_skill_update {name,instructions,tools,confirm}, user_skill_templates {}, user_skill_validate {name}, user_skill_preview {name,template,instructions}, user_skill_enable {name}, user_skill_disable {name}, user_skill_delete {name,confirm}, user_skill_list {}. Создавай или меняй skill только по явной просьбе пользователя и с confirm=true.",
|
|
14550
14922
|
"MCP tools доступны как mcp:SERVER:TOOL, например mcp:iola-local:search.",
|
|
14551
14923
|
"Для выгрузки CSV добавь export_report с format=csv и output, если пользователь назвал файл.",
|
|
@@ -14602,6 +14974,16 @@ function parseJsonObject(text) {
|
|
|
14602
14974
|
return JSON.parse(match[0]);
|
|
14603
14975
|
}
|
|
14604
14976
|
|
|
14977
|
+
function extractUfanetIntercomId(text) {
|
|
14978
|
+
return String(text || "").match(/(?:домофон|id|#|№)\s*(\d{1,10})/iu)?.[1]
|
|
14979
|
+
|| String(text || "").match(/\b(\d{2,10})\b/u)?.[1]
|
|
14980
|
+
|| "";
|
|
14981
|
+
}
|
|
14982
|
+
|
|
14983
|
+
function extractUuid(text) {
|
|
14984
|
+
return String(text || "").match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/iu)?.[0] || "";
|
|
14985
|
+
}
|
|
14986
|
+
|
|
14605
14987
|
function inferToolPlan(question, options = {}) {
|
|
14606
14988
|
const normalized = question.toLocaleLowerCase("ru-RU");
|
|
14607
14989
|
if (isCurrentDateTimeQuestion(normalized)) {
|
|
@@ -14635,6 +15017,26 @@ function inferToolPlan(question, options = {}) {
|
|
|
14635
15017
|
if (/(яндекс|yandex)/iu.test(normalized) && /(аккаунт|профил|логин|почт[аы]|email|e-mail|кто подключен)/iu.test(normalized)) {
|
|
14636
15018
|
return { steps: [{ tool: "yandex_identity_me", args: {} }] };
|
|
14637
15019
|
}
|
|
15020
|
+
if (/(уфанет|домофон|домофонн|подъезд|звонк|камера|rtsp)/iu.test(normalized)) {
|
|
15021
|
+
if (/(дом\.?ру|dom\.?ru)/iu.test(normalized)) return { directAnswer: "Мой домофон Дом.ру пока в разработке. Сейчас реализован Уфанет: /ufanet." };
|
|
15022
|
+
if (/(ростелеком|rostelecom)/iu.test(normalized)) return { directAnswer: "Мой домофон Ростелеком пока в разработке. Сейчас реализован Уфанет: /ufanet." };
|
|
15023
|
+
if (/(открой|открыть|пусти|впусти|двер)/iu.test(normalized)) {
|
|
15024
|
+
const id = extractUfanetIntercomId(question);
|
|
15025
|
+
if (!id) return { directAnswer: "Для открытия домофона нужен ID. Посмотрите доступные домофоны командой /ufanet intercoms, затем: /ufanet open ID." };
|
|
15026
|
+
return { steps: [{ tool: "ufanet_open_intercom", args: { id, confirm: true } }] };
|
|
15027
|
+
}
|
|
15028
|
+
if (/(истори|звонк|кто\s+звонил|последн)/iu.test(normalized) && !/(ссылк|запис|видео)/iu.test(normalized)) {
|
|
15029
|
+
return { steps: [{ tool: "ufanet_call_history", args: { limit: 10 } }] };
|
|
15030
|
+
}
|
|
15031
|
+
if (/(ссылк|запис|видео|preview|превью)/iu.test(normalized)) {
|
|
15032
|
+
const uuid = extractUuid(question);
|
|
15033
|
+
if (!uuid) return { directAnswer: "Для ссылки на запись нужен UUID звонка. Сначала посмотрите историю: /ufanet history." };
|
|
15034
|
+
return { steps: [{ tool: "ufanet_call_links", args: { uuid } }] };
|
|
15035
|
+
}
|
|
15036
|
+
if (/(камер|rtsp|видео)/iu.test(normalized)) return { steps: [{ tool: "ufanet_cameras", args: {} }] };
|
|
15037
|
+
if (/(статус|подключ|аккаунт|договор)/iu.test(normalized)) return { steps: [{ tool: "ufanet_status", args: {} }] };
|
|
15038
|
+
return { steps: [{ tool: "ufanet_intercoms", args: {} }] };
|
|
15039
|
+
}
|
|
14638
15040
|
if (/(яндекс|диск|облак)/iu.test(normalized)) {
|
|
14639
15041
|
const diskPath = extractCloudPath(question) || CLOUD_DEFAULT_REMOTE_DIR;
|
|
14640
15042
|
if (/(мест[оа]|сколько.*занято|сколько.*свобод|статус|инфо)/iu.test(normalized)) return { steps: [{ tool: "yandex_disk_info", args: {} }] };
|
|
@@ -15057,7 +15459,7 @@ function formatToolExecutionError(error, plan) {
|
|
|
15057
15459
|
}
|
|
15058
15460
|
|
|
15059
15461
|
function availableToolNames(options = {}) {
|
|
15060
|
-
const names = new Set([...LOCAL_TOOLS, ...YANDEX_TOOLS, ...USER_SKILL_TOOLS]);
|
|
15462
|
+
const names = new Set([...LOCAL_TOOLS, ...YANDEX_TOOLS, ...UFANET_TOOLS, ...USER_SKILL_TOOLS]);
|
|
15061
15463
|
if (options.files) {
|
|
15062
15464
|
for (const tool of FILE_TOOLS) names.add(tool);
|
|
15063
15465
|
}
|
|
@@ -15126,6 +15528,11 @@ async function executeToolPlan(plan, options = {}) {
|
|
|
15126
15528
|
const result = await executeYandexTool(step.tool, step.args || {});
|
|
15127
15529
|
current = Array.isArray(result) ? result : [result];
|
|
15128
15530
|
outputs.push({ tool: step.tool, rows: current.length });
|
|
15531
|
+
} else if (UFANET_TOOLS.includes(step.tool)) {
|
|
15532
|
+
await assertPermission("externalApi");
|
|
15533
|
+
const result = await executeUfanetTool(step.tool, step.args || {});
|
|
15534
|
+
current = Array.isArray(result) ? result : [result];
|
|
15535
|
+
outputs.push({ tool: step.tool, rows: current.length });
|
|
15129
15536
|
} else if (USER_SKILL_TOOLS.includes(step.tool)) {
|
|
15130
15537
|
const result = await executeUserSkillTool(step.tool, step.args || {});
|
|
15131
15538
|
current = Array.isArray(result) ? result : [result];
|
|
@@ -15281,6 +15688,12 @@ function formatToolResult(result, options) {
|
|
|
15281
15688
|
return `${name}: ${row.field} = ${row.value ?? "не указано"}`;
|
|
15282
15689
|
}
|
|
15283
15690
|
if (row.date && row.time) return `Сегодня ${row.date}, ${row.time}.`;
|
|
15691
|
+
if (row.provider === "ufanet" && (row.status === "opened" || row.status === "not-opened")) return `Уфанет: домофон #${row.id} ${row.status === "opened" ? "открыт" : "не открылся"}.`;
|
|
15692
|
+
if (row.provider === "ufanet" && row.uuid && (row.url || row.preview)) return `Уфанет: запись звонка ${row.uuid}\nСсылка: ${row.url || "-"}\nПревью: ${row.preview || "-"}`;
|
|
15693
|
+
if (row.rtspUrl) return `Камера Уфанет ${row.title || row.number}: ${row.address || "-"}\nRTSP: ${row.rtspUrl}`;
|
|
15694
|
+
if (row.calledAt && row.uuid) return `Звонок Уфанет: ${row.calledAt}, ${row.address || "-"}, подъезд ${row.porch || "-"}, UUID ${row.uuid}`;
|
|
15695
|
+
if (row.id && (row.address || row.role || row.blocked !== undefined)) return `Домофон Уфанет #${row.id}: ${row.name || row.address || "-"}${row.blocked === "yes" ? " (заблокирован)" : ""}`;
|
|
15696
|
+
if (row.configured !== undefined && row.enabled !== undefined && row.contract !== undefined) return `Уфанет: ${row.configured ? "настроен" : "не настроен"}, ${row.enabled ? "включен" : "выключен"}${row.contract ? `, договор ${row.contract}` : ""}.`;
|
|
15284
15697
|
if (row.status === "calendar-event-created" || row.status === "telemost-event-created" || row.status === "telemost-calendar-fallback-created") {
|
|
15285
15698
|
return `${row.status === "calendar-event-created" ? "Событие" : "Телемост"} создан: ${row.title || row.uid}${row.start ? `, ${row.start}` : ""}${row.telemost?.joinUrl ? `\nСсылка: ${row.telemost.joinUrl}` : row.status === "telemost-calendar-fallback-created" ? "\nПрямая ссылка Телемоста через API недоступна, создано событие календаря." : ""}`;
|
|
15286
15699
|
}
|
|
@@ -16669,6 +17082,9 @@ async function onboard(args = []) {
|
|
|
16669
17082
|
if (components.includes("policy")) await handlePolicy(["use", "analyst"]);
|
|
16670
17083
|
if (components.includes("archive")) await ensureArchiveTool({ install: true });
|
|
16671
17084
|
if (components.includes("city-data")) await checkHealth([]);
|
|
17085
|
+
if (components.includes("ufanet")) await setupUfanetConnector();
|
|
17086
|
+
if (components.includes("domru")) await handleDomRu();
|
|
17087
|
+
if (components.includes("rostelecom")) await handleRostelecom();
|
|
16672
17088
|
if (components.includes("iola")) {
|
|
16673
17089
|
await setupIolaLocal(["--yes"]);
|
|
16674
17090
|
}
|
|
@@ -16757,14 +17173,17 @@ async function chooseOnboardComponents(status = null) {
|
|
|
16757
17173
|
5: "browser",
|
|
16758
17174
|
6: "city-data",
|
|
16759
17175
|
7: "codex-mcp",
|
|
16760
|
-
8: "
|
|
16761
|
-
9: "
|
|
16762
|
-
10: "
|
|
16763
|
-
11: "
|
|
16764
|
-
12: "
|
|
16765
|
-
13: "
|
|
16766
|
-
14: "
|
|
16767
|
-
15: "
|
|
17176
|
+
8: "ufanet",
|
|
17177
|
+
9: "domru",
|
|
17178
|
+
10: "rostelecom",
|
|
17179
|
+
11: "iola",
|
|
17180
|
+
12: "ollama",
|
|
17181
|
+
13: "gigachat",
|
|
17182
|
+
14: "yandex",
|
|
17183
|
+
15: "yandex-cloud",
|
|
17184
|
+
16: "openai",
|
|
17185
|
+
17: "openrouter",
|
|
17186
|
+
18: "codex",
|
|
16768
17187
|
};
|
|
16769
17188
|
return [...selected].map((item) => map[item] || item).filter(Boolean);
|
|
16770
17189
|
} finally {
|
|
@@ -16802,6 +17221,9 @@ async function getOnboardComponentStatus() {
|
|
|
16802
17221
|
codex: Boolean(codexVersion !== "не найден" && readiness.codex),
|
|
16803
17222
|
"codex-mcp": false,
|
|
16804
17223
|
"city-data": cityDataHealth === "доступен",
|
|
17224
|
+
ufanet: Boolean((process.env.UFANET_CONTRACT && process.env.UFANET_PASSWORD) || (secrets.ufanet?.contract && secrets.ufanet?.password)),
|
|
17225
|
+
domru: false,
|
|
17226
|
+
rostelecom: false,
|
|
16805
17227
|
archive: Boolean(archive),
|
|
16806
17228
|
index: false,
|
|
16807
17229
|
browser: browser.installed === "yes",
|
|
@@ -16826,40 +17248,43 @@ function onboardComponentGroups(status) {
|
|
|
16826
17248
|
rows: [
|
|
16827
17249
|
["6", "city-data", "Открытые данные Йошкар-Олы", "API/MCP gateway доступен"],
|
|
16828
17250
|
["7", "codex-mcp", "Подключить городские данные к Codex", "MCP для Codex"],
|
|
17251
|
+
["8", "ufanet", "Мой домофон Уфанет", "договор и пароль хранятся локально"],
|
|
17252
|
+
["9", "domru", "Мой домофон Дом.ру", "в разработке"],
|
|
17253
|
+
["10", "rostelecom", "Мой домофон Ростелеком", "в разработке"],
|
|
16829
17254
|
],
|
|
16830
17255
|
},
|
|
16831
17256
|
{
|
|
16832
17257
|
title: "Локальный AI",
|
|
16833
17258
|
rows: [
|
|
16834
|
-
["
|
|
16835
|
-
["
|
|
17259
|
+
["11", "iola", "IOLA локальная модель", "локальная модель найдена"],
|
|
17260
|
+
["12", "ollama", "Ollama", "опциональный локальный runtime"],
|
|
16836
17261
|
],
|
|
16837
17262
|
},
|
|
16838
17263
|
{
|
|
16839
17264
|
title: "Российские AI и сервисы",
|
|
16840
17265
|
rows: [
|
|
16841
|
-
["
|
|
16842
|
-
["
|
|
16843
|
-
["
|
|
17266
|
+
["13", "gigachat", "GigaChat API", "authorization key сохранен или есть в env"],
|
|
17267
|
+
["14", "yandex", "Yandex Connector", "Диск, Почта, Календарь, Контакты"],
|
|
17268
|
+
["15", "yandex-cloud", "Yandex Cloud Connector", "геокодинг и YandexGPT"],
|
|
16844
17269
|
],
|
|
16845
17270
|
},
|
|
16846
17271
|
{
|
|
16847
17272
|
title: "Зарубежные AI",
|
|
16848
17273
|
rows: [
|
|
16849
|
-
["
|
|
16850
|
-
["
|
|
17274
|
+
["16", "openai", "OpenAI API", "API-ключ сохранен или есть в env"],
|
|
17275
|
+
["17", "openrouter", "OpenRouter API", "API-ключ сохранен или есть в env"],
|
|
16851
17276
|
],
|
|
16852
17277
|
},
|
|
16853
17278
|
{
|
|
16854
17279
|
title: "Codex",
|
|
16855
17280
|
rows: [
|
|
16856
|
-
["
|
|
17281
|
+
["18", "codex", "Codex CLI", "CLI установлен и авторизация найдена"],
|
|
16857
17282
|
],
|
|
16858
17283
|
},
|
|
16859
17284
|
];
|
|
16860
17285
|
return groups.map((group) => ({
|
|
16861
17286
|
...group,
|
|
16862
|
-
rows: group.rows.map(([number, key, title, hint]) => ({ number, key, title, hint, status: status[key] ? "готово" : "не настроено" })),
|
|
17287
|
+
rows: group.rows.map(([number, key, title, hint]) => ({ number, key, title, hint, status: key === "domru" || key === "rostelecom" ? "в разработке" : status[key] ? "готово" : "не настроено" })),
|
|
16863
17288
|
}));
|
|
16864
17289
|
}
|
|
16865
17290
|
|
|
@@ -16872,12 +17297,12 @@ function defaultOnboardSelection(status) {
|
|
|
16872
17297
|
if (!status.workspace) defaults.push("1");
|
|
16873
17298
|
if (!status.policy) defaults.push("2");
|
|
16874
17299
|
if (!status.archive) defaults.push("3");
|
|
16875
|
-
if (!status.iola) defaults.push("
|
|
17300
|
+
if (!status.iola) defaults.push("11");
|
|
16876
17301
|
return defaults.length ? defaults : ["1", "2"];
|
|
16877
17302
|
}
|
|
16878
17303
|
|
|
16879
17304
|
function defaultOnboardComponents(status) {
|
|
16880
|
-
const map = { 1: "workspace", 2: "policy", 3: "archive", 4: "index", 5: "browser", 6: "city-data", 7: "codex-mcp", 8: "
|
|
17305
|
+
const map = { 1: "workspace", 2: "policy", 3: "archive", 4: "index", 5: "browser", 6: "city-data", 7: "codex-mcp", 8: "ufanet", 9: "domru", 10: "rostelecom", 11: "iola", 12: "ollama", 13: "gigachat", 14: "yandex", 15: "yandex-cloud", 16: "openai", 17: "openrouter", 18: "codex" };
|
|
16881
17306
|
return defaultOnboardSelection(status).map((item) => map[item]).filter(Boolean);
|
|
16882
17307
|
}
|
|
16883
17308
|
|
|
@@ -16891,7 +17316,7 @@ function parseOptions(args) {
|
|
|
16891
17316
|
} else if (arg === "--check" || arg === "--upgrade-node") {
|
|
16892
17317
|
result.check = true;
|
|
16893
17318
|
result[arg.slice(2)] = true;
|
|
16894
|
-
} 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") {
|
|
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") {
|
|
16895
17320
|
result[arg.slice(2)] = args[index + 1];
|
|
16896
17321
|
index += 1;
|
|
16897
17322
|
} else {
|
|
@@ -19093,6 +19518,14 @@ function mergeConfig(base, override) {
|
|
|
19093
19518
|
...(override.cloud?.providers || {}),
|
|
19094
19519
|
},
|
|
19095
19520
|
},
|
|
19521
|
+
domophones: {
|
|
19522
|
+
...base.domophones,
|
|
19523
|
+
...(override.domophones || {}),
|
|
19524
|
+
providers: {
|
|
19525
|
+
...(base.domophones?.providers || {}),
|
|
19526
|
+
...(override.domophones?.providers || {}),
|
|
19527
|
+
},
|
|
19528
|
+
},
|
|
19096
19529
|
yandex: {
|
|
19097
19530
|
...base.yandex,
|
|
19098
19531
|
...(override.yandex || {}),
|
|
@@ -19186,6 +19619,12 @@ function sanitizeConfig(config) {
|
|
|
19186
19619
|
next.skills = next.skills || {};
|
|
19187
19620
|
next.skills.enabled = [...new Set([...(next.skills.enabled || []), "yandex-services"])];
|
|
19188
19621
|
}
|
|
19622
|
+
if (next.domophones?.providers?.ufanet?.enabled) {
|
|
19623
|
+
next.toolsets = next.toolsets || {};
|
|
19624
|
+
next.toolsets.enabled = [...new Set([...(next.toolsets.enabled || []), "ufanet"])];
|
|
19625
|
+
next.skills = next.skills || {};
|
|
19626
|
+
next.skills.enabled = [...new Set([...(next.skills.enabled || []), "ufanet-intercom"])];
|
|
19627
|
+
}
|
|
19189
19628
|
const localProfile = next.ai?.profiles?.local;
|
|
19190
19629
|
if (localProfile?.provider === "iola") {
|
|
19191
19630
|
if (!localProfile.runtime || localProfile.model === "iola-router-1b") {
|
|
@@ -19233,6 +19672,9 @@ function validateConfig(config) {
|
|
|
19233
19672
|
if (config.cloud?.activeProvider && !["yandex-disk", "mailru-cloud"].includes(config.cloud.activeProvider)) {
|
|
19234
19673
|
errors.push(`cloud.activeProvider неизвестен: ${config.cloud.activeProvider}`);
|
|
19235
19674
|
}
|
|
19675
|
+
if (config.domophones?.activeProvider && !["ufanet", "domru", "rostelecom"].includes(config.domophones.activeProvider)) {
|
|
19676
|
+
errors.push(`domophones.activeProvider неизвестен: ${config.domophones.activeProvider}`);
|
|
19677
|
+
}
|
|
19236
19678
|
for (const service of config.yandex?.enabledServices || []) {
|
|
19237
19679
|
if (!YANDEX_CONNECTOR_SERVICES[service]) errors.push(`yandex.enabledServices содержит неизвестный сервис: ${service}`);
|
|
19238
19680
|
}
|
|
@@ -19253,6 +19695,7 @@ function configSchema() {
|
|
|
19253
19695
|
toolsets: { available: Object.keys(TOOLSETS) },
|
|
19254
19696
|
files: { modes: ["locked", "read-only", "workspace-write", "full-access"], approvals: ["never", "on-write", "on-danger", "always"] },
|
|
19255
19697
|
cloud: { providers: ["yandex-disk", "mailru-cloud"], root: CLOUD_DEFAULT_REMOTE_DIR },
|
|
19698
|
+
domophones: { providers: ["ufanet", "domru", "rostelecom"] },
|
|
19256
19699
|
yandex: { services: Object.keys(YANDEX_CONNECTOR_SERVICES), statuses: ["ready", "research", "separate", "backlog"] },
|
|
19257
19700
|
skills: { enabled: "array of skill names" },
|
|
19258
19701
|
daemon: { host: "127.0.0.1", port: DAEMON_PORT },
|
package/wiki/Home.md
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
- geo-сценарии для жителей через Yandex Geocoder API;
|
|
15
15
|
- Yandex Connector для пользовательских сервисов Яндекса;
|
|
16
16
|
- Yandex Cloud Connector для геокодера, YandexGPT и deeplink Яндекс Go;
|
|
17
|
+
- Мой домофон Уфанет;
|
|
17
18
|
- личные облачные диски: Яндекс Диск и Облако Mail.ru;
|
|
18
19
|
- подключение публичного MCP-сервера.
|
|
19
20
|
|
|
@@ -35,6 +36,7 @@ iola ask "найди школу 29"
|
|
|
35
36
|
- [Yandex Geocoder API key](Yandex-Geocoder-API-key)
|
|
36
37
|
- [Yandex Cloud Connector](Yandex-Cloud-Connector)
|
|
37
38
|
- [Yandex Connector](Yandex-Connector)
|
|
39
|
+
- [Мой домофон](Мой-домофон)
|
|
38
40
|
- [Облачные диски](Облачные-диски)
|
|
39
41
|
- [Скиллы для жителей](Скиллы-для-жителей)
|
|
40
42
|
- [Локальный инструментальный агент](Локальный-инструментальный-агент)
|
|
@@ -10,6 +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
14
|
- `user-skills` - когда пользователь просит создать, включить, выключить или удалить собственный skill.
|
|
14
15
|
|
|
15
16
|
Обычный диалог вроде `привет` не получает инструкции про слои, отчеты, файлы и браузер.
|
|
@@ -90,3 +91,16 @@ Toolset `yandex` включает локальные tools для пользов
|
|
|
90
91
|
- Телемост: попытка прямого API и fallback через календарное событие, если API недоступен аккаунту.
|
|
91
92
|
|
|
92
93
|
Опасные действия ограничены: отправка письма, удаление файлов, публикация ссылок, создание/изменение документов и создание/изменение/удаление событий требуют явного подтверждения в tool-вызове. Токены хранятся локально в `~/.iola/secrets.json`.
|
|
94
|
+
|
|
95
|
+
## Ufanet toolset
|
|
96
|
+
|
|
97
|
+
Toolset `ufanet` включает tools для личного домофона Уфанет:
|
|
98
|
+
|
|
99
|
+
- `ufanet_status` - проверить подключение;
|
|
100
|
+
- `ufanet_intercoms` - список домофонов;
|
|
101
|
+
- `ufanet_open_intercom` - открыть домофон по ID;
|
|
102
|
+
- `ufanet_call_history` - история звонков;
|
|
103
|
+
- `ufanet_call_links` - ссылка на запись звонка по UUID;
|
|
104
|
+
- `ufanet_cameras` - камеры и RTSP-ссылки.
|
|
105
|
+
|
|
106
|
+
Открытие домофона требует явного подтверждения `confirm=true`. Договор, пароль и JWT хранятся локально и не выводятся в ответах.
|
|
@@ -18,7 +18,7 @@ iola master
|
|
|
18
18
|
В мастере выберите:
|
|
19
19
|
|
|
20
20
|
```text
|
|
21
|
-
|
|
21
|
+
15. Yandex Cloud Connector - геокодинг и YandexGPT
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
CLI не просто открывает консоль. Он печатает короткую инструкцию, что делать дальше, и только потом просит вставить ключи.
|
|
@@ -88,6 +88,21 @@ iola yandex token set
|
|
|
88
88
|
iola yandex token delete
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
+
Мой домофон:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
iola ufanet setup
|
|
95
|
+
iola ufanet status
|
|
96
|
+
iola ufanet intercoms
|
|
97
|
+
iola ufanet open ID
|
|
98
|
+
iola ufanet history --limit 10
|
|
99
|
+
iola ufanet links UUID
|
|
100
|
+
iola ufanet cameras
|
|
101
|
+
iola ufanet delete
|
|
102
|
+
iola dom_ru
|
|
103
|
+
iola rostelecom
|
|
104
|
+
```
|
|
105
|
+
|
|
91
106
|
Локальная БД:
|
|
92
107
|
|
|
93
108
|
```bash
|
|
@@ -25,22 +25,25 @@ iola master
|
|
|
25
25
|
Городские сервисы
|
|
26
26
|
6. Открытые данные Йошкар-Олы
|
|
27
27
|
7. Подключить городские данные к Codex
|
|
28
|
+
8. Мой домофон Уфанет
|
|
29
|
+
9. Мой домофон Дом.ру
|
|
30
|
+
10. Мой домофон Ростелеком
|
|
28
31
|
|
|
29
32
|
Локальный AI
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
11. IOLA локальная модель
|
|
34
|
+
12. Ollama
|
|
32
35
|
|
|
33
36
|
Российские AI и сервисы
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
13. GigaChat API
|
|
38
|
+
14. Yandex Connector
|
|
39
|
+
15. Yandex Cloud Connector
|
|
37
40
|
|
|
38
41
|
Зарубежные AI
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
16. OpenAI API
|
|
43
|
+
17. OpenRouter API
|
|
41
44
|
|
|
42
45
|
Codex
|
|
43
|
-
|
|
46
|
+
18. Codex CLI
|
|
44
47
|
```
|
|
45
48
|
|
|
46
49
|
## Базовая настройка
|
|
@@ -87,9 +90,25 @@ iola index folder ./docs
|
|
|
87
90
|
|
|
88
91
|
Для обычной работы `iola-cli` с городскими слоями этот пункт не обязателен: сам CLI уже ходит к городскому API/MCP-шлюзу напрямую.
|
|
89
92
|
|
|
93
|
+
### 8. Мой домофон Уфанет
|
|
94
|
+
|
|
95
|
+
Подключает личный домофон Уфанет по номеру договора и паролю. Секреты хранятся только локально в `~/.iola/secrets.json`.
|
|
96
|
+
|
|
97
|
+
После настройки доступны команды `/ufanet`, `iola ufanet intercoms`, `iola ufanet history`, `iola ufanet cameras`, `iola ufanet open ID`. Открытие двери всегда требует явного подтверждения.
|
|
98
|
+
|
|
99
|
+
Инструкция: [Мой домофон](Мой-домофон).
|
|
100
|
+
|
|
101
|
+
### 9. Мой домофон Дом.ру
|
|
102
|
+
|
|
103
|
+
Пункт-заготовка. Провайдер виден в городских сервисах, но API пока не реализован.
|
|
104
|
+
|
|
105
|
+
### 10. Мой домофон Ростелеком
|
|
106
|
+
|
|
107
|
+
Пункт-заготовка. Провайдер виден в городских сервисах, но API пока не реализован.
|
|
108
|
+
|
|
90
109
|
## Локальный AI
|
|
91
110
|
|
|
92
|
-
###
|
|
111
|
+
### 11. IOLA локальная модель
|
|
93
112
|
|
|
94
113
|
Проверяет штатную локальную модель IOLA и готовит ее к работе через доступный runtime.
|
|
95
114
|
|
|
@@ -97,7 +116,7 @@ iola index folder ./docs
|
|
|
97
116
|
|
|
98
117
|
После настройки локальную модель можно менять в интерактивном агенте через `/model`. Помимо штатной IOLA-модели можно выбрать установленную или рекомендуемую Ollama-модель, либо вручную ввести имя любой модели из библиотеки Ollama.
|
|
99
118
|
|
|
100
|
-
###
|
|
119
|
+
### 12. Ollama
|
|
101
120
|
|
|
102
121
|
Опциональный локальный runtime для выбора сторонних моделей из библиотеки Ollama.
|
|
103
122
|
|
|
@@ -105,13 +124,13 @@ iola index folder ./docs
|
|
|
105
124
|
|
|
106
125
|
## Российские AI и сервисы
|
|
107
126
|
|
|
108
|
-
###
|
|
127
|
+
### 13. GigaChat API
|
|
109
128
|
|
|
110
129
|
Настраивает профиль GigaChat и сохраняет authorization key локально у пользователя.
|
|
111
130
|
|
|
112
131
|
Российский провайдер вызывается напрямую, без gateway/proxy.
|
|
113
132
|
|
|
114
|
-
###
|
|
133
|
+
### 14. Yandex Connector
|
|
115
134
|
|
|
116
135
|
Настраивает единый коннектор пользовательских сервисов Яндекса.
|
|
117
136
|
|
|
@@ -126,7 +145,7 @@ iola index folder ./docs
|
|
|
126
145
|
|
|
127
146
|
Инструкция: [Yandex Connector](Yandex-Connector).
|
|
128
147
|
|
|
129
|
-
###
|
|
148
|
+
### 15. Yandex Cloud Connector
|
|
130
149
|
|
|
131
150
|
Настраивает Yandex Cloud Connector: геокодер и, при необходимости, YandexGPT.
|
|
132
151
|
|
|
@@ -140,13 +159,13 @@ iola index folder ./docs
|
|
|
140
159
|
|
|
141
160
|
## Зарубежные AI
|
|
142
161
|
|
|
143
|
-
###
|
|
162
|
+
### 16. OpenAI API
|
|
144
163
|
|
|
145
164
|
Настраивает профиль OpenAI и сохраняет API-ключ локально у пользователя.
|
|
146
165
|
|
|
147
166
|
После сохранения ключа мастер предлагает выбрать модель из доступного списка.
|
|
148
167
|
|
|
149
|
-
###
|
|
168
|
+
### 17. OpenRouter API
|
|
150
169
|
|
|
151
170
|
Настраивает профиль OpenRouter и сохраняет API-ключ локально у пользователя.
|
|
152
171
|
|
|
@@ -154,7 +173,7 @@ iola index folder ./docs
|
|
|
154
173
|
|
|
155
174
|
## Codex
|
|
156
175
|
|
|
157
|
-
###
|
|
176
|
+
### 18. Codex CLI
|
|
158
177
|
|
|
159
178
|
Проверяет наличие Codex CLI и авторизации. Если Codex уже установлен и вход выполнен, пункт показывается как `готово`.
|
|
160
179
|
|
package/wiki//320/234/320/276/320/271-/320/264/320/276/320/274/320/276/321/204/320/276/320/275.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Мой домофон
|
|
2
|
+
|
|
3
|
+
`iola-cli` поддерживает личные домофонные сервисы пользователя.
|
|
4
|
+
|
|
5
|
+
## Уфанет
|
|
6
|
+
|
|
7
|
+
Рабочий провайдер:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
iola ufanet setup
|
|
11
|
+
iola ufanet status
|
|
12
|
+
iola ufanet intercoms
|
|
13
|
+
iola ufanet history
|
|
14
|
+
iola ufanet cameras
|
|
15
|
+
iola ufanet links UUID
|
|
16
|
+
iola ufanet open ID
|
|
17
|
+
iola ufanet delete
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
При настройке нужны:
|
|
21
|
+
|
|
22
|
+
- номер договора Уфанет;
|
|
23
|
+
- пароль.
|
|
24
|
+
|
|
25
|
+
Они сохраняются только локально:
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
~/.iola/secrets.json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Можно также использовать переменные окружения:
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
UFANET_CONTRACT
|
|
35
|
+
UFANET_PASSWORD
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Доступные сценарии:
|
|
39
|
+
|
|
40
|
+
- показать доступные домофоны;
|
|
41
|
+
- открыть домофон по ID;
|
|
42
|
+
- показать историю звонков;
|
|
43
|
+
- получить ссылку на запись звонка по UUID;
|
|
44
|
+
- показать камеры;
|
|
45
|
+
- показать RTSP-ссылки камер;
|
|
46
|
+
- удалить локальное подключение.
|
|
47
|
+
|
|
48
|
+
Открытие двери всегда требует явного подтверждения пользователя.
|
|
49
|
+
|
|
50
|
+
## Дом.ру
|
|
51
|
+
|
|
52
|
+
Команда-заготовка:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
iola dom_ru
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Провайдер пока в разработке. API/авторизация будут добавлены после исследования.
|
|
59
|
+
|
|
60
|
+
## Ростелеком
|
|
61
|
+
|
|
62
|
+
Команда-заготовка:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
iola rostelecom
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Провайдер пока в разработке. API/авторизация будут добавлены после исследования.
|
|
@@ -403,6 +403,43 @@ iola yandex go open --from "Йошкар-Ола, Красноармейская
|
|
|
403
403
|
|
|
404
404
|
Цена, повышенный спрос, детское кресло, назначение машины и отмена поездки через API ждут партнерский `clid/apikey` от Яндекса.
|
|
405
405
|
|
|
406
|
+
## Мой домофон
|
|
407
|
+
|
|
408
|
+
Эти skills работают с личным домофоном пользователя.
|
|
409
|
+
|
|
410
|
+
### Уфанет
|
|
411
|
+
|
|
412
|
+
Реализовано:
|
|
413
|
+
|
|
414
|
+
- проверить подключение Уфанет;
|
|
415
|
+
- показать список доступных домофонов;
|
|
416
|
+
- открыть выбранный домофон по ID;
|
|
417
|
+
- показать историю звонков;
|
|
418
|
+
- получить ссылку на запись звонка по UUID;
|
|
419
|
+
- показать камеры и RTSP-ссылки;
|
|
420
|
+
- удалить локальное подключение.
|
|
421
|
+
|
|
422
|
+
Открытие двери всегда требует явного подтверждения пользователя.
|
|
423
|
+
|
|
424
|
+
Команды:
|
|
425
|
+
|
|
426
|
+
```bash
|
|
427
|
+
iola ufanet setup
|
|
428
|
+
iola ufanet intercoms
|
|
429
|
+
iola ufanet open ID
|
|
430
|
+
iola ufanet history
|
|
431
|
+
iola ufanet cameras
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Дом.ру и Ростелеком
|
|
435
|
+
|
|
436
|
+
Добавлены как направления `в разработке`:
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
iola dom_ru
|
|
440
|
+
iola rostelecom
|
|
441
|
+
```
|
|
442
|
+
|
|
406
443
|
## Yandex Connector backlog
|
|
407
444
|
|
|
408
445
|
Эти сценарии зафиксированы для следующего этапа. Они не должны оформлять заказ, списывать деньги или нажимать финальную кнопку вместо пользователя.
|