@iola_adm/iola-cli 0.1.67 → 0.1.69

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 +60 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iola_adm/iola-cli",
3
- "version": "0.1.67",
3
+ "version": "0.1.69",
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
@@ -1296,8 +1296,9 @@ function renderAgentInput(state) {
1296
1296
  clearAgentInputArea(state);
1297
1297
  const prompt = "> ";
1298
1298
  const lines = state.buffer.split("\n");
1299
- const inputLines = [`${prompt}${lines[0] || ""}`, ...lines.slice(1).map((line) => ` ${line}`)];
1300
- const cwdLine = colorMuted(` ${buildAgentStatusLine(state)}`);
1299
+ const inputLines = [`${prompt}${lines[0] || ""}`, ...lines.slice(1)];
1300
+ const statusLine = truncateTerminalLine(` ${buildAgentStatusLine(state)}`);
1301
+ const cwdLine = colorMuted(statusLine);
1301
1302
  const menuLines = [];
1302
1303
  if (state.slashOpen) {
1303
1304
  const matches = currentSlashMatches(state);
@@ -1311,11 +1312,11 @@ function renderAgentInput(state) {
1311
1312
  const absoluteIndex = offset + index;
1312
1313
  const selected = absoluteIndex === state.selected;
1313
1314
  const marker = selected ? ">" : " ";
1314
- const row = `${marker} ${visibleMatches[index].command.padEnd(24)} ${visibleMatches[index].description}`;
1315
+ const row = truncateTerminalLine(`${marker} ${visibleMatches[index].command.padEnd(24)} ${visibleMatches[index].description}`);
1315
1316
  menuLines.push(selected ? colorSlashSelection(row) : ` ${row.slice(2)}`);
1316
1317
  }
1317
1318
  const shownTo = Math.min(offset + visibleLimit, matches.length);
1318
- menuLines.push(` ↑/↓ выбрать • Enter выполнить • Esc закрыть • ${offset + 1}-${shownTo} из ${matches.length}`);
1319
+ menuLines.push(truncateTerminalLine(` ↑/↓ выбрать • Enter выполнить • Esc закрыть • ${offset + 1}-${shownTo} из ${matches.length}`));
1319
1320
  }
1320
1321
  }
1321
1322
 
@@ -1458,6 +1459,13 @@ function buildAgentStatusLine(state) {
1458
1459
  return `${cwd} | AI: ${kind}${model} (${ai.name})`;
1459
1460
  }
1460
1461
 
1462
+ function truncateTerminalLine(value) {
1463
+ const columns = Math.max(20, Number(output.columns || 100));
1464
+ const text = String(value).replace(/\r?\n/g, " ");
1465
+ if (visibleLength(text) <= columns) return text;
1466
+ return `${text.slice(0, Math.max(0, columns - 1))}…`;
1467
+ }
1468
+
1461
1469
  function compactAgentHistory(history) {
1462
1470
  if (history.length <= 8) return history;
1463
1471
  const summary = history.slice(0, -6)
@@ -4077,18 +4085,14 @@ async function listAiModels(provider) {
4077
4085
  }
4078
4086
 
4079
4087
  const payload = await response.json();
4080
- return (payload.models || []).map((model) => ({
4088
+ const models = (payload.models || []).map((model) => ({
4081
4089
  id: model.name,
4082
4090
  provider: "ollama",
4083
4091
  note: model.modified_at ? `updated ${model.modified_at}` : "local",
4084
4092
  }));
4093
+ return models.length > 0 ? models : getRecommendedOllamaModels("not installed");
4085
4094
  } catch {
4086
- return [
4087
- { id: "llama3.2:1b", provider: "ollama", note: "recommended low RAM" },
4088
- { id: "llama3.2:3b", provider: "ollama", note: "recommended standard" },
4089
- { id: "qwen3:4b", provider: "ollama", note: "recommended balanced" },
4090
- { id: "qwen3:8b", provider: "ollama", note: "recommended good GPU" },
4091
- ];
4095
+ return getRecommendedOllamaModels("recommended");
4092
4096
  }
4093
4097
  }
4094
4098
 
@@ -4139,6 +4143,15 @@ async function listAiModels(provider) {
4139
4143
  ];
4140
4144
  }
4141
4145
 
4146
+ function getRecommendedOllamaModels(notePrefix = "recommended") {
4147
+ return [
4148
+ { id: "llama3.2:1b", provider: "ollama", note: `${notePrefix} low RAM` },
4149
+ { id: "llama3.2:3b", provider: "ollama", note: `${notePrefix} standard` },
4150
+ { id: "qwen3:4b", provider: "ollama", note: `${notePrefix} balanced` },
4151
+ { id: "qwen3:8b", provider: "ollama", note: `${notePrefix} good GPU` },
4152
+ ];
4153
+ }
4154
+
4142
4155
  async function printAiProfiles() {
4143
4156
  const config = await loadConfig();
4144
4157
  const active = getActiveProfileName(config);
@@ -4455,6 +4468,10 @@ async function chooseAiModel(provider) {
4455
4468
  async function switchModelTarget(target, model) {
4456
4469
  const config = await loadConfig();
4457
4470
  const provider = target === "local" ? "ollama" : target;
4471
+ if (provider === "ollama") {
4472
+ const ready = await ensureOllamaModelAvailable(model, config);
4473
+ if (!ready) return;
4474
+ }
4458
4475
  const profileName = provider === "ollama" ? "local" : provider;
4459
4476
  const currentProfile = config.ai.profiles?.[profileName] || buildProfileFromOptions(provider, { model });
4460
4477
  const profile = {
@@ -4480,6 +4497,38 @@ async function switchModelTarget(target, model) {
4480
4497
  console.log(`Активная модель: ${profileName} (${provider}, ${model})`);
4481
4498
  }
4482
4499
 
4500
+ async function ensureOllamaModelAvailable(model, config = null) {
4501
+ if (await isOllamaModelInstalled(model, config)) return true;
4502
+
4503
+ const command = await resolveOllamaCommand();
4504
+ if (!command) {
4505
+ console.log("Ollama CLI не найден в PATH, хотя локальный API может отвечать.");
4506
+ console.log("Откройте новый PowerShell или запустите мастер: iola ai setup ollama");
4507
+ return false;
4508
+ }
4509
+
4510
+ const shouldInstall = await confirm(`Локальная модель ${model} не скачана. Скачать через "ollama pull ${model}"? [Y/n] `);
4511
+ if (!shouldInstall) {
4512
+ console.log("Переключение на локальную модель отменено.");
4513
+ return false;
4514
+ }
4515
+
4516
+ await runCommand(command, ["pull", model], { inherit: true });
4517
+ return true;
4518
+ }
4519
+
4520
+ async function isOllamaModelInstalled(model, loadedConfig = null) {
4521
+ try {
4522
+ const config = loadedConfig || await loadConfig();
4523
+ const response = await fetch(`${config.ai.profiles?.local?.baseUrl || "http://127.0.0.1:11434"}/api/tags`);
4524
+ if (!response.ok) return false;
4525
+ const payload = await response.json();
4526
+ return (payload.models || []).some((entry) => entry.name === model);
4527
+ } catch {
4528
+ return false;
4529
+ }
4530
+ }
4531
+
4483
4532
  async function askText(question) {
4484
4533
  if (!process.stdin.isTTY) return "";
4485
4534
  const rl = readline.createInterface({ input, output });