@iola_adm/iola-cli 0.2.15 → 0.2.16

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 CHANGED
@@ -199,6 +199,8 @@ iola yandex status
199
199
 
200
200
  Первый контур: Yandex ID, Яндекс Диск и Яндекс Почта. Календарь, контакты, Wiki, Tracker, Forms и документы 360 заложены как категории для проверки и могут потребовать отдельное OAuth-приложение Яндекса. Такси, Маркет и Доставка записаны в backlog только как сценарии подготовки ссылки/маршрута/списка без заказа и оплаты.
201
201
 
202
+ В `/yandex` функции выбираются номерами через запятую, как в мастере настройки. OAuth-права сами по себе не создают функциональность: под каждый сервис нужны отдельные команды и тулы.
203
+
202
204
  Инструкция: [Yandex Connector](https://github.com/adm-iola/iola-cli/wiki/Yandex-Connector).
203
205
 
204
206
  Зарубежные API-ключи:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iola_adm/iola-cli",
3
- "version": "0.2.15",
3
+ "version": "0.2.16",
4
4
  "description": "CLI и AI-агент городского округа Йошкар-Ола.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/adm-iola/iola-cli#readme",
package/src/cli.js CHANGED
@@ -3364,11 +3364,15 @@ async function chooseYandexServicesMenu() {
3364
3364
  const config = await loadConfig();
3365
3365
  const serviceIds = Object.keys(YANDEX_CONNECTOR_SERVICES);
3366
3366
  const enabled = new Set(config.yandex?.enabledServices?.length ? config.yandex.enabledServices : ["identity", "disk"]);
3367
- console.log("Yandex Connector: выберите сервисы.");
3367
+ const authState = await getYandexServiceAuthState();
3368
+ console.log("Функции Яндекса.");
3369
+ console.log("Выберите номера функций через запятую:");
3368
3370
  serviceIds.forEach((id, index) => {
3369
3371
  const service = YANDEX_CONNECTOR_SERVICES[id];
3370
3372
  const marker = enabled.has(id) ? "✓" : " ";
3371
- console.log(`${index + 1}. [${marker}] ${service.title} (${id}, ${service.status}) - ${service.hint}`);
3373
+ const auth = authState.byService[id];
3374
+ const authLabel = auth?.hasToken ? "подключено" : (auth?.authorized ? "нужен вход" : "нет прав");
3375
+ console.log(`${index + 1}. [${marker}] ${service.title} - ${service.hint} (${service.status}, ${authLabel})`);
3372
3376
  });
3373
3377
  console.log("0. Отмена");
3374
3378
  const defaults = serviceIds.map((id, index) => enabled.has(id) ? String(index + 1) : "").filter(Boolean);
@@ -3387,13 +3391,10 @@ async function chooseYandexServicesMenu() {
3387
3391
  });
3388
3392
  await saveYandexEnabledServices(selected);
3389
3393
  console.log(`Включены сервисы: ${normalizeYandexServiceList(selected).join(", ")}`);
3390
- const nextConfig = await loadConfig();
3391
- if (nextConfig.yandex?.oauth?.clientId) {
3392
- console.log("OAuth-ссылка с максимальными правами коннектора:");
3393
- console.log(await buildYandexOAuthUrlFromConfig([]));
3394
- } else {
3395
- console.log("Для авторизации создайте OAuth Client ID и выполните: iola yandex oauth-url --client-id CLIENT_ID");
3396
- }
3394
+ const missingAuth = selected.filter((id) => !authState.byService[id]?.authorized);
3395
+ const missingToken = selected.filter((id) => authState.byService[id]?.authorized && !authState.byService[id]?.hasToken);
3396
+ if (missingAuth.length) console.log(`Нет OAuth-прав в текущей сборке: ${missingAuth.join(", ")}. Для них нужно отдельное OAuth-приложение Яндекса.`);
3397
+ if (missingToken.length) console.log(`Нужно пройти вход Яндекса для: ${missingToken.join(", ")}. Запустите iola yandex setup.`);
3397
3398
  }
3398
3399
 
3399
3400
  async function updateYandexEnabledServices(rawServices, enabled) {
@@ -3441,7 +3442,9 @@ async function buildYandexOAuthUrlFromConfig(rawArgs = []) {
3441
3442
  const config = await loadConfig();
3442
3443
  const clientId = options["client-id"] || config.yandex?.oauth?.clientId || YANDEX_CONNECTOR_CLIENT_ID;
3443
3444
  if (!clientId) throw new Error("Yandex OAuth Client ID не задан. Пример: iola yandex oauth-url disk --client-id CLIENT_ID");
3444
- const services = normalizeYandexServiceList(options._.length ? options._ : (config.yandex?.authorizedServices?.length ? config.yandex.authorizedServices : getYandexOAuthCapableServiceIds()));
3445
+ const capableServices = getYandexOAuthCapableServiceIds();
3446
+ const requestedServices = normalizeYandexServiceList(options._.length ? options._ : capableServices);
3447
+ const services = requestedServices.filter((id) => capableServices.includes(id));
3445
3448
  return buildYandexOAuthUrl({ clientId, services, redirectUrl: options["redirect-url"] || config.yandex?.oauth?.redirectUrl || YANDEX_OAUTH_REDIRECT_URL });
3446
3449
  }
3447
3450
 
@@ -3617,16 +3620,16 @@ async function deleteYandexConnectorToken() {
3617
3620
  async function printYandexConnectorStatus(options = {}) {
3618
3621
  const [config, secrets] = await Promise.all([loadConfig(), loadSecrets()]);
3619
3622
  const enabled = config.yandex?.enabledServices || [];
3620
- const authorized = config.yandex?.authorizedServices?.length ? config.yandex.authorizedServices : [];
3621
3623
  const legacyDiskToken = Boolean(secrets.cloud?.["yandex-disk"]?.token && !secrets.yandex?.oauthToken);
3624
+ const authState = await getYandexServiceAuthState({ config, secrets });
3622
3625
  const token = process.env.YANDEX_OAUTH_TOKEN || secrets.yandex?.oauthToken || secrets.cloud?.["yandex-disk"]?.token || "";
3623
3626
  const rows = Object.entries(YANDEX_CONNECTOR_SERVICES).map(([id, service]) => ({
3624
3627
  id,
3625
3628
  enabled: enabled.includes(id) ? "yes" : (legacyDiskToken && id === "disk" ? "legacy" : "no"),
3626
3629
  category: service.category,
3627
3630
  status: service.status,
3628
- token: service.scope && (authorized.includes(id) || enabled.includes(id) || (legacyDiskToken && id === "disk")) ? (token ? "local/env" : "missing") : "-",
3629
- authorized: authorized.includes(id) ? "yes" : "-",
3631
+ token: service.scope && authState.byService[id]?.authorized ? (authState.byService[id]?.hasToken ? "local/env" : "missing") : "-",
3632
+ authorized: authState.byService[id]?.authorized ? "yes" : "-",
3630
3633
  title: service.title,
3631
3634
  }));
3632
3635
  printTable(rows, [
@@ -3650,6 +3653,35 @@ async function printYandexConnectorStatus(options = {}) {
3650
3653
  }
3651
3654
  }
3652
3655
 
3656
+ async function getYandexServiceAuthState({ config = null, secrets = null } = {}) {
3657
+ const loadedConfig = config || await loadConfig();
3658
+ const loadedSecrets = secrets || await loadSecrets();
3659
+ const apps = getConfiguredYandexOAuthApps();
3660
+ const appTokens = loadedSecrets.yandex?.oauthApps || {};
3661
+ const envToken = process.env.YANDEX_OAUTH_TOKEN || "";
3662
+ const byService = {};
3663
+ for (const id of Object.keys(YANDEX_CONNECTOR_SERVICES)) {
3664
+ byService[id] = { authorized: false, hasToken: false, apps: [] };
3665
+ }
3666
+ for (const app of apps) {
3667
+ const hasToken = Boolean(envToken || appTokens[app.id]?.token || (app.id === "core" && loadedSecrets.yandex?.oauthToken));
3668
+ for (const id of normalizeYandexServiceList(app.services)) {
3669
+ byService[id] = byService[id] || { authorized: false, hasToken: false, apps: [] };
3670
+ byService[id].authorized = true;
3671
+ byService[id].hasToken = byService[id].hasToken || hasToken;
3672
+ byService[id].apps.push(app.id);
3673
+ }
3674
+ }
3675
+ if (loadedSecrets.cloud?.["yandex-disk"]?.token) {
3676
+ byService.disk.authorized = true;
3677
+ byService.disk.hasToken = true;
3678
+ byService.disk.apps.push("legacy-cloud");
3679
+ }
3680
+ const authorizedServices = Object.entries(byService).filter(([, state]) => state.authorized).map(([id]) => id);
3681
+ const enabledServices = loadedConfig.yandex?.enabledServices || [];
3682
+ return { byService, authorizedServices, enabledServices };
3683
+ }
3684
+
3653
3685
  async function yandexUserInfo(token) {
3654
3686
  const response = await fetch("https://login.yandex.ru/info?format=json", {
3655
3687
  headers: { Authorization: `OAuth ${token}` },
@@ -58,6 +58,9 @@ assertIncludes(cliSource, "dedupeDatedOpenAiModels", "OpenAI model selection sho
58
58
  assertIncludes(cliSource, "chooseLocalModel", "Local model selection should support IOLA and Ollama models");
59
59
  assertIncludes(cliSource, "Другая Ollama-модель", "Local model selection should allow manual Ollama model names");
60
60
  assertIncludes(cliSource, "chooseYandexServicesMenu", "Yandex Connector should have a service selection menu");
61
+ assertIncludes(cliSource, "Функции Яндекса.", "Yandex service selection should use a numbered menu");
62
+ assertIncludes(cliSource, "Выберите номера функций через запятую", "Yandex service selection should ask for numbers");
63
+ assertIncludes(cliSource, "getYandexServiceAuthState", "Yandex status should derive permissions from configured OAuth apps");
61
64
  assertIncludes(cliSource, "OAuth-права встроенного приложения", "Yandex setup should report packaged OAuth app permissions");
62
65
  assertIncludes(cliSource, "Выбрать активные функции можно командой /yandex", "Yandex setup should direct service selection to /yandex");
63
66
  assertIncludes(cliSource, "runYandexBrowserOAuth", "Yandex setup should support browser OAuth flow");
@@ -85,6 +85,8 @@ iola yandex menu
85
85
  /yandex
86
86
  ```
87
87
 
88
+ Меню `/yandex` работает как мастер настройки: сервисы выбираются номерами через запятую, а не вводом технических названий.
89
+
88
90
  6. Проверьте:
89
91
 
90
92
  ```bash
@@ -94,6 +96,18 @@ iola cloud doctor
94
96
 
95
97
  Если включен `disk`, токен автоматически подключается и к старому облачному провайдеру `yandex-disk`, поэтому команды `iola cloud ...` продолжают работать.
96
98
 
99
+ ## Что значит включить сервис
100
+
101
+ OAuth-права дают CLI разрешение обращаться к сервису Яндекса. Но для практической работы под каждый сервис нужны отдельные команды и тулы:
102
+
103
+ - `identity` - проверить пользователя и email;
104
+ - `disk` - папки, загрузка, скачивание, поиск файлов, публичные ссылки;
105
+ - `mail` - список писем, поиск, чтение письма, отправка письма после явного подтверждения;
106
+ - `calendar` - список событий, создание события, напоминания; требует отдельного OAuth-приложения/проверки прав;
107
+ - `contacts` - поиск контактов и карточки контактов; требует отдельного OAuth-приложения/проверки прав.
108
+
109
+ Включение сервиса в `/yandex` только разрешает CLI использовать соответствующую категорию. Если прав или тула еще нет, CLI должен честно показать это, а не имитировать работу.
110
+
97
111
  ## Иконка приложения
98
112
 
99
113
  CLI поставляется с готовой иконкой OAuth-приложения. При установке она копируется в: