@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 +2 -0
- package/package.json +1 -1
- package/src/cli.js +45 -13
- package/test/smoke-test.js +3 -0
- package/wiki/Yandex-Connector.md +14 -0
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
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
|
-
|
|
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
|
-
|
|
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
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
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
|
|
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 &&
|
|
3629
|
-
authorized:
|
|
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}` },
|
package/test/smoke-test.js
CHANGED
|
@@ -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");
|
package/wiki/Yandex-Connector.md
CHANGED
|
@@ -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-приложения. При установке она копируется в:
|