@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iola_adm/iola-cli",
3
- "version": "0.1.109",
3
+ "version": "0.1.110",
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
@@ -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 MAIN_OPENROUTER_AUTHORS = [
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
- author: id.includes("/") ? id.split("/")[0] : "",
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 isOpenRouterTextModel(model) {
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
- const modality = String(model.modality || "").toLocaleLowerCase("en-US");
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(vl|vision|image|video|audio|tts|embed|embedding|rerank|moderation)\b/.test(`${id} ${note}`);
4441
+ return !/\b(image|video|audio|tts|embed|embedding|rerank|moderation|safeguard)\b/.test(`${id} ${note}`);
4442
4442
  }
4443
4443
 
4444
- function buildOpenRouterAuthorChoices(models) {
4445
- const byAuthor = new Map();
4444
+ function buildOpenRouterDeveloperChoices(models) {
4445
+ const byDeveloper = new Map();
4446
4446
  for (const model of models) {
4447
- if (!model.author) continue;
4448
- const current = byAuthor.get(model.author) || { count: 0, latestCreated: 0 };
4447
+ if (!model.developer) continue;
4448
+ const current = byDeveloper.get(model.developer) || { count: 0 };
4449
4449
  current.count += 1;
4450
- current.latestCreated = Math.max(current.latestCreated, Number(model.created || 0));
4451
- byAuthor.set(model.author, current);
4450
+ byDeveloper.set(model.developer, current);
4452
4451
  }
4453
4452
 
4454
- return MAIN_OPENROUTER_AUTHORS
4453
+ return MAIN_OPENROUTER_DEVELOPERS
4455
4454
  .map(([id, label]) => {
4456
- const stat = byAuthor.get(id);
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(isOpenRouterTextModel);
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
- const authorChoices = buildOpenRouterAuthorChoices(textModels);
4855
- console.log("Выберите автора моделей OpenRouter:");
4856
- authorChoices.forEach((choice, index) => {
4857
- const date = choice.latestReleaseDate ? `, свежая: ${choice.latestReleaseDate}` : "";
4858
- console.log(` ${index + 1}. ${choice.label} (${choice.count}${date})`);
4859
- });
4860
- const searchIndex = authorChoices.length + 1;
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
- let filtered;
4868
- if (authorAnswer === searchIndex) {
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
- filtered = filtered
4879
- .sort(sortModelsByFreshness)
4880
- .slice(0, 30);
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
- if (filtered.length === 0) {
4883
- console.log("Модели не найдены.");
4884
- return "";
4885
- }
4864
+ if (filtered.length === 0) {
4865
+ console.log("Модели не найдены.");
4866
+ continue;
4867
+ }
4886
4868
 
4887
- console.log("Выберите текстовую модель:");
4888
- filtered.forEach((model, index) => {
4889
- const date = model.releaseDate || "дата неизвестна";
4890
- const context = model.contextLength ? `, ctx ${formatCompactNumber(model.contextLength)}` : "";
4891
- console.log(` ${index + 1}. ${model.id} (${date}${context}) - ${model.note || model.id}`);
4892
- });
4893
- console.log(" 0. Отмена");
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
- const modelAnswer = Number(await askText("Номер: "));
4896
- return filtered[modelAnswer - 1]?.id || "";
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) {
@@ -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, "MAIN_OPENROUTER_AUTHORS", "OpenRouter model selection should group models by author");
51
- assertIncludes(cliSource, "modality !== \"text->text\"", "OpenRouter model selection should prefer text models");
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");