@iola_adm/iola-cli 0.1.4 → 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 +22 -0
  2. package/package.json +1 -1
  3. package/src/cli.js +149 -0
package/README.md CHANGED
@@ -75,6 +75,7 @@ iola help
75
75
 
76
76
  ```bash
77
77
  iola banner
78
+ iola agent
78
79
  iola ai doctor
79
80
  iola ai setup ollama
80
81
  iola health
@@ -91,6 +92,27 @@ iola setup codex
91
92
  По умолчанию команды выводят компактную таблицу. Для полного ответа API
92
93
  используйте `--json`.
93
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
+
94
116
  ## Назначение
95
117
 
96
118
  Первый релиз CLI дает прямой терминальный доступ к открытым данным и командам
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iola_adm/iola-cli",
3
- "version": "0.1.4",
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
@@ -27,6 +27,7 @@ const COMMANDS = new Map([
27
27
  ["help", showHelp],
28
28
  ["version", showVersion],
29
29
  ["banner", showBanner],
30
+ ["agent", startAgent],
30
31
  ["ai", handleAi],
31
32
  ["health", checkHealth],
32
33
  ["layers", listLayers],
@@ -54,6 +55,7 @@ async function showHelp() {
54
55
 
55
56
  Usage:
56
57
  iola banner
58
+ iola agent
57
59
  iola ai doctor [--json]
58
60
  iola ai setup
59
61
  iola ai setup ollama [--yes] [--model MODEL]
@@ -74,6 +76,119 @@ Environment:
74
76
  `);
75
77
  }
76
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
+
77
192
  function showBanner() {
78
193
  if (process.stdout.isTTY && process.env.NO_COLOR !== "1") {
79
194
  console.log(BANNER);
@@ -386,6 +501,40 @@ function parseOptions(args) {
386
501
  return result;
387
502
  }
388
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
+
389
538
  function filterItems(items, query) {
390
539
  const normalized = query.toLocaleLowerCase("ru-RU");
391
540
  return items.filter((item) => JSON.stringify(item).toLocaleLowerCase("ru-RU").includes(normalized));