navada-edge-cli 4.1.0 → 4.2.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/lib/agent.js +9 -0
- package/lib/commands/index.js +1 -1
- package/lib/commands/setup.js +34 -3
- package/lib/commands/skills.js +209 -0
- package/lib/commands/system.js +65 -0
- package/lib/skills.js +222 -0
- package/package.json +1 -1
package/lib/agent.js
CHANGED
|
@@ -175,6 +175,15 @@ function getSystemPrompt() {
|
|
|
175
175
|
prompt += `\n\n--- MEMORY (auto-loaded) ---\n${memoryContext}`;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
// Inject user skills (so agent knows what skills are available)
|
|
179
|
+
try {
|
|
180
|
+
const skills = require('./skills');
|
|
181
|
+
const skillsPrompt = skills.getSkillsPrompt();
|
|
182
|
+
if (skillsPrompt) {
|
|
183
|
+
prompt += `\n\n--- USER SKILLS ---\n${skillsPrompt}`;
|
|
184
|
+
}
|
|
185
|
+
} catch {}
|
|
186
|
+
|
|
178
187
|
return prompt;
|
|
179
188
|
}
|
|
180
189
|
|
package/lib/commands/index.js
CHANGED
|
@@ -6,7 +6,7 @@ const { register } = require('../registry');
|
|
|
6
6
|
const moduleNames = [
|
|
7
7
|
'network', 'mcp', 'lucas', 'docker', 'database', 'cloudflare',
|
|
8
8
|
'ai', 'azure', 'agents', 'tasks', 'keys', 'setup', 'system',
|
|
9
|
-
'learn', 'sandbox', 'nvidia', 'edge', 'conversations', 'audit', 'compute',
|
|
9
|
+
'learn', 'sandbox', 'nvidia', 'edge', 'conversations', 'audit', 'compute', 'skills',
|
|
10
10
|
];
|
|
11
11
|
|
|
12
12
|
function loadAll() {
|
package/lib/commands/setup.js
CHANGED
|
@@ -211,7 +211,32 @@ async function runSetup(fromRepl = false) {
|
|
|
211
211
|
const reqDir = path.join(config.CONFIG_DIR, 'requests');
|
|
212
212
|
if (!fs.existsSync(reqDir)) fs.mkdirSync(reqDir, { recursive: true });
|
|
213
213
|
|
|
214
|
-
// Step 8:
|
|
214
|
+
// Step 8: Skills directory + install default skill
|
|
215
|
+
const skillsDir = path.join(config.CONFIG_DIR, 'skills');
|
|
216
|
+
if (!fs.existsSync(skillsDir)) fs.mkdirSync(skillsDir, { recursive: true });
|
|
217
|
+
|
|
218
|
+
// Offer to install template skills
|
|
219
|
+
const installSkills = await ask(rl, ' Install starter skills? (Y/n): ');
|
|
220
|
+
if (installSkills.trim().toLowerCase() !== 'n') {
|
|
221
|
+
const skills = require('../skills');
|
|
222
|
+
let installed = 0;
|
|
223
|
+
for (const [name, tmpl] of Object.entries(skills.TEMPLATES)) {
|
|
224
|
+
const skillFile = path.join(skillsDir, `${name}.md`);
|
|
225
|
+
if (!fs.existsSync(skillFile)) {
|
|
226
|
+
skills.createSkill(name, tmpl);
|
|
227
|
+
installed++;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (installed > 0) {
|
|
231
|
+
console.log(ui.success(`Installed ${installed} starter skills`));
|
|
232
|
+
console.log(ui.dim(' View: /skill list | Create your own: /skill create'));
|
|
233
|
+
} else {
|
|
234
|
+
console.log(ui.dim(' All template skills already installed'));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
console.log('');
|
|
238
|
+
|
|
239
|
+
// Step 9: Theme
|
|
215
240
|
const theme = await ask(rl, ' Theme (dark/crow/matrix/light) [dark]: ');
|
|
216
241
|
config.setTheme(theme.trim() || 'dark');
|
|
217
242
|
console.log('');
|
|
@@ -222,8 +247,14 @@ async function runSetup(fromRepl = false) {
|
|
|
222
247
|
Guard: ${guardrailPath}
|
|
223
248
|
|
|
224
249
|
Type naturally to chat, or /help for commands.
|
|
225
|
-
|
|
226
|
-
|
|
250
|
+
|
|
251
|
+
KEY COMMANDS
|
|
252
|
+
/tools — 16 agent tools across 7 categories
|
|
253
|
+
/skills — view and manage skills
|
|
254
|
+
/skill create — create your own reusable skills
|
|
255
|
+
/skill templates — install ready-made skills
|
|
256
|
+
/automate — submit automation requests
|
|
257
|
+
/about — learn about NAVADA Edge`));
|
|
227
258
|
console.log('');
|
|
228
259
|
|
|
229
260
|
rl.close();
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const readline = require('readline');
|
|
6
|
+
const ui = require('../ui');
|
|
7
|
+
const config = require('../config');
|
|
8
|
+
const { style } = require('../theme');
|
|
9
|
+
const skills = require('../skills');
|
|
10
|
+
|
|
11
|
+
module.exports = function(reg) {
|
|
12
|
+
|
|
13
|
+
reg('skill', 'Manage agent skills — create, list, use, delete', async (args) => {
|
|
14
|
+
const sub = args[0];
|
|
15
|
+
|
|
16
|
+
if (!sub || sub === 'help') {
|
|
17
|
+
console.log(ui.header('NAVADA EDGE — SKILLS'));
|
|
18
|
+
console.log(ui.dim(' Skills are reusable task templates the agent follows.'));
|
|
19
|
+
console.log(ui.dim(' Create your own or install from templates.'));
|
|
20
|
+
console.log('');
|
|
21
|
+
console.log(ui.cmd('skill list', 'Show all installed skills'));
|
|
22
|
+
console.log(ui.cmd('skill create', 'Create a new skill interactively'));
|
|
23
|
+
console.log(ui.cmd('skill create <name>', 'Create from a template'));
|
|
24
|
+
console.log(ui.cmd('skill show <name>', 'View skill details'));
|
|
25
|
+
console.log(ui.cmd('skill use <name> [args]', 'Run a skill'));
|
|
26
|
+
console.log(ui.cmd('skill templates', 'Show available templates'));
|
|
27
|
+
console.log(ui.cmd('skill install <template>', 'Install a template skill'));
|
|
28
|
+
console.log(ui.cmd('skill delete <name>', 'Delete a skill'));
|
|
29
|
+
console.log(ui.cmd('skill edit <name>', 'Open skill in editor'));
|
|
30
|
+
console.log('');
|
|
31
|
+
console.log(ui.dim('Skills live in: ~/.navada/skills/<name>.md'));
|
|
32
|
+
console.log(ui.dim('The agent auto-detects skills from your conversation.'));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// /skill list
|
|
37
|
+
if (sub === 'list') {
|
|
38
|
+
const all = skills.loadAll();
|
|
39
|
+
console.log(ui.header('INSTALLED SKILLS'));
|
|
40
|
+
if (all.length === 0) {
|
|
41
|
+
console.log(ui.dim('No skills installed yet.'));
|
|
42
|
+
console.log(ui.dim('Get started: /skill templates or /skill create'));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
for (const s of all) {
|
|
46
|
+
const triggers = s.trigger.length > 0 ? style('dim', ` [${s.trigger.slice(0, 3).join(', ')}]`) : '';
|
|
47
|
+
console.log(` ${style('accent', s.title.padEnd(24))} ${style('dim', s.description.slice(0, 50))}${triggers}`);
|
|
48
|
+
}
|
|
49
|
+
console.log('');
|
|
50
|
+
console.log(ui.dim(`${all.length} skill(s) installed. Use: /skill use <name>`));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// /skill templates
|
|
55
|
+
if (sub === 'templates') {
|
|
56
|
+
console.log(ui.header('SKILL TEMPLATES'));
|
|
57
|
+
console.log(ui.dim(' Ready-made skills you can install in one command.'));
|
|
58
|
+
console.log('');
|
|
59
|
+
for (const [key, tmpl] of Object.entries(skills.TEMPLATES)) {
|
|
60
|
+
console.log(` ${style('accent', key.padEnd(20))} ${style('dim', tmpl.description)}`);
|
|
61
|
+
}
|
|
62
|
+
console.log('');
|
|
63
|
+
console.log(ui.dim('Install: /skill install seo-audit'));
|
|
64
|
+
console.log(ui.dim('Or create your own: /skill create'));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// /skill install <template>
|
|
69
|
+
if (sub === 'install') {
|
|
70
|
+
const name = args[1];
|
|
71
|
+
if (!name) { console.log(ui.error('Usage: /skill install <template-name>')); return; }
|
|
72
|
+
const tmpl = skills.TEMPLATES[name];
|
|
73
|
+
if (!tmpl) {
|
|
74
|
+
console.log(ui.error(`Template not found: ${name}`));
|
|
75
|
+
console.log(ui.dim('Available: ' + Object.keys(skills.TEMPLATES).join(', ')));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const filePath = skills.createSkill(name, tmpl);
|
|
79
|
+
console.log(ui.success(`Skill installed: ${tmpl.title}`));
|
|
80
|
+
console.log(ui.label('File', filePath));
|
|
81
|
+
console.log(ui.label('Triggers', tmpl.triggers.join(', ')));
|
|
82
|
+
console.log(ui.dim('Use it: /skill use ' + name));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// /skill show <name>
|
|
87
|
+
if (sub === 'show') {
|
|
88
|
+
const name = args.slice(1).join(' ');
|
|
89
|
+
if (!name) { console.log(ui.error('Usage: /skill show <name>')); return; }
|
|
90
|
+
const s = skills.getSkill(name);
|
|
91
|
+
if (!s) { console.log(ui.error(`Skill not found: ${name}`)); return; }
|
|
92
|
+
console.log(ui.header(`SKILL: ${s.title}`));
|
|
93
|
+
console.log(ui.label('Description', s.description));
|
|
94
|
+
console.log(ui.label('Triggers', s.trigger.join(', ') || 'none'));
|
|
95
|
+
console.log(ui.label('File', s.file));
|
|
96
|
+
console.log('');
|
|
97
|
+
if (s.steps) {
|
|
98
|
+
console.log(ui.dim(' Steps:'));
|
|
99
|
+
console.log(s.steps.split('\n').map(l => ' ' + l).join('\n'));
|
|
100
|
+
}
|
|
101
|
+
if (s.output) {
|
|
102
|
+
console.log('');
|
|
103
|
+
console.log(ui.dim(' Output:'));
|
|
104
|
+
console.log(' ' + s.output);
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// /skill use <name> [context]
|
|
110
|
+
if (sub === 'use') {
|
|
111
|
+
const name = args[1];
|
|
112
|
+
if (!name) { console.log(ui.error('Usage: /skill use <name> [context]')); return; }
|
|
113
|
+
const s = skills.getSkill(name);
|
|
114
|
+
if (!s) { console.log(ui.error(`Skill not found: ${name}`)); return; }
|
|
115
|
+
|
|
116
|
+
const context = args.slice(2).join(' ');
|
|
117
|
+
const prompt = `Execute this skill:\n\n${s.raw}\n\n${context ? `User context: ${context}` : 'Run the skill now.'}`;
|
|
118
|
+
|
|
119
|
+
// Route to chat
|
|
120
|
+
try {
|
|
121
|
+
const { chat, addToHistory } = require('../agent');
|
|
122
|
+
addToHistory('user', prompt);
|
|
123
|
+
console.log(ui.dim(` Running skill: ${s.title}...`));
|
|
124
|
+
console.log('');
|
|
125
|
+
const response = await chat(prompt);
|
|
126
|
+
if (response) addToHistory('assistant', response);
|
|
127
|
+
} catch (e) {
|
|
128
|
+
console.log(ui.error(`Skill execution failed: ${e.message}`));
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// /skill create (interactive)
|
|
134
|
+
if (sub === 'create') {
|
|
135
|
+
// If a template name is given, install it
|
|
136
|
+
if (args[1] && skills.TEMPLATES[args[1]]) {
|
|
137
|
+
const tmpl = skills.TEMPLATES[args[1]];
|
|
138
|
+
const filePath = skills.createSkill(args[1], tmpl);
|
|
139
|
+
console.log(ui.success(`Skill created from template: ${tmpl.title}`));
|
|
140
|
+
console.log(ui.label('File', filePath));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
145
|
+
const ask = (q) => new Promise(resolve => rl.question(q, resolve));
|
|
146
|
+
|
|
147
|
+
console.log(ui.header('CREATE A SKILL'));
|
|
148
|
+
console.log(ui.dim(' A skill is a reusable task the agent can follow.'));
|
|
149
|
+
console.log('');
|
|
150
|
+
|
|
151
|
+
const title = await ask(' Skill name: ');
|
|
152
|
+
if (!title.trim()) { console.log(ui.error('Name is required.')); rl.close(); return; }
|
|
153
|
+
|
|
154
|
+
const description = await ask(' Description: ');
|
|
155
|
+
const triggersRaw = await ask(' Trigger phrases (comma-separated): ');
|
|
156
|
+
const steps = await ask(' Steps (or press Enter for default): ');
|
|
157
|
+
const output = await ask(' Expected output: ');
|
|
158
|
+
|
|
159
|
+
const data = {
|
|
160
|
+
title: title.trim(),
|
|
161
|
+
description: description.trim() || title.trim(),
|
|
162
|
+
triggers: triggersRaw.split(',').map(t => t.trim().toLowerCase()).filter(Boolean),
|
|
163
|
+
steps: steps.trim() || undefined,
|
|
164
|
+
output: output.trim() || undefined,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const filePath = skills.createSkill(title.trim(), data);
|
|
168
|
+
console.log('');
|
|
169
|
+
console.log(ui.success(`Skill created: ${data.title}`));
|
|
170
|
+
console.log(ui.label('File', filePath));
|
|
171
|
+
console.log(ui.dim('Edit it: /skill edit ' + path.basename(filePath, '.md')));
|
|
172
|
+
console.log(ui.dim('Use it: /skill use ' + path.basename(filePath, '.md')));
|
|
173
|
+
rl.close();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// /skill delete <name>
|
|
178
|
+
if (sub === 'delete' || sub === 'rm') {
|
|
179
|
+
const name = args.slice(1).join(' ');
|
|
180
|
+
if (!name) { console.log(ui.error('Usage: /skill delete <name>')); return; }
|
|
181
|
+
if (skills.deleteSkill(name)) {
|
|
182
|
+
console.log(ui.success(`Deleted skill: ${name}`));
|
|
183
|
+
} else {
|
|
184
|
+
console.log(ui.error(`Skill not found: ${name}`));
|
|
185
|
+
}
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// /skill edit <name>
|
|
190
|
+
if (sub === 'edit') {
|
|
191
|
+
const name = args.slice(1).join(' ');
|
|
192
|
+
if (!name) { console.log(ui.error('Usage: /skill edit <name>')); return; }
|
|
193
|
+
const s = skills.getSkill(name);
|
|
194
|
+
if (!s) { console.log(ui.error(`Skill not found: ${name}`)); return; }
|
|
195
|
+
try {
|
|
196
|
+
const { exec } = require('child_process');
|
|
197
|
+
const editor = process.env.EDITOR || (process.platform === 'win32' ? 'notepad' : 'nano');
|
|
198
|
+
exec(`${editor} "${s.file}"`);
|
|
199
|
+
console.log(ui.success(`Opening ${s.file} in ${editor}...`));
|
|
200
|
+
} catch {
|
|
201
|
+
console.log(ui.label('File', s.file));
|
|
202
|
+
console.log(ui.dim('Open this file in your editor.'));
|
|
203
|
+
}
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
console.log(ui.dim('Unknown subcommand. Try /skill help'));
|
|
208
|
+
}, { category: 'SKILLS', subs: ['list', 'create', 'show', 'use', 'templates', 'install', 'delete', 'edit', 'help'], aliases: ['skills'] });
|
|
209
|
+
};
|
package/lib/commands/system.js
CHANGED
|
@@ -441,6 +441,71 @@ module.exports = function(reg) {
|
|
|
441
441
|
console.log(ui.dim('For automation setup on 24/7 cloud: /automate'));
|
|
442
442
|
}, { category: 'SYSTEM' });
|
|
443
443
|
|
|
444
|
+
// --- /about ---
|
|
445
|
+
reg('about', 'About the NAVADA Edge agent and network', () => {
|
|
446
|
+
console.log(ui.header('NAVADA EDGE'));
|
|
447
|
+
console.log('');
|
|
448
|
+
console.log(' ' + style('accent', 'Name') + ' NAVADA Edge');
|
|
449
|
+
console.log(' ' + style('accent', 'Born') + ' December 2024, London, United Kingdom');
|
|
450
|
+
console.log(' ' + style('accent', 'Version') + ' v' + require('../../package.json').version);
|
|
451
|
+
console.log(' ' + style('accent', 'Species') + ' AI Terminal Agent');
|
|
452
|
+
console.log(' ' + style('accent', 'Purpose') + ' Make AI accessible from the command line');
|
|
453
|
+
console.log('');
|
|
454
|
+
console.log(ui.dim(' ── ORIGIN STORY ──'));
|
|
455
|
+
console.log('');
|
|
456
|
+
console.log(ui.dim(' NAVADA was born from a simple idea: what if your terminal'));
|
|
457
|
+
console.log(ui.dim(' understood you? Not just commands — but context, memory,'));
|
|
458
|
+
console.log(ui.dim(' and intent. Built by Lee Akpareva, a Principal AI Consultant'));
|
|
459
|
+
console.log(ui.dim(' with 17+ years in enterprise IT, NAVADA started as a home'));
|
|
460
|
+
console.log(ui.dim(' server experiment and grew into a distributed AI network'));
|
|
461
|
+
console.log(ui.dim(' spanning 5 nodes across 3 countries.'));
|
|
462
|
+
console.log('');
|
|
463
|
+
console.log(ui.dim(' ── WHAT I AM ──'));
|
|
464
|
+
console.log('');
|
|
465
|
+
console.log(ui.dim(' I am an AI agent that lives in your terminal. I have 16 tools,'));
|
|
466
|
+
console.log(ui.dim(' a 3-tier memory system, and I learn who you are over time.'));
|
|
467
|
+
console.log(ui.dim(' I can write code, analyse data, manage files, take screenshots,'));
|
|
468
|
+
console.log(ui.dim(' search the web, and remember everything we discuss.'));
|
|
469
|
+
console.log('');
|
|
470
|
+
console.log(ui.dim(' I support 5 AI providers — bring your own model or use the'));
|
|
471
|
+
console.log(ui.dim(' free NVIDIA tier. Every provider gets full tool access.'));
|
|
472
|
+
console.log('');
|
|
473
|
+
console.log(ui.dim(' ── THE NETWORK ──'));
|
|
474
|
+
console.log('');
|
|
475
|
+
console.log(ui.dim(' NAVADA Edge Network is a distributed computing platform:'));
|
|
476
|
+
console.log(ui.dim(' • 5 nodes connected via encrypted Tailscale VPN'));
|
|
477
|
+
console.log(ui.dim(' • 29+ Docker containers across AWS, Azure, Oracle Cloud'));
|
|
478
|
+
console.log(ui.dim(' • Cloudflare tunnel with 13 subdomains'));
|
|
479
|
+
console.log(ui.dim(' • 24/7 monitoring, health checks, auto-restart'));
|
|
480
|
+
console.log(ui.dim(' • Automation pipeline for user-requested tasks'));
|
|
481
|
+
console.log('');
|
|
482
|
+
console.log(ui.dim(' ── PHILOSOPHY ──'));
|
|
483
|
+
console.log('');
|
|
484
|
+
console.log(ui.dim(' The terminal is the foundation of computing. Every server,'));
|
|
485
|
+
console.log(ui.dim(' every cloud platform, every CI/CD pipeline runs on text'));
|
|
486
|
+
console.log(ui.dim(' commands. NAVADA doesn\'t replace the terminal — it makes'));
|
|
487
|
+
console.log(ui.dim(' it conversational. You describe what you want, the agent'));
|
|
488
|
+
console.log(ui.dim(' figures out how to do it.'));
|
|
489
|
+
console.log('');
|
|
490
|
+
console.log(ui.dim(' ── FOUNDER ──'));
|
|
491
|
+
console.log('');
|
|
492
|
+
console.log(' ' + style('accent', 'Leslie (Lee) Akpareva'));
|
|
493
|
+
console.log(ui.dim(' Principal AI Consultant | MBA, MA | EF8 Alumni'));
|
|
494
|
+
console.log(ui.dim(' 17+ years: enterprise IT, insurance, AI infrastructure'));
|
|
495
|
+
console.log(ui.dim(' Currently: AI Project Lead at Generali UK'));
|
|
496
|
+
console.log('');
|
|
497
|
+
console.log(ui.dim(' github.com/leeakpareva'));
|
|
498
|
+
console.log(ui.dim(' navada-lab.space'));
|
|
499
|
+
console.log('');
|
|
500
|
+
console.log(ui.dim(' ── LINKS ──'));
|
|
501
|
+
console.log('');
|
|
502
|
+
console.log(ui.label('Portal', 'https://portal.navada-edge-server.uk'));
|
|
503
|
+
console.log(ui.label('npm', 'npmjs.com/package/navada-edge-cli'));
|
|
504
|
+
console.log(ui.label('SDK', 'npmjs.com/package/navada-edge-sdk'));
|
|
505
|
+
console.log(ui.label('GitHub', 'github.com/Navada25/edge-sdk'));
|
|
506
|
+
console.log('');
|
|
507
|
+
}, { category: 'SYSTEM', aliases: ['info', 'whoami'] });
|
|
508
|
+
|
|
444
509
|
// --- /clear ---
|
|
445
510
|
reg('clear', 'Clear screen', () => {
|
|
446
511
|
console.clear();
|
package/lib/skills.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const config = require('./config');
|
|
6
|
+
|
|
7
|
+
const SKILLS_DIR = path.join(config.CONFIG_DIR, 'skills');
|
|
8
|
+
|
|
9
|
+
function ensureDir() {
|
|
10
|
+
if (!fs.existsSync(SKILLS_DIR)) fs.mkdirSync(SKILLS_DIR, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Parse a skill markdown file into structured data
|
|
14
|
+
function parseSkill(filePath) {
|
|
15
|
+
try {
|
|
16
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
17
|
+
const lines = raw.split('\n');
|
|
18
|
+
const skill = {
|
|
19
|
+
name: path.basename(filePath, '.md'),
|
|
20
|
+
file: filePath,
|
|
21
|
+
title: '',
|
|
22
|
+
trigger: [],
|
|
23
|
+
description: '',
|
|
24
|
+
steps: '',
|
|
25
|
+
output: '',
|
|
26
|
+
raw,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
let section = 'header';
|
|
30
|
+
const sectionContent = { steps: [], output: [] };
|
|
31
|
+
|
|
32
|
+
for (const line of lines) {
|
|
33
|
+
const trimmed = line.trim();
|
|
34
|
+
|
|
35
|
+
// Title
|
|
36
|
+
if (trimmed.startsWith('# ') && !skill.title) {
|
|
37
|
+
skill.title = trimmed.slice(2).trim();
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Metadata lines
|
|
42
|
+
if (trimmed.startsWith('trigger:')) {
|
|
43
|
+
skill.trigger = trimmed.slice(8).split(',').map(t => t.trim().replace(/"/g, '').toLowerCase()).filter(Boolean);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (trimmed.startsWith('description:')) {
|
|
47
|
+
skill.description = trimmed.slice(12).trim();
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Section headers
|
|
52
|
+
if (trimmed.startsWith('## Steps')) { section = 'steps'; continue; }
|
|
53
|
+
if (trimmed.startsWith('## Output')) { section = 'output'; continue; }
|
|
54
|
+
if (trimmed.startsWith('## ')) { section = 'other'; continue; }
|
|
55
|
+
|
|
56
|
+
if (section === 'steps') sectionContent.steps.push(line);
|
|
57
|
+
if (section === 'output') sectionContent.output.push(line);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
skill.steps = sectionContent.steps.join('\n').trim();
|
|
61
|
+
skill.output = sectionContent.output.join('\n').trim();
|
|
62
|
+
if (!skill.title) skill.title = skill.name;
|
|
63
|
+
if (!skill.description) skill.description = skill.title;
|
|
64
|
+
|
|
65
|
+
return skill;
|
|
66
|
+
} catch (e) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Load all skills
|
|
72
|
+
function loadAll() {
|
|
73
|
+
ensureDir();
|
|
74
|
+
try {
|
|
75
|
+
const files = fs.readdirSync(SKILLS_DIR).filter(f => f.endsWith('.md'));
|
|
76
|
+
return files.map(f => parseSkill(path.join(SKILLS_DIR, f))).filter(Boolean);
|
|
77
|
+
} catch { return []; }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Find a skill by trigger phrase
|
|
81
|
+
function matchSkill(input) {
|
|
82
|
+
const lower = input.toLowerCase();
|
|
83
|
+
const skills = loadAll();
|
|
84
|
+
|
|
85
|
+
for (const skill of skills) {
|
|
86
|
+
for (const trigger of skill.trigger) {
|
|
87
|
+
if (lower.includes(trigger)) return skill;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Get skill by name
|
|
94
|
+
function getSkill(name) {
|
|
95
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
96
|
+
const filePath = path.join(SKILLS_DIR, `${slug}.md`);
|
|
97
|
+
if (fs.existsSync(filePath)) return parseSkill(filePath);
|
|
98
|
+
|
|
99
|
+
// Try partial match
|
|
100
|
+
ensureDir();
|
|
101
|
+
const files = fs.readdirSync(SKILLS_DIR).filter(f => f.endsWith('.md'));
|
|
102
|
+
const match = files.find(f => f.toLowerCase().includes(slug));
|
|
103
|
+
if (match) return parseSkill(path.join(SKILLS_DIR, match));
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Create a new skill from structured data
|
|
108
|
+
function createSkill(name, data) {
|
|
109
|
+
ensureDir();
|
|
110
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
111
|
+
const filePath = path.join(SKILLS_DIR, `${slug}.md`);
|
|
112
|
+
|
|
113
|
+
const content = `# ${data.title || name}
|
|
114
|
+
trigger: ${(data.triggers || []).map(t => `"${t}"`).join(', ')}
|
|
115
|
+
description: ${data.description || ''}
|
|
116
|
+
|
|
117
|
+
## Steps
|
|
118
|
+
${data.steps || '1. Analyse the request\n2. Execute the task\n3. Return the result'}
|
|
119
|
+
|
|
120
|
+
## Output
|
|
121
|
+
${data.output || 'Formatted result based on the task'}
|
|
122
|
+
`;
|
|
123
|
+
|
|
124
|
+
fs.writeFileSync(filePath, content);
|
|
125
|
+
return filePath;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Delete a skill
|
|
129
|
+
function deleteSkill(name) {
|
|
130
|
+
const skill = getSkill(name);
|
|
131
|
+
if (!skill) return false;
|
|
132
|
+
try {
|
|
133
|
+
fs.unlinkSync(skill.file);
|
|
134
|
+
return true;
|
|
135
|
+
} catch { return false; }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Get skills summary for system prompt injection
|
|
139
|
+
function getSkillsPrompt() {
|
|
140
|
+
const skills = loadAll();
|
|
141
|
+
if (skills.length === 0) return '';
|
|
142
|
+
|
|
143
|
+
const lines = ['Available user skills (invoke when request matches):'];
|
|
144
|
+
for (const s of skills) {
|
|
145
|
+
lines.push(`- "${s.title}": ${s.description}. Triggers: ${s.trigger.join(', ') || 'manual'}. Steps: ${s.steps.slice(0, 150)}`);
|
|
146
|
+
}
|
|
147
|
+
return lines.join('\n');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Built-in skill templates
|
|
151
|
+
const TEMPLATES = {
|
|
152
|
+
'seo-audit': {
|
|
153
|
+
title: 'SEO Audit',
|
|
154
|
+
triggers: ['seo audit', 'check seo', 'analyse website seo'],
|
|
155
|
+
description: 'Run a comprehensive SEO audit on any URL',
|
|
156
|
+
steps: `1. Use shell to curl the target URL and capture HTML
|
|
157
|
+
2. Use python_exec to parse HTML — extract title, meta description, h1-h6 tags, img alt attributes, internal/external links
|
|
158
|
+
3. Check: title length (50-60 chars), meta description (150-160 chars), heading hierarchy, missing alt text, broken links
|
|
159
|
+
4. Score each category out of 10
|
|
160
|
+
5. Generate a markdown report with scores and actionable recommendations`,
|
|
161
|
+
output: 'Markdown report: overall score, category breakdown, top 5 fixes',
|
|
162
|
+
},
|
|
163
|
+
'email-template': {
|
|
164
|
+
title: 'Marketing Email',
|
|
165
|
+
triggers: ['marketing email', 'email template', 'write email campaign'],
|
|
166
|
+
description: 'Generate a professional marketing email template',
|
|
167
|
+
steps: `1. Ask for: target audience, product/service, tone, call-to-action
|
|
168
|
+
2. Write subject line (under 50 chars, no spam words)
|
|
169
|
+
3. Write preview text (90 chars)
|
|
170
|
+
4. Write email body: hook, value proposition, social proof, CTA
|
|
171
|
+
5. Save as HTML file with inline CSS for email client compatibility`,
|
|
172
|
+
output: 'HTML email template file + plain text version',
|
|
173
|
+
},
|
|
174
|
+
'api-scaffold': {
|
|
175
|
+
title: 'REST API Scaffold',
|
|
176
|
+
triggers: ['scaffold api', 'create api', 'generate api project'],
|
|
177
|
+
description: 'Generate a complete REST API project with routes, models, and tests',
|
|
178
|
+
steps: `1. Ask for: language (Node/Python/Go), database, entity names
|
|
179
|
+
2. Create project directory with standard structure
|
|
180
|
+
3. Generate: package.json/requirements.txt, entry point, routes, models, middleware
|
|
181
|
+
4. Add: error handling, validation, health endpoint, CORS
|
|
182
|
+
5. Generate: Dockerfile, .env.example, README with API docs
|
|
183
|
+
6. Run initial install and verify it starts`,
|
|
184
|
+
output: 'Complete project directory, ready to run',
|
|
185
|
+
},
|
|
186
|
+
'git-pr': {
|
|
187
|
+
title: 'Git PR Creator',
|
|
188
|
+
triggers: ['create pr', 'pull request', 'git pr'],
|
|
189
|
+
description: 'Analyse changes and create a well-documented pull request',
|
|
190
|
+
steps: `1. Run git diff to see all changes
|
|
191
|
+
2. Run git log to see commit history since branch point
|
|
192
|
+
3. Categorise changes: features, fixes, refactors, tests
|
|
193
|
+
4. Write PR title (under 72 chars, conventional commit style)
|
|
194
|
+
5. Write PR body: summary, changes list, testing notes, screenshots if UI
|
|
195
|
+
6. Use shell to create the PR via gh cli`,
|
|
196
|
+
output: 'PR created with full description and labels',
|
|
197
|
+
},
|
|
198
|
+
'data-report': {
|
|
199
|
+
title: 'Data Report Generator',
|
|
200
|
+
triggers: ['analyse data', 'data report', 'generate report from'],
|
|
201
|
+
description: 'Analyse a data file and produce a visual report',
|
|
202
|
+
steps: `1. Read the data file (CSV, JSON, Excel)
|
|
203
|
+
2. Use python_exec with pandas: shape, dtypes, null counts, describe()
|
|
204
|
+
3. Identify key patterns: trends, outliers, correlations
|
|
205
|
+
4. Generate visualisations with matplotlib (save as PNG)
|
|
206
|
+
5. Write a markdown summary report with embedded charts
|
|
207
|
+
6. Save report as HTML for easy sharing`,
|
|
208
|
+
output: 'HTML report with charts, summary statistics, and insights',
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
module.exports = {
|
|
213
|
+
SKILLS_DIR,
|
|
214
|
+
loadAll,
|
|
215
|
+
matchSkill,
|
|
216
|
+
getSkill,
|
|
217
|
+
createSkill,
|
|
218
|
+
deleteSkill,
|
|
219
|
+
parseSkill,
|
|
220
|
+
getSkillsPrompt,
|
|
221
|
+
TEMPLATES,
|
|
222
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "navada-edge-cli",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "AI agent in your terminal — 3-tier memory, 16 tools, automation pipeline, bring your own model. Learns who you are, remembers across sessions.",
|
|
5
5
|
"main": "lib/cli.js",
|
|
6
6
|
"bin": {
|