aboutsummaryrefslogtreecommitdiff
path: root/modules/ext/skill-pack-core.js
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-10-23 21:20:19 +0200
committerAgando <roduccomm@gmail.com>2026-10-23 21:20:19 +0200
commit64e62b6d82ed7098e5b0f1cc45d520dfe6ea3a04 (patch)
tree78054f513dc11416e4921e915d75e5fa4f07f4de /modules/ext/skill-pack-core.js
parent4b69d3666a8d842d07df2689200a364b8fdd662e (diff)
fix(newos4): skill-create stops mangling what the agent wrote (v4.6.82)
Two defects in one parser, both silent: - content = args.slice(2).join(' ') destroyed every newline, so markdown arrived as one line - the last argument was treated as tags whenever it contained a comma, and stayed in the body: a multi-line skill became a single line with ~13 bogus tags, reported as success Bodies can now be read from a VFS path (the only way markdown survives an argv splitter), tags are opt-in via --tags=, category via --category=. skill-patch takes a path too. Verified: body from file -> 6 newlines preserved, tags [alpha,beta], category testing; inline body containing four commas -> tags [], body intact.
Diffstat (limited to 'modules/ext/skill-pack-core.js')
-rw-r--r--modules/ext/skill-pack-core.js51
1 files changed, 36 insertions, 15 deletions
diff --git a/modules/ext/skill-pack-core.js b/modules/ext/skill-pack-core.js
index 5ff57f8..67adf80 100644
--- a/modules/ext/skill-pack-core.js
+++ b/modules/ext/skill-pack-core.js
@@ -29,7 +29,7 @@ class SkillPackCoreModule {
'skill-pack-core': 'Seed and manage built-in core skills (navigation, memory, search, modules, backup, agents, webdav, security, etc.)',
'skill-list': 'skill-list [category] — List all skills, optionally filtered by category',
'skill-view': 'skill-view <name> — View a skill\'s full content',
- 'skill-create': 'skill-create <name> <desc> "<steps>" [tags] — Create a new skill',
+ 'skill-create': 'skill-create <name> <desc> <path-or-text> [--tags=a,b] [--category=x] — Create a new skill',
'skill-patch': 'skill-patch <name> "<content>" — Replace a skill\'s full content',
'skill-delete': 'skill-delete <name> — Delete a skill',
'skill-export': 'skill-export <name> — Export a skill as JSON',
@@ -713,20 +713,35 @@ after the behaviour itself was fixed in v4.6.58.`),
}
_cmdSkillCreate(args) {
- if (args.length < 3) {
- this.terminal.print('Usage: skill-create <name> <description> "<markdown content>" [tags]', 'warning');
+ // v4.6.82: the old parser body = args.slice(2).join(' ') collapsed every newline into a
+ // space, and any body containing a comma had its last word eaten as "tags" — so a
+ // multi-line skill arrived as one line with 13 bogus tags while reporting success.
+ // Flags are now explicit, and a path as the third argument reads the body from a file,
+ // which is the only way to keep markdown intact through an argv splitter.
+ const flags = { tags: [], category: 'general' };
+ const positional = [];
+ for (const a of args) {
+ if (a.startsWith('--tags=')) flags.tags.push(...a.slice(7).split(',').map(t => t.trim()).filter(Boolean));
+ else if (a.startsWith('--category=')) flags.category = a.slice(11).trim() || 'general';
+ else positional.push(a);
+ }
+ if (positional.length < 3) {
+ this.terminal.print('Usage: skill-create <name> <description> <path-or-text> [--tags=a,b] [--category=x]', 'warning');
return;
}
- const name = args[0].replace(/[^a-z0-9-]/g, '').toLowerCase();
+ const name = positional[0].replace(/[^a-z0-9-]/g, '').toLowerCase();
if (!name) { this.terminal.print('Name must be kebab-case (letters, numbers, hyphens only)', 'error'); return; }
- const desc = args[1];
- const content = args.slice(2).join(' ');
- const tags = [];
- // Parse tags from the last argument if it looks like a comma-separated list
- const lastArg = args[args.length - 1];
- if (lastArg && !lastArg.startsWith('"') && lastArg.includes(',')) {
- tags.push(...lastArg.split(',').map(t => t.trim()).filter(Boolean));
+ const desc = positional[1];
+ const bodyArg = positional.slice(2).join(' ');
+ const fs0 = this.terminal.fs;
+ let content = bodyArg;
+ if (bodyArg.startsWith('/')) {
+ try {
+ const fromFile = fs0.readFile(bodyArg);
+ if (typeof fromFile === 'string' && fromFile.length) content = fromFile;
+ } catch (e) { /* not a file: treat the argument as literal text */ }
}
+ const tags = flags.tags;
const fs = this.terminal.fs;
// Check for duplicate
const index = fs.fsReadJSON('/skills/index.json') || {};
@@ -735,20 +750,26 @@ after the behaviour itself was fixed in v4.6.58.`),
return;
}
const now = new Date().toISOString();
- const skillContent = `---\nname: ${name}\ndescription: ${desc}\ntags: [${tags.join(', ')}]\ncategory: general\n---\n${content}`;
+ const skillContent = `---\nname: ${name}\ndescription: ${desc}\ntags: [${tags.join(', ')}]\ncategory: ${flags.category}\n---\n${content}`;
fs.writeFile(`/skills/${name}.md`, skillContent);
- index[name] = { name, description: desc, tags, category: 'general', created: now, updated: now };
+ index[name] = { name, description: desc, tags, category: flags.category, created: now, updated: now };
fs.fsWriteJSON('/skills/index.json', index);
this.terminal.print(`✅ Skill "${name}" created (${(skillContent.length / 1024).toFixed(1)}KB)`, 'success');
}
_cmdSkillPatch(args) {
if (args.length < 2) {
- this.terminal.print('Usage: skill-patch <name> "<full content>"', 'warning');
+ this.terminal.print('Usage: skill-patch <name> <path-or-text> (a VFS path keeps newlines)', 'warning');
return;
}
const name = args[0].replace(/\.md$/, '');
- const content = args.slice(1).join(' ');
+ let content = args.slice(1).join(' ');
+ if (content.startsWith('/')) {
+ try {
+ const fromFile = this.terminal.fs.readFile(content);
+ if (typeof fromFile === 'string' && fromFile.length) content = fromFile;
+ } catch (e) { /* literal text */ }
+ }
const fs = this.terminal.fs;
const index = fs.fsReadJSON('/skills/index.json') || {};
if (!index[name]) {