@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.
- package/README.md +23 -0
- package/package.json +1 -1
- 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
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));
|