leonai-cli 1.0.0
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 +61 -0
- package/cli.js +345 -0
- package/package.json +22 -0
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# ✦ LeonAI CLI
|
|
2
|
+
|
|
3
|
+
AI coding agent for your terminal. Create, edit, debug, and deploy — all from the command line.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g leonai-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
leonai login YOUR_API_KEY
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Get your API key at [leonai.dev](https://leonai.dev)
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
leonai
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Commands
|
|
26
|
+
|
|
27
|
+
| Command | Description |
|
|
28
|
+
|---------|-------------|
|
|
29
|
+
| `leonai` | Start interactive session |
|
|
30
|
+
| `leonai login <key>` | Save API key |
|
|
31
|
+
| `leonai logout` | Remove API key |
|
|
32
|
+
| `leonai config` | Show config |
|
|
33
|
+
|
|
34
|
+
### In-session commands
|
|
35
|
+
|
|
36
|
+
| Command | Description |
|
|
37
|
+
|---------|-------------|
|
|
38
|
+
| `/run <cmd>` | Execute shell command |
|
|
39
|
+
| `/read <file>` | Read file into context |
|
|
40
|
+
| `/edit <file>` | AI edits a file |
|
|
41
|
+
| `/debug <file>` | Find and fix bugs |
|
|
42
|
+
| `/create` | Create a new project |
|
|
43
|
+
| `/tree` | Show directory structure |
|
|
44
|
+
| `/auto` | Toggle auto-execute |
|
|
45
|
+
| `/clear` | Clear conversation |
|
|
46
|
+
| `/quit` | Exit |
|
|
47
|
+
|
|
48
|
+
## Examples
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
❯ buatin REST API express dengan JWT auth
|
|
52
|
+
📄 Created: package.json
|
|
53
|
+
📄 Created: server.js
|
|
54
|
+
📄 Created: routes/auth.js
|
|
55
|
+
$ npm install && node server.js
|
|
56
|
+
✓ Server running on port 3000
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
MIT
|
package/cli.js
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const readline = require('readline');
|
|
3
|
+
const https = require('https');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { execSync, spawnSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
// ===== COLORS =====
|
|
9
|
+
const c = {
|
|
10
|
+
r: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
11
|
+
green: '\x1b[32m', cyan: '\x1b[36m', yellow: '\x1b[33m',
|
|
12
|
+
red: '\x1b[31m', magenta: '\x1b[35m', blue: '\x1b[34m',
|
|
13
|
+
bg: '\x1b[48;5;236m'
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// ===== CONFIG =====
|
|
17
|
+
const API_URL = 'https://inference.do-ai.run/v1/chat/completions';
|
|
18
|
+
const MODEL = 'deepseek-v4-pro';
|
|
19
|
+
const CWD = process.cwd();
|
|
20
|
+
const CONFIG_DIR = path.join(require('os').homedir(), '.leonai');
|
|
21
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
22
|
+
|
|
23
|
+
// Load or create config
|
|
24
|
+
if (!fs.existsSync(CONFIG_DIR)) fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
25
|
+
function loadConfig() { try { return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); } catch(e) { return {}; } }
|
|
26
|
+
function saveConfig(cfg) { fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2)); }
|
|
27
|
+
|
|
28
|
+
let config = loadConfig();
|
|
29
|
+
|
|
30
|
+
// Handle CLI args
|
|
31
|
+
const args = process.argv.slice(2);
|
|
32
|
+
if (args[0] === 'login') {
|
|
33
|
+
const key = args[1];
|
|
34
|
+
if (!key) { console.log('Usage: leonai login <YOUR_API_KEY>'); process.exit(1); }
|
|
35
|
+
config.api_key = key;
|
|
36
|
+
saveConfig(config);
|
|
37
|
+
console.log(`${c.green}✓ API key saved! You're ready to go.${c.r}`);
|
|
38
|
+
console.log(`${c.dim} Run 'leonai' to start chatting.${c.r}`);
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
if (args[0] === 'logout') {
|
|
42
|
+
delete config.api_key;
|
|
43
|
+
saveConfig(config);
|
|
44
|
+
console.log('Logged out. Run "leonai login <key>" to re-authenticate.');
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
if (args[0] === 'config') {
|
|
48
|
+
console.log(`Config: ${CONFIG_FILE}`);
|
|
49
|
+
console.log(`API Key: ${config.api_key ? config.api_key.slice(0, 12) + '...' : '(not set)'}`);
|
|
50
|
+
console.log(`Model: ${MODEL}`);
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const API_KEY = config.api_key || process.env.LEONAI_KEY || '';
|
|
55
|
+
if (!API_KEY) {
|
|
56
|
+
console.log(`\n${c.bold}${c.green} ✦ LeonAI CLI${c.r}\n`);
|
|
57
|
+
console.log(`${c.yellow} You need an API key to use LeonAI.${c.r}\n`);
|
|
58
|
+
console.log(` 1. Get your key at ${c.cyan}https://leonai.dev${c.r}`);
|
|
59
|
+
console.log(` 2. Run: ${c.green}leonai login <YOUR_API_KEY>${c.r}\n`);
|
|
60
|
+
console.log(` Or set env: ${c.dim}export LEONAI_KEY=your_key${c.r}\n`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ===== STATE =====
|
|
65
|
+
const messages = [];
|
|
66
|
+
let autoExec = true;
|
|
67
|
+
|
|
68
|
+
const SYSTEM = `You are LeonAI, a powerful AI coding agent running in the user's terminal at: ${CWD}
|
|
69
|
+
|
|
70
|
+
## CAPABILITIES:
|
|
71
|
+
- Read, create, edit, delete files
|
|
72
|
+
- Run shell commands
|
|
73
|
+
- Debug code
|
|
74
|
+
- Build full projects
|
|
75
|
+
- Analyze codebases
|
|
76
|
+
|
|
77
|
+
## OUTPUT FORMAT:
|
|
78
|
+
When you need to perform actions, use these EXACT markers:
|
|
79
|
+
|
|
80
|
+
To create/write a file:
|
|
81
|
+
<<<FILE:path/to/file>>>
|
|
82
|
+
content here
|
|
83
|
+
<<<END>>>
|
|
84
|
+
|
|
85
|
+
To run a command:
|
|
86
|
+
<<<RUN>>>
|
|
87
|
+
command here
|
|
88
|
+
<<<END>>>
|
|
89
|
+
|
|
90
|
+
To edit a specific part of a file (find and replace):
|
|
91
|
+
<<<EDIT:path/to/file>>>
|
|
92
|
+
<<<OLD>>>
|
|
93
|
+
old content
|
|
94
|
+
<<<NEW>>>
|
|
95
|
+
new content
|
|
96
|
+
<<<END>>>
|
|
97
|
+
|
|
98
|
+
## RULES:
|
|
99
|
+
- ALWAYS use the markers above when creating files or running commands. Never just show code without markers.
|
|
100
|
+
- Be concise. Minimal explanation, maximum action.
|
|
101
|
+
- Match user's language.
|
|
102
|
+
- When user says "buatin/bikin/create" → create the files immediately.
|
|
103
|
+
- When debugging → read the file, find bugs, fix with EDIT markers.
|
|
104
|
+
- You can chain multiple FILE and RUN blocks in one response.
|
|
105
|
+
- Current directory: ${CWD}
|
|
106
|
+
- OS: ${process.platform}
|
|
107
|
+
- Available: node, python3, git, bash`;
|
|
108
|
+
|
|
109
|
+
messages.push({ role: 'system', content: SYSTEM });
|
|
110
|
+
|
|
111
|
+
// ===== UI =====
|
|
112
|
+
function print(text) { process.stdout.write(text); }
|
|
113
|
+
function println(text = '') { console.log(text); }
|
|
114
|
+
function header() {
|
|
115
|
+
println(`\n${c.bold}${c.green} ✦ LeonAI CLI ${c.r}${c.dim}v1.0.0${c.r}`);
|
|
116
|
+
println(`${c.dim} Working in: ${CWD}${c.r}`);
|
|
117
|
+
println(`${c.dim} Commands: /help /run /read /clear /auto /quit${c.r}\n`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function showHelp() {
|
|
121
|
+
println(`${c.bold}Commands:${c.r}`);
|
|
122
|
+
println(` ${c.cyan}/run <cmd>${c.r} Run a shell command`);
|
|
123
|
+
println(` ${c.cyan}/read <file>${c.r} Read file and add to context`);
|
|
124
|
+
println(` ${c.cyan}/edit <file>${c.r} Ask AI to edit a file`);
|
|
125
|
+
println(` ${c.cyan}/debug <file>${c.r} Debug a file`);
|
|
126
|
+
println(` ${c.cyan}/create${c.r} Ask AI to create a project`);
|
|
127
|
+
println(` ${c.cyan}/tree${c.r} Show directory structure`);
|
|
128
|
+
println(` ${c.cyan}/auto${c.r} Toggle auto-execute (${autoExec ? 'ON' : 'OFF'})`);
|
|
129
|
+
println(` ${c.cyan}/clear${c.r} Clear conversation`);
|
|
130
|
+
println(` ${c.cyan}/quit${c.r} Exit`);
|
|
131
|
+
println();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function tree(dir, prefix = '', depth = 0) {
|
|
135
|
+
if (depth > 3) return '';
|
|
136
|
+
let out = '';
|
|
137
|
+
const items = fs.readdirSync(dir).filter(n => !n.startsWith('.') && n !== 'node_modules' && n !== '__pycache__');
|
|
138
|
+
items.forEach((item, i) => {
|
|
139
|
+
const fp = path.join(dir, item);
|
|
140
|
+
const isLast = i === items.length - 1;
|
|
141
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
142
|
+
const stat = fs.statSync(fp);
|
|
143
|
+
if (stat.isDirectory()) {
|
|
144
|
+
out += `${prefix}${connector}${c.cyan}${item}/${c.r}\n`;
|
|
145
|
+
out += tree(fp, prefix + (isLast ? ' ' : '│ '), depth + 1);
|
|
146
|
+
} else {
|
|
147
|
+
out += `${prefix}${connector}${item}\n`;
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ===== EXECUTION ENGINE =====
|
|
154
|
+
function executeResponse(text) {
|
|
155
|
+
let actions = 0;
|
|
156
|
+
|
|
157
|
+
// FILE blocks
|
|
158
|
+
const fileRegex = /<<<FILE:(.+?)>>>\n([\s\S]*?)<<<END>>>/g;
|
|
159
|
+
let m;
|
|
160
|
+
while ((m = fileRegex.exec(text)) !== null) {
|
|
161
|
+
const fp = m[1].trim();
|
|
162
|
+
const content = m[2];
|
|
163
|
+
const dir = path.dirname(fp);
|
|
164
|
+
if (dir !== '.') fs.mkdirSync(dir, { recursive: true });
|
|
165
|
+
fs.writeFileSync(fp, content);
|
|
166
|
+
println(` ${c.yellow}📄 Created: ${fp}${c.r}`);
|
|
167
|
+
actions++;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// EDIT blocks
|
|
171
|
+
const editRegex = /<<<EDIT:(.+?)>>>\n<<<OLD>>>\n([\s\S]*?)<<<NEW>>>\n([\s\S]*?)<<<END>>>/g;
|
|
172
|
+
while ((m = editRegex.exec(text)) !== null) {
|
|
173
|
+
const fp = m[1].trim();
|
|
174
|
+
const old = m[2].trimEnd();
|
|
175
|
+
const newContent = m[3].trimEnd();
|
|
176
|
+
if (fs.existsSync(fp)) {
|
|
177
|
+
let file = fs.readFileSync(fp, 'utf8');
|
|
178
|
+
if (file.includes(old)) {
|
|
179
|
+
file = file.replace(old, newContent);
|
|
180
|
+
fs.writeFileSync(fp, file);
|
|
181
|
+
println(` ${c.yellow}✏️ Edited: ${fp}${c.r}`);
|
|
182
|
+
actions++;
|
|
183
|
+
} else {
|
|
184
|
+
println(` ${c.red}⚠️ Could not find text to replace in ${fp}${c.r}`);
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
println(` ${c.red}⚠️ File not found: ${fp}${c.r}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// RUN blocks
|
|
192
|
+
const runRegex = /<<<RUN>>>\n([\s\S]*?)<<<END>>>/g;
|
|
193
|
+
while ((m = runRegex.exec(text)) !== null) {
|
|
194
|
+
const cmd = m[1].trim();
|
|
195
|
+
println(` ${c.dim}$ ${cmd}${c.r}`);
|
|
196
|
+
try {
|
|
197
|
+
const out = execSync(cmd, { encoding: 'utf8', timeout: 30000, cwd: CWD, maxBuffer: 2 * 1024 * 1024 });
|
|
198
|
+
if (out.trim()) println(` ${c.green}${out.trim()}${c.r}`);
|
|
199
|
+
actions++;
|
|
200
|
+
} catch (e) {
|
|
201
|
+
println(` ${c.red}${(e.stderr || e.stdout || e.message).trim()}${c.r}`);
|
|
202
|
+
actions++;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (actions > 0) println();
|
|
207
|
+
return actions;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ===== API =====
|
|
211
|
+
function callAPI(msgs) {
|
|
212
|
+
return new Promise((resolve, reject) => {
|
|
213
|
+
const data = JSON.stringify({ model: MODEL, messages: msgs, max_tokens: 4096 });
|
|
214
|
+
const url = new URL(API_URL);
|
|
215
|
+
const req = https.request({
|
|
216
|
+
hostname: url.hostname, path: url.pathname, method: 'POST',
|
|
217
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + API_KEY, 'Content-Length': Buffer.byteLength(data) }
|
|
218
|
+
}, res => {
|
|
219
|
+
let body = '';
|
|
220
|
+
res.on('data', c => body += c);
|
|
221
|
+
res.on('end', () => {
|
|
222
|
+
try {
|
|
223
|
+
const json = JSON.parse(body);
|
|
224
|
+
if (json.error) return reject(json.error.message || JSON.stringify(json.error));
|
|
225
|
+
const msg = json.choices?.[0]?.message;
|
|
226
|
+
resolve(msg?.content || msg?.reasoning_content || 'No response');
|
|
227
|
+
} catch (e) { reject(body); }
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
req.on('error', reject);
|
|
231
|
+
req.write(data);
|
|
232
|
+
req.end();
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ===== CHAT =====
|
|
237
|
+
async function chat(input) {
|
|
238
|
+
messages.push({ role: 'user', content: input });
|
|
239
|
+
print(`\n${c.green}✦ LeonAI${c.r} ${c.dim}thinking...${c.r}`);
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
const reply = await callAPI(messages);
|
|
243
|
+
// Clear "thinking..." line
|
|
244
|
+
if (process.stdout.isTTY) { process.stdout.clearLine(0); process.stdout.cursorTo(0); }
|
|
245
|
+
else println();
|
|
246
|
+
|
|
247
|
+
// Print reply (strip action markers for display)
|
|
248
|
+
const display = reply
|
|
249
|
+
.replace(/<<<FILE:.+?>>>\n[\s\S]*?<<<END>>>/g, '')
|
|
250
|
+
.replace(/<<<EDIT:.+?>>>\n[\s\S]*?<<<END>>>/g, '')
|
|
251
|
+
.replace(/<<<RUN>>>\n[\s\S]*?<<<END>>>/g, '')
|
|
252
|
+
.trim();
|
|
253
|
+
|
|
254
|
+
if (display) println(`${c.green}✦ LeonAI:${c.r} ${display}\n`);
|
|
255
|
+
|
|
256
|
+
messages.push({ role: 'assistant', content: reply });
|
|
257
|
+
|
|
258
|
+
// Execute actions
|
|
259
|
+
if (autoExec) {
|
|
260
|
+
executeResponse(reply);
|
|
261
|
+
} else {
|
|
262
|
+
const hasActions = /<<<(FILE|RUN|EDIT)/.test(reply);
|
|
263
|
+
if (hasActions) {
|
|
264
|
+
println(`${c.yellow} Actions detected. Run /exec to execute.${c.r}\n`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
} catch (e) {
|
|
268
|
+
if (process.stdout.isTTY) { process.stdout.clearLine?.(0); process.stdout.cursorTo?.(0); }
|
|
269
|
+
println(`${c.red}Error: ${e}${c.r}\n`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ===== MAIN LOOP =====
|
|
274
|
+
header();
|
|
275
|
+
|
|
276
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
277
|
+
|
|
278
|
+
function prompt() {
|
|
279
|
+
rl.question(`${c.cyan}❯ ${c.r}`, async (input) => {
|
|
280
|
+
input = input.trim();
|
|
281
|
+
if (!input) return prompt();
|
|
282
|
+
|
|
283
|
+
// Commands
|
|
284
|
+
if (input === '/quit' || input === '/exit' || input === '/q') { println('👋 Bye!'); process.exit(0); }
|
|
285
|
+
if (input === '/clear') { messages.length = 1; println(`${c.dim}Conversation cleared.${c.r}\n`); return prompt(); }
|
|
286
|
+
if (input === '/help' || input === '/?') { showHelp(); return prompt(); }
|
|
287
|
+
if (input === '/auto') { autoExec = !autoExec; println(`${c.dim}Auto-execute: ${autoExec ? 'ON' : 'OFF'}${c.r}\n`); return prompt(); }
|
|
288
|
+
if (input === '/tree') { println(tree(CWD)); return prompt(); }
|
|
289
|
+
if (input === '/exec') {
|
|
290
|
+
const last = messages[messages.length - 1];
|
|
291
|
+
if (last?.role === 'assistant') executeResponse(last.content);
|
|
292
|
+
return prompt();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (input.startsWith('/run ')) {
|
|
296
|
+
const cmd = input.slice(5);
|
|
297
|
+
println(`${c.dim}$ ${cmd}${c.r}`);
|
|
298
|
+
try { const out = execSync(cmd, { encoding: 'utf8', timeout: 30000, cwd: CWD }); if (out.trim()) println(out.trim()); } catch (e) { println(`${c.red}${e.stderr || e.message}${c.r}`); }
|
|
299
|
+
println();
|
|
300
|
+
return prompt();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (input.startsWith('/read ')) {
|
|
304
|
+
const fp = input.slice(6);
|
|
305
|
+
if (fs.existsSync(fp)) {
|
|
306
|
+
const content = fs.readFileSync(fp, 'utf8');
|
|
307
|
+
println(`${c.dim}Read ${fp} (${content.length} chars)${c.r}\n`);
|
|
308
|
+
messages.push({ role: 'user', content: `[File: ${fp}]\n${content}` });
|
|
309
|
+
} else { println(`${c.red}File not found: ${fp}${c.r}\n`); }
|
|
310
|
+
return prompt();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (input.startsWith('/edit ')) {
|
|
314
|
+
const fp = input.slice(6);
|
|
315
|
+
if (fs.existsSync(fp)) {
|
|
316
|
+
const content = fs.readFileSync(fp, 'utf8');
|
|
317
|
+
await chat(`Edit this file (${fp}):\n\`\`\`\n${content}\n\`\`\`\nWhat should I change?`);
|
|
318
|
+
} else { println(`${c.red}File not found: ${fp}${c.r}\n`); }
|
|
319
|
+
return prompt();
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (input.startsWith('/debug ')) {
|
|
323
|
+
const fp = input.slice(7);
|
|
324
|
+
if (fs.existsSync(fp)) {
|
|
325
|
+
const content = fs.readFileSync(fp, 'utf8');
|
|
326
|
+
await chat(`Debug this file (${fp}). Find all bugs and fix them:\n\`\`\`\n${content}\n\`\`\``);
|
|
327
|
+
} else { println(`${c.red}File not found: ${fp}${c.r}\n`); }
|
|
328
|
+
return prompt();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (input === '/create') {
|
|
332
|
+
rl.question(`${c.dim}What to create? ${c.r}`, async (desc) => {
|
|
333
|
+
if (desc.trim()) await chat(`Create this project: ${desc}`);
|
|
334
|
+
prompt();
|
|
335
|
+
});
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Regular chat
|
|
340
|
+
await chat(input);
|
|
341
|
+
prompt();
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
prompt();
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "leonai-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI coding agent for your terminal — create, edit, debug, and deploy from the command line",
|
|
5
|
+
"bin": {
|
|
6
|
+
"leonai": "./cli.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"cli.js"
|
|
10
|
+
],
|
|
11
|
+
"keywords": ["ai", "cli", "coding", "agent", "terminal", "gpt", "copilot", "developer-tools"],
|
|
12
|
+
"author": "LeonAI",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=16.0.0"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/leonai/cli"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://leonai.dev"
|
|
22
|
+
}
|