@iola_adm/iola-cli 0.2.36 → 0.2.37
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 +9 -6
- package/package.json +1 -1
- package/skills/yandex-services/SKILL.md +8 -3
- package/src/cli.js +466 -37
- package/wiki/AI-/320/277/321/200/320/276/321/204/320/270/320/273/320/270.md +12 -1
- package/wiki/Home.md +2 -0
- package/wiki/Skills-/320/270-toolsets.md +1 -1
- package/wiki/Yandex-Cloud-Connector.md +127 -0
- package/wiki/Yandex-Connector.md +25 -6
- package/wiki/Yandex-Geocoder-API-key.md +7 -1
- package/wiki//320/232/320/276/320/274/320/260/320/275/320/264/321/213.md +14 -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 +10 -14
- package/wiki//320/240/320/265/321/210/320/265/320/275/320/270/320/265-/320/277/321/200/320/276/320/261/320/273/320/265/320/274.md +2 -2
- 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 +18 -3
package/src/cli.js
CHANGED
|
@@ -98,25 +98,25 @@ const YANDEX_CONNECTOR_SERVICES = {
|
|
|
98
98
|
hint: "встречи через календарное событие, если поддерживается",
|
|
99
99
|
},
|
|
100
100
|
cloud: {
|
|
101
|
-
title: "Yandex Cloud",
|
|
101
|
+
title: "Yandex Cloud Connector",
|
|
102
102
|
category: "cloud-platform",
|
|
103
103
|
scope: "",
|
|
104
|
-
status: "
|
|
105
|
-
hint: "
|
|
104
|
+
status: "ready",
|
|
105
|
+
hint: "геокодинг и YandexGPT через ключи Yandex Cloud",
|
|
106
106
|
},
|
|
107
107
|
maps: {
|
|
108
|
-
title: "Яндекс
|
|
108
|
+
title: "Яндекс Геокодер",
|
|
109
109
|
category: "maps",
|
|
110
110
|
scope: "",
|
|
111
|
-
status: "
|
|
112
|
-
hint: "
|
|
111
|
+
status: "ready",
|
|
112
|
+
hint: "адреса, координаты, маршруты и ссылки на карты",
|
|
113
113
|
},
|
|
114
114
|
taxi: {
|
|
115
115
|
title: "Яндекс Go / Такси",
|
|
116
116
|
category: "mobility",
|
|
117
117
|
scope: "",
|
|
118
|
-
status: "
|
|
119
|
-
hint: "
|
|
118
|
+
status: "ready",
|
|
119
|
+
hint: "deeplink и маршрут; заказ через API ожидает clid/apikey",
|
|
120
120
|
},
|
|
121
121
|
market: {
|
|
122
122
|
title: "Яндекс Маркет",
|
|
@@ -248,6 +248,8 @@ const YANDEX_TOOLS = [
|
|
|
248
248
|
"yandex_daily_digest",
|
|
249
249
|
"yandex_calendar_reminders_tick",
|
|
250
250
|
"yandex_disk_maintenance_tick",
|
|
251
|
+
"yandex_cloud_status",
|
|
252
|
+
"yandex_go_deeplink",
|
|
251
253
|
];
|
|
252
254
|
const ALL_LOCAL_TOOLS = [...LOCAL_TOOLS, ...FILE_TOOLS, ...YANDEX_TOOLS, ...USER_SKILL_TOOLS];
|
|
253
255
|
const ALL_TOOL_ALIASES = [...ALL_LOCAL_TOOLS, ...LEGACY_LOCAL_TOOLS];
|
|
@@ -3433,6 +3435,16 @@ async function handleYandex(args) {
|
|
|
3433
3435
|
return;
|
|
3434
3436
|
}
|
|
3435
3437
|
|
|
3438
|
+
if (action === "cloud" || action === "cloud-connector" || action === "yc") {
|
|
3439
|
+
await handleYandexCloudConnector([target, ...rest].filter(Boolean));
|
|
3440
|
+
return;
|
|
3441
|
+
}
|
|
3442
|
+
|
|
3443
|
+
if (action === "go" || action === "taxi" || action === "такси") {
|
|
3444
|
+
await handleYandexGo([target, ...rest].filter(Boolean));
|
|
3445
|
+
return;
|
|
3446
|
+
}
|
|
3447
|
+
|
|
3436
3448
|
if (action === "mail-watch" || action === "mailwatch" || action === "watch-mail") {
|
|
3437
3449
|
await handleYandexMailWatch([target, ...rest].filter(Boolean));
|
|
3438
3450
|
return;
|
|
@@ -3493,6 +3505,11 @@ async function handleYandex(args) {
|
|
|
3493
3505
|
iola yandex menu
|
|
3494
3506
|
iola yandex status|doctor
|
|
3495
3507
|
iola yandex services
|
|
3508
|
+
iola yandex cloud setup|status|doctor|delete
|
|
3509
|
+
iola yandex cloud enable geocoder yandexgpt
|
|
3510
|
+
iola yandex cloud disable yandexgpt
|
|
3511
|
+
iola yandex go link --from "Адрес" --to "Адрес" [--tariff econom]
|
|
3512
|
+
iola yandex go open --from "Адрес" --to "Адрес" [--tariff econom]
|
|
3496
3513
|
iola yandex mail-watch on|off|status|tick [--minutes 5]
|
|
3497
3514
|
iola yandex daily-digest on|off|status|tick [--time 09:00] [--email]
|
|
3498
3515
|
iola yandex calendar-reminders on|off|status|tick [--minutes 15]
|
|
@@ -3527,6 +3544,332 @@ function printYandexServices(options = {}) {
|
|
|
3527
3544
|
]);
|
|
3528
3545
|
}
|
|
3529
3546
|
|
|
3547
|
+
async function handleYandexCloudConnector(args = []) {
|
|
3548
|
+
const [action = "status", ...rest] = args;
|
|
3549
|
+
const options = parseOptions(rest);
|
|
3550
|
+
|
|
3551
|
+
if (action === "setup" || action === "connect" || action === "onboard") {
|
|
3552
|
+
await setupYandexCloudConnector(options);
|
|
3553
|
+
return;
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
if (action === "status" || action === "doctor" || action === "check") {
|
|
3557
|
+
await printYandexCloudConnectorStatus({ check: action !== "status" || options.check });
|
|
3558
|
+
return;
|
|
3559
|
+
}
|
|
3560
|
+
|
|
3561
|
+
if (action === "enable" || action === "disable") {
|
|
3562
|
+
const services = options._.length ? options._ : rest.filter((item) => item && !String(item).startsWith("--"));
|
|
3563
|
+
await updateYandexCloudEnabledServices(services, action === "enable");
|
|
3564
|
+
return;
|
|
3565
|
+
}
|
|
3566
|
+
|
|
3567
|
+
if (action === "delete" || action === "disconnect" || action === "remove") {
|
|
3568
|
+
const ok = !process.stdin.isTTY || await askYesNo("Удалить локальные ключи и настройки Yandex Cloud Connector? [y/N] ", false);
|
|
3569
|
+
if (!ok) {
|
|
3570
|
+
console.log("Удаление отменено.");
|
|
3571
|
+
return;
|
|
3572
|
+
}
|
|
3573
|
+
await deleteYandexCloudConnector();
|
|
3574
|
+
return;
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3577
|
+
if (action === "open") {
|
|
3578
|
+
await openUrl("https://console.yandex.cloud/");
|
|
3579
|
+
return;
|
|
3580
|
+
}
|
|
3581
|
+
|
|
3582
|
+
throw new Error("Команды: iola yandex cloud setup | status | doctor | enable geocoder yandexgpt | disable yandexgpt | delete");
|
|
3583
|
+
}
|
|
3584
|
+
|
|
3585
|
+
async function setupYandexCloudConnector(options = {}) {
|
|
3586
|
+
console.log("Yandex Cloud Connector: геокодинг и YandexGPT.");
|
|
3587
|
+
console.log("Геокодер будет включен по умолчанию. YandexGPT можно выбрать в /model после сохранения ключей.");
|
|
3588
|
+
if (process.stdin.isTTY) {
|
|
3589
|
+
const openConsole = await askYesNo("Открыть Yandex Cloud Console для получения ключей? [Y/n] ", true);
|
|
3590
|
+
if (openConsole) await openUrl("https://console.yandex.cloud/");
|
|
3591
|
+
}
|
|
3592
|
+
|
|
3593
|
+
const secrets = await loadSecrets();
|
|
3594
|
+
const currentGeocoder = secrets.yandexGeocoder?.apiKey || secrets.yandexCloud?.geocoderApiKey || "";
|
|
3595
|
+
const currentGptKey = secrets.yandexgpt?.apiKey || secrets.yandexCloud?.yandexgptApiKey || "";
|
|
3596
|
+
const currentFolderId = secrets.yandexgpt?.folderId || secrets.yandexCloud?.folderId || "";
|
|
3597
|
+
|
|
3598
|
+
if (!process.stdin.isTTY) {
|
|
3599
|
+
await saveYandexCloudEnabledServices(["geocoder"]);
|
|
3600
|
+
console.log("Интерактивный ввод ключей недоступен. Запустите: iola yandex cloud setup");
|
|
3601
|
+
return;
|
|
3602
|
+
}
|
|
3603
|
+
|
|
3604
|
+
const geocoderKey = (await askText(`YANDEX_GEOCODER_API_KEY${currentGeocoder ? " [уже сохранен, Enter - оставить]" : ""}: `)).trim() || currentGeocoder;
|
|
3605
|
+
if (!geocoderKey) throw new Error("Для Cloud Connector нужен хотя бы Geocoder API key.");
|
|
3606
|
+
|
|
3607
|
+
const setupGpt = await askYesNo(`Настроить YandexGPT сейчас${currentGptKey && currentFolderId ? " (уже сохранен)" : ""}? [y/N] `, Boolean(currentGptKey && currentFolderId));
|
|
3608
|
+
let yandexgptApiKey = currentGptKey;
|
|
3609
|
+
let folderId = currentFolderId;
|
|
3610
|
+
if (setupGpt) {
|
|
3611
|
+
yandexgptApiKey = (await askText(`YANDEXGPT_API_KEY${currentGptKey ? " [Enter - оставить]" : ""}: `)).trim() || currentGptKey;
|
|
3612
|
+
folderId = (await askText(`YANDEXGPT_FOLDER_ID${currentFolderId ? " [Enter - оставить]" : ""}: `)).trim() || currentFolderId;
|
|
3613
|
+
if (!yandexgptApiKey || !folderId) throw new Error("Для YandexGPT нужны API key и folder ID.");
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
await saveYandexCloudConnectorSecrets({ geocoderApiKey: geocoderKey, yandexgptApiKey, folderId });
|
|
3617
|
+
await saveYandexCloudEnabledServices(setupGpt ? ["geocoder", "yandexgpt"] : ["geocoder"]);
|
|
3618
|
+
console.log(`Yandex Cloud Connector сохранен локально: ${SECRETS_FILE}`);
|
|
3619
|
+
await printYandexCloudConnectorStatus({ check: true });
|
|
3620
|
+
}
|
|
3621
|
+
|
|
3622
|
+
async function saveYandexCloudConnectorSecrets({ geocoderApiKey, yandexgptApiKey, folderId }) {
|
|
3623
|
+
const secrets = await loadSecrets();
|
|
3624
|
+
secrets.yandexCloud = {
|
|
3625
|
+
...(secrets.yandexCloud || {}),
|
|
3626
|
+
geocoderApiKey: geocoderApiKey || secrets.yandexCloud?.geocoderApiKey || "",
|
|
3627
|
+
yandexgptApiKey: yandexgptApiKey || secrets.yandexCloud?.yandexgptApiKey || "",
|
|
3628
|
+
folderId: folderId || secrets.yandexCloud?.folderId || "",
|
|
3629
|
+
updatedAt: new Date().toISOString(),
|
|
3630
|
+
};
|
|
3631
|
+
if (geocoderApiKey) secrets.yandexGeocoder = { ...(secrets.yandexGeocoder || {}), apiKey: geocoderApiKey };
|
|
3632
|
+
if (yandexgptApiKey || folderId) {
|
|
3633
|
+
secrets.yandexgpt = {
|
|
3634
|
+
...(secrets.yandexgpt || {}),
|
|
3635
|
+
apiKey: yandexgptApiKey || secrets.yandexgpt?.apiKey || "",
|
|
3636
|
+
folderId: folderId || secrets.yandexgpt?.folderId || "",
|
|
3637
|
+
};
|
|
3638
|
+
}
|
|
3639
|
+
await saveSecrets(secrets);
|
|
3640
|
+
}
|
|
3641
|
+
|
|
3642
|
+
async function saveYandexCloudEnabledServices(services) {
|
|
3643
|
+
const config = await loadConfig();
|
|
3644
|
+
const normalized = normalizeYandexCloudServiceList(services);
|
|
3645
|
+
await saveConfig({
|
|
3646
|
+
yandex: {
|
|
3647
|
+
...(config.yandex || {}),
|
|
3648
|
+
cloudConnector: {
|
|
3649
|
+
...(config.yandex?.cloudConnector || {}),
|
|
3650
|
+
enabledServices: normalized,
|
|
3651
|
+
updatedAt: new Date().toISOString(),
|
|
3652
|
+
},
|
|
3653
|
+
},
|
|
3654
|
+
});
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3657
|
+
async function updateYandexCloudEnabledServices(rawServices, enabled) {
|
|
3658
|
+
const config = await loadConfig();
|
|
3659
|
+
const current = new Set(config.yandex?.cloudConnector?.enabledServices || ["geocoder"]);
|
|
3660
|
+
for (const service of normalizeYandexCloudServiceList(rawServices)) {
|
|
3661
|
+
if (enabled) current.add(service);
|
|
3662
|
+
else current.delete(service);
|
|
3663
|
+
}
|
|
3664
|
+
if (current.size > 0 && !current.has("geocoder")) current.add("geocoder");
|
|
3665
|
+
await saveYandexCloudEnabledServices([...current]);
|
|
3666
|
+
console.log(`Yandex Cloud services: ${[...current].join(", ") || "-"}`);
|
|
3667
|
+
}
|
|
3668
|
+
|
|
3669
|
+
function normalizeYandexCloudServiceList(services = []) {
|
|
3670
|
+
const aliases = {
|
|
3671
|
+
geo: "geocoder",
|
|
3672
|
+
geocoder: "geocoder",
|
|
3673
|
+
maps: "geocoder",
|
|
3674
|
+
map: "geocoder",
|
|
3675
|
+
"yandex-geocoder": "geocoder",
|
|
3676
|
+
gpt: "yandexgpt",
|
|
3677
|
+
yandexgpt: "yandexgpt",
|
|
3678
|
+
"yandex-gpt": "yandexgpt",
|
|
3679
|
+
model: "yandexgpt",
|
|
3680
|
+
models: "yandexgpt",
|
|
3681
|
+
};
|
|
3682
|
+
return [...new Set([].concat(services || []).map((item) => aliases[String(item || "").toLocaleLowerCase("ru-RU")] || "").filter(Boolean))];
|
|
3683
|
+
}
|
|
3684
|
+
|
|
3685
|
+
async function printYandexCloudConnectorStatus(options = {}) {
|
|
3686
|
+
const [config, secrets] = await Promise.all([loadConfig(), loadSecrets()]);
|
|
3687
|
+
const enabled = new Set(config.yandex?.cloudConnector?.enabledServices || []);
|
|
3688
|
+
const geocoderKey = process.env.YANDEX_GEOCODER_API_KEY || process.env.YANDEX_MAPS_API_KEY || secrets.yandexCloud?.geocoderApiKey || secrets.yandexGeocoder?.apiKey || "";
|
|
3689
|
+
const gptKey = process.env.YANDEXGPT_API_KEY || process.env.YANDEX_CLOUD_API_KEY || secrets.yandexCloud?.yandexgptApiKey || secrets.yandexgpt?.apiKey || "";
|
|
3690
|
+
const folderId = process.env.YANDEXGPT_FOLDER_ID || process.env.YANDEX_CLOUD_FOLDER_ID || secrets.yandexCloud?.folderId || secrets.yandexgpt?.folderId || "";
|
|
3691
|
+
const rows = [
|
|
3692
|
+
{ service: "geocoder", enabled: enabled.has("geocoder") ? "yes" : "no", configured: geocoderKey ? "yes" : "no", source: geocoderKey ? "local/env" : "-", hint: "адреса и координаты" },
|
|
3693
|
+
{ service: "yandexgpt", enabled: enabled.has("yandexgpt") ? "yes" : "no", configured: gptKey && folderId ? "yes" : "no", source: gptKey && folderId ? "local/env" : "-", hint: "модели YandexGPT" },
|
|
3694
|
+
];
|
|
3695
|
+
printTable(rows, [
|
|
3696
|
+
["service", "Сервис"],
|
|
3697
|
+
["enabled", "Вкл"],
|
|
3698
|
+
["configured", "Настроен"],
|
|
3699
|
+
["source", "Ключ"],
|
|
3700
|
+
["hint", "Суть"],
|
|
3701
|
+
]);
|
|
3702
|
+
if (options.check) {
|
|
3703
|
+
await checkYandexGeocoderKey({ print: true });
|
|
3704
|
+
if (gptKey && folderId) console.log("YandexGPT: ключ и folder ID найдены.");
|
|
3705
|
+
else console.log("YandexGPT: не настроен.");
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
|
|
3709
|
+
async function deleteYandexCloudConnector() {
|
|
3710
|
+
const secrets = await loadSecrets();
|
|
3711
|
+
delete secrets.yandexCloud;
|
|
3712
|
+
delete secrets.yandexGeocoder;
|
|
3713
|
+
delete secrets.yandexgpt;
|
|
3714
|
+
await saveSecrets(secrets);
|
|
3715
|
+
const config = await loadConfig();
|
|
3716
|
+
await saveConfig({
|
|
3717
|
+
yandex: {
|
|
3718
|
+
...(config.yandex || {}),
|
|
3719
|
+
cloudConnector: { enabledServices: [], updatedAt: new Date().toISOString() },
|
|
3720
|
+
},
|
|
3721
|
+
});
|
|
3722
|
+
console.log("Yandex Cloud Connector удален локально.");
|
|
3723
|
+
}
|
|
3724
|
+
|
|
3725
|
+
async function handleYandexGo(args = []) {
|
|
3726
|
+
const [action = "link", ...rest] = args;
|
|
3727
|
+
const options = parseOptions(rest);
|
|
3728
|
+
if (action === "link" || action === "deeplink" || action === "url") {
|
|
3729
|
+
await ensureYandexGoGeocoderReady();
|
|
3730
|
+
const result = await buildYandexGoDeeplinkFromOptions(options);
|
|
3731
|
+
printYandexGoDeeplinkResult(result);
|
|
3732
|
+
return;
|
|
3733
|
+
}
|
|
3734
|
+
if (action === "open" || action === "prepare" || action === "route") {
|
|
3735
|
+
await ensureYandexGoGeocoderReady();
|
|
3736
|
+
const result = await buildYandexGoDeeplinkFromOptions(options);
|
|
3737
|
+
printYandexGoDeeplinkResult(result);
|
|
3738
|
+
await openUrl(result.url);
|
|
3739
|
+
return;
|
|
3740
|
+
}
|
|
3741
|
+
if (action === "status") {
|
|
3742
|
+
printKeyValue({
|
|
3743
|
+
deeplink: "ready",
|
|
3744
|
+
priceApi: "ожидает clid/apikey от Яндекса",
|
|
3745
|
+
orderApi: "не подключен",
|
|
3746
|
+
});
|
|
3747
|
+
return;
|
|
3748
|
+
}
|
|
3749
|
+
throw new Error('Команды: iola yandex go link --from "Адрес" --to "Адрес" [--tariff econom] | open --from "Адрес" --to "Адрес"');
|
|
3750
|
+
}
|
|
3751
|
+
|
|
3752
|
+
async function ensureYandexGoGeocoderReady() {
|
|
3753
|
+
if (await getYandexGeocoderKey()) return true;
|
|
3754
|
+
throw new Error([
|
|
3755
|
+
"Для Yandex Go deeplink нужен ключ Yandex Geocoder API: адреса нужно превратить в координаты.",
|
|
3756
|
+
"Откройте мастер настройки и запустите Yandex Cloud Connector (геокодинг и YandexGPT):",
|
|
3757
|
+
" iola master",
|
|
3758
|
+
"или напрямую:",
|
|
3759
|
+
" iola yandex cloud setup",
|
|
3760
|
+
"После подключения повторите команду такси.",
|
|
3761
|
+
].join("\n"));
|
|
3762
|
+
}
|
|
3763
|
+
|
|
3764
|
+
async function buildYandexGoDeeplinkFromOptions(options = {}) {
|
|
3765
|
+
const from = options.from || options._?.[0] || "";
|
|
3766
|
+
const to = options.to || options._?.[1] || "";
|
|
3767
|
+
if (!from || !to) throw new Error('Укажите маршрут: iola yandex go link --from "Медведево, Школьная 15" --to "Медведево, Советская 20"');
|
|
3768
|
+
const fromPoint = await resolveYandexGoPoint(from);
|
|
3769
|
+
const toPoint = await resolveYandexGoPoint(to);
|
|
3770
|
+
const tariff = normalizeYandexGoTariff(options.tariff || options.class || options.level || "econom");
|
|
3771
|
+
return {
|
|
3772
|
+
from,
|
|
3773
|
+
to,
|
|
3774
|
+
fromPoint,
|
|
3775
|
+
toPoint,
|
|
3776
|
+
tariff,
|
|
3777
|
+
url: buildYandexGoDeeplink({
|
|
3778
|
+
fromPoint,
|
|
3779
|
+
toPoint,
|
|
3780
|
+
tariff,
|
|
3781
|
+
ref: options.ref || "iola-cli",
|
|
3782
|
+
lang: options.lang || "ru",
|
|
3783
|
+
}),
|
|
3784
|
+
};
|
|
3785
|
+
}
|
|
3786
|
+
|
|
3787
|
+
async function resolveYandexGoPoint(query) {
|
|
3788
|
+
const parsed = parseLonLat(query);
|
|
3789
|
+
if (parsed) return { lon: parsed.lon, lat: parsed.lat, label: `${parsed.lat}, ${parsed.lon}`, address: "" };
|
|
3790
|
+
const point = await callYandexGeocoder(query);
|
|
3791
|
+
const coords = parseCoordinates(point?.coordinates);
|
|
3792
|
+
if (!Number.isFinite(coords.lat) || !Number.isFinite(coords.lon)) throw new Error(`Не смог получить координаты: ${query}`);
|
|
3793
|
+
return { lon: coords.lon, lat: coords.lat, label: point.name || query, address: point.address || "" };
|
|
3794
|
+
}
|
|
3795
|
+
|
|
3796
|
+
function parseLonLat(value) {
|
|
3797
|
+
const text = String(value || "").trim();
|
|
3798
|
+
const match = text.match(/^(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)$/u);
|
|
3799
|
+
if (!match) return null;
|
|
3800
|
+
const first = Number(match[1]);
|
|
3801
|
+
const second = Number(match[2]);
|
|
3802
|
+
if (!Number.isFinite(first) || !Number.isFinite(second)) return null;
|
|
3803
|
+
if (Math.abs(first) <= 90 && Math.abs(second) > 90) return { lat: first, lon: second };
|
|
3804
|
+
return { lon: first, lat: second };
|
|
3805
|
+
}
|
|
3806
|
+
|
|
3807
|
+
function normalizeYandexGoTariff(value) {
|
|
3808
|
+
const text = String(value || "").toLocaleLowerCase("ru-RU").replace(/\s+/g, "");
|
|
3809
|
+
const aliases = {
|
|
3810
|
+
economy: "econom",
|
|
3811
|
+
econom: "econom",
|
|
3812
|
+
эконом: "econom",
|
|
3813
|
+
comfort: "business",
|
|
3814
|
+
комфорт: "business",
|
|
3815
|
+
business: "business",
|
|
3816
|
+
comfortplus: "comfortplus",
|
|
3817
|
+
"комфорт+": "comfortplus",
|
|
3818
|
+
komfortplus: "comfortplus",
|
|
3819
|
+
minivan: "minivan",
|
|
3820
|
+
минивен: "minivan",
|
|
3821
|
+
vip: "vip",
|
|
3822
|
+
бизнес: "vip",
|
|
3823
|
+
детский: "econom",
|
|
3824
|
+
child: "econom",
|
|
3825
|
+
children: "econom",
|
|
3826
|
+
};
|
|
3827
|
+
return aliases[text] || "econom";
|
|
3828
|
+
}
|
|
3829
|
+
|
|
3830
|
+
function buildYandexGoDeeplink({ fromPoint, toPoint, tariff = "econom", ref = "iola-cli", lang = "ru" }) {
|
|
3831
|
+
const url = new URL("https://3.redirect.appmetrica.yandex.com/route");
|
|
3832
|
+
url.searchParams.set("start-lat", String(fromPoint.lat));
|
|
3833
|
+
url.searchParams.set("start-lon", String(fromPoint.lon));
|
|
3834
|
+
url.searchParams.set("end-lat", String(toPoint.lat));
|
|
3835
|
+
url.searchParams.set("end-lon", String(toPoint.lon));
|
|
3836
|
+
url.searchParams.set("tariffClass", tariff);
|
|
3837
|
+
url.searchParams.set("level", tariff);
|
|
3838
|
+
url.searchParams.set("ref", ref);
|
|
3839
|
+
url.searchParams.set("lang", lang);
|
|
3840
|
+
url.searchParams.set("appmetrica_tracking_id", "1178268795219780156");
|
|
3841
|
+
return url.toString();
|
|
3842
|
+
}
|
|
3843
|
+
|
|
3844
|
+
function printYandexGoDeeplinkResult(result) {
|
|
3845
|
+
console.log(formatYandexGoDeeplinkResult(result));
|
|
3846
|
+
}
|
|
3847
|
+
|
|
3848
|
+
function formatYandexGoDeeplinkResult(result) {
|
|
3849
|
+
return [
|
|
3850
|
+
`Откуда: ${result.fromPoint.address || result.from}`,
|
|
3851
|
+
`Куда: ${result.toPoint.address || result.to}`,
|
|
3852
|
+
`Тариф: ${result.tariff}`,
|
|
3853
|
+
`Ссылка Яндекс Go: ${result.url}`,
|
|
3854
|
+
"Детское кресло и повышенный спрос deeplink не кодирует напрямую; это выбирается/проверяется в интерфейсе Яндекс Go или через taxi_info после получения clid/apikey.",
|
|
3855
|
+
].join("\n");
|
|
3856
|
+
}
|
|
3857
|
+
|
|
3858
|
+
function extractYandexGoRouteFromText(text) {
|
|
3859
|
+
const source = String(text || "").trim();
|
|
3860
|
+
const tariffMatch = source.match(/(эконом|комфорт\+?|комфорт плюс|бизнес|минивен|детск\w*|econom|business|comfortplus|minivan|vip)/iu);
|
|
3861
|
+
const cleaned = source
|
|
3862
|
+
.replace(/^(?:построй|создай|дай|открой|сделай|подготовь|вызови|закажи)\s+/iu, "")
|
|
3863
|
+
.replace(/\b(?:яндекс\s*go|яндекс\s*го|такси|маршрут|ссылк[ау]?|диплинк|deeplink)\b/giu, " ")
|
|
3864
|
+
.replace(/\s+/g, " ")
|
|
3865
|
+
.trim();
|
|
3866
|
+
const match = cleaned.match(/(?:от|из|с)\s+(.+?)\s+(?:до|в|на)\s+(.+)$/iu);
|
|
3867
|
+
if (!match) return { from: "", to: "", tariff: tariffMatch ? normalizeYandexGoTariff(tariffMatch[1]) : "econom" };
|
|
3868
|
+
const from = match[1].replace(/[,.;]\s*$/u, "").trim();
|
|
3869
|
+
const to = match[2].replace(/[,.;]\s*(?:тариф|эконом|комфорт\+?|комфорт плюс|бизнес|минивен|детск\w*).*$/iu, "").trim();
|
|
3870
|
+
return { from, to, tariff: tariffMatch ? normalizeYandexGoTariff(tariffMatch[1]) : "econom" };
|
|
3871
|
+
}
|
|
3872
|
+
|
|
3530
3873
|
async function handleYandexMailWatch(args = []) {
|
|
3531
3874
|
const [action = "status", ...rest] = args;
|
|
3532
3875
|
const options = parseOptions(rest);
|
|
@@ -3777,9 +4120,12 @@ async function chooseYandexServicesMenu() {
|
|
|
3777
4120
|
}
|
|
3778
4121
|
const config = await loadConfig();
|
|
3779
4122
|
const serviceIds = getYandexConnectorMenuServiceIds();
|
|
3780
|
-
const
|
|
4123
|
+
const cloudNumber = serviceIds.length + 1;
|
|
4124
|
+
const goNumber = serviceIds.length + 2;
|
|
4125
|
+
const deleteNumber = serviceIds.length + 3;
|
|
3781
4126
|
const enabled = new Set(config.yandex?.enabledServices?.length ? config.yandex.enabledServices : ["identity", "disk"]);
|
|
3782
4127
|
const authState = await getYandexServiceAuthState();
|
|
4128
|
+
const cloudStatus = await getYandexCloudConnectorSummary();
|
|
3783
4129
|
console.log("Функции Яндекса.");
|
|
3784
4130
|
console.log("Выберите номера функций через запятую:");
|
|
3785
4131
|
serviceIds.forEach((id, index) => {
|
|
@@ -3789,6 +4135,8 @@ async function chooseYandexServicesMenu() {
|
|
|
3789
4135
|
const authLabel = auth?.hasToken ? "подключено" : (auth?.authorized ? "нужен вход" : "нет прав");
|
|
3790
4136
|
console.log(`${index + 1}. [${marker}] ${service.title} - ${service.hint} (${service.status}, ${authLabel})`);
|
|
3791
4137
|
});
|
|
4138
|
+
console.log(`${cloudNumber}. Yandex Cloud Connector - геокодинг и YandexGPT (${cloudStatus})`);
|
|
4139
|
+
console.log(`${goNumber}. Yandex Go / Такси - deeplink и маршрут (готово, заказ через API ожидает clid/apikey)`);
|
|
3792
4140
|
console.log(`${deleteNumber}. Удалить подключение-коннектор`);
|
|
3793
4141
|
console.log("0. Отмена");
|
|
3794
4142
|
const defaults = serviceIds.map((id, index) => enabled.has(id) ? String(index + 1) : "").filter(Boolean);
|
|
@@ -3808,6 +4156,17 @@ async function chooseYandexServicesMenu() {
|
|
|
3808
4156
|
await deleteYandexConnectorToken();
|
|
3809
4157
|
return;
|
|
3810
4158
|
}
|
|
4159
|
+
if (selectedNumbers.includes(String(cloudNumber))) {
|
|
4160
|
+
if (selectedNumbers.length > 1) throw new Error("Yandex Cloud Connector выбирается отдельно, без других пунктов.");
|
|
4161
|
+
await chooseYandexCloudConnectorMenu();
|
|
4162
|
+
return;
|
|
4163
|
+
}
|
|
4164
|
+
if (selectedNumbers.includes(String(goNumber))) {
|
|
4165
|
+
if (selectedNumbers.length > 1) throw new Error("Yandex Go выбирается отдельно, без других пунктов.");
|
|
4166
|
+
await handleYandexGo(["status"]);
|
|
4167
|
+
console.log('Для маршрута: iola yandex go open --from "Адрес" --to "Адрес" --tariff econom');
|
|
4168
|
+
return;
|
|
4169
|
+
}
|
|
3811
4170
|
const selected = selectedNumbers.map((item) => {
|
|
3812
4171
|
const index = Number(item) - 1;
|
|
3813
4172
|
if (!Number.isInteger(index) || index < 0 || index >= serviceIds.length) {
|
|
@@ -3825,10 +4184,51 @@ async function chooseYandexServicesMenu() {
|
|
|
3825
4184
|
|
|
3826
4185
|
function getYandexConnectorMenuServiceIds() {
|
|
3827
4186
|
return Object.entries(YANDEX_CONNECTOR_SERVICES)
|
|
3828
|
-
.filter(([, service]) => service.status === "ready" || service.status === "research")
|
|
4187
|
+
.filter(([, service]) => (service.status === "ready" || service.status === "research") && service.scope)
|
|
3829
4188
|
.map(([id]) => id);
|
|
3830
4189
|
}
|
|
3831
4190
|
|
|
4191
|
+
async function chooseYandexCloudConnectorMenu() {
|
|
4192
|
+
const [config, secrets] = await Promise.all([loadConfig(), loadSecrets()]);
|
|
4193
|
+
const enabled = new Set(config.yandex?.cloudConnector?.enabledServices || ["geocoder"]);
|
|
4194
|
+
const geocoderConfigured = Boolean(process.env.YANDEX_GEOCODER_API_KEY || process.env.YANDEX_MAPS_API_KEY || secrets.yandexCloud?.geocoderApiKey || secrets.yandexGeocoder?.apiKey);
|
|
4195
|
+
const gptConfigured = Boolean((process.env.YANDEXGPT_API_KEY || process.env.YANDEX_CLOUD_API_KEY || secrets.yandexCloud?.yandexgptApiKey || secrets.yandexgpt?.apiKey)
|
|
4196
|
+
&& (process.env.YANDEXGPT_FOLDER_ID || process.env.YANDEX_CLOUD_FOLDER_ID || secrets.yandexCloud?.folderId || secrets.yandexgpt?.folderId));
|
|
4197
|
+
console.log("Yandex Cloud Connector.");
|
|
4198
|
+
console.log("1. Настроить/обновить ключи");
|
|
4199
|
+
console.log(`2. [${enabled.has("geocoder") ? "✓" : " "}] Геокодер (${geocoderConfigured ? "ключ есть" : "ключ не задан"})`);
|
|
4200
|
+
console.log(`3. [${enabled.has("yandexgpt") ? "✓" : " "}] YandexGPT (${gptConfigured ? "ключ и folder ID есть" : "не настроено"})`);
|
|
4201
|
+
console.log("4. Проверить подключение");
|
|
4202
|
+
console.log("5. Удалить Cloud Connector");
|
|
4203
|
+
console.log("0. Назад");
|
|
4204
|
+
const answer = (await askText("Номер: ")).trim();
|
|
4205
|
+
if (answer === "0" || !answer) return;
|
|
4206
|
+
if (answer === "1") return setupYandexCloudConnector({});
|
|
4207
|
+
if (answer === "2") {
|
|
4208
|
+
if (enabled.has("geocoder")) await updateYandexCloudEnabledServices(["geocoder"], false);
|
|
4209
|
+
else await updateYandexCloudEnabledServices(["geocoder"], true);
|
|
4210
|
+
return;
|
|
4211
|
+
}
|
|
4212
|
+
if (answer === "3") {
|
|
4213
|
+
if (enabled.has("yandexgpt")) await updateYandexCloudEnabledServices(["yandexgpt"], false);
|
|
4214
|
+
else await updateYandexCloudEnabledServices(["yandexgpt"], true);
|
|
4215
|
+
return;
|
|
4216
|
+
}
|
|
4217
|
+
if (answer === "4") return printYandexCloudConnectorStatus({ check: true });
|
|
4218
|
+
if (answer === "5") return handleYandexCloudConnector(["delete"]);
|
|
4219
|
+
}
|
|
4220
|
+
|
|
4221
|
+
async function getYandexCloudConnectorSummary() {
|
|
4222
|
+
const [config, secrets] = await Promise.all([loadConfig(), loadSecrets()]);
|
|
4223
|
+
const enabled = config.yandex?.cloudConnector?.enabledServices || [];
|
|
4224
|
+
const geocoder = Boolean(process.env.YANDEX_GEOCODER_API_KEY || process.env.YANDEX_MAPS_API_KEY || secrets.yandexCloud?.geocoderApiKey || secrets.yandexGeocoder?.apiKey);
|
|
4225
|
+
const gpt = Boolean((process.env.YANDEXGPT_API_KEY || process.env.YANDEX_CLOUD_API_KEY || secrets.yandexCloud?.yandexgptApiKey || secrets.yandexgpt?.apiKey)
|
|
4226
|
+
&& (process.env.YANDEXGPT_FOLDER_ID || process.env.YANDEX_CLOUD_FOLDER_ID || secrets.yandexCloud?.folderId || secrets.yandexgpt?.folderId));
|
|
4227
|
+
if (geocoder && gpt) return `готово: ${enabled.join(", ") || "geocoder"}`;
|
|
4228
|
+
if (geocoder) return "частично: geocoder";
|
|
4229
|
+
return "не настроено";
|
|
4230
|
+
}
|
|
4231
|
+
|
|
3832
4232
|
async function updateYandexEnabledServices(rawServices, enabled) {
|
|
3833
4233
|
const config = await loadConfig();
|
|
3834
4234
|
const current = new Set(config.yandex?.enabledServices || []);
|
|
@@ -4382,6 +4782,19 @@ async function executeYandexTool(tool, args = {}) {
|
|
|
4382
4782
|
if (tool === "yandex_daily_digest") return yandexDailyDigestTick({ ...args, force: true });
|
|
4383
4783
|
if (tool === "yandex_calendar_reminders_tick") return yandexCalendarRemindersTick({ ...args, force: true });
|
|
4384
4784
|
if (tool === "yandex_disk_maintenance_tick") return yandexDiskMaintenanceTick({ ...args, force: true });
|
|
4785
|
+
if (tool === "yandex_cloud_status") {
|
|
4786
|
+
const [secrets, config] = await Promise.all([loadSecrets(), loadConfig()]);
|
|
4787
|
+
return {
|
|
4788
|
+
geocoder: Boolean(process.env.YANDEX_GEOCODER_API_KEY || process.env.YANDEX_MAPS_API_KEY || secrets.yandexCloud?.geocoderApiKey || secrets.yandexGeocoder?.apiKey),
|
|
4789
|
+
yandexgpt: Boolean((process.env.YANDEXGPT_API_KEY || process.env.YANDEX_CLOUD_API_KEY || secrets.yandexCloud?.yandexgptApiKey || secrets.yandexgpt?.apiKey)
|
|
4790
|
+
&& (process.env.YANDEXGPT_FOLDER_ID || process.env.YANDEX_CLOUD_FOLDER_ID || secrets.yandexCloud?.folderId || secrets.yandexgpt?.folderId)),
|
|
4791
|
+
enabled: config.yandex?.cloudConnector?.enabledServices || [],
|
|
4792
|
+
};
|
|
4793
|
+
}
|
|
4794
|
+
if (tool === "yandex_go_deeplink") {
|
|
4795
|
+
await ensureYandexGoGeocoderReady();
|
|
4796
|
+
return buildYandexGoDeeplinkFromOptions({ from: args.from, to: args.to, tariff: args.tariff || args.class || args.level, ref: args.ref, lang: args.lang });
|
|
4797
|
+
}
|
|
4385
4798
|
throw new Error(`Yandex tool неизвестен: ${tool}`);
|
|
4386
4799
|
}
|
|
4387
4800
|
|
|
@@ -9532,6 +9945,17 @@ async function chooseOpenRouterModel() {
|
|
|
9532
9945
|
async function ensureApiKeyForModelSelection(provider) {
|
|
9533
9946
|
if (!["openai", "openrouter", "yandexgpt", "gigachat"].includes(provider)) return true;
|
|
9534
9947
|
if (await getApiKey(provider) && (provider !== "yandexgpt" || await getYandexFolderId())) return true;
|
|
9948
|
+
if (provider === "yandexgpt") {
|
|
9949
|
+
console.log("YandexGPT требует Yandex Cloud Connector: API key и folder ID.");
|
|
9950
|
+
if (!process.stdin.isTTY) return false;
|
|
9951
|
+
const ok = await askYesNo("Включить Yandex Cloud Connector сейчас? [y/N] ", false);
|
|
9952
|
+
if (!ok) {
|
|
9953
|
+
console.log("Возврат в меню выбора модели.");
|
|
9954
|
+
return false;
|
|
9955
|
+
}
|
|
9956
|
+
await setupYandexCloudConnector({});
|
|
9957
|
+
return Boolean(await getApiKey("yandexgpt") && await getYandexFolderId());
|
|
9958
|
+
}
|
|
9535
9959
|
const label = {
|
|
9536
9960
|
openai: "OpenAI",
|
|
9537
9961
|
openrouter: "OpenRouter",
|
|
@@ -10368,7 +10792,7 @@ async function getYandexGeocoderKey() {
|
|
|
10368
10792
|
return process.env.YANDEX_GEOCODER_API_KEY || process.env.YANDEX_MAPS_API_KEY;
|
|
10369
10793
|
}
|
|
10370
10794
|
const secrets = await loadSecrets();
|
|
10371
|
-
return secrets.yandexGeocoder?.apiKey || "";
|
|
10795
|
+
return secrets.yandexCloud?.geocoderApiKey || secrets.yandexGeocoder?.apiKey || "";
|
|
10372
10796
|
}
|
|
10373
10797
|
|
|
10374
10798
|
function openDatabase() {
|
|
@@ -12027,6 +12451,17 @@ async function buildYandexDirectAnswer(question, history = []) {
|
|
|
12027
12451
|
].join("\n");
|
|
12028
12452
|
}
|
|
12029
12453
|
|
|
12454
|
+
if (/(яндекс\s*go|яндекс\s*го|такси|deeplink|диплинк|ссылк.*маршрут)/iu.test(normalized)
|
|
12455
|
+
&& /(маршрут|ссылк|откуда|куда|поездк|такси|от\s+.+\s+до\s+)/iu.test(normalized)) {
|
|
12456
|
+
const route = extractYandexGoRouteFromText(question);
|
|
12457
|
+
if (!route.from || !route.to) {
|
|
12458
|
+
return 'Для ссылки Яндекс Go нужны два адреса. Пример: "такси от Медведево, Школьная 15 до Медведево, Советская 20".';
|
|
12459
|
+
}
|
|
12460
|
+
await ensureYandexGoGeocoderReady();
|
|
12461
|
+
const result = await buildYandexGoDeeplinkFromOptions({ from: route.from, to: route.to, tariff: route.tariff });
|
|
12462
|
+
return formatYandexGoDeeplinkResult(result);
|
|
12463
|
+
}
|
|
12464
|
+
|
|
12030
12465
|
if (/(дайджест|сводк)/iu.test(normalized) && /(яндекс|почт|календар|контакт|диск)/iu.test(normalized)) {
|
|
12031
12466
|
if (/(включ|запусти|начни|поставь|создай)/iu.test(normalized) && /(кажд|ежеднев|авто|регуляр)/iu.test(normalized)) {
|
|
12032
12467
|
const time = question.match(/(\d{1,2}:\d{2})/u)?.[1] || "09:00";
|
|
@@ -12424,7 +12859,9 @@ async function buildYandexDirectAnswer(question, history = []) {
|
|
|
12424
12859
|
return ["Яндекс Контакты:", ...rows.map((row, index) => `${index + 1}. ${formatYandexContact(row)}`)].join("\n");
|
|
12425
12860
|
}
|
|
12426
12861
|
} catch (error) {
|
|
12427
|
-
|
|
12862
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
12863
|
+
if (/^(?:Для Yandex Go deeplink|Для Cloud Connector нужен|Для YandexGPT нужны)/u.test(message)) return message;
|
|
12864
|
+
return `Не смог выполнить запрос к сервисам Яндекса: ${message}`;
|
|
12428
12865
|
}
|
|
12429
12866
|
return "";
|
|
12430
12867
|
}
|
|
@@ -12663,7 +13100,7 @@ async function buildUserSkillDirectAnswer(question) {
|
|
|
12663
13100
|
}
|
|
12664
13101
|
|
|
12665
13102
|
function isYandexServiceQuestion(normalized) {
|
|
12666
|
-
return /(яндекс|яндес|язндекс|язндекс|яндкс|yandex|почт|письм|календар|контакт|телемост|документ|docs|360
|
|
13103
|
+
return /(яндекс|яндес|язндекс|язндекс|яндкс|yandex|почт|письм|календар|контакт|телемост|документ|docs|360|спам|чернов|отправлен|исходящ|корзин|такси|яндекс\s*go|яндекс\s*го|геокод|cloud|клауд)/iu.test(String(normalized || ""));
|
|
12667
13104
|
}
|
|
12668
13105
|
|
|
12669
13106
|
function isYandexIdentityQuestion(normalized) {
|
|
@@ -13883,7 +14320,7 @@ async function buildLocalToolPlan(question, providerConfig, options) {
|
|
|
13883
14320
|
`Доступные tools: ${availableToolNames(options).join(", ")}.`,
|
|
13884
14321
|
"Схема: {\"steps\":[{\"tool\":\"search_data\",\"args\":{\"dataset\":\"schools|kindergartens|all\",\"query\":\"text\",\"limit\":10}}]}",
|
|
13885
14322
|
"Минимальные tools: search_data {dataset,query,limit}, get_card {query}, export_report {name,format,output}, file_read {path}, browser_open {url}.",
|
|
13886
|
-
"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_daily_digest {save,email}, yandex_calendar_reminders_tick {}, yandex_disk_maintenance_tick {}.",
|
|
14323
|
+
"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 {}.",
|
|
13887
14324
|
"Опасные Yandex 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.",
|
|
13888
14325
|
"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.",
|
|
13889
14326
|
"MCP tools доступны как mcp:SERVER:TOOL, например mcp:iola-local:search.",
|
|
@@ -14627,6 +15064,8 @@ function formatToolResult(result, options) {
|
|
|
14627
15064
|
if (row.status === "calendar-event-deleted") return `Событие удалено: ${row.title || row.uid}`;
|
|
14628
15065
|
if (row.status === "mail-meeting-pack-created") return `Пакет по письму #${row.uid} создан.\nПисьмо: ${row.saved}\nСсылка: ${row.publicUrl}\nQR-код: ${row.qrPublicUrl}\nСобытие: ${row.event || "-"}`;
|
|
14629
15066
|
if (row.status === "contact-full-pack-created") return `Пакет контакта создан: ${row.contact}\nПапка: ${row.folder}\nДокумент: ${row.doc}\nСсылка: ${row.publicUrl}\nQR-код: ${row.qrPublicUrl}\nСобытие: ${row.event || "-"}`;
|
|
15067
|
+
if (row.url && row.fromPoint && row.toPoint) return formatYandexGoDeeplinkResult(row);
|
|
15068
|
+
if (row.geocoder !== undefined && row.yandexgpt !== undefined && row.enabled) return `Yandex Cloud Connector:\nГеокодер: ${row.geocoder ? "настроен" : "нет"}\nYandexGPT: ${row.yandexgpt ? "настроен" : "нет"}\nВключено: ${row.enabled.join(", ") || "-"}`;
|
|
14630
15069
|
if (row.enabled && row.text && (row.unread !== undefined || row.events !== undefined)) return row.text;
|
|
14631
15070
|
if (row.status === "ambiguous" && row.events) return [`Нашел несколько событий. Уточните:`, ...row.events.map((event, index) => `${index + 1}. ${event.title || event.uid} — ${event.startIso || event.start || "-"}`)].join("\n");
|
|
14632
15071
|
if (row.status === "not-found" && row.kind === "calendar-event") return `Событие не найдено: ${row.query}`;
|
|
@@ -15757,6 +16196,7 @@ async function getApiKey(provider) {
|
|
|
15757
16196
|
}
|
|
15758
16197
|
|
|
15759
16198
|
const secrets = await loadSecrets();
|
|
16199
|
+
if (provider === "yandexgpt") return secrets.yandexCloud?.yandexgptApiKey || secrets.yandexgpt?.apiKey || "";
|
|
15760
16200
|
return secrets[provider]?.apiKey || "";
|
|
15761
16201
|
}
|
|
15762
16202
|
|
|
@@ -15765,7 +16205,7 @@ async function getYandexFolderId() {
|
|
|
15765
16205
|
return process.env.YANDEXGPT_FOLDER_ID || process.env.YANDEX_CLOUD_FOLDER_ID;
|
|
15766
16206
|
}
|
|
15767
16207
|
const secrets = await loadSecrets();
|
|
15768
|
-
return secrets.yandexgpt?.folderId || "";
|
|
16208
|
+
return secrets.yandexCloud?.folderId || secrets.yandexgpt?.folderId || "";
|
|
15769
16209
|
}
|
|
15770
16210
|
|
|
15771
16211
|
async function listLayers(args) {
|
|
@@ -16025,12 +16465,8 @@ async function onboard(args = []) {
|
|
|
16025
16465
|
await chooseAndSaveApiModel("openrouter");
|
|
16026
16466
|
}
|
|
16027
16467
|
}
|
|
16028
|
-
if (components.includes("
|
|
16029
|
-
await
|
|
16030
|
-
if (process.stdin.isTTY) {
|
|
16031
|
-
await setAiKey("yandexgpt");
|
|
16032
|
-
await chooseAndSaveApiModel("yandexgpt");
|
|
16033
|
-
}
|
|
16468
|
+
if (components.includes("yandex-cloud")) {
|
|
16469
|
+
await setupYandexCloudConnector({});
|
|
16034
16470
|
}
|
|
16035
16471
|
if (components.includes("gigachat")) {
|
|
16036
16472
|
await aiSetup(["gigachat"]);
|
|
@@ -16039,11 +16475,6 @@ async function onboard(args = []) {
|
|
|
16039
16475
|
await chooseAndSaveApiModel("gigachat");
|
|
16040
16476
|
}
|
|
16041
16477
|
}
|
|
16042
|
-
if (components.includes("yandex-geocoder")) {
|
|
16043
|
-
if (process.stdin.isTTY) {
|
|
16044
|
-
await setYandexGeocoderKey();
|
|
16045
|
-
}
|
|
16046
|
-
}
|
|
16047
16478
|
if (components.includes("cloud")) {
|
|
16048
16479
|
if (process.stdin.isTTY) {
|
|
16049
16480
|
const providerAnswer = (await askText("Облачный диск: 1 - Яндекс Диск, 2 - Облако Mail.ru, 0 - пропустить: ")).trim();
|
|
@@ -16093,7 +16524,7 @@ async function chooseOnboardComponents(status = null) {
|
|
|
16093
16524
|
1: "workspace",
|
|
16094
16525
|
2: "policy",
|
|
16095
16526
|
3: "iola",
|
|
16096
|
-
4: "
|
|
16527
|
+
4: "yandex-cloud",
|
|
16097
16528
|
5: "gigachat",
|
|
16098
16529
|
6: "openai",
|
|
16099
16530
|
7: "openrouter",
|
|
@@ -16103,8 +16534,8 @@ async function chooseOnboardComponents(status = null) {
|
|
|
16103
16534
|
11: "index",
|
|
16104
16535
|
12: "browser",
|
|
16105
16536
|
13: "ollama",
|
|
16106
|
-
14: "
|
|
16107
|
-
15: "
|
|
16537
|
+
14: "cloud",
|
|
16538
|
+
15: "yandex",
|
|
16108
16539
|
16: "yandex",
|
|
16109
16540
|
};
|
|
16110
16541
|
return [...selected].map((item) => map[item] || item).filter(Boolean);
|
|
@@ -16136,7 +16567,7 @@ async function getOnboardComponentStatus() {
|
|
|
16136
16567
|
policy: policyReady,
|
|
16137
16568
|
iola: Boolean(readiness.iola),
|
|
16138
16569
|
ollama: Boolean(ollamaVersion && readiness.ollama),
|
|
16139
|
-
|
|
16570
|
+
"yandex-cloud": Boolean(yandexGeocoderKey || readiness.yandexgpt),
|
|
16140
16571
|
gigachat: Boolean(readiness.gigachat),
|
|
16141
16572
|
openai: Boolean(readiness.openai),
|
|
16142
16573
|
openrouter: Boolean(readiness.openrouter),
|
|
@@ -16145,7 +16576,6 @@ async function getOnboardComponentStatus() {
|
|
|
16145
16576
|
archive: Boolean(archive),
|
|
16146
16577
|
index: false,
|
|
16147
16578
|
browser: browser.installed === "yes",
|
|
16148
|
-
"yandex-geocoder": Boolean(yandexGeocoderKey),
|
|
16149
16579
|
cloud: Object.keys(cloudSecrets).length > 0,
|
|
16150
16580
|
yandex: isYandexConnectorFullyConnected(secrets),
|
|
16151
16581
|
};
|
|
@@ -16156,7 +16586,7 @@ function onboardComponentRows(status) {
|
|
|
16156
16586
|
["1", "workspace", "workspace и контекст", "рабочая папка, IOLA.md и .iola/context.md"],
|
|
16157
16587
|
["2", "policy", "policy analyst", "разрешения и профиль аналитика"],
|
|
16158
16588
|
["3", "iola", "IOLA локальная модель", "локальная модель найдена"],
|
|
16159
|
-
["4", "
|
|
16589
|
+
["4", "yandex-cloud", "Yandex Cloud Connector", "геокодинг и YandexGPT"],
|
|
16160
16590
|
["5", "gigachat", "GigaChat API", "authorization key сохранен или есть в env"],
|
|
16161
16591
|
["6", "openai", "OpenAI API", "API-ключ сохранен или есть в env"],
|
|
16162
16592
|
["7", "openrouter", "OpenRouter API", "API-ключ сохранен или есть в env"],
|
|
@@ -16166,9 +16596,8 @@ function onboardComponentRows(status) {
|
|
|
16166
16596
|
["11", "index", "Индекс локальных документов", "настраивается под выбранную папку"],
|
|
16167
16597
|
["12", "browser", "Browser runtime", "Playwright/Chromium установлен"],
|
|
16168
16598
|
["13", "ollama", "Ollama", "опциональный локальный runtime"],
|
|
16169
|
-
["14", "
|
|
16170
|
-
["15", "
|
|
16171
|
-
["16", "yandex", "Yandex Connector", "единый вход и категории сервисов Яндекса"],
|
|
16599
|
+
["14", "cloud", "Облачный диск", "Яндекс Диск или Облако Mail.ru"],
|
|
16600
|
+
["15", "yandex", "Yandex Connector", "единый вход и категории сервисов Яндекса"],
|
|
16172
16601
|
];
|
|
16173
16602
|
return rows.map(([number, key, title, hint]) => ({ number, key, title, hint, status: status[key] ? "готово" : "не настроено" }));
|
|
16174
16603
|
}
|
|
@@ -16183,7 +16612,7 @@ function defaultOnboardSelection(status) {
|
|
|
16183
16612
|
}
|
|
16184
16613
|
|
|
16185
16614
|
function defaultOnboardComponents(status) {
|
|
16186
|
-
const map = { 1: "workspace", 2: "policy", 3: "iola", 4: "
|
|
16615
|
+
const map = { 1: "workspace", 2: "policy", 3: "iola", 4: "yandex-cloud", 5: "gigachat", 6: "openai", 7: "openrouter", 8: "codex", 9: "codex-mcp", 10: "archive", 11: "index", 12: "browser", 13: "ollama", 14: "cloud", 15: "yandex", 16: "yandex" };
|
|
16187
16616
|
return defaultOnboardSelection(status).map((item) => map[item]).filter(Boolean);
|
|
16188
16617
|
}
|
|
16189
16618
|
|
|
@@ -16197,7 +16626,7 @@ function parseOptions(args) {
|
|
|
16197
16626
|
} else if (arg === "--check" || arg === "--upgrade-node") {
|
|
16198
16627
|
result.check = true;
|
|
16199
16628
|
result[arg.slice(2)] = true;
|
|
16200
|
-
} 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") {
|
|
16629
|
+
} 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") {
|
|
16201
16630
|
result[arg.slice(2)] = args[index + 1];
|
|
16202
16631
|
index += 1;
|
|
16203
16632
|
} else {
|