@iola_adm/iola-cli 0.1.47 → 0.1.49

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +63 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iola_adm/iola-cli",
3
- "version": "0.1.47",
3
+ "version": "0.1.49",
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
@@ -644,6 +644,20 @@ async function ensureAgentAiReady() {
644
644
  const readiness = await getAiReadiness();
645
645
  if (readiness.ready) return readiness;
646
646
 
647
+ if (readiness.anyReady) {
648
+ const fallback = getFallbackAiProfile(readiness);
649
+ if (fallback) {
650
+ const shouldSwitch = await confirm(`Активный AI-профиль ${readiness.activeProfile} (${readiness.activeProvider}) недоступен, найден ${fallback.name} (${fallback.provider}). Переключить активный профиль на ${fallback.name}? [Y/n] `);
651
+ if (shouldSwitch) {
652
+ await setActiveAiProfile(fallback.name, fallback);
653
+ console.log(`Активный AI-профиль: ${fallback.name} (${fallback.provider}, ${fallback.model || "-"})`);
654
+ } else {
655
+ console.log(`Для текстовых запросов будет использован доступный профиль ${fallback.name} (${fallback.provider}).`);
656
+ }
657
+ return readiness;
658
+ }
659
+ }
660
+
647
661
  if (!input.isTTY || !output.isTTY) {
648
662
  console.log("AI-провайдер не настроен. Для настройки запустите: iola wizard");
649
663
  return readiness;
@@ -696,6 +710,7 @@ async function getAiReadiness() {
696
710
  activeProvider: activeProfile.provider || "-",
697
711
  activeModel: activeProfile.model || "-",
698
712
  anyReady: Boolean(ollama || openai || openrouter || codex),
713
+ profiles: config.ai.profiles || {},
699
714
  ollama,
700
715
  openai,
701
716
  openrouter,
@@ -703,6 +718,16 @@ async function getAiReadiness() {
703
718
  };
704
719
  }
705
720
 
721
+ function getFallbackAiProfile(readiness) {
722
+ const priority = ["openai", "openrouter", "codex", "ollama"];
723
+ for (const provider of priority) {
724
+ if (!readiness[provider]) continue;
725
+ const entry = Object.entries(readiness.profiles || {}).find(([, profile]) => profile.provider === provider);
726
+ if (entry) return { name: entry[0], ...entry[1] };
727
+ }
728
+ return null;
729
+ }
730
+
706
731
  async function hasUsableOllamaModel() {
707
732
  try {
708
733
  const config = await loadConfig();
@@ -725,7 +750,7 @@ async function hasUsableCodexAuth() {
725
750
  }
726
751
 
727
752
  async function startAgentReadline() {
728
- const rl = readline.createInterface({ input, output, prompt: "iola> " });
753
+ const rl = readline.createInterface({ input, output, prompt: "> " });
729
754
  const state = {
730
755
  history: [],
731
756
  };
@@ -816,7 +841,7 @@ async function startAgentRawInput() {
816
841
  render();
817
842
  continue;
818
843
  }
819
- output.write(`iola> ${line}\n`);
844
+ output.write(`> ${line}\n`);
820
845
  try {
821
846
  const shouldExit = await handleAgentLine(line, state);
822
847
  if (shouldExit) break;
@@ -1266,7 +1291,7 @@ function currentSlashMatches(state) {
1266
1291
 
1267
1292
  function renderAgentInput(state) {
1268
1293
  clearAgentInputArea(state);
1269
- const prompt = "iola> ";
1294
+ const prompt = "> ";
1270
1295
  const lines = state.buffer.split("\n");
1271
1296
  const inputLines = [`${prompt}${lines[0] || ""}`, ...lines.slice(1).map((line) => ` ${line}`)];
1272
1297
  const cwdLine = colorMuted(` ${process.cwd()}`);
@@ -4365,6 +4390,13 @@ async function useAiProfile(name) {
4365
4390
  throw new Error(`AI-профиль не найден: ${name}`);
4366
4391
  }
4367
4392
 
4393
+ await setActiveAiProfile(name, profile, config);
4394
+
4395
+ console.log(`Активный AI-профиль: ${name} (${profile.provider}, ${profile.model || "-"})`);
4396
+ }
4397
+
4398
+ async function setActiveAiProfile(name, profile, loadedConfig = null) {
4399
+ const config = loadedConfig || await loadConfig();
4368
4400
  await saveConfig({
4369
4401
  ai: {
4370
4402
  ...config.ai,
@@ -4374,8 +4406,6 @@ async function useAiProfile(name) {
4374
4406
  baseUrl: profile.baseUrl || config.ai.baseUrl,
4375
4407
  },
4376
4408
  });
4377
-
4378
- console.log(`Активный AI-профиль: ${name} (${profile.provider}, ${profile.model || "-"})`);
4379
4409
  }
4380
4410
 
4381
4411
  async function deleteAiProfile(name) {
@@ -5866,7 +5896,7 @@ async function aiAsk(args, context = {}) {
5866
5896
  }
5867
5897
 
5868
5898
  const config = await loadConfig();
5869
- const providerConfig = resolveAiProfile(config, options);
5899
+ const providerConfig = await resolveUsableAiProfile(config, options);
5870
5900
  if (providerConfig.provider === "codex") await assertPermission("codex");
5871
5901
  if (providerConfig.provider !== "ollama") await assertPermission("externalAi");
5872
5902
  if (options["stream-json"]) options.events = true;
@@ -5928,6 +5958,33 @@ async function aiAsk(args, context = {}) {
5928
5958
  return answer;
5929
5959
  }
5930
5960
 
5961
+ async function resolveUsableAiProfile(config, options = {}) {
5962
+ const explicit = Boolean(options.profile || options.provider);
5963
+ const providerConfig = resolveAiProfile(config, options);
5964
+ if (explicit) return providerConfig;
5965
+
5966
+ const readiness = await getAiReadiness();
5967
+ if (isProviderReady(providerConfig.provider, readiness)) return providerConfig;
5968
+
5969
+ const fallback = getFallbackAiProfile(readiness);
5970
+ if (!fallback) return providerConfig;
5971
+
5972
+ if (!options.quiet) {
5973
+ console.log(`Активный AI-профиль ${providerConfig.name} (${providerConfig.provider}) недоступен. Использую ${fallback.name} (${fallback.provider}).`);
5974
+ }
5975
+
5976
+ return {
5977
+ name: fallback.name,
5978
+ ...fallback,
5979
+ model: fallback.model || providerConfig.model,
5980
+ baseUrl: fallback.baseUrl || providerConfig.baseUrl,
5981
+ };
5982
+ }
5983
+
5984
+ function isProviderReady(provider, readiness) {
5985
+ return Boolean(readiness?.[provider]);
5986
+ }
5987
+
5931
5988
  function resolveAiProfile(config, options = {}) {
5932
5989
  const profileName = options.profile || (options.provider && config.ai.profiles?.[options.provider]
5933
5990
  ? options.provider