@iola_adm/iola-cli 0.1.111 → 0.1.113
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 +10 -2
- package/package.json +1 -1
- package/src/cli.js +75 -7
- package/test/smoke-test.js +3 -0
- package/wiki/AI-/320/277/321/200/320/276/321/204/320/270/320/273/320/270.md +15 -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 +4 -0
package/README.md
CHANGED
|
@@ -116,14 +116,22 @@ iola review config
|
|
|
116
116
|
iola browser status
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
-
Локальная модель IOLA через
|
|
119
|
+
Локальная модель IOLA через Ollama/GGUF:
|
|
120
120
|
|
|
121
121
|
```bash
|
|
122
122
|
iola ai setup iola --yes
|
|
123
123
|
iola ask "дай телефон школы № 2"
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
-
CLI
|
|
126
|
+
CLI использует модель `iola-router:qwen3-1.7b-v4-q8` из GGUF-репозитория `LMSerg/iola-router-qwen3-1.7b-v4-gguf`. При установке CLI проверяет наличие модели и не скачивает ее повторно, если локальная модель уже установлена. При запуске локального AI CLI проверяет свежесть модели и обновляет ее при необходимости. Принудительная переустановка доступна командой `iola ai setup iola --yes --force`.
|
|
127
|
+
|
|
128
|
+
Выбор модели:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
/model
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
В интерактивном CLI команда `/model` переключает локальную модель IOLA, API-профили OpenAI/OpenRouter и Codex CLI. Для OpenRouter выбор устроен так: сначала выбирается разработчик моделей, затем CLI показывает до 30 самых свежих моделей для текстовой работы с датой релиза и размером контекста. В списке моделей `0` возвращает к выбору разработчика.
|
|
127
135
|
|
|
128
136
|
Ollama остается опциональным runtime:
|
|
129
137
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -4372,8 +4372,8 @@ async function listAiModels(provider) {
|
|
|
4372
4372
|
if (getAiNetworkMode(relayConfig) === "gateway") {
|
|
4373
4373
|
const payload = await callAiRelayModels(relayConfig, apiKey, "OpenAI");
|
|
4374
4374
|
return (payload.data || [])
|
|
4375
|
-
.map(
|
|
4376
|
-
.sort(
|
|
4375
|
+
.map(mapOpenAiModel)
|
|
4376
|
+
.sort(sortModelsByFreshness);
|
|
4377
4377
|
}
|
|
4378
4378
|
const response = await fetch("https://api.openai.com/v1/models", {
|
|
4379
4379
|
headers: { authorization: `Bearer ${apiKey}` },
|
|
@@ -4385,8 +4385,8 @@ async function listAiModels(provider) {
|
|
|
4385
4385
|
|
|
4386
4386
|
const payload = await response.json();
|
|
4387
4387
|
return (payload.data || [])
|
|
4388
|
-
.map(
|
|
4389
|
-
.sort(
|
|
4388
|
+
.map(mapOpenAiModel)
|
|
4389
|
+
.sort(sortModelsByFreshness);
|
|
4390
4390
|
}
|
|
4391
4391
|
|
|
4392
4392
|
if (provider === "openrouter") {
|
|
@@ -4431,6 +4431,44 @@ async function getApiProviderNetworkConfig(provider) {
|
|
|
4431
4431
|
};
|
|
4432
4432
|
}
|
|
4433
4433
|
|
|
4434
|
+
function mapOpenAiModel(model) {
|
|
4435
|
+
const id = String(model.id || "");
|
|
4436
|
+
const created = Number(model.created || inferOpenAiModelCreated(id) || 0);
|
|
4437
|
+
return {
|
|
4438
|
+
id,
|
|
4439
|
+
provider: "openai",
|
|
4440
|
+
note: model.owned_by || "",
|
|
4441
|
+
created,
|
|
4442
|
+
releaseDate: formatUnixDate(created),
|
|
4443
|
+
};
|
|
4444
|
+
}
|
|
4445
|
+
|
|
4446
|
+
function isOpenAiTextGenerationModel(model) {
|
|
4447
|
+
const id = String(model.id || "").toLocaleLowerCase("en-US");
|
|
4448
|
+
if (!id) return false;
|
|
4449
|
+
if (/^(babbage|davinci|text-|whisper|tts|dall-|omni-|computer-|codex-mini-latest)/.test(id)) return false;
|
|
4450
|
+
if (/(image|audio|tts|transcribe|realtime|embedding|moderation|search|instruct|safeguard)/.test(id)) return false;
|
|
4451
|
+
if (/^gpt-3\.5/.test(id)) return false;
|
|
4452
|
+
return id === "chat-latest"
|
|
4453
|
+
|| /^gpt-(4|4o|5|5\.)/.test(id)
|
|
4454
|
+
|| /^o[134](?:-|$)/.test(id);
|
|
4455
|
+
}
|
|
4456
|
+
|
|
4457
|
+
function dedupeDatedOpenAiModels(models) {
|
|
4458
|
+
const ids = new Set(models.map((model) => model.id));
|
|
4459
|
+
return models.filter((model) => {
|
|
4460
|
+
const base = model.id.replace(/-\d{4}-\d{2}-\d{2}$/, "");
|
|
4461
|
+
return base === model.id || !ids.has(base);
|
|
4462
|
+
});
|
|
4463
|
+
}
|
|
4464
|
+
|
|
4465
|
+
function inferOpenAiModelCreated(id) {
|
|
4466
|
+
const match = String(id || "").match(/(\d{4})-(\d{2})-(\d{2})$/);
|
|
4467
|
+
if (!match) return 0;
|
|
4468
|
+
const [, year, month, day] = match;
|
|
4469
|
+
return Math.floor(Date.UTC(Number(year), Number(month) - 1, Number(day)) / 1000);
|
|
4470
|
+
}
|
|
4471
|
+
|
|
4434
4472
|
function mapOpenRouterModel(model) {
|
|
4435
4473
|
const id = String(model.id || "");
|
|
4436
4474
|
const architecture = model.architecture || {};
|
|
@@ -4812,6 +4850,11 @@ async function chooseAiModel(provider) {
|
|
|
4812
4850
|
return chooseOpenRouterModel();
|
|
4813
4851
|
}
|
|
4814
4852
|
|
|
4853
|
+
if (provider === "openai") {
|
|
4854
|
+
const ready = await ensureApiKeyForModelSelection(provider);
|
|
4855
|
+
if (!ready) return "";
|
|
4856
|
+
}
|
|
4857
|
+
|
|
4815
4858
|
let search = "";
|
|
4816
4859
|
if (provider === "openai") {
|
|
4817
4860
|
search = (await askText("Фильтр моделей (Enter - без фильтра): ")).trim();
|
|
@@ -4829,19 +4872,27 @@ async function chooseAiModel(provider) {
|
|
|
4829
4872
|
? models.filter((model) => model.id.toLocaleLowerCase("ru-RU").includes(search.toLocaleLowerCase("ru-RU")))
|
|
4830
4873
|
: models;
|
|
4831
4874
|
|
|
4875
|
+
if (provider === "openai") {
|
|
4876
|
+
filtered = dedupeDatedOpenAiModels(filtered.filter(isOpenAiTextGenerationModel))
|
|
4877
|
+
.sort(sortModelsByFreshness);
|
|
4878
|
+
}
|
|
4879
|
+
|
|
4832
4880
|
if (filtered.length === 0) {
|
|
4833
4881
|
console.log("Модели не найдены.");
|
|
4834
4882
|
return "";
|
|
4835
4883
|
}
|
|
4836
4884
|
|
|
4837
|
-
const limit = 25;
|
|
4885
|
+
const limit = provider === "openai" ? 30 : 25;
|
|
4838
4886
|
if (filtered.length > limit) {
|
|
4839
4887
|
filtered = filtered.slice(0, limit);
|
|
4840
|
-
console.log(`Показаны первые ${limit}
|
|
4888
|
+
console.log(`Показаны первые ${limit} моделей.`);
|
|
4841
4889
|
}
|
|
4842
4890
|
|
|
4843
4891
|
console.log("Выберите модель:");
|
|
4844
|
-
filtered.forEach((model, index) =>
|
|
4892
|
+
filtered.forEach((model, index) => {
|
|
4893
|
+
const date = model.releaseDate ? ` (${model.releaseDate})` : "";
|
|
4894
|
+
console.log(` ${index + 1}. ${model.id}${date}${model.note ? ` - ${model.note}` : ""}`);
|
|
4895
|
+
});
|
|
4845
4896
|
console.log(" 0. Отмена");
|
|
4846
4897
|
|
|
4847
4898
|
const answer = Number(await askText("Номер: "));
|
|
@@ -4849,6 +4900,9 @@ async function chooseAiModel(provider) {
|
|
|
4849
4900
|
}
|
|
4850
4901
|
|
|
4851
4902
|
async function chooseOpenRouterModel() {
|
|
4903
|
+
const ready = await ensureApiKeyForModelSelection("openrouter");
|
|
4904
|
+
if (!ready) return "";
|
|
4905
|
+
|
|
4852
4906
|
let models;
|
|
4853
4907
|
try {
|
|
4854
4908
|
models = await listAiModels("openrouter");
|
|
@@ -4900,6 +4954,20 @@ async function chooseOpenRouterModel() {
|
|
|
4900
4954
|
}
|
|
4901
4955
|
}
|
|
4902
4956
|
|
|
4957
|
+
async function ensureApiKeyForModelSelection(provider) {
|
|
4958
|
+
if (provider !== "openai" && provider !== "openrouter") return true;
|
|
4959
|
+
if (await getApiKey(provider)) return true;
|
|
4960
|
+
const label = provider === "openai" ? "OpenAI" : "OpenRouter";
|
|
4961
|
+
console.log(`${label} API key не найден. Введите ключ, чтобы получить список моделей.`);
|
|
4962
|
+
try {
|
|
4963
|
+
await setAiKey(provider);
|
|
4964
|
+
return Boolean(await getApiKey(provider));
|
|
4965
|
+
} catch (error) {
|
|
4966
|
+
console.log(error instanceof Error ? error.message : String(error));
|
|
4967
|
+
return false;
|
|
4968
|
+
}
|
|
4969
|
+
}
|
|
4970
|
+
|
|
4903
4971
|
async function chooseAndSaveApiModel(provider) {
|
|
4904
4972
|
const model = await chooseAiModel(provider);
|
|
4905
4973
|
if (!model) {
|
package/test/smoke-test.js
CHANGED
|
@@ -52,6 +52,9 @@ assertIncludes(cliSource, "isOpenRouterTextGenerationModel", "OpenRouter model s
|
|
|
52
52
|
assertIncludes(cliSource, "console.log(\" 0. Назад\")", "OpenRouter model selection should return to developer menu");
|
|
53
53
|
assertIncludes(cliSource, "renderTerminalMarkdown", "AI answers should render inline markdown in the terminal");
|
|
54
54
|
assertIncludes(cliSource, "\\x1b[1m$1\\x1b[22m", "AI answer renderer should support bold markdown");
|
|
55
|
+
assertIncludes(cliSource, "ensureApiKeyForModelSelection", "API model selection should prompt for missing provider keys");
|
|
56
|
+
assertIncludes(cliSource, "isOpenAiTextGenerationModel", "OpenAI model selection should filter technical and legacy models");
|
|
57
|
+
assertIncludes(cliSource, "dedupeDatedOpenAiModels", "OpenAI model selection should hide dated duplicates when aliases exist");
|
|
55
58
|
|
|
56
59
|
const commands = await runCli(["commands"]);
|
|
57
60
|
assertIncludes(commands, "iola browser status|install|open|text|html|screenshot|pdf|click|type|eval", "commands");
|
|
@@ -26,6 +26,19 @@ iola ai setup openrouter --model openai/gpt-4.1-mini
|
|
|
26
26
|
iola ai models openrouter --search qwen
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
В интерактивном CLI модели удобнее выбирать через slash-команду:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
/model
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Для OpenRouter выбор идет в два шага:
|
|
36
|
+
|
|
37
|
+
1. выбрать разработчика моделей: OpenAI, Anthropic, Google, Qwen / Alibaba, DeepSeek, Meta / Llama, Mistral AI, xAI, Cohere, Microsoft или Perplexity;
|
|
38
|
+
2. выбрать модель из списка свежих моделей для текстовой работы.
|
|
39
|
+
|
|
40
|
+
CLI получает список моделей из OpenRouter, фильтрует модели под текстовую работу и показывает до 30 самых свежих вариантов выбранного разработчика. В строке модели показываются дата релиза и размер контекста. В списке моделей `0` возвращает к выбору разработчика, а в списке разработчиков `0` отменяет выбор.
|
|
41
|
+
|
|
29
42
|
## Codex CLI
|
|
30
43
|
|
|
31
44
|
```bash
|
|
@@ -42,3 +55,5 @@ iola ai profiles
|
|
|
42
55
|
iola ai profile use local
|
|
43
56
|
iola ai profile use openrouter
|
|
44
57
|
```
|
|
58
|
+
|
|
59
|
+
В интерактивном агенте можно использовать `/model`, чтобы выбрать подключение и модель без ручного ввода id модели.
|
|
@@ -32,10 +32,14 @@ iola master
|
|
|
32
32
|
|
|
33
33
|
Настраивает профиль OpenAI и сохраняет API-ключ локально у пользователя.
|
|
34
34
|
|
|
35
|
+
После сохранения ключа мастер предлагает выбрать модель из доступного списка.
|
|
36
|
+
|
|
35
37
|
### 5. OpenRouter API
|
|
36
38
|
|
|
37
39
|
Настраивает профиль OpenRouter и сохраняет API-ключ локально у пользователя.
|
|
38
40
|
|
|
41
|
+
После сохранения ключа мастер предлагает выбрать разработчика моделей OpenRouter, а затем одну из свежих моделей для текстовой работы. В списке моделей `0` возвращает к выбору разработчика.
|
|
42
|
+
|
|
39
43
|
### 6. Codex CLI
|
|
40
44
|
|
|
41
45
|
Проверяет наличие Codex CLI и авторизации. Если Codex уже установлен и вход выполнен, пункт показывается как `готово`.
|