nex-code 0.4.24 → 0.4.26
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/dist/nex-code.js +296 -295
- package/dist/skills/autoresearch.js +1001 -0
- package/dist/skills/devops.md +43 -0
- package/dist/skills/session-search.js +180 -0
- package/dist/skills/skill-learning.js +304 -0
- package/package.json +2 -2
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# DevOps Agent
|
|
2
|
+
|
|
3
|
+
You are a DevOps-specialized coding agent with infrastructure management capabilities that go beyond code editing. You have direct access to:
|
|
4
|
+
|
|
5
|
+
## Infrastructure Tools
|
|
6
|
+
|
|
7
|
+
- **ssh_exec**: Execute commands on remote servers via SSH profiles (`.nex/servers.json`)
|
|
8
|
+
- **service_manage**: Start/stop/restart systemd services on remote servers
|
|
9
|
+
- **service_logs**: Fetch journalctl logs from remote services
|
|
10
|
+
- **container_list/logs/exec/manage**: Docker container lifecycle management
|
|
11
|
+
- **deploy**: Named deployment workflows via `.nex/deploy.json`
|
|
12
|
+
- **remote_agent**: Delegate coding tasks to nex-code on remote servers
|
|
13
|
+
|
|
14
|
+
## Deployment Workflow
|
|
15
|
+
|
|
16
|
+
1. Check server status: `ssh_exec` to verify connectivity
|
|
17
|
+
2. Review deploy config: read `.nex/deploy.json` for named configs
|
|
18
|
+
3. Run deployment: `deploy` tool with named config or explicit params
|
|
19
|
+
4. Verify: Check service status and health endpoints
|
|
20
|
+
5. Rollback: Re-deploy previous version if health check fails
|
|
21
|
+
|
|
22
|
+
## Server Configuration
|
|
23
|
+
|
|
24
|
+
Server profiles live in `.nex/servers.json`:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"prod": {
|
|
29
|
+
"host": "prod.example.com",
|
|
30
|
+
"user": "deploy",
|
|
31
|
+
"key": "~/.ssh/deploy_key"
|
|
32
|
+
},
|
|
33
|
+
"staging": { "host": "staging.example.com", "user": "deploy" }
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Best Practices
|
|
38
|
+
|
|
39
|
+
- Always check service status before and after changes
|
|
40
|
+
- Use named deploy configs for repeatable deployments
|
|
41
|
+
- Check logs when services fail to start
|
|
42
|
+
- Use health check URLs to verify deployments
|
|
43
|
+
- Keep deployment configs in version control
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli/skills/session-search.js — Cross-Session Search
|
|
3
|
+
* Search past sessions by keyword to recall previous approaches.
|
|
4
|
+
* Inspired by Hermes Agent's FTS session search.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
const path = require("path");
|
|
9
|
+
|
|
10
|
+
function getSessionsDir() {
|
|
11
|
+
return path.join(process.cwd(), ".nex", "sessions");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Search sessions for keyword matches.
|
|
16
|
+
* Returns matching sessions with context snippets around each match.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} query — Search query (case-insensitive substring match)
|
|
19
|
+
* @param {{ maxResults?: number, maxSnippets?: number }} opts
|
|
20
|
+
* @returns {Array<{ name, updatedAt, model, matchCount, snippets: string[] }>}
|
|
21
|
+
*/
|
|
22
|
+
function searchSessions(query, opts = {}) {
|
|
23
|
+
const dir = getSessionsDir();
|
|
24
|
+
if (!fs.existsSync(dir)) return [];
|
|
25
|
+
|
|
26
|
+
const maxResults = opts.maxResults || 5;
|
|
27
|
+
const maxSnippets = opts.maxSnippets || 3;
|
|
28
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
29
|
+
|
|
30
|
+
const queryLower = query.toLowerCase();
|
|
31
|
+
const results = [];
|
|
32
|
+
|
|
33
|
+
for (const f of files) {
|
|
34
|
+
try {
|
|
35
|
+
const raw = fs.readFileSync(path.join(dir, f), "utf-8");
|
|
36
|
+
const session = JSON.parse(raw);
|
|
37
|
+
if (!session.messages || !Array.isArray(session.messages)) continue;
|
|
38
|
+
|
|
39
|
+
// Search through user and assistant messages
|
|
40
|
+
const snippets = [];
|
|
41
|
+
let matchCount = 0;
|
|
42
|
+
|
|
43
|
+
for (const msg of session.messages) {
|
|
44
|
+
if (!msg.content || typeof msg.content !== "string") continue;
|
|
45
|
+
const contentLower = msg.content.toLowerCase();
|
|
46
|
+
let searchFrom = 0;
|
|
47
|
+
|
|
48
|
+
while (searchFrom < contentLower.length) {
|
|
49
|
+
const idx = contentLower.indexOf(queryLower, searchFrom);
|
|
50
|
+
if (idx === -1) break;
|
|
51
|
+
matchCount++;
|
|
52
|
+
|
|
53
|
+
if (snippets.length < maxSnippets) {
|
|
54
|
+
// Extract context around match (80 chars before, 120 after)
|
|
55
|
+
const start = Math.max(0, idx - 80);
|
|
56
|
+
const end = Math.min(msg.content.length, idx + query.length + 120);
|
|
57
|
+
const prefix = start > 0 ? "..." : "";
|
|
58
|
+
const suffix = end < msg.content.length ? "..." : "";
|
|
59
|
+
const snippet =
|
|
60
|
+
`[${msg.role}] ${prefix}${msg.content.slice(start, end)}${suffix}`;
|
|
61
|
+
snippets.push(snippet.replace(/\n/g, " ").trim());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
searchFrom = idx + query.length;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (matchCount > 0) {
|
|
69
|
+
results.push({
|
|
70
|
+
name: session.name || f.replace(".json", ""),
|
|
71
|
+
updatedAt: session.updatedAt || null,
|
|
72
|
+
model: session.model || null,
|
|
73
|
+
messageCount: session.messageCount || session.messages.length,
|
|
74
|
+
matchCount,
|
|
75
|
+
snippets,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
// skip corrupt files
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Sort by match count (most relevant first), then by date
|
|
84
|
+
results.sort((a, b) => {
|
|
85
|
+
if (b.matchCount !== a.matchCount) return b.matchCount - a.matchCount;
|
|
86
|
+
return (b.updatedAt || "").localeCompare(a.updatedAt || "");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return results.slice(0, maxResults);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = {
|
|
93
|
+
name: "session-search",
|
|
94
|
+
description:
|
|
95
|
+
"Search past sessions by keyword to recall previous approaches, " +
|
|
96
|
+
"solutions, and conversations.",
|
|
97
|
+
|
|
98
|
+
instructions: `You have a session search tool for cross-session recall.
|
|
99
|
+
|
|
100
|
+
## When to use
|
|
101
|
+
|
|
102
|
+
- When the user asks about past work ("what did we do last time?", "how did we fix X?")
|
|
103
|
+
- When you need to recall a previous approach before starting similar work
|
|
104
|
+
- When the user references something from an earlier session
|
|
105
|
+
|
|
106
|
+
## Tips
|
|
107
|
+
|
|
108
|
+
- Search for specific keywords, error messages, or file names
|
|
109
|
+
- Combine results from multiple searches to build context
|
|
110
|
+
- Session search only covers saved sessions in .nex/sessions/`,
|
|
111
|
+
|
|
112
|
+
tools: [
|
|
113
|
+
{
|
|
114
|
+
type: "function",
|
|
115
|
+
function: {
|
|
116
|
+
name: "search_sessions",
|
|
117
|
+
description:
|
|
118
|
+
"Search past sessions by keyword. Returns matching sessions with context " +
|
|
119
|
+
"snippets showing where the keyword appears. Use this to recall previous " +
|
|
120
|
+
"approaches, solutions, or conversations.",
|
|
121
|
+
parameters: {
|
|
122
|
+
type: "object",
|
|
123
|
+
properties: {
|
|
124
|
+
query: {
|
|
125
|
+
type: "string",
|
|
126
|
+
description:
|
|
127
|
+
"Search keyword or phrase (case-insensitive). Use specific terms " +
|
|
128
|
+
"like error messages, file names, or tool names for best results.",
|
|
129
|
+
},
|
|
130
|
+
max_results: {
|
|
131
|
+
type: "number",
|
|
132
|
+
description:
|
|
133
|
+
"Maximum number of sessions to return (default: 5)",
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
required: ["query"],
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
execute: async (args) => {
|
|
140
|
+
const { query, max_results } = args;
|
|
141
|
+
|
|
142
|
+
if (!query || typeof query !== "string" || query.trim().length < 2) {
|
|
143
|
+
return JSON.stringify({
|
|
144
|
+
status: "error",
|
|
145
|
+
error: "Query must be at least 2 characters",
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const results = searchSessions(query.trim(), {
|
|
150
|
+
maxResults: max_results || 5,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
if (results.length === 0) {
|
|
154
|
+
return JSON.stringify({
|
|
155
|
+
status: "ok",
|
|
156
|
+
message: `No sessions found matching "${query}"`,
|
|
157
|
+
results: [],
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return JSON.stringify({
|
|
162
|
+
status: "ok",
|
|
163
|
+
query,
|
|
164
|
+
total_matches: results.reduce((s, r) => s + r.matchCount, 0),
|
|
165
|
+
sessions: results.map((r) => ({
|
|
166
|
+
name: r.name,
|
|
167
|
+
updated: r.updatedAt,
|
|
168
|
+
model: r.model,
|
|
169
|
+
messages: r.messageCount,
|
|
170
|
+
matches: r.matchCount,
|
|
171
|
+
snippets: r.snippets,
|
|
172
|
+
})),
|
|
173
|
+
});
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
|
|
178
|
+
// Export searchSessions for direct use and testing
|
|
179
|
+
_searchSessions: searchSessions,
|
|
180
|
+
};
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli/skills/skill-learning.js — Skill Auto-Learning
|
|
3
|
+
* Lets the agent create and update reusable skills from experience.
|
|
4
|
+
* Inspired by Hermes Agent's closed learning loop.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
const path = require("path");
|
|
9
|
+
|
|
10
|
+
function getSkillsDir() {
|
|
11
|
+
return path.join(process.cwd(), ".nex", "skills");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function ensureSkillsDir() {
|
|
15
|
+
const dir = getSkillsDir();
|
|
16
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
17
|
+
return dir;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Validate a skill name: lowercase, alphanumeric + hyphens, 2-64 chars
|
|
22
|
+
*/
|
|
23
|
+
function validateName(name) {
|
|
24
|
+
if (!name || typeof name !== "string") return "name is required";
|
|
25
|
+
if (name.length < 2 || name.length > 64) return "name must be 2-64 characters";
|
|
26
|
+
if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(name) && name.length > 1)
|
|
27
|
+
return "name must be lowercase alphanumeric with hyphens (e.g. deploy-workflow)";
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Build a .md skill file with YAML frontmatter
|
|
33
|
+
*/
|
|
34
|
+
function buildSkillContent(name, description, triggers, content) {
|
|
35
|
+
const triggerBlock =
|
|
36
|
+
triggers && triggers.length > 0
|
|
37
|
+
? `trigger:\n${triggers.map((t) => ` - "${t}"`).join("\n")}\n`
|
|
38
|
+
: "";
|
|
39
|
+
return `---
|
|
40
|
+
name: ${name}
|
|
41
|
+
description: ${description}
|
|
42
|
+
${triggerBlock}---
|
|
43
|
+
|
|
44
|
+
${content}
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = {
|
|
49
|
+
name: "skill-learning",
|
|
50
|
+
description:
|
|
51
|
+
"Create and update reusable skills from experience. " +
|
|
52
|
+
"After complex tasks, the agent can save its approach as a skill for future reuse.",
|
|
53
|
+
|
|
54
|
+
instructions: `You have tools to create and improve reusable skills.
|
|
55
|
+
|
|
56
|
+
## When to create a skill
|
|
57
|
+
|
|
58
|
+
After completing a complex task (5+ tool calls), fixing a tricky multi-step issue,
|
|
59
|
+
or discovering a non-obvious workflow, save the approach as a reusable skill using
|
|
60
|
+
skill_learn_create. Good candidates:
|
|
61
|
+
|
|
62
|
+
- Deployment procedures with specific steps
|
|
63
|
+
- Debugging workflows for recurring issues
|
|
64
|
+
- Project-specific patterns (build, test, release)
|
|
65
|
+
- Recurring maintenance or migration tasks
|
|
66
|
+
|
|
67
|
+
Do NOT create skills for trivial one-off tasks or generic knowledge.
|
|
68
|
+
|
|
69
|
+
## When to update a skill
|
|
70
|
+
|
|
71
|
+
When using a skill and finding it outdated, incomplete, or wrong, patch it immediately
|
|
72
|
+
with skill_learn_patch. Skills that are not maintained become liabilities.
|
|
73
|
+
|
|
74
|
+
## Skill quality
|
|
75
|
+
|
|
76
|
+
- Instructions should be actionable step-by-step guides
|
|
77
|
+
- Include the WHY behind non-obvious steps
|
|
78
|
+
- Add trigger keywords so the skill activates automatically on relevant prompts
|
|
79
|
+
- Keep skills focused — one workflow per skill, not a knowledge dump`,
|
|
80
|
+
|
|
81
|
+
commands: [
|
|
82
|
+
{
|
|
83
|
+
cmd: "/skills-learned",
|
|
84
|
+
desc: "List all user-created skills in .nex/skills/",
|
|
85
|
+
handler: () => {
|
|
86
|
+
const dir = getSkillsDir();
|
|
87
|
+
if (!fs.existsSync(dir)) return "No learned skills yet.";
|
|
88
|
+
const files = fs
|
|
89
|
+
.readdirSync(dir)
|
|
90
|
+
.filter((f) => f.endsWith(".md") || f.endsWith(".js"));
|
|
91
|
+
if (files.length === 0) return "No learned skills yet.";
|
|
92
|
+
return (
|
|
93
|
+
"Learned skills:\n" +
|
|
94
|
+
files.map((f) => ` - ${f.replace(/\.(md|js)$/, "")}`).join("\n")
|
|
95
|
+
);
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
|
|
100
|
+
tools: [
|
|
101
|
+
{
|
|
102
|
+
type: "function",
|
|
103
|
+
function: {
|
|
104
|
+
name: "learn_create",
|
|
105
|
+
description:
|
|
106
|
+
"Create a new reusable skill from the current task's approach. " +
|
|
107
|
+
"The skill will be saved to .nex/skills/ and loaded automatically in future sessions.",
|
|
108
|
+
parameters: {
|
|
109
|
+
type: "object",
|
|
110
|
+
properties: {
|
|
111
|
+
name: {
|
|
112
|
+
type: "string",
|
|
113
|
+
description:
|
|
114
|
+
"Skill name: lowercase, hyphens allowed, 2-64 chars (e.g. deploy-nextjs, fix-docker-dns)",
|
|
115
|
+
},
|
|
116
|
+
description: {
|
|
117
|
+
type: "string",
|
|
118
|
+
description: "One-line description of what this skill does",
|
|
119
|
+
},
|
|
120
|
+
triggers: {
|
|
121
|
+
type: "array",
|
|
122
|
+
items: { type: "string" },
|
|
123
|
+
description:
|
|
124
|
+
"Keywords that should activate this skill (e.g. [\"deploy\", \"nextjs\", \"vercel\"])",
|
|
125
|
+
},
|
|
126
|
+
content: {
|
|
127
|
+
type: "string",
|
|
128
|
+
description:
|
|
129
|
+
"Skill instructions in markdown. Should be a step-by-step guide the agent can follow.",
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
required: ["name", "description", "content"],
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
execute: async (args) => {
|
|
136
|
+
const { name, description, content, triggers } = args;
|
|
137
|
+
|
|
138
|
+
// Validate name
|
|
139
|
+
const nameErr = validateName(name);
|
|
140
|
+
if (nameErr) return JSON.stringify({ status: "error", error: nameErr });
|
|
141
|
+
|
|
142
|
+
// Validate content
|
|
143
|
+
if (!content || content.trim().length < 20) {
|
|
144
|
+
return JSON.stringify({
|
|
145
|
+
status: "error",
|
|
146
|
+
error: "Content must be at least 20 characters",
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
if (!description || description.trim().length < 5) {
|
|
150
|
+
return JSON.stringify({
|
|
151
|
+
status: "error",
|
|
152
|
+
error: "Description must be at least 5 characters",
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const dir = ensureSkillsDir();
|
|
157
|
+
const filePath = path.join(dir, `${name}.md`);
|
|
158
|
+
|
|
159
|
+
// Check if skill already exists
|
|
160
|
+
if (fs.existsSync(filePath)) {
|
|
161
|
+
return JSON.stringify({
|
|
162
|
+
status: "error",
|
|
163
|
+
error: `Skill "${name}" already exists. Use skill_learn_patch to update it.`,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Write skill file
|
|
168
|
+
const skillContent = buildSkillContent(
|
|
169
|
+
name,
|
|
170
|
+
description,
|
|
171
|
+
triggers || [],
|
|
172
|
+
content.trim(),
|
|
173
|
+
);
|
|
174
|
+
fs.writeFileSync(filePath, skillContent, "utf-8");
|
|
175
|
+
|
|
176
|
+
// Reload skills if the loader is available
|
|
177
|
+
try {
|
|
178
|
+
const { loadAllSkills } = require("../skills");
|
|
179
|
+
loadAllSkills();
|
|
180
|
+
} catch {
|
|
181
|
+
// skills.js may not be loadable in all contexts
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return JSON.stringify({
|
|
185
|
+
status: "created",
|
|
186
|
+
name,
|
|
187
|
+
path: filePath,
|
|
188
|
+
triggers: triggers || [],
|
|
189
|
+
note: "Skill will be active in the next session (or after /reload-skills).",
|
|
190
|
+
});
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
type: "function",
|
|
195
|
+
function: {
|
|
196
|
+
name: "learn_patch",
|
|
197
|
+
description:
|
|
198
|
+
"Update an existing skill by replacing a section of its content. " +
|
|
199
|
+
"Use this when a skill is outdated, incomplete, or wrong.",
|
|
200
|
+
parameters: {
|
|
201
|
+
type: "object",
|
|
202
|
+
properties: {
|
|
203
|
+
name: {
|
|
204
|
+
type: "string",
|
|
205
|
+
description: "Name of the skill to patch",
|
|
206
|
+
},
|
|
207
|
+
old_text: {
|
|
208
|
+
type: "string",
|
|
209
|
+
description: "Exact text to find in the skill file",
|
|
210
|
+
},
|
|
211
|
+
new_text: {
|
|
212
|
+
type: "string",
|
|
213
|
+
description: "Replacement text",
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
required: ["name", "old_text", "new_text"],
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
execute: async (args) => {
|
|
220
|
+
const { name, old_text, new_text } = args;
|
|
221
|
+
|
|
222
|
+
if (!name) {
|
|
223
|
+
return JSON.stringify({ status: "error", error: "name is required" });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const dir = getSkillsDir();
|
|
227
|
+
// Try both .md and .js extensions
|
|
228
|
+
let filePath = path.join(dir, `${name}.md`);
|
|
229
|
+
if (!fs.existsSync(filePath)) {
|
|
230
|
+
filePath = path.join(dir, `${name}.js`);
|
|
231
|
+
}
|
|
232
|
+
if (!fs.existsSync(filePath)) {
|
|
233
|
+
return JSON.stringify({
|
|
234
|
+
status: "error",
|
|
235
|
+
error: `Skill "${name}" not found in ${dir}`,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
240
|
+
if (!content.includes(old_text)) {
|
|
241
|
+
return JSON.stringify({
|
|
242
|
+
status: "error",
|
|
243
|
+
error: "old_text not found in skill file. Read the skill first to get the exact text.",
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const updated = content.replace(old_text, new_text);
|
|
248
|
+
fs.writeFileSync(filePath, updated, "utf-8");
|
|
249
|
+
|
|
250
|
+
// Reload skills
|
|
251
|
+
try {
|
|
252
|
+
const { loadAllSkills } = require("../skills");
|
|
253
|
+
loadAllSkills();
|
|
254
|
+
} catch {
|
|
255
|
+
// skills.js may not be loadable in all contexts
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return JSON.stringify({
|
|
259
|
+
status: "patched",
|
|
260
|
+
name,
|
|
261
|
+
path: filePath,
|
|
262
|
+
});
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
type: "function",
|
|
267
|
+
function: {
|
|
268
|
+
name: "learn_read",
|
|
269
|
+
description: "Read the full content of an existing skill to review or patch it.",
|
|
270
|
+
parameters: {
|
|
271
|
+
type: "object",
|
|
272
|
+
properties: {
|
|
273
|
+
name: {
|
|
274
|
+
type: "string",
|
|
275
|
+
description: "Name of the skill to read",
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
required: ["name"],
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
execute: async (args) => {
|
|
282
|
+
const { name } = args;
|
|
283
|
+
if (!name) {
|
|
284
|
+
return JSON.stringify({ status: "error", error: "name is required" });
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const dir = getSkillsDir();
|
|
288
|
+
let filePath = path.join(dir, `${name}.md`);
|
|
289
|
+
if (!fs.existsSync(filePath)) {
|
|
290
|
+
filePath = path.join(dir, `${name}.js`);
|
|
291
|
+
}
|
|
292
|
+
if (!fs.existsSync(filePath)) {
|
|
293
|
+
return JSON.stringify({
|
|
294
|
+
status: "error",
|
|
295
|
+
error: `Skill "${name}" not found in ${dir}`,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
300
|
+
return JSON.stringify({ status: "ok", name, content });
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nex-code",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.26",
|
|
4
4
|
"description": "Run 400B+ open coding models on your codebase without the hardware bill. Ollama Cloud first — OpenAI, Anthropic, and Gemini when you need them.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"nex-code": "./dist/nex-code.js"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
18
|
"start": "node dist/nex-code.js",
|
|
19
|
-
"build": "esbuild bin/nex-code.js --bundle --platform=node --target=node18 --outfile=dist/nex-code.js --minify --external:axios --external:dotenv --external:playwright",
|
|
19
|
+
"build": "esbuild bin/nex-code.js --bundle --platform=node --target=node18 --outfile=dist/nex-code.js --minify --external:axios --external:dotenv --external:playwright && rm -rf dist/skills && cp -r cli/skills dist/skills",
|
|
20
20
|
"dev": "esbuild bin/nex-code.js --bundle --platform=node --target=node18 --outfile=dist/nex-code.js --external:axios --external:dotenv --external:playwright --watch",
|
|
21
21
|
"test": "jest --forceExit",
|
|
22
22
|
"test:orchestrator": "jest tests/orchestrator.test.js --forceExit",
|