@iola_adm/iola-cli 0.2.16 → 0.2.17

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
@@ -197,9 +197,9 @@ iola yandex menu
197
197
  iola yandex status
198
198
  ```
199
199
 
200
- Первый контур: Yandex ID, Яндекс Диск и Яндекс Почта. Календарь, контакты, Wiki, Tracker, Forms и документы 360 заложены как категории для проверки и могут потребовать отдельное OAuth-приложение Яндекса. Такси, Маркет и Доставка записаны в backlog только как сценарии подготовки ссылки/маршрута/списка без заказа и оплаты.
200
+ Yandex Connector использует две встроенные OAuth-группы: `IOLA CLI A` для Yandex ID, Диска, Почты и документов через Диск; `IOLA CLI B` для Календаря, Контактов и Телемоста через календарь. Такси, Маркет и Доставка записаны в backlog только как сценарии подготовки ссылки/маршрута/списка без заказа и оплаты.
201
201
 
202
- В `/yandex` функции выбираются номерами через запятую, как в мастере настройки. OAuth-права сами по себе не создают функциональность: под каждый сервис нужны отдельные команды и тулы.
202
+ В `/yandex` функции выбираются номерами через запятую, как в мастере настройки. Там же есть пункт `Удалить подключение-коннектор`, который чистит локальные токены и настройки Yandex Connector. OAuth-права сами по себе не создают функциональность: под каждый сервис нужны отдельные команды и тулы.
203
203
 
204
204
  Инструкция: [Yandex Connector](https://github.com/adm-iola/iola-cli/wiki/Yandex-Connector).
205
205
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iola_adm/iola-cli",
3
- "version": "0.2.16",
3
+ "version": "0.2.17",
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
@@ -42,8 +42,8 @@ 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 || "";
45
+ const YANDEX_CONNECTOR_CLIENT_ID = process.env.IOLA_YANDEX_OAUTH_CLIENT_ID || process.env.YANDEX_OAUTH_CLIENT_ID || "9b7c9bcf81e8491f9bd36ba44fa76128";
46
+ const YANDEX_CONNECTOR_ORGANIZER_CLIENT_ID = process.env.IOLA_YANDEX_ORGANIZER_OAUTH_CLIENT_ID || "4ab53d8557e64ac98534ed60295cb138";
47
47
  const YANDEX_CONNECTOR_REDIRECT_HOST = "127.0.0.1";
48
48
  const YANDEX_CONNECTOR_REDIRECT_PORT = Number(process.env.IOLA_YANDEX_OAUTH_PORT || 18791);
49
49
  const YANDEX_CONNECTOR_REDIRECT_PATH = "/yandex/oauth/callback";
@@ -79,30 +79,9 @@ const YANDEX_CONNECTOR_SERVICES = {
79
79
  contacts: {
80
80
  title: "Яндекс Контакты",
81
81
  category: "contacts",
82
- scope: "carddav",
82
+ scope: "addressbook:all",
83
83
  status: "research",
84
- hint: "контакты через CardDAV/360, требует проверки",
85
- },
86
- wiki: {
87
- title: "Yandex Wiki",
88
- category: "workspace",
89
- scope: "wiki:read wiki:write",
90
- status: "research",
91
- hint: "страницы wiki, больше полезно организациям",
92
- },
93
- tracker: {
94
- title: "Yandex Tracker",
95
- category: "workspace",
96
- scope: "tracker:read tracker:write",
97
- status: "research",
98
- hint: "задачи и обращения, больше полезно организациям",
99
- },
100
- forms: {
101
- title: "Yandex Forms",
102
- category: "forms",
103
- scope: "forms:read forms:write",
104
- status: "research",
105
- hint: "формы и опросы, API надо подтвердить",
84
+ hint: "адресная книга и контакты, требует проверки API",
106
85
  },
107
86
  docs: {
108
87
  title: "Яндекс Документы / 360",
@@ -114,9 +93,9 @@ const YANDEX_CONNECTOR_SERVICES = {
114
93
  telemost: {
115
94
  title: "Яндекс Телемост",
116
95
  category: "meetings",
117
- scope: "",
96
+ scope: "calendar:all",
118
97
  status: "research",
119
- hint: "создание встреч через публичный API надо подтвердить",
98
+ hint: "встречи через календарное событие, если поддерживается",
120
99
  },
121
100
  cloud: {
122
101
  title: "Yandex Cloud",
@@ -157,15 +136,15 @@ const YANDEX_CONNECTOR_SERVICES = {
157
136
  const YANDEX_CONNECTOR_OAUTH_APPS = [
158
137
  {
159
138
  id: "core",
160
- title: "IOLA Yandex Core",
139
+ title: "IOLA CLI A",
161
140
  clientId: YANDEX_CONNECTOR_CLIENT_ID,
162
- services: ["identity", "disk", "mail"],
141
+ services: ["identity", "disk", "mail", "docs"],
163
142
  },
164
143
  {
165
- id: "workspace",
166
- title: "IOLA Yandex Workspace",
167
- clientId: YANDEX_CONNECTOR_WORKSPACE_CLIENT_ID,
168
- services: ["contacts", "wiki", "tracker", "forms", "docs"],
144
+ id: "organizer",
145
+ title: "IOLA CLI B",
146
+ clientId: YANDEX_CONNECTOR_ORGANIZER_CLIENT_ID,
147
+ services: ["calendar", "contacts", "telemost"],
169
148
  },
170
149
  ];
171
150
  const INDEXABLE_EXTENSIONS = /\.(md|txt|csv|json|html|docx|xlsx|pptx|pdf)$/i;
@@ -2040,7 +2019,7 @@ async function doctor(args = []) {
2040
2019
  openaiKey: process.env.OPENAI_API_KEY ? "env" : secrets.openai?.apiKey ? "local" : "missing",
2041
2020
  openrouterKey: process.env.OPENROUTER_API_KEY ? "env" : secrets.openrouter?.apiKey ? "local" : "missing",
2042
2021
  yandexGeocoderKey: (process.env.YANDEX_GEOCODER_API_KEY || process.env.YANDEX_MAPS_API_KEY) ? "env" : secrets.yandexGeocoder?.apiKey ? "local" : "missing",
2043
- yandexConnector: (process.env.YANDEX_OAUTH_TOKEN || secrets.yandex?.oauthToken || secrets.cloud?.["yandex-disk"]?.token) ? "local/env" : "missing",
2022
+ yandexConnector: (process.env.YANDEX_OAUTH_TOKEN || secrets.yandex?.oauthToken || Object.keys(secrets.yandex?.oauthApps || {}).length || secrets.cloud?.["yandex-disk"]?.token) ? "local/env" : "missing",
2044
2023
  yandexAuthorized: config.yandex?.authorizedServices?.join(", ") || "-",
2045
2024
  yandexServices: config.yandex?.enabledServices?.join(", ") || (secrets.cloud?.["yandex-disk"]?.token ? "disk (legacy cloud token)" : "-"),
2046
2025
  ollama: diagnostics.ollama.installed ? diagnostics.ollama.version : "not-installed",
@@ -3285,7 +3264,7 @@ async function handleYandex(args) {
3285
3264
  iola yandex disable mail
3286
3265
  iola yandex oauth-url [disk mail calendar] [--client-id ID] [--open]
3287
3266
  iola yandex token set
3288
- iola yandex token delete
3267
+ iola yandex token delete удалить локальные токены и настройки коннектора
3289
3268
  iola yandex backlog`);
3290
3269
  }
3291
3270
 
@@ -3343,11 +3322,12 @@ async function setupYandexConnector(args = []) {
3343
3322
  }
3344
3323
  await printYandexConnectorStatus({ check: true });
3345
3324
  } 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);
3325
+ console.log("Откройте ссылки авторизации, получите OAuth-токены и сохраните их командой: iola yandex token set --app APP_ID");
3326
+ for (const app of oauthApps.length ? oauthApps : [{ id: "custom", title: "Yandex Connector", clientId, services: authorizedServices }]) {
3327
+ const url = buildYandexOAuthUrl({ clientId: app.clientId, services: app.services, redirectUrl });
3328
+ console.log(`${app.id}: ${url}`);
3329
+ if (options.open) await openUrl(url);
3330
+ }
3351
3331
  }
3352
3332
  } else {
3353
3333
  console.log("Yandex Connector не может открыть браузер: в этой сборке не задан public OAuth client_id приложения IOLA.");
@@ -3362,7 +3342,8 @@ async function chooseYandexServicesMenu() {
3362
3342
  return;
3363
3343
  }
3364
3344
  const config = await loadConfig();
3365
- const serviceIds = Object.keys(YANDEX_CONNECTOR_SERVICES);
3345
+ const serviceIds = getYandexConnectorMenuServiceIds();
3346
+ const deleteNumber = serviceIds.length + 1;
3366
3347
  const enabled = new Set(config.yandex?.enabledServices?.length ? config.yandex.enabledServices : ["identity", "disk"]);
3367
3348
  const authState = await getYandexServiceAuthState();
3368
3349
  console.log("Функции Яндекса.");
@@ -3374,6 +3355,7 @@ async function chooseYandexServicesMenu() {
3374
3355
  const authLabel = auth?.hasToken ? "подключено" : (auth?.authorized ? "нужен вход" : "нет прав");
3375
3356
  console.log(`${index + 1}. [${marker}] ${service.title} - ${service.hint} (${service.status}, ${authLabel})`);
3376
3357
  });
3358
+ console.log(`${deleteNumber}. Удалить подключение-коннектор`);
3377
3359
  console.log("0. Отмена");
3378
3360
  const defaults = serviceIds.map((id, index) => enabled.has(id) ? String(index + 1) : "").filter(Boolean);
3379
3361
  const answer = (await askText(`Номера через запятую [${defaults.join(",") || "1,2"}]: `)).trim();
@@ -3382,6 +3364,16 @@ async function chooseYandexServicesMenu() {
3382
3364
  return;
3383
3365
  }
3384
3366
  const selectedNumbers = answer ? answer.split(/[,\s]+/).filter(Boolean) : (defaults.length ? defaults : ["1", "2"]);
3367
+ if (selectedNumbers.includes(String(deleteNumber))) {
3368
+ if (selectedNumbers.length > 1) throw new Error("Удаление коннектора выбирается отдельно, без других пунктов.");
3369
+ const ok = await askYesNo("Удалить локальные токены и настройки Yandex Connector? [y/N] ", false);
3370
+ if (!ok) {
3371
+ console.log("Удаление отменено.");
3372
+ return;
3373
+ }
3374
+ await deleteYandexConnectorToken();
3375
+ return;
3376
+ }
3385
3377
  const selected = selectedNumbers.map((item) => {
3386
3378
  const index = Number(item) - 1;
3387
3379
  if (!Number.isInteger(index) || index < 0 || index >= serviceIds.length) {
@@ -3397,6 +3389,12 @@ async function chooseYandexServicesMenu() {
3397
3389
  if (missingToken.length) console.log(`Нужно пройти вход Яндекса для: ${missingToken.join(", ")}. Запустите iola yandex setup.`);
3398
3390
  }
3399
3391
 
3392
+ function getYandexConnectorMenuServiceIds() {
3393
+ return Object.entries(YANDEX_CONNECTOR_SERVICES)
3394
+ .filter(([, service]) => service.status === "ready" || service.status === "research")
3395
+ .map(([id]) => id);
3396
+ }
3397
+
3400
3398
  async function updateYandexEnabledServices(rawServices, enabled) {
3401
3399
  const config = await loadConfig();
3402
3400
  const current = new Set(config.yandex?.enabledServices || []);
@@ -3440,11 +3438,15 @@ async function saveYandexAuthorizedServices(services) {
3440
3438
  async function buildYandexOAuthUrlFromConfig(rawArgs = []) {
3441
3439
  const options = parseOptions(rawArgs);
3442
3440
  const config = await loadConfig();
3443
- const clientId = options["client-id"] || config.yandex?.oauth?.clientId || YANDEX_CONNECTOR_CLIENT_ID;
3444
- if (!clientId) throw new Error("Yandex OAuth Client ID не задан. Пример: iola yandex oauth-url disk --client-id CLIENT_ID");
3441
+ const apps = getConfiguredYandexOAuthApps();
3445
3442
  const capableServices = getYandexOAuthCapableServiceIds();
3446
3443
  const requestedServices = normalizeYandexServiceList(options._.length ? options._ : capableServices);
3447
- const services = requestedServices.filter((id) => capableServices.includes(id));
3444
+ const app = options.app
3445
+ ? apps.find((item) => item.id === options.app)
3446
+ : apps.find((item) => requestedServices.some((service) => item.services.includes(service))) || apps[0];
3447
+ const clientId = options["client-id"] || app?.clientId || config.yandex?.oauth?.clientId || YANDEX_CONNECTOR_CLIENT_ID;
3448
+ if (!clientId) throw new Error("Yandex OAuth Client ID не задан. Пример: iola yandex oauth-url disk --client-id CLIENT_ID");
3449
+ const services = requestedServices.filter((id) => capableServices.includes(id) && (!app || app.services.includes(id)));
3448
3450
  return buildYandexOAuthUrl({ clientId, services, redirectUrl: options["redirect-url"] || config.yandex?.oauth?.redirectUrl || YANDEX_OAUTH_REDIRECT_URL });
3449
3451
  }
3450
3452
 
@@ -3557,7 +3559,6 @@ function waitForYandexOAuthToken({ clientId, services, redirectUrl }) {
3557
3559
  function getYandexScopesForServices(services) {
3558
3560
  const scopes = new Set();
3559
3561
  const normalized = normalizeYandexServiceList(services);
3560
- if (normalized.length > 0 && !normalized.includes("identity")) normalized.unshift("identity");
3561
3562
  for (const id of normalized) {
3562
3563
  const raw = YANDEX_CONNECTOR_SERVICES[id]?.scope || "";
3563
3564
  for (const scope of raw.split(/\s+/).filter(Boolean)) scopes.add(scope);
@@ -3614,7 +3615,18 @@ async function deleteYandexConnectorToken() {
3614
3615
  if (secrets.cloud?.["yandex-disk"]) delete secrets.cloud["yandex-disk"];
3615
3616
  if (secrets.cloud && Object.keys(secrets.cloud).length === 0) delete secrets.cloud;
3616
3617
  await saveSecrets(secrets);
3617
- console.log("Yandex Connector token удален. Токен Яндекс Диска в cloud тоже удален.");
3618
+ await deleteLocalYandexConnectorConfig();
3619
+ console.log("Yandex Connector удален локально. Токены и настройки приложений очищены.");
3620
+ }
3621
+
3622
+ async function deleteLocalYandexConnectorConfig() {
3623
+ const local = await readConfigLayer(CONFIG_FILE);
3624
+ if (!local) return;
3625
+ delete local.yandex;
3626
+ if (local.cloud?.activeProvider === "yandex-disk") local.cloud.activeProvider = "";
3627
+ await mkdir(CONFIG_DIR, { recursive: true });
3628
+ if (existsSync(CONFIG_FILE)) await copyFile(CONFIG_FILE, LAST_GOOD_CONFIG_FILE).catch(() => {});
3629
+ await writeFile(CONFIG_FILE, `${JSON.stringify(local, null, 2)}\n`, "utf8");
3618
3630
  }
3619
3631
 
3620
3632
  async function printYandexConnectorStatus(options = {}) {
@@ -11390,7 +11402,7 @@ async function getOnboardComponentStatus() {
11390
11402
  browser: browser.installed === "yes",
11391
11403
  "yandex-geocoder": Boolean(yandexGeocoderKey),
11392
11404
  cloud: Object.keys(cloudSecrets).length > 0,
11393
- yandex: Boolean(secrets.yandex?.oauthToken || config.yandex?.enabledServices?.length),
11405
+ yandex: Boolean(secrets.yandex?.oauthToken || Object.keys(secrets.yandex?.oauthApps || {}).length || config.yandex?.enabledServices?.length),
11394
11406
  };
11395
11407
  }
11396
11408
 
@@ -13415,6 +13427,12 @@ function sanitizeConfig(config) {
13415
13427
  if (Array.isArray(next.skills?.enabled) && next.skills.enabled.includes("local-files") && !next.skills.enabled.includes("personal-docs")) {
13416
13428
  next.skills.enabled = [...next.skills.enabled, "personal-docs"];
13417
13429
  }
13430
+ if (Array.isArray(next.yandex?.enabledServices)) {
13431
+ next.yandex.enabledServices = next.yandex.enabledServices.filter((service) => Boolean(YANDEX_CONNECTOR_SERVICES[service]));
13432
+ }
13433
+ if (Array.isArray(next.yandex?.authorizedServices)) {
13434
+ next.yandex.authorizedServices = next.yandex.authorizedServices.filter((service) => Boolean(YANDEX_CONNECTOR_SERVICES[service]));
13435
+ }
13418
13436
  const localProfile = next.ai?.profiles?.local;
13419
13437
  if (localProfile?.provider === "iola") {
13420
13438
  if (!localProfile.runtime || localProfile.model === "iola-router-1b") {
@@ -60,11 +60,14 @@ assertIncludes(cliSource, "Другая Ollama-модель", "Local model selec
60
60
  assertIncludes(cliSource, "chooseYandexServicesMenu", "Yandex Connector should have a service selection menu");
61
61
  assertIncludes(cliSource, "Функции Яндекса.", "Yandex service selection should use a numbered menu");
62
62
  assertIncludes(cliSource, "Выберите номера функций через запятую", "Yandex service selection should ask for numbers");
63
+ assertIncludes(cliSource, "Удалить подключение-коннектор", "Yandex service selection should allow connector deletion");
63
64
  assertIncludes(cliSource, "getYandexServiceAuthState", "Yandex status should derive permissions from configured OAuth apps");
64
65
  assertIncludes(cliSource, "OAuth-права встроенного приложения", "Yandex setup should report packaged OAuth app permissions");
65
66
  assertIncludes(cliSource, "Выбрать активные функции можно командой /yandex", "Yandex setup should direct service selection to /yandex");
66
67
  assertIncludes(cliSource, "runYandexBrowserOAuth", "Yandex setup should support browser OAuth flow");
67
68
  assertIncludes(cliSource, "IOLA_YANDEX_OAUTH_CLIENT_ID", "Yandex setup should use a packaged/env OAuth client id");
69
+ assertIncludes(cliSource, "IOLA_YANDEX_ORGANIZER_OAUTH_CLIENT_ID", "Yandex setup should support the organizer OAuth app group");
70
+ assertIncludes(cliSource, "addressbook:all", "Yandex contacts should use the addressbook OAuth scope");
68
71
  assertIncludes(cliSource, "--app", "Yandex token command should persist tokens by OAuth app group");
69
72
  assertNotIncludes(cliSource, "Сервисы через запятую [identity,disk]", "Yandex setup should not ask for services during connector setup");
70
73
  if (!packageJson.files.includes("docs/assets/iola-oauth-icon.png")) {
@@ -21,6 +21,15 @@ iola yandex token set
21
21
  iola yandex token delete
22
22
  ```
23
23
 
24
+ ## OAuth-приложения
25
+
26
+ Для пользователя это один `Yandex Connector`, но внутри CLI проходит две авторизации Яндекса:
27
+
28
+ - `IOLA CLI A` - Yandex ID, Яндекс Диск, Яндекс Почта, Яндекс Документы через Диск;
29
+ - `IOLA CLI B` - Яндекс Календарь, Яндекс Контакты, Телемост через календарное событие.
30
+
31
+ `client_id` этих приложений публичный и встроен в CLI. Пользователь не создает OAuth-приложения вручную: он только входит в свой Яндекс-аккаунт и разрешает доступ. Токены сохраняются локально на компьютере пользователя.
32
+
24
33
  ## Категории
25
34
 
26
35
  Готово к первому контуру:
@@ -33,10 +42,8 @@ iola yandex token delete
33
42
  - `mail` - Яндекс Почта, чтение и поиск писем, отправка только после явного подтверждения;
34
43
  - `calendar` - Яндекс Календарь;
35
44
  - `contacts` - Яндекс Контакты;
36
- - `wiki` - Yandex Wiki;
37
- - `tracker` - Yandex Tracker;
38
- - `forms` - Yandex Forms;
39
- - `docs` - Яндекс Документы / 360.
45
+ - `docs` - Яндекс Документы / 360 через Диск;
46
+ - `telemost` - Яндекс Телемост через календарь, если сценарий подтвердится.
40
47
 
41
48
  Отдельные ключи, не обычный OAuth бытового Яндекса:
42
49
 
@@ -57,7 +64,7 @@ Backlog после первого контура:
57
64
  iola yandex setup
58
65
  ```
59
66
 
60
- 2. Авторизуйтесь в Яндексе и разрешите доступ. В текущем встроенном приложении `IOLA Yandex Core` запрашиваются права:
67
+ 2. Авторизуйтесь в Яндексе и разрешите доступ для `IOLA CLI A`. Запрашиваются права:
61
68
  - `login:info`;
62
69
  - `login:email`;
63
70
  - `cloud_api:disk.read`;
@@ -65,15 +72,18 @@ iola yandex setup
65
72
  - `cloud_api:disk.info`;
66
73
  - `mail:imap_full`;
67
74
  - `mail:smtp`.
68
- 3. После успешной авторизации браузер вернется на локальную страницу `iola-cli`, а CLI сам сохранит OAuth-токен.
69
- 4. Если автоматический браузерный flow недоступен, используйте fallback для разработки:
75
+ 3. Затем CLI откроет вторую авторизацию для `IOLA CLI B`. Запрашиваются права:
76
+ - `calendar:all`;
77
+ - `addressbook:all`.
78
+ 4. После каждой успешной авторизации браузер вернется на локальную страницу `iola-cli`, а CLI сам сохранит OAuth-токен нужной группы.
79
+ 5. Если автоматический браузерный flow недоступен, используйте fallback для разработки:
70
80
 
71
81
  ```bash
72
82
  iola yandex setup --client-id CLIENT_ID --print-url
73
83
  iola yandex token set
74
84
  ```
75
85
 
76
- 5. Выберите, какие функции CLI реально использует:
86
+ 6. Выберите, какие функции CLI реально использует:
77
87
 
78
88
  ```bash
79
89
  iola yandex menu
@@ -86,8 +96,9 @@ iola yandex menu
86
96
  ```
87
97
 
88
98
  Меню `/yandex` работает как мастер настройки: сервисы выбираются номерами через запятую, а не вводом технических названий.
99
+ В этом же меню есть отдельный пункт `Удалить подключение-коннектор`: он удаляет локальные токены и настройки Yandex Connector, чтобы можно было подключить другой Яндекс-аккаунт или отказаться от сервисов Яндекса.
89
100
 
90
- 6. Проверьте:
101
+ 7. Проверьте:
91
102
 
92
103
  ```bash
93
104
  iola yandex doctor
@@ -103,8 +114,10 @@ OAuth-права дают CLI разрешение обращаться к се
103
114
  - `identity` - проверить пользователя и email;
104
115
  - `disk` - папки, загрузка, скачивание, поиск файлов, публичные ссылки;
105
116
  - `mail` - список писем, поиск, чтение письма, отправка письма после явного подтверждения;
106
- - `calendar` - список событий, создание события, напоминания; требует отдельного OAuth-приложения/проверки прав;
107
- - `contacts` - поиск контактов и карточки контактов; требует отдельного OAuth-приложения/проверки прав.
117
+ - `calendar` - список событий, создание события, напоминания;
118
+ - `contacts` - поиск контактов и карточки контактов;
119
+ - `docs` - поиск и работа с документами через Диск;
120
+ - `telemost` - подготовка встречи через календарь, если подтвердится.
108
121
 
109
122
  Включение сервиса в `/yandex` только разрешает CLI использовать соответствующую категорию. Если прав или тула еще нет, CLI должен честно показать это, а не имитировать работу.
110
123
 
@@ -132,6 +145,6 @@ Yandex Connector не является универсальным ключом
132
145
 
133
146
  Для браузерного подключения в сборке CLI уже указан public OAuth `client_id` приложения IOLA. Это не секретный ключ. Пользователь не должен создавать OAuth-приложение вручную.
134
147
 
135
- Яндекс ограничивает количество разных групп сервисов в одном OAuth-приложении. Поэтому один токен не всегда может покрыть Диск, Почту, Календарь, Контакты, Wiki, Tracker и Forms сразу. CLI поддерживает группировку по нескольким OAuth-приложениям; текущий первый контур - `identity`, `disk`, `mail`.
148
+ Яндекс ограничивает количество разных групп сервисов в одном OAuth-приложении. Поэтому один токен не может покрыть Диск, Почту, Календарь и Контакты сразу. CLI использует две встроенные группы: `IOLA CLI A` и `IOLA CLI B`.
136
149
 
137
150
  CLI не должен автоматически оформлять покупки, вызывать такси, подтверждать доставку или выполнять платежи. Для таких сценариев допустима только подготовка ссылки, маршрута или списка, а финальное действие делает пользователь в приложении Яндекса.