@iola_adm/iola-cli 0.1.68 → 0.1.70

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 +41 -14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iola_adm/iola-cli",
3
- "version": "0.1.68",
3
+ "version": "0.1.70",
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
 
@@ -1342,27 +1343,35 @@ function clearAgentInputArea(state = null) {
1342
1343
  function startActivityIndicator(label = "работаю") {
1343
1344
  const doneLabel = "готово";
1344
1345
  if (!output.isTTY || process.env.NO_COLOR === "1") {
1345
- output.write(`${label}...\n`);
1346
+ output.write(`${formatActivityLine(label)}\n`);
1346
1347
  const started = Date.now();
1347
1348
  return () => {
1348
1349
  const seconds = ((Date.now() - started) / 1000).toFixed(1);
1349
- output.write(`- ${doneLabel} ${seconds}s\n`);
1350
+ output.write(`${formatActivityLine(doneLabel, seconds)}\n`);
1350
1351
  };
1351
1352
  }
1352
1353
  const started = Date.now();
1353
1354
  const render = () => {
1354
1355
  const seconds = ((Date.now() - started) / 1000).toFixed(1);
1355
- output.write(`\r\x1b[2K${colorMuted(`─ ${label} ${seconds}s`)}`);
1356
+ output.write(`\r\x1b[2K${colorMuted(formatActivityLine(label, seconds))}`);
1356
1357
  };
1357
1358
  render();
1358
1359
  const timer = setInterval(render, 120);
1359
1360
  return () => {
1360
1361
  clearInterval(timer);
1361
1362
  const seconds = ((Date.now() - started) / 1000).toFixed(1);
1362
- output.write(`\r\x1b[2K${colorMuted(`─ ${doneLabel} ${seconds}s`)}\n`);
1363
+ output.write(`\r\x1b[2K${colorMuted(formatActivityLine(doneLabel, seconds))}\n`);
1363
1364
  };
1364
1365
  }
1365
1366
 
1367
+ function formatActivityLine(label, seconds = null) {
1368
+ const columns = Math.max(60, Number(output.columns || 100));
1369
+ const middle = ` ${label}${seconds == null ? "" : ` ${seconds}s`} `;
1370
+ const leftWidth = Math.max(1, Math.floor((columns - visibleLength(middle)) / 3));
1371
+ const rightWidth = Math.max(1, columns - leftWidth - visibleLength(middle));
1372
+ return `${"─".repeat(leftWidth)}${middle}${"─".repeat(rightWidth)}`;
1373
+ }
1374
+
1366
1375
  function suspendRawInputForCommand(stream) {
1367
1376
  if (!stream.isTTY || !stream.isRaw) return () => {};
1368
1377
  stream.setRawMode(false);
@@ -1458,6 +1467,13 @@ function buildAgentStatusLine(state) {
1458
1467
  return `${cwd} | AI: ${kind}${model} (${ai.name})`;
1459
1468
  }
1460
1469
 
1470
+ function truncateTerminalLine(value) {
1471
+ const columns = Math.max(20, Number(output.columns || 100));
1472
+ const text = String(value).replace(/\r?\n/g, " ");
1473
+ if (visibleLength(text) <= columns) return text;
1474
+ return `${text.slice(0, Math.max(0, columns - 1))}…`;
1475
+ }
1476
+
1461
1477
  function compactAgentHistory(history) {
1462
1478
  if (history.length <= 8) return history;
1463
1479
  const summary = history.slice(0, -6)
@@ -4077,12 +4093,15 @@ async function listAiModels(provider) {
4077
4093
  }
4078
4094
 
4079
4095
  const payload = await response.json();
4080
- const models = (payload.models || []).map((model) => ({
4096
+ const installed = (payload.models || []).map((model) => ({
4081
4097
  id: model.name,
4082
4098
  provider: "ollama",
4083
4099
  note: model.modified_at ? `updated ${model.modified_at}` : "local",
4084
4100
  }));
4085
- return models.length > 0 ? models : getRecommendedOllamaModels("not installed");
4101
+ const installedIds = new Set(installed.map((model) => model.id));
4102
+ const recommended = getRecommendedOllamaModels("not installed")
4103
+ .filter((model) => !installedIds.has(model.id));
4104
+ return [...installed, ...recommended];
4086
4105
  } catch {
4087
4106
  return getRecommendedOllamaModels("recommended");
4088
4107
  }
@@ -4137,10 +4156,12 @@ async function listAiModels(provider) {
4137
4156
 
4138
4157
  function getRecommendedOllamaModels(notePrefix = "recommended") {
4139
4158
  return [
4140
- { id: "llama3.2:1b", provider: "ollama", note: `${notePrefix} low RAM` },
4141
- { id: "llama3.2:3b", provider: "ollama", note: `${notePrefix} standard` },
4142
- { id: "qwen3:4b", provider: "ollama", note: `${notePrefix} balanced` },
4143
- { id: "qwen3:8b", provider: "ollama", note: `${notePrefix} good GPU` },
4159
+ { id: "qwen3:1.7b", provider: "ollama", note: `${notePrefix} recommended low RAM` },
4160
+ { id: "qwen3:4b", provider: "ollama", note: `${notePrefix} recommended balanced` },
4161
+ { id: "gemma3:1b", provider: "ollama", note: `${notePrefix} Gemma low RAM` },
4162
+ { id: "gemma3:4b", provider: "ollama", note: `${notePrefix} Gemma balanced` },
4163
+ { id: "llama3.2:3b", provider: "ollama", note: `${notePrefix} legacy fallback` },
4164
+ { id: "llama3.2:1b", provider: "ollama", note: `${notePrefix} minimal fallback only` },
4144
4165
  ];
4145
4166
  }
4146
4167
 
@@ -6559,7 +6580,10 @@ async function buildAiMessages(question, dataContext, history, options = {}, con
6559
6580
  const system = [
6560
6581
  "Ты терминальный AI-агент городского округа Йошкар-Ола.",
6561
6582
  "Отвечай на русском языке естественно и по смыслу запроса пользователя.",
6583
+ "Не смешивай языки. Не выдумывай факты, географию и числа.",
6584
+ "Если пользователь просто здоровается, ответь коротким приветствием и спроси, чем помочь.",
6562
6585
  hasDataContext ? "Используй только данные из переданного контекста открытых данных." : "Для обычного диалога отвечай как полноценный AI-ассистент, не перечисляй слои и возможности без запроса пользователя.",
6586
+ hasDataContext ? "" : "Не рассказывай сведения о Йошкар-Оле, школах или детских садах без прямого запроса и контекста данных.",
6563
6587
  hasDataContext ? "Если в контексте нет нужных сведений, прямо напиши, что данных недостаточно." : "",
6564
6588
  hasDataContext ? "Не выдумывай адреса, телефоны, лицензии и руководителей." : "",
6565
6589
  hasDataContext ? "Если отвечаешь по конкретным организациям, укажи источник в конце: слой, название и ИНН." : "",
@@ -6665,6 +6689,9 @@ async function callOllama(config, messages) {
6665
6689
  model: config.model || "llama3.2:1b",
6666
6690
  messages,
6667
6691
  stream: false,
6692
+ options: {
6693
+ temperature: Number(config.temperature ?? 0.1),
6694
+ },
6668
6695
  }),
6669
6696
  });
6670
6697
  } catch {