@iola_adm/iola-cli 0.1.109 → 0.1.110
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/package.json +1 -1
- package/src/cli.js +44 -60
- package/test/smoke-test.js +3 -2
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -101,7 +101,7 @@ const FEATURES = {
|
|
|
101
101
|
"mcp-management": { stage: "stable", defaultEnabled: true, description: "Команды управления MCP-интеграциями." },
|
|
102
102
|
"web-search": { stage: "experimental", defaultEnabled: false, description: "Резерв под web-search режимы AI." },
|
|
103
103
|
};
|
|
104
|
-
const
|
|
104
|
+
const MAIN_OPENROUTER_DEVELOPERS = [
|
|
105
105
|
["openai", "OpenAI"],
|
|
106
106
|
["anthropic", "Anthropic"],
|
|
107
107
|
["google", "Google"],
|
|
@@ -4415,11 +4415,12 @@ function mapOpenRouterModel(model) {
|
|
|
4415
4415
|
const id = String(model.id || "");
|
|
4416
4416
|
const architecture = model.architecture || {};
|
|
4417
4417
|
const created = Number(model.created || 0);
|
|
4418
|
+
const developer = id.includes("/") ? id.split("/")[0] : "";
|
|
4418
4419
|
return {
|
|
4419
4420
|
id,
|
|
4420
4421
|
provider: "openrouter",
|
|
4421
4422
|
note: model.name || "",
|
|
4422
|
-
|
|
4423
|
+
developer,
|
|
4423
4424
|
created,
|
|
4424
4425
|
releaseDate: formatUnixDate(created),
|
|
4425
4426
|
modality: architecture.modality || "",
|
|
@@ -4429,48 +4430,39 @@ function mapOpenRouterModel(model) {
|
|
|
4429
4430
|
};
|
|
4430
4431
|
}
|
|
4431
4432
|
|
|
4432
|
-
function
|
|
4433
|
+
function isOpenRouterTextGenerationModel(model) {
|
|
4433
4434
|
const inputs = model.inputModalities || [];
|
|
4434
4435
|
const outputs = model.outputModalities || [];
|
|
4435
4436
|
if (inputs.length > 0 && !inputs.includes("text")) return false;
|
|
4436
4437
|
if (outputs.length > 0 && !outputs.includes("text")) return false;
|
|
4437
|
-
|
|
4438
|
-
if (modality && modality !== "text->text") return false;
|
|
4438
|
+
if (outputs.includes("image") || outputs.includes("audio") || outputs.includes("video")) return false;
|
|
4439
4439
|
const id = String(model.id || "").toLocaleLowerCase("en-US");
|
|
4440
4440
|
const note = String(model.note || "").toLocaleLowerCase("en-US");
|
|
4441
|
-
return !/\b(
|
|
4441
|
+
return !/\b(image|video|audio|tts|embed|embedding|rerank|moderation|safeguard)\b/.test(`${id} ${note}`);
|
|
4442
4442
|
}
|
|
4443
4443
|
|
|
4444
|
-
function
|
|
4445
|
-
const
|
|
4444
|
+
function buildOpenRouterDeveloperChoices(models) {
|
|
4445
|
+
const byDeveloper = new Map();
|
|
4446
4446
|
for (const model of models) {
|
|
4447
|
-
if (!model.
|
|
4448
|
-
const current =
|
|
4447
|
+
if (!model.developer) continue;
|
|
4448
|
+
const current = byDeveloper.get(model.developer) || { count: 0 };
|
|
4449
4449
|
current.count += 1;
|
|
4450
|
-
|
|
4451
|
-
byAuthor.set(model.author, current);
|
|
4450
|
+
byDeveloper.set(model.developer, current);
|
|
4452
4451
|
}
|
|
4453
4452
|
|
|
4454
|
-
return
|
|
4453
|
+
return MAIN_OPENROUTER_DEVELOPERS
|
|
4455
4454
|
.map(([id, label]) => {
|
|
4456
|
-
const stat =
|
|
4455
|
+
const stat = byDeveloper.get(id);
|
|
4457
4456
|
if (!stat) return null;
|
|
4458
4457
|
return {
|
|
4459
4458
|
id,
|
|
4460
4459
|
label,
|
|
4461
4460
|
count: stat.count,
|
|
4462
|
-
latestReleaseDate: formatUnixDate(stat.latestCreated),
|
|
4463
4461
|
};
|
|
4464
4462
|
})
|
|
4465
4463
|
.filter(Boolean);
|
|
4466
4464
|
}
|
|
4467
4465
|
|
|
4468
|
-
function modelMatchesSearch(model, search) {
|
|
4469
|
-
const needle = search.toLocaleLowerCase("ru-RU");
|
|
4470
|
-
return [model.id, model.note, model.author]
|
|
4471
|
-
.some((value) => String(value || "").toLocaleLowerCase("ru-RU").includes(needle));
|
|
4472
|
-
}
|
|
4473
|
-
|
|
4474
4466
|
function sortModelsByFreshness(left, right) {
|
|
4475
4467
|
return Number(right.created || 0) - Number(left.created || 0)
|
|
4476
4468
|
|| String(left.id).localeCompare(String(right.id));
|
|
@@ -4845,55 +4837,47 @@ async function chooseOpenRouterModel() {
|
|
|
4845
4837
|
return "";
|
|
4846
4838
|
}
|
|
4847
4839
|
|
|
4848
|
-
const textModels = models.filter(
|
|
4840
|
+
const textModels = models.filter(isOpenRouterTextGenerationModel);
|
|
4849
4841
|
if (textModels.length === 0) {
|
|
4850
4842
|
console.log("Текстовые модели OpenRouter не найдены.");
|
|
4851
4843
|
return "";
|
|
4852
4844
|
}
|
|
4853
4845
|
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
console.log(` ${searchIndex}. Поиск по всем текстовым моделям`);
|
|
4862
|
-
console.log(" 0. Отмена");
|
|
4863
|
-
|
|
4864
|
-
const authorAnswer = Number(await askText("Номер: "));
|
|
4865
|
-
if (!authorAnswer) return "";
|
|
4846
|
+
while (true) {
|
|
4847
|
+
const developerChoices = buildOpenRouterDeveloperChoices(textModels);
|
|
4848
|
+
console.log("Выберите разработчика моделей OpenRouter:");
|
|
4849
|
+
developerChoices.forEach((choice, index) => {
|
|
4850
|
+
console.log(` ${index + 1}. ${choice.label} (${choice.count})`);
|
|
4851
|
+
});
|
|
4852
|
+
console.log(" 0. Отмена");
|
|
4866
4853
|
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
const search = (await askText("Фильтр моделей: ")).trim();
|
|
4870
|
-
if (!search) return "";
|
|
4871
|
-
filtered = textModels.filter((model) => modelMatchesSearch(model, search));
|
|
4872
|
-
} else {
|
|
4873
|
-
const selectedAuthor = authorChoices[authorAnswer - 1];
|
|
4874
|
-
if (!selectedAuthor) return "";
|
|
4875
|
-
filtered = textModels.filter((model) => model.author === selectedAuthor.id);
|
|
4876
|
-
}
|
|
4854
|
+
const developerAnswer = Number(await askText("Номер: "));
|
|
4855
|
+
if (!developerAnswer) return "";
|
|
4877
4856
|
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4857
|
+
const selectedDeveloper = developerChoices[developerAnswer - 1];
|
|
4858
|
+
if (!selectedDeveloper) continue;
|
|
4859
|
+
const filtered = textModels
|
|
4860
|
+
.filter((model) => model.developer === selectedDeveloper.id)
|
|
4861
|
+
.sort(sortModelsByFreshness)
|
|
4862
|
+
.slice(0, 30);
|
|
4881
4863
|
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4864
|
+
if (filtered.length === 0) {
|
|
4865
|
+
console.log("Модели не найдены.");
|
|
4866
|
+
continue;
|
|
4867
|
+
}
|
|
4886
4868
|
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4869
|
+
console.log("Выберите текстовую модель:");
|
|
4870
|
+
filtered.forEach((model, index) => {
|
|
4871
|
+
const date = model.releaseDate || "дата неизвестна";
|
|
4872
|
+
const context = model.contextLength ? `, ctx ${formatCompactNumber(model.contextLength)}` : "";
|
|
4873
|
+
console.log(` ${index + 1}. ${model.id} (${date}${context}) - ${model.note || model.id}`);
|
|
4874
|
+
});
|
|
4875
|
+
console.log(" 0. Назад");
|
|
4894
4876
|
|
|
4895
|
-
|
|
4896
|
-
|
|
4877
|
+
const modelAnswer = Number(await askText("Номер: "));
|
|
4878
|
+
if (!modelAnswer) continue;
|
|
4879
|
+
return filtered[modelAnswer - 1]?.id || "";
|
|
4880
|
+
}
|
|
4897
4881
|
}
|
|
4898
4882
|
|
|
4899
4883
|
async function chooseAndSaveApiModel(provider) {
|
package/test/smoke-test.js
CHANGED
|
@@ -47,8 +47,9 @@ assertIncludes(help, "iola master", "help");
|
|
|
47
47
|
assertIncludes(help, "iola ask", "help");
|
|
48
48
|
|
|
49
49
|
assertIncludes(cliSource, "force: Boolean(options.force)", "IOLA setup should not force model reinstall by default");
|
|
50
|
-
assertIncludes(cliSource, "
|
|
51
|
-
assertIncludes(cliSource, "
|
|
50
|
+
assertIncludes(cliSource, "MAIN_OPENROUTER_DEVELOPERS", "OpenRouter model selection should group models by developer");
|
|
51
|
+
assertIncludes(cliSource, "isOpenRouterTextGenerationModel", "OpenRouter model selection should prefer text-generation models");
|
|
52
|
+
assertIncludes(cliSource, "console.log(\" 0. Назад\")", "OpenRouter model selection should return to developer menu");
|
|
52
53
|
|
|
53
54
|
const commands = await runCli(["commands"]);
|
|
54
55
|
assertIncludes(commands, "iola browser status|install|open|text|html|screenshot|pdf|click|type|eval", "commands");
|