@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/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: "separate",
105
- hint: "YandexGPT, Geocoder, SpeechKit, Vision, IAM и folder ID",
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: "separate",
112
- hint: "геокодер, маршруты и ссылки на карты через отдельный API key",
111
+ status: "ready",
112
+ hint: "адреса, координаты, маршруты и ссылки на карты",
113
113
  },
114
114
  taxi: {
115
115
  title: "Яндекс Go / Такси",
116
116
  category: "mobility",
117
117
  scope: "",
118
- status: "backlog",
119
- hint: "только подготовка маршрута/deep link, без заказа и оплаты",
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 deleteNumber = serviceIds.length + 1;
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
- return `Не смог выполнить запрос к сервисам Яндекса: ${error instanceof Error ? error.message : String(error)}`;
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|спам|чернов|отправлен|исходящ|корзин)/iu.test(String(normalized || ""));
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("yandexgpt")) {
16029
- await aiSetup(["yandexgpt"]);
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: "yandexgpt",
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: "yandex-geocoder",
16107
- 15: "cloud",
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
- yandexgpt: Boolean(readiness.yandexgpt),
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", "yandexgpt", "YandexGPT API", "ключ и folder ID сохранены или есть в env"],
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", "yandex-geocoder", "Yandex Geocoder API", "ключ геокодера сохранен или есть в env"],
16170
- ["15", "cloud", "Облачный диск", "Яндекс Диск или Облако Mail.ru"],
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: "yandexgpt", 5: "gigachat", 6: "openai", 7: "openrouter", 8: "codex", 9: "codex-mcp", 10: "archive", 11: "index", 12: "browser", 13: "ollama", 14: "yandex-geocoder", 15: "cloud", 16: "yandex" };
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 {