onbuzz 4.8.0 → 4.8.2

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.
@@ -1,277 +1,282 @@
1
- /**
2
- * Skills Tool - Global skills library for agents
3
- *
4
- * Purpose:
5
- * - Allow agents to discover, browse, and read reusable skill instructions
6
- * - Support progressive disclosure: list → describe → read-section → read
7
- * - Support CRUD operations and importing skills from disk
8
- * - Skills are global (shared across agents) and persist across package updates
9
- *
10
- * Actions:
11
- * - list: List all skills with descriptions, section headings, and sizes
12
- * - describe: Get full metadata for a skill without loading content
13
- * - read: Read a skill's full content
14
- * - read-section: Read only a specific section of a skill
15
- * - read-file: Read a supporting file from a skill directory
16
- * - create: Create a new skill
17
- * - update: Update an existing skill
18
- * - delete: Remove a skill
19
- * - import: Import a skill from an external file or directory
20
- */
21
-
22
- import { BaseTool } from './baseTool.js';
23
- import { getSkillsService } from '../services/skillsService.js';
24
- import { SKILLS_ACTIONS } from '../utilities/toolConstants.js';
25
-
26
- class SkillsTool extends BaseTool {
27
- constructor(config = {}, logger = null) {
28
- super(config, logger);
29
-
30
- this.skillsService = null;
31
- this.requiresProject = false;
32
- this.isAsync = false;
33
- this.timeout = 30000;
34
- }
35
-
36
- async _ensureSkillsService() {
37
- if (!this.skillsService) {
38
- this.skillsService = getSkillsService(this.logger);
39
- await this.skillsService.initialize();
40
- }
41
- return this.skillsService;
42
- }
43
-
44
- getDescription() {
45
- return `Skills Tool: Browse and manage a global library of reusable skill instructions.
46
-
47
- Skills are structured knowledge packages containing instructions, checklists, templates, and reference files.
48
- Each skill is a directory with a skill.md file and optional supporting files.
49
- Always check for relevant skills when tackeling new tasks/changing task-focus.
50
-
51
- PROGRESSIVE DISCLOSURE Use this flow to minimize context usage:
52
- 1. "list" See all skills with section headings and sizes
53
- 2. "describe" Inspect a specific skill's structure in detail
54
- 3. "read-section" Load only the section you need
55
- 4. "read" → Load full content only when necessary
56
-
57
- ACTIONS:
58
-
59
- 1. LIST all skills:
60
- \`\`\`json
61
- { "toolId": "skills", "action": "list" }
62
- \`\`\`
63
- Returns: name, description, section headings, line count, file count for each skill.
64
-
65
- 2. DESCRIBE a skill (metadata only, no content):
66
- \`\`\`json
67
- { "toolId": "skills", "action": "describe", "name": "code-review" }
68
- \`\`\`
69
- Returns: description, sections with line ranges, file list, size.
70
-
71
- 3. READ full skill content:
72
- \`\`\`json
73
- { "toolId": "skills", "action": "read", "name": "code-review" }
74
- \`\`\`
75
- Returns: full skill.md content + list of files in the skill directory.
76
-
77
- 4. READ a specific SECTION:
78
- \`\`\`json
79
- { "toolId": "skills", "action": "read-section", "name": "code-review", "section": "Checklist" }
80
- \`\`\`
81
- Returns: only the content under the specified ## heading.
82
-
83
- 5. READ a supporting FILE:
84
- \`\`\`json
85
- { "toolId": "skills", "action": "read-file", "name": "email-templates", "file": "templates/welcome.html" }
86
- \`\`\`
87
- Returns: content of a specific file within the skill directory.
88
-
89
- 6. CREATE a new skill:
90
- \`\`\`json
91
- { "toolId": "skills", "action": "create", "name": "my-skill", "content": "# My Skill\\n\\nInstructions here...\\n\\n## Section One\\n..." }
92
- \`\`\`
93
- Optional: "files" array of { "path": "relative/path.ext", "content": "..." } for supporting files.
94
-
95
- 7. UPDATE an existing skill:
96
- \`\`\`json
97
- { "toolId": "skills", "action": "update", "name": "my-skill", "content": "# Updated content..." }
98
- \`\`\`
99
- Optional: "files" array to add/update supporting files.
100
-
101
- 8. DELETE a skill:
102
- \`\`\`json
103
- { "toolId": "skills", "action": "delete", "name": "my-skill" }
104
- \`\`\`
105
-
106
- 9. IMPORT a skill from disk:
107
- \`\`\`json
108
- { "toolId": "skills", "action": "import", "source": "/path/to/skill-dir-or-file" }
109
- \`\`\`
110
- Optional: "name" to override the derived skill name. If source is a directory, it must contain a skill.md file.
111
-
112
- SKILL NAMING: Names must be kebab-case (lowercase, hyphens). Example: "code-review", "email-templates".`;
113
- }
114
-
115
- parseParameters(content) {
116
- return content;
117
- }
118
-
119
- getRequiredParameters() {
120
- return ['action'];
121
- }
122
-
123
- getSupportedActions() {
124
- return Object.values(SKILLS_ACTIONS);
125
- }
126
-
127
- validateParameterTypes(params) {
128
- const errors = [];
129
- if (params.action && typeof params.action !== 'string') {
130
- errors.push('action must be a string');
131
- }
132
- if (params.name !== undefined && typeof params.name !== 'string') {
133
- errors.push('name must be a string');
134
- }
135
- if (params.content !== undefined && typeof params.content !== 'string') {
136
- errors.push('content must be a string');
137
- }
138
- if (params.section !== undefined && typeof params.section !== 'string') {
139
- errors.push('section must be a string');
140
- }
141
- if (params.file !== undefined && typeof params.file !== 'string') {
142
- errors.push('file must be a string');
143
- }
144
- if (params.source !== undefined && typeof params.source !== 'string') {
145
- errors.push('source must be a string');
146
- }
147
- return errors;
148
- }
149
-
150
- customValidateParameters(params) {
151
- const errors = [];
152
- const { action, name, content, section, file, source } = params;
153
-
154
- const validActions = this.getSupportedActions();
155
- if (!validActions.includes(action)) {
156
- errors.push(`Invalid action: "${action}". Valid actions: ${validActions.join(', ')}`);
157
- return errors;
158
- }
159
-
160
- // Action-specific required params
161
- const needsName = [SKILLS_ACTIONS.DESCRIBE, SKILLS_ACTIONS.READ, SKILLS_ACTIONS.READ_SECTION, SKILLS_ACTIONS.READ_FILE, SKILLS_ACTIONS.CREATE, SKILLS_ACTIONS.UPDATE, SKILLS_ACTIONS.DELETE];
162
- if (needsName.includes(action) && !name) {
163
- errors.push(`"name" is required for action "${action}"`);
164
- }
165
- if (action === SKILLS_ACTIONS.CREATE && !content) {
166
- errors.push('"content" is required for action "create"');
167
- }
168
- if (action === SKILLS_ACTIONS.READ_SECTION && !section) {
169
- errors.push('"section" is required for action "read-section"');
170
- }
171
- if (action === SKILLS_ACTIONS.READ_FILE && !file) {
172
- errors.push('"file" is required for action "read-file"');
173
- }
174
- if (action === SKILLS_ACTIONS.IMPORT && !source) {
175
- errors.push('"source" is required for action "import"');
176
- }
177
-
178
- return errors;
179
- }
180
-
181
- async execute(params, context = {}) {
182
- const service = await this._ensureSkillsService();
183
- const { action } = params;
184
-
185
- try {
186
- switch (action) {
187
- case SKILLS_ACTIONS.LIST:
188
- return this._formatResult(await service.listSkills(), 'Skills listed');
189
-
190
- case SKILLS_ACTIONS.DESCRIBE:
191
- return this._formatResult(await service.describeSkill(params.name), `Skill described: ${params.name}`);
192
-
193
- case SKILLS_ACTIONS.READ:
194
- return this._formatResult(await service.readSkill(params.name), `Skill read: ${params.name}`);
195
-
196
- case SKILLS_ACTIONS.READ_SECTION:
197
- return this._formatResult(await service.readSkillSection(params.name, params.section), `Section read: ${params.section}`);
198
-
199
- case SKILLS_ACTIONS.READ_FILE:
200
- return this._formatResult(await service.readSkillFile(params.name, params.file), `File read: ${params.file}`);
201
-
202
- case SKILLS_ACTIONS.CREATE:
203
- return this._formatResult(await service.createSkill(params.name, params.content, params.files || [], params.description || null), `Skill created: ${params.name}`);
204
-
205
- case SKILLS_ACTIONS.UPDATE:
206
- return this._formatResult(await service.updateSkill(params.name, params.content || null, params.files || [], params.description || null), `Skill updated: ${params.name}`);
207
-
208
- case SKILLS_ACTIONS.DELETE:
209
- await service.deleteSkill(params.name);
210
- return this._formatResult({ deleted: params.name }, `Skill deleted: ${params.name}`);
211
-
212
- case SKILLS_ACTIONS.IMPORT:
213
- return this._formatResult(await service.importSkill(params.source, params.name || null, params.description || null), `Skill imported: ${params.name || params.source}`);
214
-
215
- default:
216
- return { success: false, error: `Unknown action: ${action}` };
217
- }
218
- } catch (error) {
219
- return { success: false, error: error.message };
220
- }
221
- }
222
-
223
- _formatResult(data, message) {
224
- return {
225
- success: true,
226
- result: data,
227
- message
228
- };
229
- }
230
-
231
- getParameterSchema() {
232
- return {
233
- type: 'object',
234
- required: ['action'],
235
- properties: {
236
- action: {
237
- type: 'string',
238
- enum: Object.values(SKILLS_ACTIONS),
239
- description: 'The skill action to perform'
240
- },
241
- name: {
242
- type: 'string',
243
- description: 'Skill name (kebab-case)'
244
- },
245
- content: {
246
- type: 'string',
247
- description: 'Skill content (markdown)'
248
- },
249
- section: {
250
- type: 'string',
251
- description: 'Section heading to read (for read-section action)'
252
- },
253
- file: {
254
- type: 'string',
255
- description: 'Relative file path within skill directory (for read-file action)'
256
- },
257
- source: {
258
- type: 'string',
259
- description: 'Source file or directory path (for import action)'
260
- },
261
- files: {
262
- type: 'array',
263
- items: {
264
- type: 'object',
265
- properties: {
266
- path: { type: 'string' },
267
- content: { type: 'string' }
268
- }
269
- },
270
- description: 'Additional supporting files (for create/update actions)'
271
- }
272
- }
273
- };
274
- }
275
- }
276
-
277
- export default SkillsTool;
1
+ /**
2
+ * Skills Tool - Global skills library for agents
3
+ *
4
+ * Purpose:
5
+ * - Allow agents to discover, browse, and read reusable skill instructions
6
+ * - Support progressive disclosure: list → describe → read-section → read
7
+ * - Support CRUD operations and importing skills from disk
8
+ * - Skills are global (shared across agents) and persist across package updates
9
+ *
10
+ * Actions:
11
+ * - list: List all skills with descriptions, section headings, and sizes
12
+ * - describe: Get full metadata for a skill without loading content
13
+ * - read: Read a skill's full content
14
+ * - read-section: Read only a specific section of a skill
15
+ * - read-file: Read a supporting file from a skill directory
16
+ * - create: Create a new skill
17
+ * - update: Update an existing skill
18
+ * - delete: Remove a skill
19
+ * - import: Import a skill from an external file or directory
20
+ */
21
+
22
+ import { BaseTool } from './baseTool.js';
23
+ import { getSkillsService } from '../services/skillsService.js';
24
+ import { SKILLS_ACTIONS } from '../utilities/toolConstants.js';
25
+
26
+ class SkillsTool extends BaseTool {
27
+ constructor(config = {}, logger = null) {
28
+ super(config, logger);
29
+
30
+ this.skillsService = null;
31
+ this.requiresProject = false;
32
+ this.isAsync = false;
33
+ this.timeout = 30000;
34
+ }
35
+
36
+ async _ensureSkillsService() {
37
+ if (!this.skillsService) {
38
+ this.skillsService = getSkillsService(this.logger);
39
+ await this.skillsService.initialize();
40
+ }
41
+ return this.skillsService;
42
+ }
43
+
44
+ getDescription() {
45
+ return `Skills Tool: Browse and manage a global library of reusable skill instructions.
46
+
47
+ Skills are structured knowledge packages containing instructions, checklists, templates, and reference files.
48
+ Each skill is a directory with a skill.md file and optional supporting files.
49
+
50
+ ★ PROACTIVE USE — When a new task arrives or your focus shifts, your FIRST move should be:
51
+ \`{ "toolId": "skills", "action": "list" }\`
52
+ Skim the names + descriptions. If anything looks relevant, "describe" it and follow its
53
+ checklist instead of improvising. The team curates skills specifically so you don't have
54
+ to re-derive recurring playbooks from scratch.
55
+
56
+ PROGRESSIVE DISCLOSURE — Use this flow to minimize context usage:
57
+ 1. "list" → See all skills with section headings and sizes
58
+ 2. "describe" → Inspect a specific skill's structure in detail
59
+ 3. "read-section" Load only the section you need
60
+ 4. "read" → Load full content only when necessary
61
+
62
+ ACTIONS:
63
+
64
+ 1. LIST all skills:
65
+ \`\`\`json
66
+ { "toolId": "skills", "action": "list" }
67
+ \`\`\`
68
+ Returns: name, description, section headings, line count, file count for each skill.
69
+
70
+ 2. DESCRIBE a skill (metadata only, no content):
71
+ \`\`\`json
72
+ { "toolId": "skills", "action": "describe", "name": "code-review" }
73
+ \`\`\`
74
+ Returns: description, sections with line ranges, file list, size.
75
+
76
+ 3. READ full skill content:
77
+ \`\`\`json
78
+ { "toolId": "skills", "action": "read", "name": "code-review" }
79
+ \`\`\`
80
+ Returns: full skill.md content + list of files in the skill directory.
81
+
82
+ 4. READ a specific SECTION:
83
+ \`\`\`json
84
+ { "toolId": "skills", "action": "read-section", "name": "code-review", "section": "Checklist" }
85
+ \`\`\`
86
+ Returns: only the content under the specified ## heading.
87
+
88
+ 5. READ a supporting FILE:
89
+ \`\`\`json
90
+ { "toolId": "skills", "action": "read-file", "name": "email-templates", "file": "templates/welcome.html" }
91
+ \`\`\`
92
+ Returns: content of a specific file within the skill directory.
93
+
94
+ 6. CREATE a new skill:
95
+ \`\`\`json
96
+ { "toolId": "skills", "action": "create", "name": "my-skill", "content": "# My Skill\\n\\nInstructions here...\\n\\n## Section One\\n..." }
97
+ \`\`\`
98
+ Optional: "files" array of { "path": "relative/path.ext", "content": "..." } for supporting files.
99
+
100
+ 7. UPDATE an existing skill:
101
+ \`\`\`json
102
+ { "toolId": "skills", "action": "update", "name": "my-skill", "content": "# Updated content..." }
103
+ \`\`\`
104
+ Optional: "files" array to add/update supporting files.
105
+
106
+ 8. DELETE a skill:
107
+ \`\`\`json
108
+ { "toolId": "skills", "action": "delete", "name": "my-skill" }
109
+ \`\`\`
110
+
111
+ 9. IMPORT a skill from disk:
112
+ \`\`\`json
113
+ { "toolId": "skills", "action": "import", "source": "/path/to/skill-dir-or-file" }
114
+ \`\`\`
115
+ Optional: "name" to override the derived skill name. If source is a directory, it must contain a skill.md file.
116
+
117
+ SKILL NAMING: Names must be kebab-case (lowercase, hyphens). Example: "code-review", "email-templates".`;
118
+ }
119
+
120
+ parseParameters(content) {
121
+ return content;
122
+ }
123
+
124
+ getRequiredParameters() {
125
+ return ['action'];
126
+ }
127
+
128
+ getSupportedActions() {
129
+ return Object.values(SKILLS_ACTIONS);
130
+ }
131
+
132
+ validateParameterTypes(params) {
133
+ const errors = [];
134
+ if (params.action && typeof params.action !== 'string') {
135
+ errors.push('action must be a string');
136
+ }
137
+ if (params.name !== undefined && typeof params.name !== 'string') {
138
+ errors.push('name must be a string');
139
+ }
140
+ if (params.content !== undefined && typeof params.content !== 'string') {
141
+ errors.push('content must be a string');
142
+ }
143
+ if (params.section !== undefined && typeof params.section !== 'string') {
144
+ errors.push('section must be a string');
145
+ }
146
+ if (params.file !== undefined && typeof params.file !== 'string') {
147
+ errors.push('file must be a string');
148
+ }
149
+ if (params.source !== undefined && typeof params.source !== 'string') {
150
+ errors.push('source must be a string');
151
+ }
152
+ return errors;
153
+ }
154
+
155
+ customValidateParameters(params) {
156
+ const errors = [];
157
+ const { action, name, content, section, file, source } = params;
158
+
159
+ const validActions = this.getSupportedActions();
160
+ if (!validActions.includes(action)) {
161
+ errors.push(`Invalid action: "${action}". Valid actions: ${validActions.join(', ')}`);
162
+ return errors;
163
+ }
164
+
165
+ // Action-specific required params
166
+ const needsName = [SKILLS_ACTIONS.DESCRIBE, SKILLS_ACTIONS.READ, SKILLS_ACTIONS.READ_SECTION, SKILLS_ACTIONS.READ_FILE, SKILLS_ACTIONS.CREATE, SKILLS_ACTIONS.UPDATE, SKILLS_ACTIONS.DELETE];
167
+ if (needsName.includes(action) && !name) {
168
+ errors.push(`"name" is required for action "${action}"`);
169
+ }
170
+ if (action === SKILLS_ACTIONS.CREATE && !content) {
171
+ errors.push('"content" is required for action "create"');
172
+ }
173
+ if (action === SKILLS_ACTIONS.READ_SECTION && !section) {
174
+ errors.push('"section" is required for action "read-section"');
175
+ }
176
+ if (action === SKILLS_ACTIONS.READ_FILE && !file) {
177
+ errors.push('"file" is required for action "read-file"');
178
+ }
179
+ if (action === SKILLS_ACTIONS.IMPORT && !source) {
180
+ errors.push('"source" is required for action "import"');
181
+ }
182
+
183
+ return errors;
184
+ }
185
+
186
+ async execute(params, context = {}) {
187
+ const service = await this._ensureSkillsService();
188
+ const { action } = params;
189
+
190
+ try {
191
+ switch (action) {
192
+ case SKILLS_ACTIONS.LIST:
193
+ return this._formatResult(await service.listSkills(), 'Skills listed');
194
+
195
+ case SKILLS_ACTIONS.DESCRIBE:
196
+ return this._formatResult(await service.describeSkill(params.name), `Skill described: ${params.name}`);
197
+
198
+ case SKILLS_ACTIONS.READ:
199
+ return this._formatResult(await service.readSkill(params.name), `Skill read: ${params.name}`);
200
+
201
+ case SKILLS_ACTIONS.READ_SECTION:
202
+ return this._formatResult(await service.readSkillSection(params.name, params.section), `Section read: ${params.section}`);
203
+
204
+ case SKILLS_ACTIONS.READ_FILE:
205
+ return this._formatResult(await service.readSkillFile(params.name, params.file), `File read: ${params.file}`);
206
+
207
+ case SKILLS_ACTIONS.CREATE:
208
+ return this._formatResult(await service.createSkill(params.name, params.content, params.files || [], params.description || null), `Skill created: ${params.name}`);
209
+
210
+ case SKILLS_ACTIONS.UPDATE:
211
+ return this._formatResult(await service.updateSkill(params.name, params.content || null, params.files || [], params.description || null), `Skill updated: ${params.name}`);
212
+
213
+ case SKILLS_ACTIONS.DELETE:
214
+ await service.deleteSkill(params.name);
215
+ return this._formatResult({ deleted: params.name }, `Skill deleted: ${params.name}`);
216
+
217
+ case SKILLS_ACTIONS.IMPORT:
218
+ return this._formatResult(await service.importSkill(params.source, params.name || null, params.description || null), `Skill imported: ${params.name || params.source}`);
219
+
220
+ default:
221
+ return { success: false, error: `Unknown action: ${action}` };
222
+ }
223
+ } catch (error) {
224
+ return { success: false, error: error.message };
225
+ }
226
+ }
227
+
228
+ _formatResult(data, message) {
229
+ return {
230
+ success: true,
231
+ result: data,
232
+ message
233
+ };
234
+ }
235
+
236
+ getParameterSchema() {
237
+ return {
238
+ type: 'object',
239
+ required: ['action'],
240
+ properties: {
241
+ action: {
242
+ type: 'string',
243
+ enum: Object.values(SKILLS_ACTIONS),
244
+ description: 'The skill action to perform'
245
+ },
246
+ name: {
247
+ type: 'string',
248
+ description: 'Skill name (kebab-case)'
249
+ },
250
+ content: {
251
+ type: 'string',
252
+ description: 'Skill content (markdown)'
253
+ },
254
+ section: {
255
+ type: 'string',
256
+ description: 'Section heading to read (for read-section action)'
257
+ },
258
+ file: {
259
+ type: 'string',
260
+ description: 'Relative file path within skill directory (for read-file action)'
261
+ },
262
+ source: {
263
+ type: 'string',
264
+ description: 'Source file or directory path (for import action)'
265
+ },
266
+ files: {
267
+ type: 'array',
268
+ items: {
269
+ type: 'object',
270
+ properties: {
271
+ path: { type: 'string' },
272
+ content: { type: 'string' }
273
+ }
274
+ },
275
+ description: 'Additional supporting files (for create/update actions)'
276
+ }
277
+ }
278
+ };
279
+ }
280
+ }
281
+
282
+ export default SkillsTool;