@iola_adm/iola-cli 0.1.69 → 0.1.71

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 +37 -14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iola_adm/iola-cli",
3
- "version": "0.1.69",
3
+ "version": "0.1.71",
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
@@ -766,7 +766,7 @@ async function startAgentReadline() {
766
766
  }
767
767
 
768
768
  async function startAgentRawInput() {
769
- const state = { history: [], buffer: "", selected: 0, slashOffset: 0, slashOpen: false, running: false, renderedInputLines: 0, rawMode: true, pendingOutput: "", aiStatus: null };
769
+ const state = { history: [], buffer: "", selected: 0, slashOffset: 0, slashOpen: false, running: false, renderedInputLines: 0, renderedLines: 0, rawMode: true, pendingOutput: "", aiStatus: null };
770
770
  const wasRaw = input.isRaw;
771
771
  activateRawInput(input);
772
772
 
@@ -1330,40 +1330,52 @@ function renderAgentInput(state) {
1330
1330
  output.write(`\x1b[${cursorColumn + 1}G`);
1331
1331
  }
1332
1332
  state.renderedInputLines = inputLines.length;
1333
+ state.renderedLines = renderedLines.length;
1333
1334
  }
1334
1335
 
1335
1336
  function clearAgentInputArea(state = null) {
1336
1337
  if (!output.isTTY) return;
1337
- const inputLines = Math.max(1, Number(state?.renderedInputLines || 1));
1338
- if (inputLines > 1) output.write(`\x1b[${inputLines - 1}A`);
1338
+ const renderedLines = Math.max(1, Number(state?.renderedLines || state?.renderedInputLines || 1));
1339
+ if (renderedLines > 1) output.write(`\x1b[${renderedLines - 1}A`);
1339
1340
  output.write("\r\x1b[0J");
1340
- if (state) state.renderedInputLines = 0;
1341
+ if (state) {
1342
+ state.renderedInputLines = 0;
1343
+ state.renderedLines = 0;
1344
+ }
1341
1345
  }
1342
1346
 
1343
1347
  function startActivityIndicator(label = "работаю") {
1344
1348
  const doneLabel = "готово";
1345
1349
  if (!output.isTTY || process.env.NO_COLOR === "1") {
1346
- output.write(`${label}...\n`);
1350
+ output.write(`${formatActivityLine(label)}\n`);
1347
1351
  const started = Date.now();
1348
1352
  return () => {
1349
1353
  const seconds = ((Date.now() - started) / 1000).toFixed(1);
1350
- output.write(`- ${doneLabel} ${seconds}s\n`);
1354
+ output.write(`${formatActivityLine(doneLabel, seconds)}\n`);
1351
1355
  };
1352
1356
  }
1353
1357
  const started = Date.now();
1354
1358
  const render = () => {
1355
1359
  const seconds = ((Date.now() - started) / 1000).toFixed(1);
1356
- output.write(`\r\x1b[2K${colorMuted(`─ ${label} ${seconds}s`)}`);
1360
+ output.write(`\r\x1b[2K${colorMuted(formatActivityLine(label, seconds))}`);
1357
1361
  };
1358
1362
  render();
1359
1363
  const timer = setInterval(render, 120);
1360
1364
  return () => {
1361
1365
  clearInterval(timer);
1362
1366
  const seconds = ((Date.now() - started) / 1000).toFixed(1);
1363
- output.write(`\r\x1b[2K${colorMuted(`─ ${doneLabel} ${seconds}s`)}\n`);
1367
+ output.write(`\r\x1b[2K${colorMuted(formatActivityLine(doneLabel, seconds))}\n`);
1364
1368
  };
1365
1369
  }
1366
1370
 
1371
+ function formatActivityLine(label, seconds = null) {
1372
+ const columns = Math.max(60, Number(output.columns || 100));
1373
+ const middle = ` ${label}${seconds == null ? "" : ` ${seconds}s`} `;
1374
+ const leftWidth = Math.max(1, Math.floor((columns - visibleLength(middle)) / 3));
1375
+ const rightWidth = Math.max(1, columns - leftWidth - visibleLength(middle));
1376
+ return `${"─".repeat(leftWidth)}${middle}${"─".repeat(rightWidth)}`;
1377
+ }
1378
+
1367
1379
  function suspendRawInputForCommand(stream) {
1368
1380
  if (!stream.isTTY || !stream.isRaw) return () => {};
1369
1381
  stream.setRawMode(false);
@@ -4085,12 +4097,15 @@ async function listAiModels(provider) {
4085
4097
  }
4086
4098
 
4087
4099
  const payload = await response.json();
4088
- const models = (payload.models || []).map((model) => ({
4100
+ const installed = (payload.models || []).map((model) => ({
4089
4101
  id: model.name,
4090
4102
  provider: "ollama",
4091
4103
  note: model.modified_at ? `updated ${model.modified_at}` : "local",
4092
4104
  }));
4093
- return models.length > 0 ? models : getRecommendedOllamaModels("not installed");
4105
+ const installedIds = new Set(installed.map((model) => model.id));
4106
+ const recommended = getRecommendedOllamaModels("not installed")
4107
+ .filter((model) => !installedIds.has(model.id));
4108
+ return [...installed, ...recommended];
4094
4109
  } catch {
4095
4110
  return getRecommendedOllamaModels("recommended");
4096
4111
  }
@@ -4145,10 +4160,12 @@ async function listAiModels(provider) {
4145
4160
 
4146
4161
  function getRecommendedOllamaModels(notePrefix = "recommended") {
4147
4162
  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` },
4163
+ { id: "qwen3:1.7b", provider: "ollama", note: `${notePrefix} recommended low RAM` },
4164
+ { id: "qwen3:4b", provider: "ollama", note: `${notePrefix} recommended balanced` },
4165
+ { id: "gemma3:1b", provider: "ollama", note: `${notePrefix} Gemma low RAM` },
4166
+ { id: "gemma3:4b", provider: "ollama", note: `${notePrefix} Gemma balanced` },
4167
+ { id: "llama3.2:3b", provider: "ollama", note: `${notePrefix} legacy fallback` },
4168
+ { id: "llama3.2:1b", provider: "ollama", note: `${notePrefix} minimal fallback only` },
4152
4169
  ];
4153
4170
  }
4154
4171
 
@@ -6567,7 +6584,10 @@ async function buildAiMessages(question, dataContext, history, options = {}, con
6567
6584
  const system = [
6568
6585
  "Ты терминальный AI-агент городского округа Йошкар-Ола.",
6569
6586
  "Отвечай на русском языке естественно и по смыслу запроса пользователя.",
6587
+ "Не смешивай языки. Не выдумывай факты, географию и числа.",
6588
+ "Если пользователь просто здоровается, ответь коротким приветствием и спроси, чем помочь.",
6570
6589
  hasDataContext ? "Используй только данные из переданного контекста открытых данных." : "Для обычного диалога отвечай как полноценный AI-ассистент, не перечисляй слои и возможности без запроса пользователя.",
6590
+ hasDataContext ? "" : "Не рассказывай сведения о Йошкар-Оле, школах или детских садах без прямого запроса и контекста данных.",
6571
6591
  hasDataContext ? "Если в контексте нет нужных сведений, прямо напиши, что данных недостаточно." : "",
6572
6592
  hasDataContext ? "Не выдумывай адреса, телефоны, лицензии и руководителей." : "",
6573
6593
  hasDataContext ? "Если отвечаешь по конкретным организациям, укажи источник в конце: слой, название и ИНН." : "",
@@ -6673,6 +6693,9 @@ async function callOllama(config, messages) {
6673
6693
  model: config.model || "llama3.2:1b",
6674
6694
  messages,
6675
6695
  stream: false,
6696
+ options: {
6697
+ temperature: Number(config.temperature ?? 0.1),
6698
+ },
6676
6699
  }),
6677
6700
  });
6678
6701
  } catch {