@iola_adm/iola-cli 0.2.13 → 0.2.15
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 +3 -2
- package/bin/postinstall.js +29 -4
- package/docs/assets/iola-oauth-icon.png +0 -0
- package/package.json +2 -1
- package/src/cli.js +254 -37
- package/test/smoke-test.js +11 -0
- package/wiki/Yandex-Connector.md +45 -11
- package/wiki//320/232/320/276/320/274/320/260/320/275/320/264/321/213.md +1 -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 +2 -0
package/README.md
CHANGED
|
@@ -188,15 +188,16 @@ iola cloud backup
|
|
|
188
188
|
|
|
189
189
|
Инструкция: [Облачные диски](https://github.com/adm-iola/iola-cli/wiki/Облачные-диски).
|
|
190
190
|
|
|
191
|
-
Yandex Connector
|
|
191
|
+
Yandex Connector открывает браузер, авторизует пользователя в Яндексе и сохраняет OAuth-токен локально. Какие функции CLI реально использует, выбирается отдельно:
|
|
192
192
|
|
|
193
193
|
```bash
|
|
194
194
|
iola yandex services
|
|
195
195
|
iola yandex setup
|
|
196
|
+
iola yandex menu
|
|
196
197
|
iola yandex status
|
|
197
198
|
```
|
|
198
199
|
|
|
199
|
-
Первый контур: Yandex ID и Яндекс
|
|
200
|
+
Первый контур: Yandex ID, Яндекс Диск и Яндекс Почта. Календарь, контакты, Wiki, Tracker, Forms и документы 360 заложены как категории для проверки и могут потребовать отдельное OAuth-приложение Яндекса. Такси, Маркет и Доставка записаны в backlog только как сценарии подготовки ссылки/маршрута/списка без заказа и оплаты.
|
|
200
201
|
|
|
201
202
|
Инструкция: [Yandex Connector](https://github.com/adm-iola/iola-cli/wiki/Yandex-Connector).
|
|
202
203
|
|
package/bin/postinstall.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import {
|
|
4
|
+
import { copyFileSync, existsSync, mkdirSync } from "node:fs";
|
|
5
|
+
import { dirname, join, resolve } from "node:path";
|
|
6
|
+
import os from "node:os";
|
|
5
7
|
|
|
6
8
|
const rootDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
9
|
const cliPath = resolve(rootDir, "bin", "iola.js");
|
|
10
|
+
const oauthIconSource = resolve(rootDir, "docs", "assets", "iola-oauth-icon.png");
|
|
11
|
+
const oauthIconTarget = join(os.homedir(), ".iola", "assets", "iola-oauth-icon.png");
|
|
8
12
|
const node = process.execPath;
|
|
9
13
|
const frames = ["|", "/", "-", "\\"];
|
|
10
14
|
|
|
@@ -21,6 +25,10 @@ const steps = [
|
|
|
21
25
|
title: "Проверка локальной модели IOLA",
|
|
22
26
|
args: [cliPath, "ai", "setup", "iola", "--yes", "--quiet", "--optional", "--preserve-active"],
|
|
23
27
|
},
|
|
28
|
+
{
|
|
29
|
+
title: "Установка иконки Yandex OAuth",
|
|
30
|
+
local: installOauthIcon,
|
|
31
|
+
},
|
|
24
32
|
];
|
|
25
33
|
|
|
26
34
|
const canAnimate = process.stdout.isTTY && process.env.CI !== "true";
|
|
@@ -52,9 +60,11 @@ async function runStep(step, current, total) {
|
|
|
52
60
|
}
|
|
53
61
|
render();
|
|
54
62
|
const timer = setInterval(render, 120);
|
|
55
|
-
const result =
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
const result = step.local
|
|
64
|
+
? await runLocalStep(step.local)
|
|
65
|
+
: await run(node, ["--no-warnings", ...step.args], (chunk) => {
|
|
66
|
+
lastOutput = chunk.trim() || lastOutput;
|
|
67
|
+
});
|
|
58
68
|
clearInterval(timer);
|
|
59
69
|
|
|
60
70
|
if (result.code !== 0) {
|
|
@@ -71,6 +81,21 @@ async function runStep(step, current, total) {
|
|
|
71
81
|
}
|
|
72
82
|
}
|
|
73
83
|
|
|
84
|
+
async function runLocalStep(fn) {
|
|
85
|
+
try {
|
|
86
|
+
await fn();
|
|
87
|
+
return { code: 0 };
|
|
88
|
+
} catch (error) {
|
|
89
|
+
return { code: 1, error };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function installOauthIcon() {
|
|
94
|
+
if (!existsSync(oauthIconSource)) return;
|
|
95
|
+
mkdirSync(dirname(oauthIconTarget), { recursive: true });
|
|
96
|
+
copyFileSync(oauthIconSource, oauthIconTarget);
|
|
97
|
+
}
|
|
98
|
+
|
|
74
99
|
function run(command, args, onOutput) {
|
|
75
100
|
return new Promise((resolvePromise) => {
|
|
76
101
|
const child = spawn(command, args, {
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iola_adm/iola-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.15",
|
|
4
4
|
"description": "CLI и AI-агент городского округа Йошкар-Ола.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/adm-iola/iola-cli#readme",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"skills",
|
|
28
28
|
"wiki",
|
|
29
29
|
"docs/assets/readme-header.png",
|
|
30
|
+
"docs/assets/iola-oauth-icon.png",
|
|
30
31
|
"README.md",
|
|
31
32
|
"LICENSE"
|
|
32
33
|
],
|
package/src/cli.js
CHANGED
|
@@ -42,6 +42,11 @@ const BROWSER_RUNTIME_PACKAGE = path.join(BROWSER_RUNTIME_DIR, "node_modules", "
|
|
|
42
42
|
const CLOUD_DEFAULT_REMOTE_DIR = "/IOLA";
|
|
43
43
|
const YANDEX_OAUTH_AUTHORIZE_URL = "https://oauth.yandex.ru/authorize";
|
|
44
44
|
const YANDEX_OAUTH_REDIRECT_URL = "https://oauth.yandex.ru/verification_code";
|
|
45
|
+
const YANDEX_CONNECTOR_CLIENT_ID = process.env.IOLA_YANDEX_OAUTH_CLIENT_ID || process.env.YANDEX_OAUTH_CLIENT_ID || "915b0b6ef0474f3a9b3edd70515c5d60";
|
|
46
|
+
const YANDEX_CONNECTOR_WORKSPACE_CLIENT_ID = process.env.IOLA_YANDEX_WORKSPACE_OAUTH_CLIENT_ID || "";
|
|
47
|
+
const YANDEX_CONNECTOR_REDIRECT_HOST = "127.0.0.1";
|
|
48
|
+
const YANDEX_CONNECTOR_REDIRECT_PORT = Number(process.env.IOLA_YANDEX_OAUTH_PORT || 18791);
|
|
49
|
+
const YANDEX_CONNECTOR_REDIRECT_PATH = "/yandex/oauth/callback";
|
|
45
50
|
const YANDEX_CONNECTOR_SERVICES = {
|
|
46
51
|
identity: {
|
|
47
52
|
title: "Yandex ID",
|
|
@@ -149,6 +154,20 @@ const YANDEX_CONNECTOR_SERVICES = {
|
|
|
149
154
|
hint: "только подготовка заявки/ссылки, без оформления и оплаты",
|
|
150
155
|
},
|
|
151
156
|
};
|
|
157
|
+
const YANDEX_CONNECTOR_OAUTH_APPS = [
|
|
158
|
+
{
|
|
159
|
+
id: "core",
|
|
160
|
+
title: "IOLA Yandex Core",
|
|
161
|
+
clientId: YANDEX_CONNECTOR_CLIENT_ID,
|
|
162
|
+
services: ["identity", "disk", "mail"],
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: "workspace",
|
|
166
|
+
title: "IOLA Yandex Workspace",
|
|
167
|
+
clientId: YANDEX_CONNECTOR_WORKSPACE_CLIENT_ID,
|
|
168
|
+
services: ["contacts", "wiki", "tracker", "forms", "docs"],
|
|
169
|
+
},
|
|
170
|
+
];
|
|
152
171
|
const INDEXABLE_EXTENSIONS = /\.(md|txt|csv|json|html|docx|xlsx|pptx|pdf)$/i;
|
|
153
172
|
const LOCAL_TOOLS = ["search_data", "search_entities", "resolve_entity_field", "get_card", "export_report", "file_read", "browser_open", "get_current_date"];
|
|
154
173
|
const LEGACY_LOCAL_TOOLS = ["search_local", "export_data", "run_report", "save_view"];
|
|
@@ -348,6 +367,7 @@ const DEFAULT_AI_CONFIG = {
|
|
|
348
367
|
},
|
|
349
368
|
},
|
|
350
369
|
yandex: {
|
|
370
|
+
authorizedServices: [],
|
|
351
371
|
enabledServices: [],
|
|
352
372
|
categories: {},
|
|
353
373
|
oauth: {
|
|
@@ -440,6 +460,7 @@ const SLASH_COMMANDS = [
|
|
|
440
460
|
{ command: "/tools", description: "tools и toolsets" },
|
|
441
461
|
{ command: "/files status", description: "локальные файловые операции" },
|
|
442
462
|
{ command: "/cloud status", description: "облачные диски" },
|
|
463
|
+
{ command: "/yandex", description: "выбор сервисов Yandex Connector" },
|
|
443
464
|
{ command: "/archive doctor", description: "архиватор" },
|
|
444
465
|
{ command: "/changes list", description: "подготовленные изменения" },
|
|
445
466
|
{ command: "/index status", description: "индекс документов" },
|
|
@@ -707,7 +728,7 @@ Usage:
|
|
|
707
728
|
iola tools list|toolsets|enable|disable|profile
|
|
708
729
|
iola files status|mode|approvals|tree|read|search|write|patch
|
|
709
730
|
iola cloud setup|status|ls|find|upload|download|share|save|backup
|
|
710
|
-
iola yandex setup|status|services|enable|disable|oauth-url|token
|
|
731
|
+
iola yandex setup|menu|status|services|enable|disable|oauth-url|token
|
|
711
732
|
iola archive doctor|list|test|extract|create|index
|
|
712
733
|
iola changes list|show|apply|discard
|
|
713
734
|
iola import file|folder
|
|
@@ -1446,6 +1467,7 @@ async function handleAgentLine(line, state) {
|
|
|
1446
1467
|
skills: ["skills", args],
|
|
1447
1468
|
files: ["files", args],
|
|
1448
1469
|
archive: ["archive", args],
|
|
1470
|
+
yandex: ["yandex", args.length ? args : ["menu"]],
|
|
1449
1471
|
changes: ["changes", args],
|
|
1450
1472
|
index: ["index", args],
|
|
1451
1473
|
reports: ["reports", args],
|
|
@@ -2019,6 +2041,7 @@ async function doctor(args = []) {
|
|
|
2019
2041
|
openrouterKey: process.env.OPENROUTER_API_KEY ? "env" : secrets.openrouter?.apiKey ? "local" : "missing",
|
|
2020
2042
|
yandexGeocoderKey: (process.env.YANDEX_GEOCODER_API_KEY || process.env.YANDEX_MAPS_API_KEY) ? "env" : secrets.yandexGeocoder?.apiKey ? "local" : "missing",
|
|
2021
2043
|
yandexConnector: (process.env.YANDEX_OAUTH_TOKEN || secrets.yandex?.oauthToken || secrets.cloud?.["yandex-disk"]?.token) ? "local/env" : "missing",
|
|
2044
|
+
yandexAuthorized: config.yandex?.authorizedServices?.join(", ") || "-",
|
|
2022
2045
|
yandexServices: config.yandex?.enabledServices?.join(", ") || (secrets.cloud?.["yandex-disk"]?.token ? "disk (legacy cloud token)" : "-"),
|
|
2023
2046
|
ollama: diagnostics.ollama.installed ? diagnostics.ollama.version : "not-installed",
|
|
2024
2047
|
},
|
|
@@ -3200,9 +3223,14 @@ async function handleCloud(args) {
|
|
|
3200
3223
|
}
|
|
3201
3224
|
|
|
3202
3225
|
async function handleYandex(args) {
|
|
3203
|
-
const [action = "status", target, ...rest] = args;
|
|
3226
|
+
const [action = process.stdin.isTTY ? "menu" : "status", target, ...rest] = args;
|
|
3204
3227
|
const options = parseOptions(rest);
|
|
3205
3228
|
|
|
3229
|
+
if (action === "menu" || action === "choose" || action === "select") {
|
|
3230
|
+
await chooseYandexServicesMenu();
|
|
3231
|
+
return;
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3206
3234
|
if (action === "services" || action === "list") {
|
|
3207
3235
|
printYandexServices();
|
|
3208
3236
|
return;
|
|
@@ -3250,6 +3278,7 @@ async function handleYandex(args) {
|
|
|
3250
3278
|
|
|
3251
3279
|
throw new Error(`Команды yandex:
|
|
3252
3280
|
iola yandex setup
|
|
3281
|
+
iola yandex menu
|
|
3253
3282
|
iola yandex status|doctor
|
|
3254
3283
|
iola yandex services
|
|
3255
3284
|
iola yandex enable disk mail calendar
|
|
@@ -3284,37 +3313,86 @@ function printYandexServices(options = {}) {
|
|
|
3284
3313
|
async function setupYandexConnector(args = []) {
|
|
3285
3314
|
const options = parseOptions(args);
|
|
3286
3315
|
const config = await loadConfig();
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
if (services.length === 0) services = ["identity", "disk"];
|
|
3297
|
-
await saveYandexEnabledServices(services);
|
|
3298
|
-
|
|
3299
|
-
const clientId = options["client-id"] || config.yandex?.oauth?.clientId || (process.stdin.isTTY ? (await askText("Yandex OAuth Client ID [Enter - пропустить]: ")).trim() : "");
|
|
3316
|
+
const oauthApps = getConfiguredYandexOAuthApps();
|
|
3317
|
+
const authorizedServices = getYandexOAuthCapableServiceIds();
|
|
3318
|
+
const enabledServices = config.yandex?.enabledServices?.length ? config.yandex.enabledServices : ["identity", "disk"];
|
|
3319
|
+
await saveYandexAuthorizedServices(authorizedServices);
|
|
3320
|
+
await saveYandexEnabledServices(enabledServices);
|
|
3321
|
+
|
|
3322
|
+
const clientId = options["client-id"] || config.yandex?.oauth?.clientId || oauthApps[0]?.clientId || "";
|
|
3323
|
+
const redirectUrl = options["redirect-url"] || getYandexConnectorRedirectUrl();
|
|
3300
3324
|
if (clientId) {
|
|
3301
3325
|
await saveConfig({
|
|
3302
3326
|
yandex: {
|
|
3303
3327
|
...(config.yandex || {}),
|
|
3304
|
-
oauth: { ...(config.yandex?.oauth || {}), clientId, redirectUrl
|
|
3328
|
+
oauth: { ...(config.yandex?.oauth || {}), clientId, redirectUrl },
|
|
3305
3329
|
},
|
|
3306
3330
|
});
|
|
3307
3331
|
}
|
|
3308
3332
|
|
|
3309
3333
|
console.log("Yandex Connector настроен.");
|
|
3310
|
-
console.log(
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3334
|
+
console.log(`OAuth-права встроенного приложения: ${authorizedServices.join(", ")}`);
|
|
3335
|
+
console.log(`Активные функции CLI: ${normalizeYandexServiceList(enabledServices).join(", ")}`);
|
|
3336
|
+
console.log("Выбрать активные функции можно командой /yandex или iola yandex menu.");
|
|
3337
|
+
if (clientId || oauthApps.length) {
|
|
3338
|
+
if (process.stdin.isTTY && !options["print-url"]) {
|
|
3339
|
+
console.log("Открываю браузер для входа в Яндекс. После авторизации токен сохранится автоматически.");
|
|
3340
|
+
for (const app of oauthApps.length ? oauthApps : [{ id: "custom", title: "Yandex Connector", clientId, services: authorizedServices }]) {
|
|
3341
|
+
console.log(`Авторизация: ${app.title}`);
|
|
3342
|
+
await runYandexBrowserOAuth({ appId: app.id, clientId: app.clientId, services: app.services, redirectUrl });
|
|
3343
|
+
}
|
|
3344
|
+
await printYandexConnectorStatus({ check: true });
|
|
3345
|
+
} else {
|
|
3346
|
+
const app = oauthApps[0] || { clientId, services: authorizedServices };
|
|
3347
|
+
const url = buildYandexOAuthUrl({ clientId: app.clientId, services: app.services, redirectUrl });
|
|
3348
|
+
console.log("Откройте ссылку авторизации, получите OAuth-токен и сохраните его командой: iola yandex token set");
|
|
3349
|
+
console.log(url);
|
|
3350
|
+
if (options.open) await openUrl(url);
|
|
3351
|
+
}
|
|
3352
|
+
} else {
|
|
3353
|
+
console.log("Yandex Connector не может открыть браузер: в этой сборке не задан public OAuth client_id приложения IOLA.");
|
|
3354
|
+
console.log("Нужно один раз зарегистрировать OAuth-приложение IOLA и задать IOLA_YANDEX_OAUTH_CLIENT_ID при сборке/запуске CLI.");
|
|
3355
|
+
console.log("Ручной fallback для разработки: iola yandex setup --client-id CLIENT_ID");
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
|
|
3359
|
+
async function chooseYandexServicesMenu() {
|
|
3360
|
+
if (!process.stdin.isTTY) {
|
|
3361
|
+
await printYandexConnectorStatus();
|
|
3362
|
+
return;
|
|
3363
|
+
}
|
|
3364
|
+
const config = await loadConfig();
|
|
3365
|
+
const serviceIds = Object.keys(YANDEX_CONNECTOR_SERVICES);
|
|
3366
|
+
const enabled = new Set(config.yandex?.enabledServices?.length ? config.yandex.enabledServices : ["identity", "disk"]);
|
|
3367
|
+
console.log("Yandex Connector: выберите сервисы.");
|
|
3368
|
+
serviceIds.forEach((id, index) => {
|
|
3369
|
+
const service = YANDEX_CONNECTOR_SERVICES[id];
|
|
3370
|
+
const marker = enabled.has(id) ? "✓" : " ";
|
|
3371
|
+
console.log(`${index + 1}. [${marker}] ${service.title} (${id}, ${service.status}) - ${service.hint}`);
|
|
3372
|
+
});
|
|
3373
|
+
console.log("0. Отмена");
|
|
3374
|
+
const defaults = serviceIds.map((id, index) => enabled.has(id) ? String(index + 1) : "").filter(Boolean);
|
|
3375
|
+
const answer = (await askText(`Номера через запятую [${defaults.join(",") || "1,2"}]: `)).trim();
|
|
3376
|
+
if (answer === "0") {
|
|
3377
|
+
console.log("Выбор сервисов отменен.");
|
|
3378
|
+
return;
|
|
3379
|
+
}
|
|
3380
|
+
const selectedNumbers = answer ? answer.split(/[,\s]+/).filter(Boolean) : (defaults.length ? defaults : ["1", "2"]);
|
|
3381
|
+
const selected = selectedNumbers.map((item) => {
|
|
3382
|
+
const index = Number(item) - 1;
|
|
3383
|
+
if (!Number.isInteger(index) || index < 0 || index >= serviceIds.length) {
|
|
3384
|
+
throw new Error(`Неизвестный номер сервиса: ${item}`);
|
|
3385
|
+
}
|
|
3386
|
+
return serviceIds[index];
|
|
3387
|
+
});
|
|
3388
|
+
await saveYandexEnabledServices(selected);
|
|
3389
|
+
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([]));
|
|
3316
3394
|
} else {
|
|
3317
|
-
console.log("
|
|
3395
|
+
console.log("Для авторизации создайте OAuth Client ID и выполните: iola yandex oauth-url --client-id CLIENT_ID");
|
|
3318
3396
|
}
|
|
3319
3397
|
}
|
|
3320
3398
|
|
|
@@ -3347,25 +3425,132 @@ async function saveYandexEnabledServices(services) {
|
|
|
3347
3425
|
});
|
|
3348
3426
|
}
|
|
3349
3427
|
|
|
3428
|
+
async function saveYandexAuthorizedServices(services) {
|
|
3429
|
+
const config = await loadConfig();
|
|
3430
|
+
const normalized = normalizeYandexServiceList(services.length ? services : getYandexOAuthCapableServiceIds());
|
|
3431
|
+
await saveConfig({
|
|
3432
|
+
yandex: {
|
|
3433
|
+
...(config.yandex || {}),
|
|
3434
|
+
authorizedServices: normalized,
|
|
3435
|
+
},
|
|
3436
|
+
});
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3350
3439
|
async function buildYandexOAuthUrlFromConfig(rawArgs = []) {
|
|
3351
3440
|
const options = parseOptions(rawArgs);
|
|
3352
3441
|
const config = await loadConfig();
|
|
3353
|
-
const clientId = options["client-id"] || config.yandex?.oauth?.clientId;
|
|
3442
|
+
const clientId = options["client-id"] || config.yandex?.oauth?.clientId || YANDEX_CONNECTOR_CLIENT_ID;
|
|
3354
3443
|
if (!clientId) throw new Error("Yandex OAuth Client ID не задан. Пример: iola yandex oauth-url disk --client-id CLIENT_ID");
|
|
3355
|
-
const services = normalizeYandexServiceList(options._.length ? options._ : (config.yandex?.
|
|
3356
|
-
return buildYandexOAuthUrl({ clientId, services });
|
|
3444
|
+
const services = normalizeYandexServiceList(options._.length ? options._ : (config.yandex?.authorizedServices?.length ? config.yandex.authorizedServices : getYandexOAuthCapableServiceIds()));
|
|
3445
|
+
return buildYandexOAuthUrl({ clientId, services, redirectUrl: options["redirect-url"] || config.yandex?.oauth?.redirectUrl || YANDEX_OAUTH_REDIRECT_URL });
|
|
3357
3446
|
}
|
|
3358
3447
|
|
|
3359
|
-
function buildYandexOAuthUrl({ clientId, services }) {
|
|
3448
|
+
function buildYandexOAuthUrl({ clientId, services, redirectUrl = YANDEX_OAUTH_REDIRECT_URL }) {
|
|
3360
3449
|
const scopes = getYandexScopesForServices(services);
|
|
3361
3450
|
const url = new URL(YANDEX_OAUTH_AUTHORIZE_URL);
|
|
3362
3451
|
url.searchParams.set("response_type", "token");
|
|
3363
3452
|
url.searchParams.set("client_id", clientId);
|
|
3364
|
-
url.searchParams.set("redirect_uri",
|
|
3453
|
+
url.searchParams.set("redirect_uri", redirectUrl);
|
|
3365
3454
|
if (scopes) url.searchParams.set("scope", scopes);
|
|
3366
3455
|
return url.toString();
|
|
3367
3456
|
}
|
|
3368
3457
|
|
|
3458
|
+
function getYandexConnectorRedirectUrl() {
|
|
3459
|
+
return `http://${YANDEX_CONNECTOR_REDIRECT_HOST}:${YANDEX_CONNECTOR_REDIRECT_PORT}${YANDEX_CONNECTOR_REDIRECT_PATH}`;
|
|
3460
|
+
}
|
|
3461
|
+
|
|
3462
|
+
async function runYandexBrowserOAuth({ appId = "core", clientId, services, redirectUrl }) {
|
|
3463
|
+
const token = await waitForYandexOAuthToken({ clientId, services, redirectUrl });
|
|
3464
|
+
await setYandexConnectorToken(["--token", token, "--app", appId]);
|
|
3465
|
+
console.log("Yandex Connector подключен.");
|
|
3466
|
+
}
|
|
3467
|
+
|
|
3468
|
+
function waitForYandexOAuthToken({ clientId, services, redirectUrl }) {
|
|
3469
|
+
return new Promise((resolvePromise, reject) => {
|
|
3470
|
+
let settled = false;
|
|
3471
|
+
const timeoutMs = 180000;
|
|
3472
|
+
const server = createServer(async (req, res) => {
|
|
3473
|
+
try {
|
|
3474
|
+
const url = new URL(req.url || "/", redirectUrl);
|
|
3475
|
+
if (url.pathname === YANDEX_CONNECTOR_REDIRECT_PATH && req.method === "GET") {
|
|
3476
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
3477
|
+
res.end(`<!doctype html>
|
|
3478
|
+
<html lang="ru"><head><meta charset="utf-8"><title>IOLA Yandex Connector</title></head>
|
|
3479
|
+
<body>
|
|
3480
|
+
<p>Передаю токен в iola-cli...</p>
|
|
3481
|
+
<script>
|
|
3482
|
+
(async () => {
|
|
3483
|
+
const params = new URLSearchParams(location.hash.replace(/^#/, ""));
|
|
3484
|
+
const token = params.get("access_token");
|
|
3485
|
+
const error = params.get("error") || "";
|
|
3486
|
+
await fetch("/yandex/oauth/token", {
|
|
3487
|
+
method: "POST",
|
|
3488
|
+
headers: { "content-type": "application/json" },
|
|
3489
|
+
body: JSON.stringify({ token, error })
|
|
3490
|
+
});
|
|
3491
|
+
document.body.innerHTML = token
|
|
3492
|
+
? "<p>Yandex Connector подключен. Можно закрыть вкладку и вернуться в терминал.</p>"
|
|
3493
|
+
: "<p>Не удалось получить токен. Вернитесь в терминал.</p>";
|
|
3494
|
+
})();
|
|
3495
|
+
</script>
|
|
3496
|
+
</body></html>`);
|
|
3497
|
+
return;
|
|
3498
|
+
}
|
|
3499
|
+
if (url.pathname === "/yandex/oauth/token" && req.method === "POST") {
|
|
3500
|
+
const chunks = [];
|
|
3501
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
3502
|
+
const payload = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
3503
|
+
if (payload.error) throw new Error(`Yandex OAuth error: ${payload.error}`);
|
|
3504
|
+
if (!payload.token) throw new Error("Yandex OAuth token не получен.");
|
|
3505
|
+
if (!settled) {
|
|
3506
|
+
settled = true;
|
|
3507
|
+
clearTimeout(timer);
|
|
3508
|
+
resolvePromise(String(payload.token));
|
|
3509
|
+
}
|
|
3510
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
3511
|
+
res.end(JSON.stringify({ ok: true }));
|
|
3512
|
+
server.close();
|
|
3513
|
+
return;
|
|
3514
|
+
}
|
|
3515
|
+
res.writeHead(404);
|
|
3516
|
+
res.end("not found");
|
|
3517
|
+
} catch (error) {
|
|
3518
|
+
if (!settled) {
|
|
3519
|
+
settled = true;
|
|
3520
|
+
clearTimeout(timer);
|
|
3521
|
+
reject(error);
|
|
3522
|
+
}
|
|
3523
|
+
res.writeHead(500, { "content-type": "text/plain; charset=utf-8" });
|
|
3524
|
+
res.end(error instanceof Error ? error.message : String(error));
|
|
3525
|
+
server.close();
|
|
3526
|
+
}
|
|
3527
|
+
});
|
|
3528
|
+
const timer = setTimeout(() => {
|
|
3529
|
+
if (!settled) {
|
|
3530
|
+
settled = true;
|
|
3531
|
+
server.close();
|
|
3532
|
+
reject(new Error("Время ожидания авторизации Яндекса истекло."));
|
|
3533
|
+
}
|
|
3534
|
+
}, timeoutMs);
|
|
3535
|
+
server.on("error", (error) => {
|
|
3536
|
+
clearTimeout(timer);
|
|
3537
|
+
if (!settled) {
|
|
3538
|
+
settled = true;
|
|
3539
|
+
reject(error);
|
|
3540
|
+
}
|
|
3541
|
+
});
|
|
3542
|
+
server.listen(YANDEX_CONNECTOR_REDIRECT_PORT, YANDEX_CONNECTOR_REDIRECT_HOST, async () => {
|
|
3543
|
+
const authUrl = buildYandexOAuthUrl({ clientId, services, redirectUrl });
|
|
3544
|
+
console.log(`Если браузер не открылся, откройте ссылку вручную: ${authUrl}`);
|
|
3545
|
+
try {
|
|
3546
|
+
await openUrl(authUrl);
|
|
3547
|
+
} catch (error) {
|
|
3548
|
+
console.log(`Не удалось открыть браузер автоматически: ${error instanceof Error ? error.message : String(error)}`);
|
|
3549
|
+
}
|
|
3550
|
+
});
|
|
3551
|
+
});
|
|
3552
|
+
}
|
|
3553
|
+
|
|
3369
3554
|
function getYandexScopesForServices(services) {
|
|
3370
3555
|
const scopes = new Set();
|
|
3371
3556
|
const normalized = normalizeYandexServiceList(services);
|
|
@@ -3377,21 +3562,47 @@ function getYandexScopesForServices(services) {
|
|
|
3377
3562
|
return [...scopes].join(" ");
|
|
3378
3563
|
}
|
|
3379
3564
|
|
|
3565
|
+
function getYandexOAuthCapableServiceIds() {
|
|
3566
|
+
return [...new Set(getConfiguredYandexOAuthApps().flatMap((app) => app.services))];
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3569
|
+
function getConfiguredYandexOAuthApps() {
|
|
3570
|
+
return YANDEX_CONNECTOR_OAUTH_APPS
|
|
3571
|
+
.filter((app) => app.clientId)
|
|
3572
|
+
.map((app) => ({ ...app, services: normalizeYandexServiceList(app.services) }));
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3575
|
+
function getYandexOAuthAppById(appId) {
|
|
3576
|
+
return getConfiguredYandexOAuthApps().find((app) => app.id === appId)
|
|
3577
|
+
|| YANDEX_CONNECTOR_OAUTH_APPS.find((app) => app.id === appId)
|
|
3578
|
+
|| null;
|
|
3579
|
+
}
|
|
3580
|
+
|
|
3380
3581
|
async function setYandexConnectorToken(args = []) {
|
|
3381
3582
|
const options = parseOptions(args);
|
|
3583
|
+
const appId = options.app || "core";
|
|
3382
3584
|
const token = options.token || (process.stdin.isTTY ? (await askText("Yandex OAuth token: ")).trim() : "");
|
|
3383
3585
|
if (!token) throw new Error("OAuth token обязателен.");
|
|
3586
|
+
const app = getYandexOAuthAppById(appId);
|
|
3587
|
+
const appServices = normalizeYandexServiceList(app?.services || []);
|
|
3588
|
+
const hasDiskAccess = appServices.includes("disk") || appId === "core";
|
|
3384
3589
|
const secrets = await loadSecrets();
|
|
3385
3590
|
secrets.yandex = secrets.yandex || {};
|
|
3386
|
-
secrets.yandex.
|
|
3591
|
+
secrets.yandex.oauthApps = secrets.yandex.oauthApps || {};
|
|
3592
|
+
secrets.yandex.oauthApps[appId] = { token, updatedAt: new Date().toISOString() };
|
|
3593
|
+
if (appId === "core") secrets.yandex.oauthToken = token;
|
|
3387
3594
|
secrets.yandex.updatedAt = new Date().toISOString();
|
|
3388
|
-
|
|
3389
|
-
|
|
3595
|
+
if (hasDiskAccess) {
|
|
3596
|
+
secrets.cloud = secrets.cloud || {};
|
|
3597
|
+
secrets.cloud["yandex-disk"] = { token };
|
|
3598
|
+
}
|
|
3390
3599
|
await saveSecrets(secrets);
|
|
3391
|
-
|
|
3392
|
-
|
|
3600
|
+
if (hasDiskAccess) {
|
|
3601
|
+
const config = await loadConfig();
|
|
3602
|
+
await saveConfig({ cloud: { ...(config.cloud || {}), activeProvider: "yandex-disk" } });
|
|
3603
|
+
}
|
|
3393
3604
|
console.log(`Yandex OAuth token сохранен локально: ${SECRETS_FILE}`);
|
|
3394
|
-
console.log("Токен также подключен к cloud provider yandex-disk.");
|
|
3605
|
+
if (hasDiskAccess) console.log("Токен также подключен к cloud provider yandex-disk.");
|
|
3395
3606
|
}
|
|
3396
3607
|
|
|
3397
3608
|
async function deleteYandexConnectorToken() {
|
|
@@ -3406,6 +3617,7 @@ async function deleteYandexConnectorToken() {
|
|
|
3406
3617
|
async function printYandexConnectorStatus(options = {}) {
|
|
3407
3618
|
const [config, secrets] = await Promise.all([loadConfig(), loadSecrets()]);
|
|
3408
3619
|
const enabled = config.yandex?.enabledServices || [];
|
|
3620
|
+
const authorized = config.yandex?.authorizedServices?.length ? config.yandex.authorizedServices : [];
|
|
3409
3621
|
const legacyDiskToken = Boolean(secrets.cloud?.["yandex-disk"]?.token && !secrets.yandex?.oauthToken);
|
|
3410
3622
|
const token = process.env.YANDEX_OAUTH_TOKEN || secrets.yandex?.oauthToken || secrets.cloud?.["yandex-disk"]?.token || "";
|
|
3411
3623
|
const rows = Object.entries(YANDEX_CONNECTOR_SERVICES).map(([id, service]) => ({
|
|
@@ -3413,7 +3625,8 @@ async function printYandexConnectorStatus(options = {}) {
|
|
|
3413
3625
|
enabled: enabled.includes(id) ? "yes" : (legacyDiskToken && id === "disk" ? "legacy" : "no"),
|
|
3414
3626
|
category: service.category,
|
|
3415
3627
|
status: service.status,
|
|
3416
|
-
token: service.scope && (enabled.includes(id) || (legacyDiskToken && id === "disk")) ? (token ? "local/env" : "missing") : "-",
|
|
3628
|
+
token: service.scope && (authorized.includes(id) || enabled.includes(id) || (legacyDiskToken && id === "disk")) ? (token ? "local/env" : "missing") : "-",
|
|
3629
|
+
authorized: authorized.includes(id) ? "yes" : "-",
|
|
3417
3630
|
title: service.title,
|
|
3418
3631
|
}));
|
|
3419
3632
|
printTable(rows, [
|
|
@@ -3421,6 +3634,7 @@ async function printYandexConnectorStatus(options = {}) {
|
|
|
3421
3634
|
["enabled", "Вкл"],
|
|
3422
3635
|
["category", "Категория"],
|
|
3423
3636
|
["status", "Статус"],
|
|
3637
|
+
["authorized", "Права"],
|
|
3424
3638
|
["token", "Токен"],
|
|
3425
3639
|
["title", "Сервис"],
|
|
3426
3640
|
]);
|
|
@@ -11189,12 +11403,12 @@ function parseOptions(args) {
|
|
|
11189
11403
|
|
|
11190
11404
|
for (let index = 0; index < args.length; index += 1) {
|
|
11191
11405
|
const arg = args[index];
|
|
11192
|
-
if (arg === "--json" || arg === "--yes" || arg === "--silent" || arg === "--events" || arg === "--stream-json" || arg === "--stdio" || arg === "--system" || arg === "--headed" || arg === "--headless" || arg === "--no-history" || arg === "--summary" || arg === "--all" || arg === "--full" || arg === "--unread" || arg === "--once" || arg === "--local" || arg === "--cache" || arg === "--tools" || arg === "--files" || arg === "--plan" || arg === "--trace" || arg === "--diff" || arg === "--stage" || arg === "--fts" || arg === "--bare" || arg === "--quiet" || arg === "--optional" || arg === "--project" || arg === "--dry-run" || arg === "--no-color" || arg === "--fail-on-empty" || arg === "--debug" || arg === "--fix" || arg === "--append" || arg === "--preserve-active" || arg === "--open") {
|
|
11406
|
+
if (arg === "--json" || arg === "--yes" || arg === "--silent" || arg === "--events" || arg === "--stream-json" || arg === "--stdio" || arg === "--system" || arg === "--headed" || arg === "--headless" || arg === "--no-history" || arg === "--summary" || arg === "--all" || arg === "--full" || arg === "--unread" || arg === "--once" || arg === "--local" || arg === "--cache" || arg === "--tools" || arg === "--files" || arg === "--plan" || arg === "--trace" || arg === "--diff" || arg === "--stage" || arg === "--fts" || arg === "--bare" || arg === "--quiet" || arg === "--optional" || arg === "--project" || arg === "--dry-run" || arg === "--no-color" || arg === "--fail-on-empty" || arg === "--debug" || arg === "--fix" || arg === "--append" || arg === "--preserve-active" || arg === "--open" || arg === "--print-url") {
|
|
11193
11407
|
result[arg.slice(2)] = true;
|
|
11194
11408
|
} else if (arg === "--check" || arg === "--upgrade-node") {
|
|
11195
11409
|
result.check = true;
|
|
11196
11410
|
result[arg.slice(2)] = true;
|
|
11197
|
-
} 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 === "--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-host" || arg === "--redirect-port" || arg === "--redirect-path" || arg === "--debug-file" || arg === "--from" || arg === "--to" || arg === "--radius" || arg === "--address" || arg === "--token") {
|
|
11411
|
+
} 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 === "--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") {
|
|
11198
11412
|
result[arg.slice(2)] = args[index + 1];
|
|
11199
11413
|
index += 1;
|
|
11200
11414
|
} else {
|
|
@@ -13219,6 +13433,9 @@ function validateConfig(config) {
|
|
|
13219
13433
|
for (const service of config.yandex?.enabledServices || []) {
|
|
13220
13434
|
if (!YANDEX_CONNECTOR_SERVICES[service]) errors.push(`yandex.enabledServices содержит неизвестный сервис: ${service}`);
|
|
13221
13435
|
}
|
|
13436
|
+
for (const service of config.yandex?.authorizedServices || []) {
|
|
13437
|
+
if (!YANDEX_CONNECTOR_SERVICES[service]) errors.push(`yandex.authorizedServices содержит неизвестный сервис: ${service}`);
|
|
13438
|
+
}
|
|
13222
13439
|
return errors;
|
|
13223
13440
|
}
|
|
13224
13441
|
|
package/test/smoke-test.js
CHANGED
|
@@ -57,10 +57,21 @@ assertIncludes(cliSource, "isOpenAiTextGenerationModel", "OpenAI model selection
|
|
|
57
57
|
assertIncludes(cliSource, "dedupeDatedOpenAiModels", "OpenAI model selection should hide dated duplicates when aliases exist");
|
|
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
|
+
assertIncludes(cliSource, "chooseYandexServicesMenu", "Yandex Connector should have a service selection menu");
|
|
61
|
+
assertIncludes(cliSource, "OAuth-права встроенного приложения", "Yandex setup should report packaged OAuth app permissions");
|
|
62
|
+
assertIncludes(cliSource, "Выбрать активные функции можно командой /yandex", "Yandex setup should direct service selection to /yandex");
|
|
63
|
+
assertIncludes(cliSource, "runYandexBrowserOAuth", "Yandex setup should support browser OAuth flow");
|
|
64
|
+
assertIncludes(cliSource, "IOLA_YANDEX_OAUTH_CLIENT_ID", "Yandex setup should use a packaged/env OAuth client id");
|
|
65
|
+
assertIncludes(cliSource, "--app", "Yandex token command should persist tokens by OAuth app group");
|
|
66
|
+
assertNotIncludes(cliSource, "Сервисы через запятую [identity,disk]", "Yandex setup should not ask for services during connector setup");
|
|
67
|
+
if (!packageJson.files.includes("docs/assets/iola-oauth-icon.png")) {
|
|
68
|
+
throw new Error("package files should include the Yandex OAuth icon");
|
|
69
|
+
}
|
|
60
70
|
|
|
61
71
|
const commands = await runCli(["commands"]);
|
|
62
72
|
assertIncludes(commands, "iola browser status|install|open|text|html|screenshot|pdf|click|type|eval", "commands");
|
|
63
73
|
assertIncludes(commands, "iola mcp list|status|install|remove|serve [--stdio]", "commands");
|
|
74
|
+
assertIncludes(commands, "iola yandex setup|menu|status|services|enable|disable|oauth-url|token", "commands");
|
|
64
75
|
assertIncludes(commands, "iola delete", "commands");
|
|
65
76
|
assertNotIncludes(commands, "iola uninstall", "commands");
|
|
66
77
|
assertNotIncludes(commands, "Госуслуг", "commands");
|
package/wiki/Yandex-Connector.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`Yandex Connector` - единая точка подключения пользовательских сервисов Яндекса в `iola-cli`.
|
|
4
4
|
|
|
5
|
-
Цель: пользователь
|
|
5
|
+
Цель: пользователь подключает сервисы Яндекса через обычный вход в браузере, а CLI хранит OAuth-токены локально. Какие функции CLI реально использует, пользователь выбирает отдельно через `/yandex`.
|
|
6
6
|
|
|
7
7
|
Секреты сохраняются только на компьютере пользователя в `~/.iola/secrets.json`. Они не отправляются на сервер IOLA и не попадают в `iola cloud backup`.
|
|
8
8
|
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
```bash
|
|
12
12
|
iola yandex services
|
|
13
13
|
iola yandex setup
|
|
14
|
+
iola yandex menu
|
|
14
15
|
iola yandex status
|
|
15
16
|
iola yandex doctor
|
|
16
17
|
iola yandex enable disk mail calendar
|
|
@@ -50,28 +51,41 @@ Backlog после первого контура:
|
|
|
50
51
|
|
|
51
52
|
## Как подключить
|
|
52
53
|
|
|
53
|
-
1.
|
|
54
|
-
|
|
54
|
+
1. Запустите подключение. Оно не спрашивает список сервисов, а открывает браузер для входа в Яндекс:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
iola yandex setup
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
2. Авторизуйтесь в Яндексе и разрешите доступ. В текущем встроенном приложении `IOLA Yandex Core` запрашиваются права:
|
|
55
61
|
- `login:info`;
|
|
56
62
|
- `login:email`;
|
|
57
63
|
- `cloud_api:disk.read`;
|
|
58
64
|
- `cloud_api:disk.write`;
|
|
59
|
-
- `cloud_api:disk.info
|
|
60
|
-
|
|
65
|
+
- `cloud_api:disk.info`;
|
|
66
|
+
- `mail:imap_full`;
|
|
67
|
+
- `mail:smtp`.
|
|
68
|
+
3. После успешной авторизации браузер вернется на локальную страницу `iola-cli`, а CLI сам сохранит OAuth-токен.
|
|
69
|
+
4. Если автоматический браузерный flow недоступен, используйте fallback для разработки:
|
|
61
70
|
|
|
62
71
|
```bash
|
|
63
|
-
iola yandex setup
|
|
72
|
+
iola yandex setup --client-id CLIENT_ID --print-url
|
|
73
|
+
iola yandex token set
|
|
64
74
|
```
|
|
65
75
|
|
|
66
|
-
|
|
67
|
-
5. Скопируйте OAuth-токен.
|
|
68
|
-
6. Сохраните токен:
|
|
76
|
+
5. Выберите, какие функции CLI реально использует:
|
|
69
77
|
|
|
70
78
|
```bash
|
|
71
|
-
iola yandex
|
|
79
|
+
iola yandex menu
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
В интерактивном CLI это же меню открывается slash-командой:
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
/yandex
|
|
72
86
|
```
|
|
73
87
|
|
|
74
|
-
|
|
88
|
+
6. Проверьте:
|
|
75
89
|
|
|
76
90
|
```bash
|
|
77
91
|
iola yandex doctor
|
|
@@ -80,10 +94,30 @@ iola cloud doctor
|
|
|
80
94
|
|
|
81
95
|
Если включен `disk`, токен автоматически подключается и к старому облачному провайдеру `yandex-disk`, поэтому команды `iola cloud ...` продолжают работать.
|
|
82
96
|
|
|
97
|
+
## Иконка приложения
|
|
98
|
+
|
|
99
|
+
CLI поставляется с готовой иконкой OAuth-приложения. При установке она копируется в:
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
~/.iola/assets/iola-oauth-icon.png
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
В репозитории файл лежит здесь:
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
docs/assets/iola-oauth-icon.png
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Если создается новое OAuth-приложение Яндекса вручную, эту иконку можно указать в поле "Иконка сервиса".
|
|
112
|
+
|
|
83
113
|
## Важно
|
|
84
114
|
|
|
85
115
|
Yandex Connector не является универсальным ключом ко всему Яндексу.
|
|
86
116
|
|
|
87
117
|
Обычные пользовательские сервисы работают через OAuth и scope. Yandex Cloud, YandexGPT и Geocoder требуют отдельные ключи, folder ID или настройки в Yandex Cloud.
|
|
88
118
|
|
|
119
|
+
Для браузерного подключения в сборке CLI уже указан public OAuth `client_id` приложения IOLA. Это не секретный ключ. Пользователь не должен создавать OAuth-приложение вручную.
|
|
120
|
+
|
|
121
|
+
Яндекс ограничивает количество разных групп сервисов в одном OAuth-приложении. Поэтому один токен не всегда может покрыть Диск, Почту, Календарь, Контакты, Wiki, Tracker и Forms сразу. CLI поддерживает группировку по нескольким OAuth-приложениям; текущий первый контур - `identity`, `disk`, `mail`.
|
|
122
|
+
|
|
89
123
|
CLI не должен автоматически оформлять покупки, вызывать такси, подтверждать доставку или выполнять платежи. Для таких сценариев допустима только подготовка ссылки, маршрута или списка, а финальное действие делает пользователь в приложении Яндекса.
|
|
@@ -114,6 +114,8 @@ iola index folder ./docs
|
|
|
114
114
|
- Yandex ID;
|
|
115
115
|
- Яндекс Диск.
|
|
116
116
|
|
|
117
|
+
Мастер не спрашивает список сервисов. Он подключает коннектор с максимальным набором OAuth-прав, а активные функции CLI выбираются позже командой `/yandex` в интерактивном CLI или `iola yandex menu` в терминале.
|
|
118
|
+
|
|
117
119
|
В коннектор также заложены категории для проверки: Почта, Календарь, Контакты, Wiki, Tracker, Forms, Документы/360. Такси, Маркет и Доставка записаны в backlog только как подготовка ссылки, маршрута или списка без заказа и оплаты.
|
|
118
120
|
|
|
119
121
|
Инструкция: [Yandex Connector](Yandex-Connector).
|