@iola_adm/iola-cli 0.1.37 → 0.1.38

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 +40 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iola_adm/iola-cli",
3
- "version": "0.1.37",
3
+ "version": "0.1.38",
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
@@ -5,6 +5,7 @@ import { createServer } from "node:http";
5
5
  import { appendFile, copyFile, cp, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
6
6
  import os from "node:os";
7
7
  import path from "node:path";
8
+ import { emitKeypressEvents } from "node:readline";
8
9
  import readline from "node:readline/promises";
9
10
  import { stdin as input, stdout as output } from "node:process";
10
11
  import { DatabaseSync } from "node:sqlite";
@@ -331,13 +332,7 @@ const SLASH_COMMANDS = [
331
332
  ];
332
333
  const BANNER = `\x1b[38;5;45m┌────────────────────────────────────────────────────────────────────────────┐
333
334
  │ │
334
- │\x1b[38;5;51m ____ _ ___ __ ______ ____ _ _ _ __ _ ____ \x1b[38;5;45m│
335
- │\x1b[38;5;51m / ___| | |_ _| \\ \\ / / ___|| _ \\| | | | |/ / / \\ | _ \\ \x1b[38;5;45m│
336
- │\x1b[38;5;51m | | | | | | _____ \\ V /\\___ \\| | | | |_| | ' / / _ \\ | |_) | \x1b[38;5;45m│
337
- │\x1b[38;5;51m | |___| |___ | | |_____| | | ___) | |_| | _ | . \\ / ___ \\| _ < \x1b[38;5;45m│
338
- │\x1b[38;5;51m \\____|_____|___| |_| |____/|____/|_| |_|_|\\_\\/_/ \\_\\_| \\_\\ \x1b[38;5;45m│
339
- │ │
340
- │\x1b[38;5;213m CLI-ЙОШКАР-ОЛА \x1b[38;5;45m│
335
+ │\x1b[38;5;213m CLI-Йошкар-Ола \x1b[38;5;45m│
341
336
  │ │
342
337
  │\x1b[38;5;250m открытые данные • MCP • локальный AI \x1b[38;5;45m│
343
338
  │ │
@@ -645,6 +640,7 @@ async function startAgent() {
645
640
  rl.on("close", () => {
646
641
  closed = true;
647
642
  });
643
+ const detachSlashSuggestions = attachSlashSuggestions(rl);
648
644
  safePrompt(rl);
649
645
 
650
646
  for await (const rawLine of rl) {
@@ -670,6 +666,7 @@ async function startAgent() {
670
666
  if (!closed) {
671
667
  rl.close();
672
668
  }
669
+ detachSlashSuggestions();
673
670
  await runHooks("SessionEnd", { mode: "agent" });
674
671
  }
675
672
 
@@ -1064,10 +1061,10 @@ function printAgentHelp() {
1064
1061
  console.log("Обычный текст без slash-команды отправляется в настроенный AI-провайдер.");
1065
1062
  }
1066
1063
 
1067
- function printSlashMenu(filter = "") {
1064
+ function printSlashMenu(filter = "", options = {}) {
1068
1065
  const normalized = String(filter || "").replace(/^\//, "");
1069
1066
  const rows = getSlashCommandMatches(normalized)
1070
- .slice(0, 30)
1067
+ .slice(0, Number(options.limit || 30))
1071
1068
  .map((item) => ({ command: item.command, description: item.description }));
1072
1069
  if (rows.length === 0) {
1073
1070
  console.log(`Нет slash-команд по фильтру: ${filter}`);
@@ -1076,7 +1073,7 @@ function printSlashMenu(filter = "") {
1076
1073
  }
1077
1074
  console.log(normalized ? `Slash-команды по фильтру "${filter}":` : "Slash-команды:");
1078
1075
  printTable(rows, [["command", "Команда"], ["description", "Описание"]]);
1079
- if (SLASH_COMMANDS.length > rows.length && !normalized) {
1076
+ if (!options.compact && SLASH_COMMANDS.length > rows.length && !normalized) {
1080
1077
  console.log(`Показано ${rows.length} из ${SLASH_COMMANDS.length}. Введите /текст для фильтра.`);
1081
1078
  }
1082
1079
  }
@@ -1126,12 +1123,44 @@ function safePrompt(rl, closed = false) {
1126
1123
  }
1127
1124
 
1128
1125
  try {
1126
+ writePromptBottomPadding();
1129
1127
  rl.prompt();
1130
1128
  } catch {
1131
1129
  // The input stream can close while an async slash-command is still running.
1132
1130
  }
1133
1131
  }
1134
1132
 
1133
+ function attachSlashSuggestions(rl) {
1134
+ if (!input.isTTY) return () => {};
1135
+ emitKeypressEvents(input, rl);
1136
+ let lastFilter = null;
1137
+ const onKeypress = () => {
1138
+ setTimeout(() => {
1139
+ const line = rl.line || "";
1140
+ if (!line.startsWith("/")) {
1141
+ lastFilter = null;
1142
+ return;
1143
+ }
1144
+ const filter = line.slice(1);
1145
+ if (filter === lastFilter) return;
1146
+ lastFilter = filter;
1147
+ output.write("\n");
1148
+ printSlashMenu(filter, { compact: true, limit: 10 });
1149
+ writePromptBottomPadding();
1150
+ rl.prompt(true);
1151
+ }, 0);
1152
+ };
1153
+ input.on("keypress", onKeypress);
1154
+ return () => input.off("keypress", onKeypress);
1155
+ }
1156
+
1157
+ function writePromptBottomPadding() {
1158
+ if (!output.isTTY) return;
1159
+ const padding = Math.max(0, Math.min(5, Number(process.env.IOLA_PROMPT_BOTTOM_PADDING || 2)));
1160
+ if (padding === 0) return;
1161
+ output.write(`${"\n".repeat(padding)}\x1b[${padding}A`);
1162
+ }
1163
+
1135
1164
  async function showBanner(options = {}) {
1136
1165
  const version = getPackageVersion();
1137
1166
  const latest = options.skipUpdate ? null : await getLatestNpmVersion("@iola_adm/iola-cli");
@@ -1146,7 +1175,7 @@ async function showBanner(options = {}) {
1146
1175
  return;
1147
1176
  }
1148
1177
 
1149
- console.log(`CLI-ЙОШКАР-ОЛА ${updateAvailable ? `v${version} -> v${latest}` : `v${version}`}`);
1178
+ console.log(`CLI-Йошкар-Ола ${updateAvailable ? `v${version} -> v${latest}` : `v${version}`}`);
1150
1179
  console.log("открытые данные • MCP • локальный AI");
1151
1180
  if (updateAvailable) console.log("Обновить: npm install -g @iola_adm/iola-cli@latest");
1152
1181
  }