@iola_adm/iola-cli 0.1.3 → 0.1.5

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 (3) hide show
  1. package/README.md +23 -0
  2. package/package.json +1 -1
  3. package/src/cli.js +177 -0
package/README.md CHANGED
@@ -74,6 +74,8 @@ iola help
74
74
  ## Команды
75
75
 
76
76
  ```bash
77
+ iola banner
78
+ iola agent
77
79
  iola ai doctor
78
80
  iola ai setup ollama
79
81
  iola health
@@ -90,6 +92,27 @@ iola setup codex
90
92
  По умолчанию команды выводят компактную таблицу. Для полного ответа API
91
93
  используйте `--json`.
92
94
 
95
+ ## Интерактивный режим
96
+
97
+ ```bash
98
+ iola agent
99
+ ```
100
+
101
+ Внутри agent доступны slash-команды:
102
+
103
+ ```text
104
+ /help
105
+ /health
106
+ /layers
107
+ /schools --limit 10
108
+ /schools get --inn 1215067180
109
+ /kindergartens --search 29
110
+ /search лицей --limit 3
111
+ /mcp-info
112
+ /ai doctor
113
+ /exit
114
+ ```
115
+
93
116
  ## Назначение
94
117
 
95
118
  Первый релиз CLI дает прямой терминальный доступ к открытым данным и командам
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iola_adm/iola-cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
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
@@ -9,10 +9,25 @@ const API_BASE_URL = process.env.IOLA_API_BASE_URL || "https://apiiola.yasg.ru/a
9
9
  const MCP_BASE_URL = process.env.IOLA_MCP_BASE_URL || "https://apiiola.yasg.ru";
10
10
  const CONFIG_DIR = path.join(os.homedir(), ".iola");
11
11
  const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
12
+ const BANNER = `\x1b[38;5;45m┌────────────────────────────────────────────────────────────────────────────┐
13
+ │\x1b[38;5;51m ____ _ ___ ____ ____ ___ _____ _ _______ \x1b[38;5;45m│
14
+ │\x1b[38;5;51m / ___| | |_ _| | _ \\| _ \\ / _ \\| ____| |/ /_ _| \x1b[38;5;45m│
15
+ │\x1b[38;5;51m | | | | | |_____| |_) | |_) | | | | _| | ' / | | \x1b[38;5;45m│
16
+ │\x1b[38;5;51m | |___| |___ | |_____| __/| _ <| |_| | |___| . \\ | | \x1b[38;5;45m│
17
+ │\x1b[38;5;51m \\____|_____|___| |_| |_| \\_\\\\___/|_____|_|\\_\\ |_| \x1b[38;5;45m│
18
+ │ │
19
+ │\x1b[38;5;213m Й О Ш К А Р - О Л Ы \x1b[38;5;45m│
20
+ │ │
21
+ │\x1b[38;5;250m открытые данные • MCP • локальный AI \x1b[38;5;45m│
22
+ │ │
23
+ │\x1b[38;5;82m > iola help \x1b[38;5;45m│
24
+ └────────────────────────────────────────────────────────────────────────────┘\x1b[0m`;
12
25
 
13
26
  const COMMANDS = new Map([
14
27
  ["help", showHelp],
15
28
  ["version", showVersion],
29
+ ["banner", showBanner],
30
+ ["agent", startAgent],
16
31
  ["ai", handleAi],
17
32
  ["health", checkHealth],
18
33
  ["layers", listLayers],
@@ -35,9 +50,12 @@ export async function main(argv) {
35
50
  }
36
51
 
37
52
  async function showHelp() {
53
+ showBanner();
38
54
  console.log(`iola - CLI для открытых данных городского округа "Город Йошкар-Ола"
39
55
 
40
56
  Usage:
57
+ iola banner
58
+ iola agent
41
59
  iola ai doctor [--json]
42
60
  iola ai setup
43
61
  iola ai setup ollama [--yes] [--model MODEL]
@@ -58,6 +76,129 @@ Environment:
58
76
  `);
59
77
  }
60
78
 
79
+ async function startAgent() {
80
+ showBanner();
81
+ console.log("Интерактивный режим. Введите /help для списка команд, /exit для выхода.");
82
+
83
+ const rl = readline.createInterface({ input, output, prompt: "iola> " });
84
+ let closed = false;
85
+ rl.on("close", () => {
86
+ closed = true;
87
+ });
88
+ safePrompt(rl);
89
+
90
+ for await (const rawLine of rl) {
91
+ const line = rawLine.trim();
92
+
93
+ if (!line) {
94
+ safePrompt(rl, closed);
95
+ continue;
96
+ }
97
+
98
+ try {
99
+ const shouldExit = await handleAgentLine(line);
100
+ if (shouldExit) {
101
+ break;
102
+ }
103
+ } catch (error) {
104
+ console.error(error instanceof Error ? error.message : String(error));
105
+ }
106
+
107
+ safePrompt(rl, closed);
108
+ }
109
+
110
+ if (!closed) {
111
+ rl.close();
112
+ }
113
+ }
114
+
115
+ async function handleAgentLine(line) {
116
+ if (!line.startsWith("/")) {
117
+ console.log("AI-ответы будут добавлены следующим этапом. Сейчас используйте slash-команды, например /search лицей.");
118
+ return false;
119
+ }
120
+
121
+ const [command, ...args] = splitCommandLine(line.slice(1));
122
+
123
+ if (command === "exit" || command === "quit") {
124
+ return true;
125
+ }
126
+
127
+ if (command === "help") {
128
+ printAgentHelp();
129
+ return false;
130
+ }
131
+
132
+ if (command === "banner") {
133
+ showBanner();
134
+ return false;
135
+ }
136
+
137
+ if (command === "ai") {
138
+ await handleAi(args);
139
+ return false;
140
+ }
141
+
142
+ const mapped = {
143
+ health: ["health", args],
144
+ layers: ["layers", args],
145
+ schools: ["schools", args],
146
+ kindergartens: ["kindergartens", args],
147
+ search: ["search", args],
148
+ "mcp-info": ["mcp-info", args],
149
+ setup: ["setup", args],
150
+ }[command];
151
+
152
+ if (!mapped) {
153
+ console.log(`Неизвестная slash-команда: /${command}`);
154
+ printAgentHelp();
155
+ return false;
156
+ }
157
+
158
+ const [cliCommand, cliArgs] = mapped;
159
+ await COMMANDS.get(cliCommand)(cliArgs);
160
+ return false;
161
+ }
162
+
163
+ function printAgentHelp() {
164
+ console.log(`Slash-команды:
165
+ /help
166
+ /health
167
+ /layers
168
+ /schools --limit 10
169
+ /schools get --inn 1215067180
170
+ /kindergartens --search 29
171
+ /kindergartens get --inn 1215077421
172
+ /search лицей --limit 3
173
+ /mcp-info
174
+ /ai doctor
175
+ /ai setup ollama
176
+ /banner
177
+ /exit`);
178
+ }
179
+
180
+ function safePrompt(rl, closed = false) {
181
+ if (closed) {
182
+ return;
183
+ }
184
+
185
+ try {
186
+ rl.prompt();
187
+ } catch {
188
+ // The input stream can close while an async slash-command is still running.
189
+ }
190
+ }
191
+
192
+ function showBanner() {
193
+ if (process.stdout.isTTY && process.env.NO_COLOR !== "1") {
194
+ console.log(BANNER);
195
+ return;
196
+ }
197
+
198
+ console.log("CLI-ПРОЕКТ ЙОШКАР-ОЛЫ");
199
+ console.log("открытые данные • MCP • локальный AI");
200
+ }
201
+
61
202
  async function showVersion() {
62
203
  const packageJson = await import("../package.json", { with: { type: "json" } });
63
204
  console.log(packageJson.default.version);
@@ -84,6 +225,7 @@ async function handleAi(args) {
84
225
  const [subcommand = "help", ...rest] = args;
85
226
 
86
227
  if (subcommand === "help") {
228
+ showBanner();
87
229
  console.log(`AI-команды:
88
230
  iola ai doctor [--json]
89
231
  iola ai setup
@@ -123,6 +265,7 @@ async function aiSetup(args) {
123
265
  const [provider] = args;
124
266
 
125
267
  if (!provider) {
268
+ showBanner();
126
269
  const selected = await chooseAiProvider();
127
270
  await aiSetup([selected]);
128
271
  return;
@@ -358,6 +501,40 @@ function parseOptions(args) {
358
501
  return result;
359
502
  }
360
503
 
504
+ function splitCommandLine(line) {
505
+ const result = [];
506
+ let current = "";
507
+ let quote = null;
508
+
509
+ for (const char of line) {
510
+ if ((char === "\"" || char === "'") && quote === null) {
511
+ quote = char;
512
+ continue;
513
+ }
514
+
515
+ if (char === quote) {
516
+ quote = null;
517
+ continue;
518
+ }
519
+
520
+ if (/\s/.test(char) && quote === null) {
521
+ if (current) {
522
+ result.push(current);
523
+ current = "";
524
+ }
525
+ continue;
526
+ }
527
+
528
+ current += char;
529
+ }
530
+
531
+ if (current) {
532
+ result.push(current);
533
+ }
534
+
535
+ return result;
536
+ }
537
+
361
538
  function filterItems(items, query) {
362
539
  const normalized = query.toLocaleLowerCase("ru-RU");
363
540
  return items.filter((item) => JSON.stringify(item).toLocaleLowerCase("ru-RU").includes(normalized));