nex-code 0.3.5 → 0.3.7
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 -1
- package/dist/bundle.js +505 -0
- package/dist/nex-code.js +485 -0
- package/package.json +8 -6
- package/bin/nex-code.js +0 -99
- package/cli/agent.js +0 -835
- package/cli/compactor.js +0 -85
- package/cli/context-engine.js +0 -507
- package/cli/context.js +0 -98
- package/cli/costs.js +0 -290
- package/cli/diff.js +0 -366
- package/cli/file-history.js +0 -94
- package/cli/format.js +0 -211
- package/cli/fuzzy-match.js +0 -270
- package/cli/git.js +0 -211
- package/cli/hooks.js +0 -173
- package/cli/index.js +0 -1289
- package/cli/mcp.js +0 -284
- package/cli/memory.js +0 -170
- package/cli/ollama.js +0 -130
- package/cli/permissions.js +0 -124
- package/cli/picker.js +0 -201
- package/cli/planner.js +0 -282
- package/cli/providers/anthropic.js +0 -333
- package/cli/providers/base.js +0 -116
- package/cli/providers/gemini.js +0 -239
- package/cli/providers/local.js +0 -249
- package/cli/providers/ollama.js +0 -228
- package/cli/providers/openai.js +0 -237
- package/cli/providers/registry.js +0 -454
- package/cli/render.js +0 -495
- package/cli/safety.js +0 -241
- package/cli/session.js +0 -133
- package/cli/skills.js +0 -412
- package/cli/spinner.js +0 -371
- package/cli/sub-agent.js +0 -441
- package/cli/tasks.js +0 -179
- package/cli/tool-tiers.js +0 -164
- package/cli/tool-validator.js +0 -138
- package/cli/tools.js +0 -1050
- package/cli/ui.js +0 -93
package/cli/skills.js
DELETED
|
@@ -1,412 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* cli/skills.js — Skills System
|
|
3
|
-
* Load .md and .js skill files from .nex/skills/ to extend the system.
|
|
4
|
-
* - Prompt Skills (.md): inject instructions into system prompt
|
|
5
|
-
* - Script Skills (.js): provide instructions, commands, and tools
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
|
|
11
|
-
// Loaded skills registry
|
|
12
|
-
let loadedSkills = [];
|
|
13
|
-
|
|
14
|
-
function getSkillsDir() {
|
|
15
|
-
return path.join(process.cwd(), '.nex', 'skills');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function getConfigPath() {
|
|
19
|
-
return path.join(process.cwd(), '.nex', 'config.json');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Initialize the skills directory
|
|
24
|
-
* @returns {string} path to .nex/skills/
|
|
25
|
-
*/
|
|
26
|
-
function initSkillsDir() {
|
|
27
|
-
const dir = getSkillsDir();
|
|
28
|
-
if (!fs.existsSync(dir)) {
|
|
29
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
30
|
-
}
|
|
31
|
-
return dir;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Load disabled list from .nex/config.json
|
|
36
|
-
* @returns {string[]}
|
|
37
|
-
*/
|
|
38
|
-
function getDisabledSkills() {
|
|
39
|
-
const configPath = getConfigPath();
|
|
40
|
-
if (!fs.existsSync(configPath)) return [];
|
|
41
|
-
try {
|
|
42
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
43
|
-
return (config.skills && Array.isArray(config.skills.disabled)) ? config.skills.disabled : [];
|
|
44
|
-
} catch {
|
|
45
|
-
return [];
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Save disabled list to .nex/config.json
|
|
51
|
-
* @param {string[]} disabled
|
|
52
|
-
*/
|
|
53
|
-
function saveDisabledSkills(disabled) {
|
|
54
|
-
const configPath = getConfigPath();
|
|
55
|
-
let config = {};
|
|
56
|
-
if (fs.existsSync(configPath)) {
|
|
57
|
-
try {
|
|
58
|
-
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
59
|
-
} catch {
|
|
60
|
-
config = {};
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (!config.skills) config.skills = {};
|
|
64
|
-
config.skills.disabled = disabled;
|
|
65
|
-
const dir = path.dirname(configPath);
|
|
66
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
67
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Validate a script skill module
|
|
72
|
-
* @param {Object} mod - require()'d module
|
|
73
|
-
* @param {string} filePath - for error messages
|
|
74
|
-
* @returns {{valid: boolean, errors: string[]}}
|
|
75
|
-
*/
|
|
76
|
-
function validateScriptSkill(mod, filePath) {
|
|
77
|
-
const errors = [];
|
|
78
|
-
|
|
79
|
-
if (typeof mod !== 'object' || mod === null) {
|
|
80
|
-
return { valid: false, errors: ['Module must export an object'] };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (mod.name !== undefined && typeof mod.name !== 'string') {
|
|
84
|
-
errors.push('name must be a string');
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (mod.description !== undefined && typeof mod.description !== 'string') {
|
|
88
|
-
errors.push('description must be a string');
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (mod.instructions !== undefined && typeof mod.instructions !== 'string') {
|
|
92
|
-
errors.push('instructions must be a string');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (mod.commands !== undefined) {
|
|
96
|
-
if (!Array.isArray(mod.commands)) {
|
|
97
|
-
errors.push('commands must be an array');
|
|
98
|
-
} else {
|
|
99
|
-
for (let i = 0; i < mod.commands.length; i++) {
|
|
100
|
-
const c = mod.commands[i];
|
|
101
|
-
if (!c.cmd || typeof c.cmd !== 'string') {
|
|
102
|
-
errors.push(`commands[${i}].cmd must be a non-empty string`);
|
|
103
|
-
}
|
|
104
|
-
if (c.handler !== undefined && typeof c.handler !== 'function') {
|
|
105
|
-
errors.push(`commands[${i}].handler must be a function`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (mod.tools !== undefined) {
|
|
112
|
-
if (!Array.isArray(mod.tools)) {
|
|
113
|
-
errors.push('tools must be an array');
|
|
114
|
-
} else {
|
|
115
|
-
for (let i = 0; i < mod.tools.length; i++) {
|
|
116
|
-
const t = mod.tools[i];
|
|
117
|
-
if (!t.function || !t.function.name || typeof t.function.name !== 'string') {
|
|
118
|
-
errors.push(`tools[${i}].function.name must be a non-empty string`);
|
|
119
|
-
}
|
|
120
|
-
if (t.execute !== undefined && typeof t.execute !== 'function') {
|
|
121
|
-
errors.push(`tools[${i}].execute must be a function`);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return { valid: errors.length === 0, errors };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Load a single .md skill
|
|
132
|
-
* @param {string} filePath
|
|
133
|
-
* @returns {Object|null}
|
|
134
|
-
*/
|
|
135
|
-
function loadMarkdownSkill(filePath) {
|
|
136
|
-
try {
|
|
137
|
-
const content = fs.readFileSync(filePath, 'utf-8').trim();
|
|
138
|
-
if (!content) return null;
|
|
139
|
-
const name = path.basename(filePath, '.md');
|
|
140
|
-
return {
|
|
141
|
-
name,
|
|
142
|
-
type: 'prompt',
|
|
143
|
-
filePath,
|
|
144
|
-
instructions: content,
|
|
145
|
-
commands: [],
|
|
146
|
-
tools: [],
|
|
147
|
-
};
|
|
148
|
-
} catch {
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Load a single .js skill
|
|
155
|
-
* @param {string} filePath
|
|
156
|
-
* @returns {Object|null}
|
|
157
|
-
*/
|
|
158
|
-
function loadScriptSkill(filePath) {
|
|
159
|
-
try {
|
|
160
|
-
const mod = require(filePath);
|
|
161
|
-
const { valid, errors } = validateScriptSkill(mod, filePath);
|
|
162
|
-
if (!valid) {
|
|
163
|
-
console.error(`Skill validation failed: ${filePath}\n ${errors.join('\n ')}`);
|
|
164
|
-
return null;
|
|
165
|
-
}
|
|
166
|
-
const name = mod.name || path.basename(filePath, '.js');
|
|
167
|
-
return {
|
|
168
|
-
name,
|
|
169
|
-
type: 'script',
|
|
170
|
-
filePath,
|
|
171
|
-
description: mod.description || '',
|
|
172
|
-
instructions: mod.instructions || '',
|
|
173
|
-
commands: (mod.commands || []).map((c) => ({
|
|
174
|
-
cmd: c.cmd.startsWith('/') ? c.cmd : `/${c.cmd}`,
|
|
175
|
-
desc: c.desc || c.description || '',
|
|
176
|
-
handler: c.handler || null,
|
|
177
|
-
})),
|
|
178
|
-
tools: (mod.tools || []).map((t) => ({
|
|
179
|
-
type: t.type || 'function',
|
|
180
|
-
function: {
|
|
181
|
-
name: t.function.name,
|
|
182
|
-
description: t.function.description || '',
|
|
183
|
-
parameters: t.function.parameters || { type: 'object', properties: {} },
|
|
184
|
-
},
|
|
185
|
-
execute: t.execute || null,
|
|
186
|
-
})),
|
|
187
|
-
};
|
|
188
|
-
} catch (err) {
|
|
189
|
-
console.error(`Failed to load skill: ${filePath}: ${err.message}`);
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Load all skills from .nex/skills/
|
|
196
|
-
* @returns {Object[]} array of loaded skills
|
|
197
|
-
*/
|
|
198
|
-
function loadAllSkills() {
|
|
199
|
-
loadedSkills = [];
|
|
200
|
-
const dir = getSkillsDir();
|
|
201
|
-
if (!fs.existsSync(dir)) return loadedSkills;
|
|
202
|
-
|
|
203
|
-
const disabled = getDisabledSkills();
|
|
204
|
-
let entries;
|
|
205
|
-
try {
|
|
206
|
-
entries = fs.readdirSync(dir);
|
|
207
|
-
} catch {
|
|
208
|
-
return loadedSkills;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
for (const entry of entries) {
|
|
212
|
-
const filePath = path.join(dir, entry);
|
|
213
|
-
let stat;
|
|
214
|
-
try {
|
|
215
|
-
stat = fs.statSync(filePath);
|
|
216
|
-
} catch {
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
if (!stat.isFile()) continue;
|
|
220
|
-
|
|
221
|
-
let skill = null;
|
|
222
|
-
if (entry.endsWith('.md')) {
|
|
223
|
-
skill = loadMarkdownSkill(filePath);
|
|
224
|
-
} else if (entry.endsWith('.js')) {
|
|
225
|
-
skill = loadScriptSkill(filePath);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (skill) {
|
|
229
|
-
skill.enabled = !disabled.includes(skill.name);
|
|
230
|
-
loadedSkills.push(skill);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return loadedSkills;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Get combined instructions from all enabled skills
|
|
239
|
-
* @returns {string}
|
|
240
|
-
*/
|
|
241
|
-
function getSkillInstructions() {
|
|
242
|
-
const parts = [];
|
|
243
|
-
for (const skill of loadedSkills) {
|
|
244
|
-
if (!skill.enabled || !skill.instructions) continue;
|
|
245
|
-
parts.push(`[Skill: ${skill.name}]\n${skill.instructions}`);
|
|
246
|
-
}
|
|
247
|
-
if (parts.length === 0) return '';
|
|
248
|
-
return `SKILL INSTRUCTIONS:\n${parts.join('\n\n')}`;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Get all commands from enabled skills
|
|
253
|
-
* @returns {Array<{cmd: string, desc: string}>}
|
|
254
|
-
*/
|
|
255
|
-
function getSkillCommands() {
|
|
256
|
-
const cmds = [];
|
|
257
|
-
for (const skill of loadedSkills) {
|
|
258
|
-
if (!skill.enabled) continue;
|
|
259
|
-
for (const c of skill.commands) {
|
|
260
|
-
cmds.push({ cmd: c.cmd, desc: c.desc || `[skill: ${skill.name}]` });
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
return cmds;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Get tool definitions from enabled skills (OpenAI format with skill_ prefix)
|
|
268
|
-
* @returns {Array}
|
|
269
|
-
*/
|
|
270
|
-
function getSkillToolDefinitions() {
|
|
271
|
-
const defs = [];
|
|
272
|
-
for (const skill of loadedSkills) {
|
|
273
|
-
if (!skill.enabled) continue;
|
|
274
|
-
for (const t of skill.tools) {
|
|
275
|
-
defs.push({
|
|
276
|
-
type: 'function',
|
|
277
|
-
function: {
|
|
278
|
-
name: `skill_${t.function.name}`,
|
|
279
|
-
description: `[Skill:${skill.name}] ${t.function.description}`,
|
|
280
|
-
parameters: t.function.parameters,
|
|
281
|
-
},
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
return defs;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Route a skill_ tool call to its execute function
|
|
290
|
-
* @param {string} fnName
|
|
291
|
-
* @param {Object} args
|
|
292
|
-
* @returns {Promise<string|null>} null if not a skill tool
|
|
293
|
-
*/
|
|
294
|
-
async function routeSkillCall(fnName, args) {
|
|
295
|
-
if (!fnName.startsWith('skill_')) return null;
|
|
296
|
-
const toolName = fnName.substring(6);
|
|
297
|
-
|
|
298
|
-
for (const skill of loadedSkills) {
|
|
299
|
-
if (!skill.enabled) continue;
|
|
300
|
-
for (const t of skill.tools) {
|
|
301
|
-
if (t.function.name === toolName && t.execute) {
|
|
302
|
-
try {
|
|
303
|
-
const result = await t.execute(args);
|
|
304
|
-
return typeof result === 'string' ? result : JSON.stringify(result);
|
|
305
|
-
} catch (err) {
|
|
306
|
-
return `ERROR: Skill tool '${toolName}' failed: ${err.message}`;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
return `ERROR: Skill tool '${toolName}' not found`;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Check if input matches a skill command and run its handler
|
|
317
|
-
* @param {string} input - full user input (e.g. "/deploy staging")
|
|
318
|
-
* @returns {boolean} true if handled
|
|
319
|
-
*/
|
|
320
|
-
function handleSkillCommand(input) {
|
|
321
|
-
const [cmd, ...rest] = input.split(/\s+/);
|
|
322
|
-
const args = rest.join(' ').trim();
|
|
323
|
-
|
|
324
|
-
for (const skill of loadedSkills) {
|
|
325
|
-
if (!skill.enabled) continue;
|
|
326
|
-
for (const c of skill.commands) {
|
|
327
|
-
if (c.cmd === cmd && c.handler) {
|
|
328
|
-
try {
|
|
329
|
-
c.handler(args);
|
|
330
|
-
} catch (err) {
|
|
331
|
-
console.error(`Skill command error (${cmd}): ${err.message}`);
|
|
332
|
-
}
|
|
333
|
-
return true;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
return false;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* List all loaded skills with their status
|
|
342
|
-
* @returns {Array<{name: string, type: string, enabled: boolean, commands: number, tools: number}>}
|
|
343
|
-
*/
|
|
344
|
-
function listSkills() {
|
|
345
|
-
return loadedSkills.map((s) => ({
|
|
346
|
-
name: s.name,
|
|
347
|
-
type: s.type,
|
|
348
|
-
enabled: s.enabled,
|
|
349
|
-
description: s.description || '',
|
|
350
|
-
commands: s.commands.length,
|
|
351
|
-
tools: s.tools.length,
|
|
352
|
-
filePath: s.filePath,
|
|
353
|
-
}));
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Enable a skill by name
|
|
358
|
-
* @param {string} name
|
|
359
|
-
* @returns {boolean}
|
|
360
|
-
*/
|
|
361
|
-
function enableSkill(name) {
|
|
362
|
-
const skill = loadedSkills.find((s) => s.name === name);
|
|
363
|
-
if (!skill) return false;
|
|
364
|
-
skill.enabled = true;
|
|
365
|
-
const disabled = getDisabledSkills().filter((n) => n !== name);
|
|
366
|
-
saveDisabledSkills(disabled);
|
|
367
|
-
return true;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Disable a skill by name
|
|
372
|
-
* @param {string} name
|
|
373
|
-
* @returns {boolean}
|
|
374
|
-
*/
|
|
375
|
-
function disableSkill(name) {
|
|
376
|
-
const skill = loadedSkills.find((s) => s.name === name);
|
|
377
|
-
if (!skill) return false;
|
|
378
|
-
skill.enabled = false;
|
|
379
|
-
const disabled = getDisabledSkills();
|
|
380
|
-
if (!disabled.includes(name)) {
|
|
381
|
-
disabled.push(name);
|
|
382
|
-
saveDisabledSkills(disabled);
|
|
383
|
-
}
|
|
384
|
-
return true;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Get loaded skills array (for testing)
|
|
389
|
-
* @returns {Object[]}
|
|
390
|
-
*/
|
|
391
|
-
function getLoadedSkills() {
|
|
392
|
-
return loadedSkills;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
module.exports = {
|
|
396
|
-
initSkillsDir,
|
|
397
|
-
loadAllSkills,
|
|
398
|
-
getSkillInstructions,
|
|
399
|
-
getSkillCommands,
|
|
400
|
-
getSkillToolDefinitions,
|
|
401
|
-
routeSkillCall,
|
|
402
|
-
handleSkillCommand,
|
|
403
|
-
listSkills,
|
|
404
|
-
enableSkill,
|
|
405
|
-
disableSkill,
|
|
406
|
-
getLoadedSkills,
|
|
407
|
-
// exported for testing
|
|
408
|
-
_getSkillsDir: getSkillsDir,
|
|
409
|
-
_validateScriptSkill: validateScriptSkill,
|
|
410
|
-
_loadMarkdownSkill: loadMarkdownSkill,
|
|
411
|
-
_loadScriptSkill: loadScriptSkill,
|
|
412
|
-
};
|