@iola_adm/iola-cli 0.2.9 → 0.2.11
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 +14 -0
- package/bin/postinstall.js +1 -1
- package/package.json +1 -1
- package/src/cli.js +486 -24
- package/wiki/Home.md +2 -0
- package/wiki/Yandex-Connector.md +89 -0
- package/wiki//320/232/320/276/320/274/320/260/320/275/320/264/321/213.md +14 -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 +15 -2
- package/wiki//320/236/320/261/320/273/320/260/321/207/320/275/321/213/320/265-/320/264/320/270/321/201/320/272/320/270.md +4 -0
- package/wiki//320/241/320/272/320/270/320/273/320/273/321/213-/320/264/320/273/321/217-/320/266/320/270/321/202/320/265/320/273/320/265/320/271.md +33 -0
package/README.md
CHANGED
|
@@ -188,6 +188,18 @@ iola cloud backup
|
|
|
188
188
|
|
|
189
189
|
Инструкция: [Облачные диски](https://github.com/adm-iola/iola-cli/wiki/Облачные-диски).
|
|
190
190
|
|
|
191
|
+
Yandex Connector объединяет пользовательские сервисы Яндекса в категории:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
iola yandex services
|
|
195
|
+
iola yandex setup
|
|
196
|
+
iola yandex status
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Первый контур: Yandex ID и Яндекс Диск. Почта, календарь, контакты, Wiki, Tracker, Forms и документы 360 заложены как категории для проверки. Такси, Маркет и Доставка записаны в backlog только как сценарии подготовки ссылки/маршрута/списка без заказа и оплаты.
|
|
200
|
+
|
|
201
|
+
Инструкция: [Yandex Connector](https://github.com/adm-iola/iola-cli/wiki/Yandex-Connector).
|
|
202
|
+
|
|
191
203
|
Зарубежные API-ключи:
|
|
192
204
|
|
|
193
205
|
- OpenAI Platform: регистрация `https://platform.openai.com/`, ключи `https://platform.openai.com/api-keys`;
|
|
@@ -215,6 +227,7 @@ iola version --check
|
|
|
215
227
|
- [Мастер настройки](https://github.com/adm-iola/iola-cli/wiki/Мастер-настройки)
|
|
216
228
|
- [AI-профили](https://github.com/adm-iola/iola-cli/wiki/AI-профили)
|
|
217
229
|
- [Yandex Geocoder API key](https://github.com/adm-iola/iola-cli/wiki/Yandex-Geocoder-API-key)
|
|
230
|
+
- [Yandex Connector](https://github.com/adm-iola/iola-cli/wiki/Yandex-Connector)
|
|
218
231
|
- [Облачные диски](https://github.com/adm-iola/iola-cli/wiki/Облачные-диски)
|
|
219
232
|
- [Скиллы для жителей](https://github.com/adm-iola/iola-cli/wiki/Скиллы-для-жителей)
|
|
220
233
|
- [Локальный инструментальный агент](https://github.com/adm-iola/iola-cli/wiki/Локальный-инструментальный-агент)
|
|
@@ -236,6 +249,7 @@ iola version --check
|
|
|
236
249
|
- поиск и выгрузка открытых данных;
|
|
237
250
|
- локальная SQLite-БД, история, сессии и FTS-поиск;
|
|
238
251
|
- AI-профили для IOLA local, Ollama, YandexGPT, GigaChat, OpenAI, OpenRouter и Codex CLI;
|
|
252
|
+
- Yandex Connector: единая точка подключения пользовательских сервисов Яндекса с локальным хранением OAuth-токена;
|
|
239
253
|
- локальный tool-agent для модели IOLA с tools `search_data`, `search_entities`, `resolve_entity_field`, `get_card`, `export_report`, `file_read`, `browser_open`;
|
|
240
254
|
- ленивые skills, toolsets, permissions, memory, hooks и готовые agents;
|
|
241
255
|
- личные облачные диски: Яндекс Диск и Облако Mail.ru для сохранения отчетов, backup и документов;
|
package/bin/postinstall.js
CHANGED
|
@@ -19,7 +19,7 @@ const steps = [
|
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
title: "Проверка локальной модели IOLA",
|
|
22
|
-
args: [cliPath, "ai", "setup", "iola", "--yes", "--quiet", "--optional"],
|
|
22
|
+
args: [cliPath, "ai", "setup", "iola", "--yes", "--quiet", "--optional", "--preserve-active"],
|
|
23
23
|
},
|
|
24
24
|
];
|
|
25
25
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -40,6 +40,115 @@ const LOCAL_CONFIG_FILE = path.join(PROJECT_IOLA_DIR, "local.json");
|
|
|
40
40
|
const BROWSER_RUNTIME_DIR = path.join(CONFIG_DIR, "browser-runtime");
|
|
41
41
|
const BROWSER_RUNTIME_PACKAGE = path.join(BROWSER_RUNTIME_DIR, "node_modules", "playwright", "package.json");
|
|
42
42
|
const CLOUD_DEFAULT_REMOTE_DIR = "/IOLA";
|
|
43
|
+
const YANDEX_OAUTH_AUTHORIZE_URL = "https://oauth.yandex.ru/authorize";
|
|
44
|
+
const YANDEX_OAUTH_REDIRECT_URL = "https://oauth.yandex.ru/verification_code";
|
|
45
|
+
const YANDEX_CONNECTOR_SERVICES = {
|
|
46
|
+
identity: {
|
|
47
|
+
title: "Yandex ID",
|
|
48
|
+
category: "identity",
|
|
49
|
+
scope: "login:info login:email",
|
|
50
|
+
status: "ready",
|
|
51
|
+
hint: "профиль, логин и email пользователя",
|
|
52
|
+
},
|
|
53
|
+
disk: {
|
|
54
|
+
title: "Яндекс Диск",
|
|
55
|
+
category: "cloud-storage",
|
|
56
|
+
scope: "cloud_api:disk.read cloud_api:disk.write cloud_api:disk.info",
|
|
57
|
+
status: "ready",
|
|
58
|
+
hint: "файлы, папка /IOLA, загрузка, скачивание, публичные ссылки",
|
|
59
|
+
},
|
|
60
|
+
mail: {
|
|
61
|
+
title: "Яндекс Почта",
|
|
62
|
+
category: "mail",
|
|
63
|
+
scope: "mail:imap_full mail:smtp",
|
|
64
|
+
status: "research",
|
|
65
|
+
hint: "чтение/поиск писем и отправка только после подтверждения",
|
|
66
|
+
},
|
|
67
|
+
calendar: {
|
|
68
|
+
title: "Яндекс Календарь",
|
|
69
|
+
category: "calendar",
|
|
70
|
+
scope: "calendar:all",
|
|
71
|
+
status: "research",
|
|
72
|
+
hint: "события и напоминания, протокол требует отдельной проверки",
|
|
73
|
+
},
|
|
74
|
+
contacts: {
|
|
75
|
+
title: "Яндекс Контакты",
|
|
76
|
+
category: "contacts",
|
|
77
|
+
scope: "carddav",
|
|
78
|
+
status: "research",
|
|
79
|
+
hint: "контакты через CardDAV/360, требует проверки",
|
|
80
|
+
},
|
|
81
|
+
wiki: {
|
|
82
|
+
title: "Yandex Wiki",
|
|
83
|
+
category: "workspace",
|
|
84
|
+
scope: "wiki:read wiki:write",
|
|
85
|
+
status: "research",
|
|
86
|
+
hint: "страницы wiki, больше полезно организациям",
|
|
87
|
+
},
|
|
88
|
+
tracker: {
|
|
89
|
+
title: "Yandex Tracker",
|
|
90
|
+
category: "workspace",
|
|
91
|
+
scope: "tracker:read tracker:write",
|
|
92
|
+
status: "research",
|
|
93
|
+
hint: "задачи и обращения, больше полезно организациям",
|
|
94
|
+
},
|
|
95
|
+
forms: {
|
|
96
|
+
title: "Yandex Forms",
|
|
97
|
+
category: "forms",
|
|
98
|
+
scope: "forms:read forms:write",
|
|
99
|
+
status: "research",
|
|
100
|
+
hint: "формы и опросы, API надо подтвердить",
|
|
101
|
+
},
|
|
102
|
+
docs: {
|
|
103
|
+
title: "Яндекс Документы / 360",
|
|
104
|
+
category: "documents",
|
|
105
|
+
scope: "cloud_api:disk.read cloud_api:disk.write",
|
|
106
|
+
status: "research",
|
|
107
|
+
hint: "обычно работает через файлы на Диске",
|
|
108
|
+
},
|
|
109
|
+
telemost: {
|
|
110
|
+
title: "Яндекс Телемост",
|
|
111
|
+
category: "meetings",
|
|
112
|
+
scope: "",
|
|
113
|
+
status: "research",
|
|
114
|
+
hint: "создание встреч через публичный API надо подтвердить",
|
|
115
|
+
},
|
|
116
|
+
cloud: {
|
|
117
|
+
title: "Yandex Cloud",
|
|
118
|
+
category: "cloud-platform",
|
|
119
|
+
scope: "",
|
|
120
|
+
status: "separate",
|
|
121
|
+
hint: "YandexGPT, Geocoder, SpeechKit, Vision, IAM и folder ID",
|
|
122
|
+
},
|
|
123
|
+
maps: {
|
|
124
|
+
title: "Яндекс Карты",
|
|
125
|
+
category: "maps",
|
|
126
|
+
scope: "",
|
|
127
|
+
status: "separate",
|
|
128
|
+
hint: "геокодер, маршруты и ссылки на карты через отдельный API key",
|
|
129
|
+
},
|
|
130
|
+
taxi: {
|
|
131
|
+
title: "Яндекс Go / Такси",
|
|
132
|
+
category: "mobility",
|
|
133
|
+
scope: "",
|
|
134
|
+
status: "backlog",
|
|
135
|
+
hint: "только подготовка маршрута/deep link, без заказа и оплаты",
|
|
136
|
+
},
|
|
137
|
+
market: {
|
|
138
|
+
title: "Яндекс Маркет",
|
|
139
|
+
category: "shopping",
|
|
140
|
+
scope: "",
|
|
141
|
+
status: "backlog",
|
|
142
|
+
hint: "только поиск и список покупок, без корзины и оплаты",
|
|
143
|
+
},
|
|
144
|
+
delivery: {
|
|
145
|
+
title: "Яндекс Доставка",
|
|
146
|
+
category: "delivery",
|
|
147
|
+
scope: "",
|
|
148
|
+
status: "backlog",
|
|
149
|
+
hint: "только подготовка заявки/ссылки, без оформления и оплаты",
|
|
150
|
+
},
|
|
151
|
+
};
|
|
43
152
|
const INDEXABLE_EXTENSIONS = /\.(md|txt|csv|json|html|docx|xlsx|pptx|pdf)$/i;
|
|
44
153
|
const LOCAL_TOOLS = ["search_data", "search_entities", "resolve_entity_field", "get_card", "export_report", "file_read", "browser_open", "get_current_date"];
|
|
45
154
|
const LEGACY_LOCAL_TOOLS = ["search_local", "export_data", "run_report", "save_view"];
|
|
@@ -238,6 +347,14 @@ const DEFAULT_AI_CONFIG = {
|
|
|
238
347
|
"mailru-cloud": { root: CLOUD_DEFAULT_REMOTE_DIR },
|
|
239
348
|
},
|
|
240
349
|
},
|
|
350
|
+
yandex: {
|
|
351
|
+
enabledServices: [],
|
|
352
|
+
categories: {},
|
|
353
|
+
oauth: {
|
|
354
|
+
clientId: "",
|
|
355
|
+
redirectUrl: YANDEX_OAUTH_REDIRECT_URL,
|
|
356
|
+
},
|
|
357
|
+
},
|
|
241
358
|
daemon: {
|
|
242
359
|
host: "127.0.0.1",
|
|
243
360
|
port: DAEMON_PORT,
|
|
@@ -399,6 +516,7 @@ const COMMANDS = new Map([
|
|
|
399
516
|
["tools", handleTools],
|
|
400
517
|
["files", handleFiles],
|
|
401
518
|
["cloud", handleCloud],
|
|
519
|
+
["yandex", handleYandex],
|
|
402
520
|
["archive", handleArchive],
|
|
403
521
|
["changes", handleChanges],
|
|
404
522
|
["import", handleImport],
|
|
@@ -548,6 +666,7 @@ async function showHelp() {
|
|
|
548
666
|
iola ai setup настройка AI-профиля
|
|
549
667
|
iola browser status браузерный runtime
|
|
550
668
|
iola cloud status облачные диски
|
|
669
|
+
iola yandex status Yandex Connector
|
|
551
670
|
iola mcp status MCP-подключение
|
|
552
671
|
iola doctor диагностика
|
|
553
672
|
iola wiki документация
|
|
@@ -588,6 +707,7 @@ Usage:
|
|
|
588
707
|
iola tools list|toolsets|enable|disable|profile
|
|
589
708
|
iola files status|mode|approvals|tree|read|search|write|patch
|
|
590
709
|
iola cloud setup|status|ls|find|upload|download|share|save|backup
|
|
710
|
+
iola yandex setup|status|services|enable|disable|oauth-url|token
|
|
591
711
|
iola archive doctor|list|test|extract|create|index
|
|
592
712
|
iola changes list|show|apply|discard
|
|
593
713
|
iola import file|folder
|
|
@@ -1898,6 +2018,8 @@ async function doctor(args = []) {
|
|
|
1898
2018
|
openaiKey: process.env.OPENAI_API_KEY ? "env" : secrets.openai?.apiKey ? "local" : "missing",
|
|
1899
2019
|
openrouterKey: process.env.OPENROUTER_API_KEY ? "env" : secrets.openrouter?.apiKey ? "local" : "missing",
|
|
1900
2020
|
yandexGeocoderKey: (process.env.YANDEX_GEOCODER_API_KEY || process.env.YANDEX_MAPS_API_KEY) ? "env" : secrets.yandexGeocoder?.apiKey ? "local" : "missing",
|
|
2021
|
+
yandexConnector: (process.env.YANDEX_OAUTH_TOKEN || secrets.yandex?.oauthToken || secrets.cloud?.["yandex-disk"]?.token) ? "local/env" : "missing",
|
|
2022
|
+
yandexServices: config.yandex?.enabledServices?.join(", ") || (secrets.cloud?.["yandex-disk"]?.token ? "disk (legacy cloud token)" : "-"),
|
|
1901
2023
|
ollama: diagnostics.ollama.installed ? diagnostics.ollama.version : "not-installed",
|
|
1902
2024
|
},
|
|
1903
2025
|
skills: {
|
|
@@ -1961,9 +2083,10 @@ async function doctor(args = []) {
|
|
|
1961
2083
|
printKeyValue(report.ai);
|
|
1962
2084
|
console.log("");
|
|
1963
2085
|
console.log("Skills/Toolsets/Daemon");
|
|
1964
|
-
|
|
2086
|
+
printKeyValueFull({ ...report.skills, toolsets: report.toolsets.enabled, daemon: report.daemon.status });
|
|
1965
2087
|
console.log("");
|
|
1966
|
-
|
|
2088
|
+
const isLocalAi = ["iola", "ollama"].includes(activeAiProfile.provider);
|
|
2089
|
+
printDiagnostics(diagnostics, isLocalAi ? recommendOllamaModel(diagnostics) : null);
|
|
1967
2090
|
if (options.all) {
|
|
1968
2091
|
console.log("");
|
|
1969
2092
|
console.log("Фичи");
|
|
@@ -3021,6 +3144,296 @@ async function handleCloud(args) {
|
|
|
3021
3144
|
iola cloud backup`);
|
|
3022
3145
|
}
|
|
3023
3146
|
|
|
3147
|
+
async function handleYandex(args) {
|
|
3148
|
+
const [action = "status", target, ...rest] = args;
|
|
3149
|
+
const options = parseOptions(rest);
|
|
3150
|
+
|
|
3151
|
+
if (action === "services" || action === "list") {
|
|
3152
|
+
printYandexServices();
|
|
3153
|
+
return;
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
if (action === "status" || action === "doctor") {
|
|
3157
|
+
await printYandexConnectorStatus({ check: action === "doctor" || options.check });
|
|
3158
|
+
return;
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3161
|
+
if (action === "setup") {
|
|
3162
|
+
await setupYandexConnector([target, ...rest].filter(Boolean));
|
|
3163
|
+
return;
|
|
3164
|
+
}
|
|
3165
|
+
|
|
3166
|
+
if (action === "enable" || action === "disable") {
|
|
3167
|
+
const services = [target, ...rest].filter((item) => item && !String(item).startsWith("--"));
|
|
3168
|
+
if (services.length === 0) throw new Error("Укажите сервисы. Пример: iola yandex enable disk mail calendar");
|
|
3169
|
+
await updateYandexEnabledServices(services, action === "enable");
|
|
3170
|
+
return;
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3173
|
+
if (action === "oauth-url" || action === "url") {
|
|
3174
|
+
const url = await buildYandexOAuthUrlFromConfig([target, ...rest].filter(Boolean));
|
|
3175
|
+
console.log(url);
|
|
3176
|
+
if (options.open) await openUrl(url);
|
|
3177
|
+
return;
|
|
3178
|
+
}
|
|
3179
|
+
|
|
3180
|
+
if (action === "token") {
|
|
3181
|
+
if (target === "set") {
|
|
3182
|
+
await setYandexConnectorToken(rest);
|
|
3183
|
+
return;
|
|
3184
|
+
}
|
|
3185
|
+
if (target === "delete") {
|
|
3186
|
+
await deleteYandexConnectorToken();
|
|
3187
|
+
return;
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
if (action === "backlog") {
|
|
3192
|
+
printYandexServices({ status: "backlog" });
|
|
3193
|
+
return;
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3196
|
+
throw new Error(`Команды yandex:
|
|
3197
|
+
iola yandex setup
|
|
3198
|
+
iola yandex status|doctor
|
|
3199
|
+
iola yandex services
|
|
3200
|
+
iola yandex enable disk mail calendar
|
|
3201
|
+
iola yandex disable mail
|
|
3202
|
+
iola yandex oauth-url [disk mail calendar] [--client-id ID] [--open]
|
|
3203
|
+
iola yandex token set
|
|
3204
|
+
iola yandex token delete
|
|
3205
|
+
iola yandex backlog`);
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3208
|
+
function printYandexServices(options = {}) {
|
|
3209
|
+
const rows = Object.entries(YANDEX_CONNECTOR_SERVICES)
|
|
3210
|
+
.filter(([, service]) => !options.status || service.status === options.status)
|
|
3211
|
+
.map(([id, service]) => ({
|
|
3212
|
+
id,
|
|
3213
|
+
title: service.title,
|
|
3214
|
+
category: service.category,
|
|
3215
|
+
status: service.status,
|
|
3216
|
+
scope: service.scope || "-",
|
|
3217
|
+
hint: service.hint,
|
|
3218
|
+
}));
|
|
3219
|
+
printTable(rows, [
|
|
3220
|
+
["id", "ID"],
|
|
3221
|
+
["title", "Сервис"],
|
|
3222
|
+
["category", "Категория"],
|
|
3223
|
+
["status", "Статус"],
|
|
3224
|
+
["scope", "Scope"],
|
|
3225
|
+
["hint", "Суть"],
|
|
3226
|
+
]);
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
async function setupYandexConnector(args = []) {
|
|
3230
|
+
const options = parseOptions(args);
|
|
3231
|
+
const config = await loadConfig();
|
|
3232
|
+
let services = normalizeYandexServiceList(options._);
|
|
3233
|
+
|
|
3234
|
+
if (process.stdin.isTTY && services.length === 0) {
|
|
3235
|
+
console.log("Yandex Connector: выберите функции Яндекса.");
|
|
3236
|
+
printYandexServices();
|
|
3237
|
+
const answer = await askText("Сервисы через запятую [identity,disk]: ");
|
|
3238
|
+
services = normalizeYandexServiceList(answer.trim() ? answer.split(/[,\s]+/) : ["identity", "disk"]);
|
|
3239
|
+
}
|
|
3240
|
+
|
|
3241
|
+
if (services.length === 0) services = ["identity", "disk"];
|
|
3242
|
+
await saveYandexEnabledServices(services);
|
|
3243
|
+
|
|
3244
|
+
const clientId = options["client-id"] || config.yandex?.oauth?.clientId || (process.stdin.isTTY ? (await askText("Yandex OAuth Client ID [Enter - пропустить]: ")).trim() : "");
|
|
3245
|
+
if (clientId) {
|
|
3246
|
+
await saveConfig({
|
|
3247
|
+
yandex: {
|
|
3248
|
+
...(config.yandex || {}),
|
|
3249
|
+
oauth: { ...(config.yandex?.oauth || {}), clientId, redirectUrl: YANDEX_OAUTH_REDIRECT_URL },
|
|
3250
|
+
},
|
|
3251
|
+
});
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
console.log("Yandex Connector настроен.");
|
|
3255
|
+
console.log(`Включены сервисы: ${services.join(", ")}`);
|
|
3256
|
+
if (clientId) {
|
|
3257
|
+
const url = buildYandexOAuthUrl({ clientId, services });
|
|
3258
|
+
console.log("Откройте ссылку авторизации, получите OAuth-токен и сохраните его командой: iola yandex token set");
|
|
3259
|
+
console.log(url);
|
|
3260
|
+
if (options.open) await openUrl(url);
|
|
3261
|
+
} else {
|
|
3262
|
+
console.log("Client ID не задан. Создайте OAuth-приложение Яндекса и запустите: iola yandex oauth-url --client-id CLIENT_ID");
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
async function updateYandexEnabledServices(rawServices, enabled) {
|
|
3267
|
+
const config = await loadConfig();
|
|
3268
|
+
const current = new Set(config.yandex?.enabledServices || []);
|
|
3269
|
+
for (const service of normalizeYandexServiceList(rawServices)) {
|
|
3270
|
+
if (enabled) current.add(service);
|
|
3271
|
+
else current.delete(service);
|
|
3272
|
+
}
|
|
3273
|
+
await saveYandexEnabledServices([...current]);
|
|
3274
|
+
console.log(`Yandex services: ${[...current].join(", ") || "-"}`);
|
|
3275
|
+
}
|
|
3276
|
+
|
|
3277
|
+
async function saveYandexEnabledServices(services) {
|
|
3278
|
+
const config = await loadConfig();
|
|
3279
|
+
const normalized = normalizeYandexServiceList(services);
|
|
3280
|
+
if (normalized.length > 0 && !normalized.includes("identity")) normalized.unshift("identity");
|
|
3281
|
+
const categories = {};
|
|
3282
|
+
for (const id of normalized) {
|
|
3283
|
+
const meta = YANDEX_CONNECTOR_SERVICES[id];
|
|
3284
|
+
if (meta) categories[meta.category] = [...new Set([...(categories[meta.category] || []), id])];
|
|
3285
|
+
}
|
|
3286
|
+
await saveConfig({
|
|
3287
|
+
yandex: {
|
|
3288
|
+
...(config.yandex || {}),
|
|
3289
|
+
enabledServices: normalized,
|
|
3290
|
+
categories,
|
|
3291
|
+
},
|
|
3292
|
+
});
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
async function buildYandexOAuthUrlFromConfig(rawArgs = []) {
|
|
3296
|
+
const options = parseOptions(rawArgs);
|
|
3297
|
+
const config = await loadConfig();
|
|
3298
|
+
const clientId = options["client-id"] || config.yandex?.oauth?.clientId;
|
|
3299
|
+
if (!clientId) throw new Error("Yandex OAuth Client ID не задан. Пример: iola yandex oauth-url disk --client-id CLIENT_ID");
|
|
3300
|
+
const services = normalizeYandexServiceList(options._.length ? options._ : (config.yandex?.enabledServices || ["identity", "disk"]));
|
|
3301
|
+
return buildYandexOAuthUrl({ clientId, services });
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
function buildYandexOAuthUrl({ clientId, services }) {
|
|
3305
|
+
const scopes = getYandexScopesForServices(services);
|
|
3306
|
+
const url = new URL(YANDEX_OAUTH_AUTHORIZE_URL);
|
|
3307
|
+
url.searchParams.set("response_type", "token");
|
|
3308
|
+
url.searchParams.set("client_id", clientId);
|
|
3309
|
+
url.searchParams.set("redirect_uri", YANDEX_OAUTH_REDIRECT_URL);
|
|
3310
|
+
if (scopes) url.searchParams.set("scope", scopes);
|
|
3311
|
+
return url.toString();
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
function getYandexScopesForServices(services) {
|
|
3315
|
+
const scopes = new Set();
|
|
3316
|
+
const normalized = normalizeYandexServiceList(services);
|
|
3317
|
+
if (normalized.length > 0 && !normalized.includes("identity")) normalized.unshift("identity");
|
|
3318
|
+
for (const id of normalized) {
|
|
3319
|
+
const raw = YANDEX_CONNECTOR_SERVICES[id]?.scope || "";
|
|
3320
|
+
for (const scope of raw.split(/\s+/).filter(Boolean)) scopes.add(scope);
|
|
3321
|
+
}
|
|
3322
|
+
return [...scopes].join(" ");
|
|
3323
|
+
}
|
|
3324
|
+
|
|
3325
|
+
async function setYandexConnectorToken(args = []) {
|
|
3326
|
+
const options = parseOptions(args);
|
|
3327
|
+
const token = options.token || (process.stdin.isTTY ? (await askText("Yandex OAuth token: ")).trim() : "");
|
|
3328
|
+
if (!token) throw new Error("OAuth token обязателен.");
|
|
3329
|
+
const secrets = await loadSecrets();
|
|
3330
|
+
secrets.yandex = secrets.yandex || {};
|
|
3331
|
+
secrets.yandex.oauthToken = token;
|
|
3332
|
+
secrets.yandex.updatedAt = new Date().toISOString();
|
|
3333
|
+
secrets.cloud = secrets.cloud || {};
|
|
3334
|
+
secrets.cloud["yandex-disk"] = { token };
|
|
3335
|
+
await saveSecrets(secrets);
|
|
3336
|
+
const config = await loadConfig();
|
|
3337
|
+
await saveConfig({ cloud: { ...(config.cloud || {}), activeProvider: "yandex-disk" } });
|
|
3338
|
+
console.log(`Yandex OAuth token сохранен локально: ${SECRETS_FILE}`);
|
|
3339
|
+
console.log("Токен также подключен к cloud provider yandex-disk.");
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3342
|
+
async function deleteYandexConnectorToken() {
|
|
3343
|
+
const secrets = await loadSecrets();
|
|
3344
|
+
delete secrets.yandex;
|
|
3345
|
+
if (secrets.cloud?.["yandex-disk"]) delete secrets.cloud["yandex-disk"];
|
|
3346
|
+
if (secrets.cloud && Object.keys(secrets.cloud).length === 0) delete secrets.cloud;
|
|
3347
|
+
await saveSecrets(secrets);
|
|
3348
|
+
console.log("Yandex Connector token удален. Токен Яндекс Диска в cloud тоже удален.");
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3351
|
+
async function printYandexConnectorStatus(options = {}) {
|
|
3352
|
+
const [config, secrets] = await Promise.all([loadConfig(), loadSecrets()]);
|
|
3353
|
+
const enabled = config.yandex?.enabledServices || [];
|
|
3354
|
+
const legacyDiskToken = Boolean(secrets.cloud?.["yandex-disk"]?.token && !secrets.yandex?.oauthToken);
|
|
3355
|
+
const token = process.env.YANDEX_OAUTH_TOKEN || secrets.yandex?.oauthToken || secrets.cloud?.["yandex-disk"]?.token || "";
|
|
3356
|
+
const rows = Object.entries(YANDEX_CONNECTOR_SERVICES).map(([id, service]) => ({
|
|
3357
|
+
id,
|
|
3358
|
+
enabled: enabled.includes(id) ? "yes" : (legacyDiskToken && id === "disk" ? "legacy" : "no"),
|
|
3359
|
+
category: service.category,
|
|
3360
|
+
status: service.status,
|
|
3361
|
+
token: service.scope && (enabled.includes(id) || (legacyDiskToken && id === "disk")) ? (token ? "local/env" : "missing") : "-",
|
|
3362
|
+
title: service.title,
|
|
3363
|
+
}));
|
|
3364
|
+
printTable(rows, [
|
|
3365
|
+
["id", "ID"],
|
|
3366
|
+
["enabled", "Вкл"],
|
|
3367
|
+
["category", "Категория"],
|
|
3368
|
+
["status", "Статус"],
|
|
3369
|
+
["token", "Токен"],
|
|
3370
|
+
["title", "Сервис"],
|
|
3371
|
+
]);
|
|
3372
|
+
if (options.check && token) {
|
|
3373
|
+
const profile = await yandexUserInfo(token).catch((error) => ({ error: error instanceof Error ? error.message : String(error) }));
|
|
3374
|
+
console.log("");
|
|
3375
|
+
if (profile.error) console.log(`Yandex ID check: ${profile.error}`);
|
|
3376
|
+
else printKeyValue({
|
|
3377
|
+
login: profile.login || "-",
|
|
3378
|
+
displayName: profile.display_name || profile.real_name || "-",
|
|
3379
|
+
defaultEmail: profile.default_email || "-",
|
|
3380
|
+
});
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
|
|
3384
|
+
async function yandexUserInfo(token) {
|
|
3385
|
+
const response = await fetch("https://login.yandex.ru/info?format=json", {
|
|
3386
|
+
headers: { Authorization: `OAuth ${token}` },
|
|
3387
|
+
});
|
|
3388
|
+
if (!response.ok) {
|
|
3389
|
+
const text = await response.text().catch(() => "");
|
|
3390
|
+
throw new Error(`Yandex ID недоступен: ${response.status} ${text.slice(0, 200)}`);
|
|
3391
|
+
}
|
|
3392
|
+
return response.json();
|
|
3393
|
+
}
|
|
3394
|
+
|
|
3395
|
+
function normalizeYandexServiceList(values) {
|
|
3396
|
+
const aliases = {
|
|
3397
|
+
id: "identity",
|
|
3398
|
+
login: "identity",
|
|
3399
|
+
"профиль": "identity",
|
|
3400
|
+
"диск": "disk",
|
|
3401
|
+
"яндекс-диск": "disk",
|
|
3402
|
+
yandexdisk: "disk",
|
|
3403
|
+
"yandex-disk": "disk",
|
|
3404
|
+
"почта": "mail",
|
|
3405
|
+
"календарь": "calendar",
|
|
3406
|
+
"контакты": "contacts",
|
|
3407
|
+
"вики": "wiki",
|
|
3408
|
+
"трекер": "tracker",
|
|
3409
|
+
"формы": "forms",
|
|
3410
|
+
"документы": "docs",
|
|
3411
|
+
"телемост": "telemost",
|
|
3412
|
+
"облако": "cloud",
|
|
3413
|
+
"карты": "maps",
|
|
3414
|
+
"такси": "taxi",
|
|
3415
|
+
"маркет": "market",
|
|
3416
|
+
"доставка": "delivery",
|
|
3417
|
+
all: "all",
|
|
3418
|
+
"все": "all",
|
|
3419
|
+
};
|
|
3420
|
+
const result = [];
|
|
3421
|
+
for (const raw of values.flatMap((item) => String(item || "").split(","))) {
|
|
3422
|
+
const normalized = raw.trim().toLocaleLowerCase("ru-RU");
|
|
3423
|
+
if (!normalized) continue;
|
|
3424
|
+
const id = aliases[normalized] || normalized;
|
|
3425
|
+
if (id === "all") {
|
|
3426
|
+
result.push(...Object.keys(YANDEX_CONNECTOR_SERVICES));
|
|
3427
|
+
continue;
|
|
3428
|
+
}
|
|
3429
|
+
if (!YANDEX_CONNECTOR_SERVICES[id]) {
|
|
3430
|
+
throw new Error(`Неизвестный сервис Яндекса: ${raw}. Список: iola yandex services`);
|
|
3431
|
+
}
|
|
3432
|
+
result.push(id);
|
|
3433
|
+
}
|
|
3434
|
+
return [...new Set(result)];
|
|
3435
|
+
}
|
|
3436
|
+
|
|
3024
3437
|
async function setupCloudProvider(providerValue, options = {}) {
|
|
3025
3438
|
const provider = normalizeCloudProvider(providerValue);
|
|
3026
3439
|
if (!process.stdin.isTTY) throw new Error("Для настройки облака запустите команду в интерактивном терминале.");
|
|
@@ -7838,25 +8251,32 @@ async function setupIolaLocal(args) {
|
|
|
7838
8251
|
}
|
|
7839
8252
|
|
|
7840
8253
|
const config = await loadConfig();
|
|
8254
|
+
const localProfile = {
|
|
8255
|
+
provider: "iola",
|
|
8256
|
+
model,
|
|
8257
|
+
runtime,
|
|
8258
|
+
baseUrl: "http://127.0.0.1:11434",
|
|
8259
|
+
repo,
|
|
8260
|
+
ggufRepo,
|
|
8261
|
+
ggufFile,
|
|
8262
|
+
modelDir,
|
|
8263
|
+
};
|
|
8264
|
+
const shouldActivate = !options["preserve-active"];
|
|
8265
|
+
const previousActiveProfile = getActiveProfileName(config);
|
|
8266
|
+
const nextActiveProfile = shouldActivate ? profileName : previousActiveProfile;
|
|
8267
|
+
const nextActiveConfig = nextActiveProfile === profileName
|
|
8268
|
+
? localProfile
|
|
8269
|
+
: (config.ai.profiles?.[nextActiveProfile] || config.ai.profiles?.[previousActiveProfile] || localProfile);
|
|
7841
8270
|
await saveConfig({
|
|
7842
8271
|
ai: {
|
|
7843
8272
|
...config.ai,
|
|
7844
|
-
activeProfile:
|
|
7845
|
-
provider:
|
|
7846
|
-
model,
|
|
7847
|
-
baseUrl:
|
|
8273
|
+
activeProfile: nextActiveProfile,
|
|
8274
|
+
provider: nextActiveConfig.provider,
|
|
8275
|
+
model: nextActiveConfig.model,
|
|
8276
|
+
baseUrl: nextActiveConfig.baseUrl || config.ai.baseUrl,
|
|
7848
8277
|
profiles: {
|
|
7849
8278
|
...(config.ai.profiles || {}),
|
|
7850
|
-
[profileName]:
|
|
7851
|
-
provider: "iola",
|
|
7852
|
-
model,
|
|
7853
|
-
runtime,
|
|
7854
|
-
baseUrl: "http://127.0.0.1:11434",
|
|
7855
|
-
repo,
|
|
7856
|
-
ggufRepo,
|
|
7857
|
-
ggufFile,
|
|
7858
|
-
modelDir,
|
|
7859
|
-
},
|
|
8279
|
+
[profileName]: localProfile,
|
|
7860
8280
|
},
|
|
7861
8281
|
},
|
|
7862
8282
|
});
|
|
@@ -10574,6 +10994,9 @@ async function onboard(args = []) {
|
|
|
10574
10994
|
else console.log("Настройка облачного диска пропущена.");
|
|
10575
10995
|
}
|
|
10576
10996
|
}
|
|
10997
|
+
if (components.includes("yandex")) {
|
|
10998
|
+
await setupYandexConnector([]);
|
|
10999
|
+
}
|
|
10577
11000
|
if (components.includes("codex")) {
|
|
10578
11001
|
await installCodexIfMissing();
|
|
10579
11002
|
await aiSetup(["codex"]);
|
|
@@ -10624,6 +11047,7 @@ async function chooseOnboardComponents(status = null) {
|
|
|
10624
11047
|
13: "ollama",
|
|
10625
11048
|
14: "yandex-geocoder",
|
|
10626
11049
|
15: "cloud",
|
|
11050
|
+
16: "yandex",
|
|
10627
11051
|
};
|
|
10628
11052
|
return [...selected].map((item) => map[item] || item).filter(Boolean);
|
|
10629
11053
|
} finally {
|
|
@@ -10636,7 +11060,7 @@ function isOnboardExitAnswer(answer) {
|
|
|
10636
11060
|
}
|
|
10637
11061
|
|
|
10638
11062
|
async function getOnboardComponentStatus() {
|
|
10639
|
-
const [config, readiness, browser, archive, codexVersion, ollamaVersion, yandexGeocoderKey,
|
|
11063
|
+
const [config, readiness, browser, archive, codexVersion, ollamaVersion, yandexGeocoderKey, secrets] = await Promise.all([
|
|
10640
11064
|
loadConfig(),
|
|
10641
11065
|
getAiReadiness(),
|
|
10642
11066
|
getBrowserStatus(),
|
|
@@ -10644,8 +11068,9 @@ async function getOnboardComponentStatus() {
|
|
|
10644
11068
|
getCommandVersion("codex", ["--version"]),
|
|
10645
11069
|
getOllamaVersion(),
|
|
10646
11070
|
getYandexGeocoderKey(),
|
|
10647
|
-
loadSecrets()
|
|
11071
|
+
loadSecrets(),
|
|
10648
11072
|
]);
|
|
11073
|
+
const cloudSecrets = secrets.cloud || {};
|
|
10649
11074
|
const workspaceReady = existsSync(PROJECT_CONTEXT_FILE) || existsSync(PROJECT_CONTEXT_DIR_FILE) || existsSync(PROJECT_IOLA_DIR);
|
|
10650
11075
|
const policyReady = (config.toolsets?.enabled || []).includes("analyst");
|
|
10651
11076
|
return {
|
|
@@ -10664,6 +11089,7 @@ async function getOnboardComponentStatus() {
|
|
|
10664
11089
|
browser: browser.installed === "yes",
|
|
10665
11090
|
"yandex-geocoder": Boolean(yandexGeocoderKey),
|
|
10666
11091
|
cloud: Object.keys(cloudSecrets).length > 0,
|
|
11092
|
+
yandex: Boolean(secrets.yandex?.oauthToken || config.yandex?.enabledServices?.length),
|
|
10667
11093
|
};
|
|
10668
11094
|
}
|
|
10669
11095
|
|
|
@@ -10684,6 +11110,7 @@ function onboardComponentRows(status) {
|
|
|
10684
11110
|
["13", "ollama", "Ollama", "опциональный локальный runtime"],
|
|
10685
11111
|
["14", "yandex-geocoder", "Yandex Geocoder API", "ключ геокодера сохранен или есть в env"],
|
|
10686
11112
|
["15", "cloud", "Облачный диск", "Яндекс Диск или Облако Mail.ru"],
|
|
11113
|
+
["16", "yandex", "Yandex Connector", "единый вход и категории сервисов Яндекса"],
|
|
10687
11114
|
];
|
|
10688
11115
|
return rows.map(([number, key, title, hint]) => ({ number, key, title, hint, status: status[key] ? "готово" : "не настроено" }));
|
|
10689
11116
|
}
|
|
@@ -10698,7 +11125,7 @@ function defaultOnboardSelection(status) {
|
|
|
10698
11125
|
}
|
|
10699
11126
|
|
|
10700
11127
|
function defaultOnboardComponents(status) {
|
|
10701
|
-
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" };
|
|
11128
|
+
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" };
|
|
10702
11129
|
return defaultOnboardSelection(status).map((item) => map[item]).filter(Boolean);
|
|
10703
11130
|
}
|
|
10704
11131
|
|
|
@@ -10707,12 +11134,12 @@ function parseOptions(args) {
|
|
|
10707
11134
|
|
|
10708
11135
|
for (let index = 0; index < args.length; index += 1) {
|
|
10709
11136
|
const arg = args[index];
|
|
10710
|
-
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") {
|
|
11137
|
+
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") {
|
|
10711
11138
|
result[arg.slice(2)] = true;
|
|
10712
11139
|
} else if (arg === "--check" || arg === "--upgrade-node") {
|
|
10713
11140
|
result.check = true;
|
|
10714
11141
|
result[arg.slice(2)] = true;
|
|
10715
|
-
} 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") {
|
|
11142
|
+
} 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") {
|
|
10716
11143
|
result[arg.slice(2)] = args[index + 1];
|
|
10717
11144
|
index += 1;
|
|
10718
11145
|
} else {
|
|
@@ -12404,8 +12831,8 @@ async function executeRpc(method, options = {}) {
|
|
|
12404
12831
|
|
|
12405
12832
|
async function getLatestNpmVersion(packageName) {
|
|
12406
12833
|
try {
|
|
12407
|
-
const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`, {
|
|
12408
|
-
headers: { accept: "application/json" },
|
|
12834
|
+
const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest?t=${Date.now()}`, {
|
|
12835
|
+
headers: { accept: "application/json", "cache-control": "no-cache" },
|
|
12409
12836
|
});
|
|
12410
12837
|
|
|
12411
12838
|
if (!response.ok) {
|
|
@@ -12480,8 +12907,11 @@ function printDiagnostics(diagnostics, recommendation) {
|
|
|
12480
12907
|
vram: diagnostics.gpu.vramGb ? `${diagnostics.gpu.vramGb} GB` : "-",
|
|
12481
12908
|
ollama: diagnostics.ollama.installed ? diagnostics.ollama.version : "не установлен",
|
|
12482
12909
|
});
|
|
12910
|
+
if (!recommendation) {
|
|
12911
|
+
return;
|
|
12912
|
+
}
|
|
12483
12913
|
console.log("");
|
|
12484
|
-
console.log("Рекомендация");
|
|
12914
|
+
console.log("Рекомендация локальной модели");
|
|
12485
12915
|
printKeyValue({
|
|
12486
12916
|
profile: recommendation.profile,
|
|
12487
12917
|
model: recommendation.model,
|
|
@@ -12607,6 +13037,18 @@ function mergeConfig(base, override) {
|
|
|
12607
13037
|
...(override.cloud?.providers || {}),
|
|
12608
13038
|
},
|
|
12609
13039
|
},
|
|
13040
|
+
yandex: {
|
|
13041
|
+
...base.yandex,
|
|
13042
|
+
...(override.yandex || {}),
|
|
13043
|
+
oauth: {
|
|
13044
|
+
...(base.yandex?.oauth || {}),
|
|
13045
|
+
...(override.yandex?.oauth || {}),
|
|
13046
|
+
},
|
|
13047
|
+
categories: {
|
|
13048
|
+
...(base.yandex?.categories || {}),
|
|
13049
|
+
...(override.yandex?.categories || {}),
|
|
13050
|
+
},
|
|
13051
|
+
},
|
|
12610
13052
|
memory: {
|
|
12611
13053
|
...base.memory,
|
|
12612
13054
|
...(override.memory || {}),
|
|
@@ -12666,6 +13108,9 @@ function sanitizeConfig(config) {
|
|
|
12666
13108
|
if (Array.isArray(next.skills?.enabled) && next.skills.enabled.includes("open-data") && !next.skills.enabled.includes("education")) {
|
|
12667
13109
|
next.skills.enabled = ["education", ...next.skills.enabled];
|
|
12668
13110
|
}
|
|
13111
|
+
if (Array.isArray(next.skills?.enabled) && next.skills.enabled.includes("open-data") && !next.skills.enabled.includes("geo")) {
|
|
13112
|
+
next.skills.enabled = [...next.skills.enabled, "geo"];
|
|
13113
|
+
}
|
|
12669
13114
|
if (Array.isArray(next.skills?.enabled) && next.skills.enabled.includes("local-files") && !next.skills.enabled.includes("personal-docs")) {
|
|
12670
13115
|
next.skills.enabled = [...next.skills.enabled, "personal-docs"];
|
|
12671
13116
|
}
|
|
@@ -12716,6 +13161,9 @@ function validateConfig(config) {
|
|
|
12716
13161
|
if (config.cloud?.activeProvider && !["yandex-disk", "mailru-cloud"].includes(config.cloud.activeProvider)) {
|
|
12717
13162
|
errors.push(`cloud.activeProvider неизвестен: ${config.cloud.activeProvider}`);
|
|
12718
13163
|
}
|
|
13164
|
+
for (const service of config.yandex?.enabledServices || []) {
|
|
13165
|
+
if (!YANDEX_CONNECTOR_SERVICES[service]) errors.push(`yandex.enabledServices содержит неизвестный сервис: ${service}`);
|
|
13166
|
+
}
|
|
12719
13167
|
return errors;
|
|
12720
13168
|
}
|
|
12721
13169
|
|
|
@@ -12730,6 +13178,7 @@ function configSchema() {
|
|
|
12730
13178
|
toolsets: { available: Object.keys(TOOLSETS) },
|
|
12731
13179
|
files: { modes: ["locked", "read-only", "workspace-write", "full-access"], approvals: ["never", "on-write", "on-danger", "always"] },
|
|
12732
13180
|
cloud: { providers: ["yandex-disk", "mailru-cloud"], root: CLOUD_DEFAULT_REMOTE_DIR },
|
|
13181
|
+
yandex: { services: Object.keys(YANDEX_CONNECTOR_SERVICES), statuses: ["ready", "research", "separate", "backlog"] },
|
|
12733
13182
|
skills: { enabled: "array of skill names" },
|
|
12734
13183
|
daemon: { host: "127.0.0.1", port: DAEMON_PORT },
|
|
12735
13184
|
},
|
|
@@ -13052,6 +13501,19 @@ function printKeyValue(value) {
|
|
|
13052
13501
|
]);
|
|
13053
13502
|
}
|
|
13054
13503
|
|
|
13504
|
+
function printKeyValueFull(value) {
|
|
13505
|
+
const rows = Object.entries(value).map(([key, raw]) => ({
|
|
13506
|
+
key,
|
|
13507
|
+
value: raw == null || raw === "" ? "-" : String(raw),
|
|
13508
|
+
}));
|
|
13509
|
+
const keyWidth = Math.max(4, ...rows.map((row) => visibleLength(row.key)));
|
|
13510
|
+
console.log(`${padCell("Поле", keyWidth)} Значение`);
|
|
13511
|
+
console.log(`${"-".repeat(keyWidth)} ${"-".repeat(8)}`);
|
|
13512
|
+
for (const row of rows) {
|
|
13513
|
+
console.log(`${padCell(row.key, keyWidth)} ${row.value}`);
|
|
13514
|
+
}
|
|
13515
|
+
}
|
|
13516
|
+
|
|
13055
13517
|
function printTable(rows, columns) {
|
|
13056
13518
|
if (rows.length === 0) {
|
|
13057
13519
|
console.log("Нет данных.");
|
package/wiki/Home.md
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
- работа с локальной моделью Ollama;
|
|
13
13
|
- работа с YandexGPT, GigaChat, OpenAI, OpenRouter и Codex CLI;
|
|
14
14
|
- geo-сценарии для жителей через Yandex Geocoder API;
|
|
15
|
+
- Yandex Connector для пользовательских сервисов Яндекса;
|
|
15
16
|
- личные облачные диски: Яндекс Диск и Облако Mail.ru;
|
|
16
17
|
- подключение публичного MCP-сервера.
|
|
17
18
|
|
|
@@ -31,6 +32,7 @@ iola ask "найди школу 29"
|
|
|
31
32
|
- [Мастер настройки](Мастер-настройки)
|
|
32
33
|
- [AI-профили](AI-профили)
|
|
33
34
|
- [Yandex Geocoder API key](Yandex-Geocoder-API-key)
|
|
35
|
+
- [Yandex Connector](Yandex-Connector)
|
|
34
36
|
- [Облачные диски](Облачные-диски)
|
|
35
37
|
- [Скиллы для жителей](Скиллы-для-жителей)
|
|
36
38
|
- [Локальный инструментальный агент](Локальный-инструментальный-агент)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Yandex Connector
|
|
2
|
+
|
|
3
|
+
`Yandex Connector` - единая точка подключения пользовательских сервисов Яндекса в `iola-cli`.
|
|
4
|
+
|
|
5
|
+
Цель: пользователь один раз настраивает вход через Яндекс, а CLI хранит токен локально и включает только выбранные категории функций.
|
|
6
|
+
|
|
7
|
+
Секреты сохраняются только на компьютере пользователя в `~/.iola/secrets.json`. Они не отправляются на сервер IOLA и не попадают в `iola cloud backup`.
|
|
8
|
+
|
|
9
|
+
## Команды
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
iola yandex services
|
|
13
|
+
iola yandex setup
|
|
14
|
+
iola yandex status
|
|
15
|
+
iola yandex doctor
|
|
16
|
+
iola yandex enable disk mail calendar
|
|
17
|
+
iola yandex disable mail
|
|
18
|
+
iola yandex oauth-url disk --client-id CLIENT_ID
|
|
19
|
+
iola yandex token set
|
|
20
|
+
iola yandex token delete
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Категории
|
|
24
|
+
|
|
25
|
+
Готово к первому контуру:
|
|
26
|
+
|
|
27
|
+
- `identity` - Yandex ID, профиль пользователя, логин и email;
|
|
28
|
+
- `disk` - Яндекс Диск, папка `/IOLA`, файлы, папки, загрузка, скачивание и публичные ссылки.
|
|
29
|
+
|
|
30
|
+
Исследуется:
|
|
31
|
+
|
|
32
|
+
- `mail` - Яндекс Почта, чтение и поиск писем, отправка только после явного подтверждения;
|
|
33
|
+
- `calendar` - Яндекс Календарь;
|
|
34
|
+
- `contacts` - Яндекс Контакты;
|
|
35
|
+
- `wiki` - Yandex Wiki;
|
|
36
|
+
- `tracker` - Yandex Tracker;
|
|
37
|
+
- `forms` - Yandex Forms;
|
|
38
|
+
- `docs` - Яндекс Документы / 360.
|
|
39
|
+
|
|
40
|
+
Отдельные ключи, не обычный OAuth бытового Яндекса:
|
|
41
|
+
|
|
42
|
+
- `cloud` - Yandex Cloud, YandexGPT, SpeechKit, Vision, IAM и folder ID;
|
|
43
|
+
- `maps` - Yandex Geocoder API и карты.
|
|
44
|
+
|
|
45
|
+
Backlog после первого контура:
|
|
46
|
+
|
|
47
|
+
- `taxi` - Яндекс Go / Такси: только подготовить маршрут и открыть приложение, без заказа и оплаты;
|
|
48
|
+
- `market` - Яндекс Маркет: поиск и список покупок, без корзины и оплаты;
|
|
49
|
+
- `delivery` - Яндекс Доставка: подготовка заявки/ссылки, без оформления и оплаты.
|
|
50
|
+
|
|
51
|
+
## Как подключить
|
|
52
|
+
|
|
53
|
+
1. Создайте OAuth-приложение Яндекса по инструкции на странице [Облачные диски](Облачные-диски).
|
|
54
|
+
2. Включите нужные scope. Для первого контура нужны:
|
|
55
|
+
- `login:info`;
|
|
56
|
+
- `login:email`;
|
|
57
|
+
- `cloud_api:disk.read`;
|
|
58
|
+
- `cloud_api:disk.write`;
|
|
59
|
+
- `cloud_api:disk.info`.
|
|
60
|
+
3. Запустите:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
iola yandex setup disk --client-id CLIENT_ID
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
4. Откройте ссылку авторизации, которую выведет CLI.
|
|
67
|
+
5. Скопируйте OAuth-токен.
|
|
68
|
+
6. Сохраните токен:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
iola yandex token set
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
7. Проверьте:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
iola yandex doctor
|
|
78
|
+
iola cloud doctor
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Если включен `disk`, токен автоматически подключается и к старому облачному провайдеру `yandex-disk`, поэтому команды `iola cloud ...` продолжают работать.
|
|
82
|
+
|
|
83
|
+
## Важно
|
|
84
|
+
|
|
85
|
+
Yandex Connector не является универсальным ключом ко всему Яндексу.
|
|
86
|
+
|
|
87
|
+
Обычные пользовательские сервисы работают через OAuth и scope. Yandex Cloud, YandexGPT и Geocoder требуют отдельные ключи, folder ID или настройки в Yandex Cloud.
|
|
88
|
+
|
|
89
|
+
CLI не должен автоматически оформлять покупки, вызывать такси, подтверждать доставку или выполнять платежи. Для таких сценариев допустима только подготовка ссылки, маршрута или списка, а финальное действие делает пользователь в приложении Яндекса.
|
|
@@ -59,6 +59,20 @@ iola cloud save --text "Текст заметки" --path /IOLA/notes/note.txt
|
|
|
59
59
|
iola cloud backup
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
Yandex Connector:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
iola yandex services
|
|
66
|
+
iola yandex setup
|
|
67
|
+
iola yandex status
|
|
68
|
+
iola yandex doctor
|
|
69
|
+
iola yandex enable disk mail calendar
|
|
70
|
+
iola yandex disable mail
|
|
71
|
+
iola yandex oauth-url disk --client-id CLIENT_ID
|
|
72
|
+
iola yandex token set
|
|
73
|
+
iola yandex token delete
|
|
74
|
+
```
|
|
75
|
+
|
|
62
76
|
Локальная БД:
|
|
63
77
|
|
|
64
78
|
```bash
|
|
@@ -22,9 +22,9 @@ iola master
|
|
|
22
22
|
|
|
23
23
|
Включает профиль разрешений для аналитической работы: открытые данные, отчеты, безопасные локальные операции.
|
|
24
24
|
|
|
25
|
-
### 3.
|
|
25
|
+
### 3. IOLA локальная модель
|
|
26
26
|
|
|
27
|
-
Проверяет
|
|
27
|
+
Проверяет штатную локальную модель IOLA и готовит ее к работе через доступный runtime.
|
|
28
28
|
|
|
29
29
|
Если модель уже скачана и доступна, пункт показывается как `готово`.
|
|
30
30
|
|
|
@@ -105,6 +105,19 @@ iola index folder ./docs
|
|
|
105
105
|
|
|
106
106
|
Инструкция: [Облачные диски](Облачные-диски).
|
|
107
107
|
|
|
108
|
+
### 16. Yandex Connector
|
|
109
|
+
|
|
110
|
+
Настраивает единый коннектор пользовательских сервисов Яндекса.
|
|
111
|
+
|
|
112
|
+
Первый контур:
|
|
113
|
+
|
|
114
|
+
- Yandex ID;
|
|
115
|
+
- Яндекс Диск.
|
|
116
|
+
|
|
117
|
+
В коннектор также заложены категории для проверки: Почта, Календарь, Контакты, Wiki, Tracker, Forms, Документы/360. Такси, Маркет и Доставка записаны в backlog только как подготовка ссылки, маршрута или списка без заказа и оплаты.
|
|
118
|
+
|
|
119
|
+
Инструкция: [Yandex Connector](Yandex-Connector).
|
|
120
|
+
|
|
108
121
|
## Повторный запуск
|
|
109
122
|
|
|
110
123
|
Если компонент уже настроен, его можно не выбирать. Если нужно переустановить или обновить компонент, выберите его номер вручную.
|
|
@@ -31,6 +31,10 @@ iola cloud backup
|
|
|
31
31
|
|
|
32
32
|
Яндекс Диск подключается через OAuth-токен пользователя.
|
|
33
33
|
|
|
34
|
+
Новый рекомендуемый путь - через [Yandex Connector](Yandex-Connector). Он сохраняет общий OAuth-токен Яндекса и автоматически подключает его к провайдеру `yandex-disk`.
|
|
35
|
+
|
|
36
|
+
Старый прямой способ `iola cloud setup yandex-disk` остается доступным.
|
|
37
|
+
|
|
34
38
|
Официальная документация:
|
|
35
39
|
|
|
36
40
|
- REST API Диска: `https://yandex.ru/dev/disk/rest?lang=ru`
|
|
@@ -146,3 +146,36 @@ iola geo services "Йошкар-Ола, улица Петрова, 15"
|
|
|
146
146
|
### cloud-organize
|
|
147
147
|
|
|
148
148
|
Помочь разобрать личные документы: найти старые отчеты, крупные файлы, дубликаты и предложить структуру папок.
|
|
149
|
+
|
|
150
|
+
## Yandex Connector backlog
|
|
151
|
+
|
|
152
|
+
Эти сценарии зафиксированы для следующего этапа после базового Yandex Connector. Они не должны оформлять заказ, списывать деньги или нажимать финальную кнопку вместо пользователя.
|
|
153
|
+
|
|
154
|
+
### taxi-prepare-ride
|
|
155
|
+
|
|
156
|
+
Подготовить поездку в Яндекс Go:
|
|
157
|
+
|
|
158
|
+
- уточнить точку отправления и назначения;
|
|
159
|
+
- геокодировать адреса;
|
|
160
|
+
- сформировать ссылку/deep link в Яндекс Go;
|
|
161
|
+
- открыть приложение или страницу;
|
|
162
|
+
- пользователь сам проверяет цену и нажимает заказ.
|
|
163
|
+
|
|
164
|
+
### market-prepare-cart
|
|
165
|
+
|
|
166
|
+
Подготовить список покупок для Яндекс Маркета:
|
|
167
|
+
|
|
168
|
+
- разобрать запрос пользователя;
|
|
169
|
+
- найти подходящие товары или поисковые ссылки;
|
|
170
|
+
- собрать список вариантов;
|
|
171
|
+
- сохранить список в CLI или на Диск;
|
|
172
|
+
- пользователь сам добавляет товары в корзину и оплачивает.
|
|
173
|
+
|
|
174
|
+
### delivery-prepare-order
|
|
175
|
+
|
|
176
|
+
Подготовить доставку:
|
|
177
|
+
|
|
178
|
+
- уточнить адрес отправления и получения;
|
|
179
|
+
- сформировать ссылку или маршрут;
|
|
180
|
+
- открыть подходящий сервис Яндекса;
|
|
181
|
+
- пользователь сам подтверждает заявку и оплату.
|