@iola_adm/iola-cli 0.2.36 → 0.2.38
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 +494 -60
- 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 +21 -36
- 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",
|
|
@@ -9638,6 +10062,15 @@ async function askText(question) {
|
|
|
9638
10062
|
}
|
|
9639
10063
|
}
|
|
9640
10064
|
|
|
10065
|
+
async function askYesNo(question, defaultValue = false) {
|
|
10066
|
+
if (!process.stdin.isTTY) return Boolean(defaultValue);
|
|
10067
|
+
const answer = (await askText(question)).trim().toLocaleLowerCase("ru-RU");
|
|
10068
|
+
if (!answer) return Boolean(defaultValue);
|
|
10069
|
+
if (answer === "y" || answer === "yes" || answer === "д" || answer === "да") return true;
|
|
10070
|
+
if (answer === "n" || answer === "no" || answer === "н" || answer === "нет") return false;
|
|
10071
|
+
return Boolean(defaultValue);
|
|
10072
|
+
}
|
|
10073
|
+
|
|
9641
10074
|
async function aiContext(args) {
|
|
9642
10075
|
const options = parseOptions(args);
|
|
9643
10076
|
const query = options._.join(" ").trim();
|
|
@@ -10368,7 +10801,7 @@ async function getYandexGeocoderKey() {
|
|
|
10368
10801
|
return process.env.YANDEX_GEOCODER_API_KEY || process.env.YANDEX_MAPS_API_KEY;
|
|
10369
10802
|
}
|
|
10370
10803
|
const secrets = await loadSecrets();
|
|
10371
|
-
return secrets.yandexGeocoder?.apiKey || "";
|
|
10804
|
+
return secrets.yandexCloud?.geocoderApiKey || secrets.yandexGeocoder?.apiKey || "";
|
|
10372
10805
|
}
|
|
10373
10806
|
|
|
10374
10807
|
function openDatabase() {
|
|
@@ -12027,6 +12460,17 @@ async function buildYandexDirectAnswer(question, history = []) {
|
|
|
12027
12460
|
].join("\n");
|
|
12028
12461
|
}
|
|
12029
12462
|
|
|
12463
|
+
if (/(яндекс\s*go|яндекс\s*го|такси|deeplink|диплинк|ссылк.*маршрут)/iu.test(normalized)
|
|
12464
|
+
&& /(маршрут|ссылк|откуда|куда|поездк|такси|от\s+.+\s+до\s+)/iu.test(normalized)) {
|
|
12465
|
+
const route = extractYandexGoRouteFromText(question);
|
|
12466
|
+
if (!route.from || !route.to) {
|
|
12467
|
+
return 'Для ссылки Яндекс Go нужны два адреса. Пример: "такси от Медведево, Школьная 15 до Медведево, Советская 20".';
|
|
12468
|
+
}
|
|
12469
|
+
await ensureYandexGoGeocoderReady();
|
|
12470
|
+
const result = await buildYandexGoDeeplinkFromOptions({ from: route.from, to: route.to, tariff: route.tariff });
|
|
12471
|
+
return formatYandexGoDeeplinkResult(result);
|
|
12472
|
+
}
|
|
12473
|
+
|
|
12030
12474
|
if (/(дайджест|сводк)/iu.test(normalized) && /(яндекс|почт|календар|контакт|диск)/iu.test(normalized)) {
|
|
12031
12475
|
if (/(включ|запусти|начни|поставь|создай)/iu.test(normalized) && /(кажд|ежеднев|авто|регуляр)/iu.test(normalized)) {
|
|
12032
12476
|
const time = question.match(/(\d{1,2}:\d{2})/u)?.[1] || "09:00";
|
|
@@ -12424,7 +12868,9 @@ async function buildYandexDirectAnswer(question, history = []) {
|
|
|
12424
12868
|
return ["Яндекс Контакты:", ...rows.map((row, index) => `${index + 1}. ${formatYandexContact(row)}`)].join("\n");
|
|
12425
12869
|
}
|
|
12426
12870
|
} catch (error) {
|
|
12427
|
-
|
|
12871
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
12872
|
+
if (/^(?:Для Yandex Go deeplink|Для Cloud Connector нужен|Для YandexGPT нужны)/u.test(message)) return message;
|
|
12873
|
+
return `Не смог выполнить запрос к сервисам Яндекса: ${message}`;
|
|
12428
12874
|
}
|
|
12429
12875
|
return "";
|
|
12430
12876
|
}
|
|
@@ -12663,7 +13109,7 @@ async function buildUserSkillDirectAnswer(question) {
|
|
|
12663
13109
|
}
|
|
12664
13110
|
|
|
12665
13111
|
function isYandexServiceQuestion(normalized) {
|
|
12666
|
-
return /(яндекс|яндес|язндекс|язндекс|яндкс|yandex|почт|письм|календар|контакт|телемост|документ|docs|360
|
|
13112
|
+
return /(яндекс|яндес|язндекс|язндекс|яндкс|yandex|почт|письм|календар|контакт|телемост|документ|docs|360|спам|чернов|отправлен|исходящ|корзин|такси|яндекс\s*go|яндекс\s*го|геокод|cloud|клауд)/iu.test(String(normalized || ""));
|
|
12667
13113
|
}
|
|
12668
13114
|
|
|
12669
13115
|
function isYandexIdentityQuestion(normalized) {
|
|
@@ -13883,7 +14329,7 @@ async function buildLocalToolPlan(question, providerConfig, options) {
|
|
|
13883
14329
|
`Доступные tools: ${availableToolNames(options).join(", ")}.`,
|
|
13884
14330
|
"Схема: {\"steps\":[{\"tool\":\"search_data\",\"args\":{\"dataset\":\"schools|kindergartens|all\",\"query\":\"text\",\"limit\":10}}]}",
|
|
13885
14331
|
"Минимальные 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 {}.",
|
|
14332
|
+
"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
14333
|
"Опасные 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
14334
|
"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
14335
|
"MCP tools доступны как mcp:SERVER:TOOL, например mcp:iola-local:search.",
|
|
@@ -14627,6 +15073,8 @@ function formatToolResult(result, options) {
|
|
|
14627
15073
|
if (row.status === "calendar-event-deleted") return `Событие удалено: ${row.title || row.uid}`;
|
|
14628
15074
|
if (row.status === "mail-meeting-pack-created") return `Пакет по письму #${row.uid} создан.\nПисьмо: ${row.saved}\nСсылка: ${row.publicUrl}\nQR-код: ${row.qrPublicUrl}\nСобытие: ${row.event || "-"}`;
|
|
14629
15075
|
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 || "-"}`;
|
|
15076
|
+
if (row.url && row.fromPoint && row.toPoint) return formatYandexGoDeeplinkResult(row);
|
|
15077
|
+
if (row.geocoder !== undefined && row.yandexgpt !== undefined && row.enabled) return `Yandex Cloud Connector:\nГеокодер: ${row.geocoder ? "настроен" : "нет"}\nYandexGPT: ${row.yandexgpt ? "настроен" : "нет"}\nВключено: ${row.enabled.join(", ") || "-"}`;
|
|
14630
15078
|
if (row.enabled && row.text && (row.unread !== undefined || row.events !== undefined)) return row.text;
|
|
14631
15079
|
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
15080
|
if (row.status === "not-found" && row.kind === "calendar-event") return `Событие не найдено: ${row.query}`;
|
|
@@ -15757,6 +16205,7 @@ async function getApiKey(provider) {
|
|
|
15757
16205
|
}
|
|
15758
16206
|
|
|
15759
16207
|
const secrets = await loadSecrets();
|
|
16208
|
+
if (provider === "yandexgpt") return secrets.yandexCloud?.yandexgptApiKey || secrets.yandexgpt?.apiKey || "";
|
|
15760
16209
|
return secrets[provider]?.apiKey || "";
|
|
15761
16210
|
}
|
|
15762
16211
|
|
|
@@ -15765,7 +16214,7 @@ async function getYandexFolderId() {
|
|
|
15765
16214
|
return process.env.YANDEXGPT_FOLDER_ID || process.env.YANDEX_CLOUD_FOLDER_ID;
|
|
15766
16215
|
}
|
|
15767
16216
|
const secrets = await loadSecrets();
|
|
15768
|
-
return secrets.yandexgpt?.folderId || "";
|
|
16217
|
+
return secrets.yandexCloud?.folderId || secrets.yandexgpt?.folderId || "";
|
|
15769
16218
|
}
|
|
15770
16219
|
|
|
15771
16220
|
async function listLayers(args) {
|
|
@@ -16025,13 +16474,6 @@ async function onboard(args = []) {
|
|
|
16025
16474
|
await chooseAndSaveApiModel("openrouter");
|
|
16026
16475
|
}
|
|
16027
16476
|
}
|
|
16028
|
-
if (components.includes("yandexgpt")) {
|
|
16029
|
-
await aiSetup(["yandexgpt"]);
|
|
16030
|
-
if (process.stdin.isTTY) {
|
|
16031
|
-
await setAiKey("yandexgpt");
|
|
16032
|
-
await chooseAndSaveApiModel("yandexgpt");
|
|
16033
|
-
}
|
|
16034
|
-
}
|
|
16035
16477
|
if (components.includes("gigachat")) {
|
|
16036
16478
|
await aiSetup(["gigachat"]);
|
|
16037
16479
|
if (process.stdin.isTTY) {
|
|
@@ -16039,11 +16481,6 @@ async function onboard(args = []) {
|
|
|
16039
16481
|
await chooseAndSaveApiModel("gigachat");
|
|
16040
16482
|
}
|
|
16041
16483
|
}
|
|
16042
|
-
if (components.includes("yandex-geocoder")) {
|
|
16043
|
-
if (process.stdin.isTTY) {
|
|
16044
|
-
await setYandexGeocoderKey();
|
|
16045
|
-
}
|
|
16046
|
-
}
|
|
16047
16484
|
if (components.includes("cloud")) {
|
|
16048
16485
|
if (process.stdin.isTTY) {
|
|
16049
16486
|
const providerAnswer = (await askText("Облачный диск: 1 - Яндекс Диск, 2 - Облако Mail.ru, 0 - пропустить: ")).trim();
|
|
@@ -16055,6 +16492,9 @@ async function onboard(args = []) {
|
|
|
16055
16492
|
if (components.includes("yandex")) {
|
|
16056
16493
|
await setupYandexConnector([]);
|
|
16057
16494
|
}
|
|
16495
|
+
if (components.includes("yandex-cloud")) {
|
|
16496
|
+
await setupYandexCloudConnector({});
|
|
16497
|
+
}
|
|
16058
16498
|
if (components.includes("codex")) {
|
|
16059
16499
|
await installCodexIfMissing();
|
|
16060
16500
|
await aiSetup(["codex"]);
|
|
@@ -16093,19 +16533,18 @@ async function chooseOnboardComponents(status = null) {
|
|
|
16093
16533
|
1: "workspace",
|
|
16094
16534
|
2: "policy",
|
|
16095
16535
|
3: "iola",
|
|
16096
|
-
4: "
|
|
16097
|
-
5: "
|
|
16098
|
-
6: "
|
|
16099
|
-
7: "
|
|
16100
|
-
8: "codex",
|
|
16101
|
-
9: "
|
|
16102
|
-
10: "
|
|
16103
|
-
11: "
|
|
16104
|
-
12: "
|
|
16105
|
-
13: "
|
|
16106
|
-
14: "yandex-
|
|
16107
|
-
15: "cloud",
|
|
16108
|
-
16: "yandex",
|
|
16536
|
+
4: "gigachat",
|
|
16537
|
+
5: "openai",
|
|
16538
|
+
6: "openrouter",
|
|
16539
|
+
7: "codex",
|
|
16540
|
+
8: "codex-mcp",
|
|
16541
|
+
9: "archive",
|
|
16542
|
+
10: "index",
|
|
16543
|
+
11: "browser",
|
|
16544
|
+
12: "ollama",
|
|
16545
|
+
13: "yandex",
|
|
16546
|
+
14: "yandex-cloud",
|
|
16547
|
+
15: "yandex-cloud",
|
|
16109
16548
|
};
|
|
16110
16549
|
return [...selected].map((item) => map[item] || item).filter(Boolean);
|
|
16111
16550
|
} finally {
|
|
@@ -16128,7 +16567,6 @@ async function getOnboardComponentStatus() {
|
|
|
16128
16567
|
getYandexGeocoderKey(),
|
|
16129
16568
|
loadSecrets(),
|
|
16130
16569
|
]);
|
|
16131
|
-
const cloudSecrets = secrets.cloud || {};
|
|
16132
16570
|
const workspaceReady = existsSync(PROJECT_CONTEXT_FILE) || existsSync(PROJECT_CONTEXT_DIR_FILE) || existsSync(PROJECT_IOLA_DIR);
|
|
16133
16571
|
const policyReady = (config.toolsets?.enabled || []).includes("analyst");
|
|
16134
16572
|
return {
|
|
@@ -16136,7 +16574,7 @@ async function getOnboardComponentStatus() {
|
|
|
16136
16574
|
policy: policyReady,
|
|
16137
16575
|
iola: Boolean(readiness.iola),
|
|
16138
16576
|
ollama: Boolean(ollamaVersion && readiness.ollama),
|
|
16139
|
-
|
|
16577
|
+
"yandex-cloud": Boolean(yandexGeocoderKey || readiness.yandexgpt),
|
|
16140
16578
|
gigachat: Boolean(readiness.gigachat),
|
|
16141
16579
|
openai: Boolean(readiness.openai),
|
|
16142
16580
|
openrouter: Boolean(readiness.openrouter),
|
|
@@ -16145,8 +16583,6 @@ async function getOnboardComponentStatus() {
|
|
|
16145
16583
|
archive: Boolean(archive),
|
|
16146
16584
|
index: false,
|
|
16147
16585
|
browser: browser.installed === "yes",
|
|
16148
|
-
"yandex-geocoder": Boolean(yandexGeocoderKey),
|
|
16149
|
-
cloud: Object.keys(cloudSecrets).length > 0,
|
|
16150
16586
|
yandex: isYandexConnectorFullyConnected(secrets),
|
|
16151
16587
|
};
|
|
16152
16588
|
}
|
|
@@ -16156,19 +16592,17 @@ function onboardComponentRows(status) {
|
|
|
16156
16592
|
["1", "workspace", "workspace и контекст", "рабочая папка, IOLA.md и .iola/context.md"],
|
|
16157
16593
|
["2", "policy", "policy analyst", "разрешения и профиль аналитика"],
|
|
16158
16594
|
["3", "iola", "IOLA локальная модель", "локальная модель найдена"],
|
|
16159
|
-
["4", "
|
|
16160
|
-
["5", "
|
|
16161
|
-
["6", "
|
|
16162
|
-
["7", "
|
|
16163
|
-
["8", "codex", "Codex
|
|
16164
|
-
["9", "
|
|
16165
|
-
["10", "
|
|
16166
|
-
["11", "
|
|
16167
|
-
["12", "
|
|
16168
|
-
["13", "
|
|
16169
|
-
["14", "yandex-
|
|
16170
|
-
["15", "cloud", "Облачный диск", "Яндекс Диск или Облако Mail.ru"],
|
|
16171
|
-
["16", "yandex", "Yandex Connector", "единый вход и категории сервисов Яндекса"],
|
|
16595
|
+
["4", "gigachat", "GigaChat API", "authorization key сохранен или есть в env"],
|
|
16596
|
+
["5", "openai", "OpenAI API", "API-ключ сохранен или есть в env"],
|
|
16597
|
+
["6", "openrouter", "OpenRouter API", "API-ключ сохранен или есть в env"],
|
|
16598
|
+
["7", "codex", "Codex CLI", "CLI установлен и авторизация найдена"],
|
|
16599
|
+
["8", "codex-mcp", "MCP для Codex", "можно переустановить/обновить"],
|
|
16600
|
+
["9", "archive", "7-Zip / архивы", "архиватор найден"],
|
|
16601
|
+
["10", "index", "Индекс локальных документов", "настраивается под выбранную папку"],
|
|
16602
|
+
["11", "browser", "Browser runtime", "Playwright/Chromium установлен"],
|
|
16603
|
+
["12", "ollama", "Ollama", "опциональный локальный runtime"],
|
|
16604
|
+
["13", "yandex", "Yandex Connector", "единый вход и категории сервисов Яндекса"],
|
|
16605
|
+
["14", "yandex-cloud", "Yandex Cloud Connector", "геокодинг и YandexGPT"],
|
|
16172
16606
|
];
|
|
16173
16607
|
return rows.map(([number, key, title, hint]) => ({ number, key, title, hint, status: status[key] ? "готово" : "не настроено" }));
|
|
16174
16608
|
}
|
|
@@ -16178,12 +16612,12 @@ function defaultOnboardSelection(status) {
|
|
|
16178
16612
|
if (!status.workspace) defaults.push("1");
|
|
16179
16613
|
if (!status.policy) defaults.push("2");
|
|
16180
16614
|
if (!status.iola) defaults.push("3");
|
|
16181
|
-
if (!status.archive) defaults.push("
|
|
16615
|
+
if (!status.archive) defaults.push("9");
|
|
16182
16616
|
return defaults.length ? defaults : ["1", "2"];
|
|
16183
16617
|
}
|
|
16184
16618
|
|
|
16185
16619
|
function defaultOnboardComponents(status) {
|
|
16186
|
-
const map = { 1: "workspace", 2: "policy", 3: "iola", 4: "
|
|
16620
|
+
const map = { 1: "workspace", 2: "policy", 3: "iola", 4: "gigachat", 5: "openai", 6: "openrouter", 7: "codex", 8: "codex-mcp", 9: "archive", 10: "index", 11: "browser", 12: "ollama", 13: "yandex", 14: "yandex-cloud", 15: "yandex-cloud" };
|
|
16187
16621
|
return defaultOnboardSelection(status).map((item) => map[item]).filter(Boolean);
|
|
16188
16622
|
}
|
|
16189
16623
|
|
|
@@ -16197,7 +16631,7 @@ function parseOptions(args) {
|
|
|
16197
16631
|
} else if (arg === "--check" || arg === "--upgrade-node") {
|
|
16198
16632
|
result.check = true;
|
|
16199
16633
|
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") {
|
|
16634
|
+
} 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
16635
|
result[arg.slice(2)] = args[index + 1];
|
|
16202
16636
|
index += 1;
|
|
16203
16637
|
} else {
|