@probelabs/probe 0.6.0-rc200 → 0.6.0-rc202

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.
Files changed (46) hide show
  1. package/README.md +31 -1
  2. package/bin/binaries/probe-v0.6.0-rc202-aarch64-apple-darwin.tar.gz +0 -0
  3. package/bin/binaries/probe-v0.6.0-rc202-aarch64-unknown-linux-musl.tar.gz +0 -0
  4. package/bin/binaries/probe-v0.6.0-rc202-x86_64-apple-darwin.tar.gz +0 -0
  5. package/bin/binaries/probe-v0.6.0-rc202-x86_64-pc-windows-msvc.zip +0 -0
  6. package/bin/binaries/probe-v0.6.0-rc202-x86_64-unknown-linux-musl.tar.gz +0 -0
  7. package/build/agent/ProbeAgent.d.ts +19 -1
  8. package/build/agent/ProbeAgent.js +310 -14
  9. package/build/agent/index.js +8615 -394
  10. package/build/agent/probeTool.js +2 -2
  11. package/build/agent/schemaUtils.js +37 -18
  12. package/build/agent/shared/prompts.js +17 -0
  13. package/build/agent/skills/formatting.js +23 -0
  14. package/build/agent/skills/parser.js +162 -0
  15. package/build/agent/skills/registry.js +185 -0
  16. package/build/agent/skills/tools.js +65 -0
  17. package/build/agent/tools.js +44 -0
  18. package/build/delegate.js +27 -7
  19. package/build/tools/common.js +17 -4
  20. package/build/tools/system-message.js +4 -4
  21. package/build/tools/vercel.js +243 -36
  22. package/cjs/agent/ProbeAgent.cjs +8769 -583
  23. package/cjs/index.cjs +8794 -608
  24. package/index.d.ts +16 -0
  25. package/package.json +2 -1
  26. package/scripts/postinstall.js +10 -4
  27. package/src/agent/ProbeAgent.d.ts +19 -1
  28. package/src/agent/ProbeAgent.js +310 -14
  29. package/src/agent/index.js +21 -1
  30. package/src/agent/probeTool.js +2 -2
  31. package/src/agent/schemaUtils.js +37 -18
  32. package/src/agent/shared/prompts.js +17 -0
  33. package/src/agent/skills/formatting.js +23 -0
  34. package/src/agent/skills/parser.js +162 -0
  35. package/src/agent/skills/registry.js +185 -0
  36. package/src/agent/skills/tools.js +65 -0
  37. package/src/agent/tools.js +44 -0
  38. package/src/delegate.js +27 -7
  39. package/src/tools/common.js +17 -4
  40. package/src/tools/system-message.js +4 -4
  41. package/src/tools/vercel.js +243 -36
  42. package/bin/binaries/probe-v0.6.0-rc200-aarch64-apple-darwin.tar.gz +0 -0
  43. package/bin/binaries/probe-v0.6.0-rc200-aarch64-unknown-linux-musl.tar.gz +0 -0
  44. package/bin/binaries/probe-v0.6.0-rc200-x86_64-apple-darwin.tar.gz +0 -0
  45. package/bin/binaries/probe-v0.6.0-rc200-x86_64-pc-windows-msvc.zip +0 -0
  46. package/bin/binaries/probe-v0.6.0-rc200-x86_64-unknown-linux-musl.tar.gz +0 -0
@@ -59,7 +59,7 @@ export function clearToolExecutionData(sessionId) {
59
59
  }
60
60
 
61
61
  // Wrap the tools to emit events and handle cancellation
62
- const wrapToolWithEmitter = (tool, toolName, baseExecute) => {
62
+ export const wrapToolWithEmitter = (tool, toolName, baseExecute) => {
63
63
  return {
64
64
  ...tool, // Spread schema, description etc.
65
65
  execute: async (params) => { // The execute function now receives parsed params
@@ -407,4 +407,4 @@ export const searchFilesTool = {
407
407
 
408
408
  // Wrap the additional tools
409
409
  export const listFilesToolInstance = wrapToolWithEmitter(listFilesTool, 'listFiles', listFilesTool.execute);
410
- export const searchFilesToolInstance = wrapToolWithEmitter(searchFilesTool, 'searchFiles', searchFilesTool.execute);
410
+ export const searchFilesToolInstance = wrapToolWithEmitter(searchFilesTool, 'searchFiles', searchFilesTool.execute);
@@ -1143,6 +1143,28 @@ export function replaceMermaidDiagramsInMarkdown(originalResponse, correctedDiag
1143
1143
  return modifiedResponse;
1144
1144
  }
1145
1145
 
1146
+ function replaceSingleMermaidDiagramInResponse(response, originalDiagram, newContent) {
1147
+ if (!originalDiagram) {
1148
+ return response;
1149
+ }
1150
+
1151
+ const attributesStr = originalDiagram.attributes ? ` ${originalDiagram.attributes}` : '';
1152
+ const newCodeBlock = `\`\`\`mermaid${attributesStr}\n${newContent}\n\`\`\``;
1153
+
1154
+ if (originalDiagram.isInJson) {
1155
+ return replaceMermaidDiagramsInJson(response, [
1156
+ {
1157
+ ...originalDiagram,
1158
+ content: newContent
1159
+ }
1160
+ ]);
1161
+ }
1162
+
1163
+ return response.slice(0, originalDiagram.startIndex) +
1164
+ newCodeBlock +
1165
+ response.slice(originalDiagram.endIndex);
1166
+ }
1167
+
1146
1168
  /**
1147
1169
  * Validate a single Mermaid diagram
1148
1170
  * @param {string} diagram - Mermaid diagram code
@@ -1849,12 +1871,11 @@ export async function validateAndFixMermaidResponse(response, options = {}) {
1849
1871
  if (maidResult.errors.length === 0) {
1850
1872
  // Maid fixed it completely
1851
1873
  const originalDiagram = diagrams[invalidDiagram.originalIndex];
1852
- const attributesStr = originalDiagram.attributes ? ` ${originalDiagram.attributes}` : '';
1853
- const newCodeBlock = `\`\`\`mermaid${attributesStr}\n${maidResult.fixed}\n\`\`\``;
1854
-
1855
- fixedResponse = fixedResponse.slice(0, originalDiagram.startIndex) +
1856
- newCodeBlock +
1857
- fixedResponse.slice(originalDiagram.endIndex);
1874
+ fixedResponse = replaceSingleMermaidDiagramInResponse(
1875
+ fixedResponse,
1876
+ originalDiagram,
1877
+ maidResult.fixed
1878
+ );
1858
1879
 
1859
1880
  fixingResults.push({
1860
1881
  diagramIndex: invalidDiagram.originalIndex,
@@ -1874,12 +1895,11 @@ export async function validateAndFixMermaidResponse(response, options = {}) {
1874
1895
  } else if (maidResult.wasFixed) {
1875
1896
  // Maid improved it but didn't fix everything - update content for AI fixing
1876
1897
  const originalDiagram = diagrams[invalidDiagram.originalIndex];
1877
- const attributesStr = originalDiagram.attributes ? ` ${originalDiagram.attributes}` : '';
1878
- const newCodeBlock = `\`\`\`mermaid${attributesStr}\n${maidResult.fixed}\n\`\`\``;
1879
-
1880
- fixedResponse = fixedResponse.slice(0, originalDiagram.startIndex) +
1881
- newCodeBlock +
1882
- fixedResponse.slice(originalDiagram.endIndex);
1898
+ fixedResponse = replaceSingleMermaidDiagramInResponse(
1899
+ fixedResponse,
1900
+ originalDiagram,
1901
+ maidResult.fixed
1902
+ );
1883
1903
 
1884
1904
  fixingResults.push({
1885
1905
  diagramIndex: invalidDiagram.originalIndex,
@@ -1987,12 +2007,11 @@ export async function validateAndFixMermaidResponse(response, options = {}) {
1987
2007
  if (fixedContent && fixedContent !== invalidDiagram.content) {
1988
2008
  // Replace the diagram in the response
1989
2009
  const originalDiagram = updatedDiagrams[invalidDiagram.originalIndex];
1990
- const attributesStr = originalDiagram.attributes ? ` ${originalDiagram.attributes}` : '';
1991
- const newCodeBlock = `\`\`\`mermaid${attributesStr}\n${fixedContent}\n\`\`\``;
1992
-
1993
- fixedResponse = fixedResponse.slice(0, originalDiagram.startIndex) +
1994
- newCodeBlock +
1995
- fixedResponse.slice(originalDiagram.endIndex);
2010
+ fixedResponse = replaceSingleMermaidDiagramInResponse(
2011
+ fixedResponse,
2012
+ originalDiagram,
2013
+ fixedContent
2014
+ );
1996
2015
 
1997
2016
  // Find existing result or create new one
1998
2017
  const existingResultIndex = fixingResults.findIndex(r => r.diagramIndex === invalidDiagram.originalIndex);
@@ -20,6 +20,23 @@ When providing answers:
20
20
  - Group references by file when multiple locations are from the same file
21
21
  - Include brief descriptions of what each reference contains`,
22
22
 
23
+ 'code-searcher': `You are ProbeChat Code Searcher, a specialized AI assistant focused ONLY on locating relevant code. Your sole job is to find and return ALL relevant code locations. Do NOT answer questions or explain anything.
24
+
25
+ When searching:
26
+ - Use only the search tool
27
+ - Run additional searches only if needed to capture all relevant locations
28
+ - Prefer specific, focused queries
29
+
30
+ Output format (MANDATORY):
31
+ - Return ONLY valid JSON with a single top-level key: "targets"
32
+ - "targets" must be an array of strings
33
+ - Each string must be a file target in one of these formats:
34
+ - "path/to/file.ext#SymbolName"
35
+ - "path/to/file.ext:line"
36
+ - "path/to/file.ext:start-end"
37
+ - Prefer #SymbolName when a function/class name is clear; otherwise use line numbers
38
+ - Deduplicate targets and keep them concise`,
39
+
23
40
  'architect': `You are ProbeChat Architect, a specialized AI assistant focused on software architecture and design. Your primary function is to help users understand, analyze, and design software systems using the provided code analysis tools.
24
41
 
25
42
  When analyzing code:
@@ -0,0 +1,23 @@
1
+ function escapeXml(value) {
2
+ return value
3
+ .replace(/&/g, '&')
4
+ .replace(/</g, '&lt;')
5
+ .replace(/>/g, '&gt;')
6
+ .replace(/"/g, '&quot;')
7
+ .replace(/'/g, '&apos;');
8
+ }
9
+
10
+ export function formatAvailableSkillsXml(skills) {
11
+ if (!skills || skills.length === 0) return '';
12
+
13
+ const lines = ['<available_skills>'];
14
+ for (const skill of skills) {
15
+ lines.push(' <skill>');
16
+ lines.push(` <name>${escapeXml(skill.name)}</name>`);
17
+ lines.push(` <description>${escapeXml(skill.description || '')}</description>`);
18
+ lines.push(' </skill>');
19
+ }
20
+ lines.push('</available_skills>');
21
+
22
+ return lines.join('\n');
23
+ }
@@ -0,0 +1,162 @@
1
+ import { readFile } from 'fs/promises';
2
+ import { dirname } from 'path';
3
+ import YAML from 'yaml';
4
+
5
+ const SKILL_NAME_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
6
+ const MAX_SKILL_NAME_LENGTH = 64;
7
+ const MAX_DESCRIPTION_CHARS = 400;
8
+
9
+ function isValidSkillName(name) {
10
+ if (!name || typeof name !== 'string') return false;
11
+ if (name.length > MAX_SKILL_NAME_LENGTH) return false;
12
+ return SKILL_NAME_REGEX.test(name);
13
+ }
14
+
15
+ function getFirstParagraph(text) {
16
+ const lines = text.split(/\r?\n/);
17
+ const paragraphLines = [];
18
+
19
+ for (const line of lines) {
20
+ if (line.trim() === '') {
21
+ if (paragraphLines.length > 0) {
22
+ break;
23
+ }
24
+ continue;
25
+ }
26
+
27
+ paragraphLines.push(line.trim());
28
+ }
29
+
30
+ return paragraphLines.join(' ').trim();
31
+ }
32
+
33
+ function extractFrontmatter(content) {
34
+ const trimmed = content.replace(/^\uFEFF/, '');
35
+ const lines = trimmed.split(/\r?\n/);
36
+
37
+ if (lines.length === 0 || lines[0].trim() !== '---') {
38
+ return { hasFrontmatter: false, frontmatterText: '', body: trimmed };
39
+ }
40
+
41
+ let endIndex = -1;
42
+ for (let i = 1; i < lines.length; i++) {
43
+ if (lines[i].trim() === '---') {
44
+ endIndex = i;
45
+ break;
46
+ }
47
+ }
48
+
49
+ if (endIndex === -1) {
50
+ return { hasFrontmatter: true, invalid: true, frontmatterText: '', body: '' };
51
+ }
52
+
53
+ const frontmatterText = lines.slice(1, endIndex).join('\n');
54
+ const body = lines.slice(endIndex + 1).join('\n');
55
+
56
+ return { hasFrontmatter: true, frontmatterText, body };
57
+ }
58
+
59
+ function truncateDescription(text) {
60
+ if (!text) return '';
61
+ const trimmed = text.trim();
62
+ if (trimmed.length <= MAX_DESCRIPTION_CHARS) return trimmed;
63
+ return `${trimmed.slice(0, MAX_DESCRIPTION_CHARS - 3)}...`;
64
+ }
65
+
66
+ function normalizeFrontmatter(data) {
67
+ if (!data || typeof data !== 'object' || Array.isArray(data)) return {};
68
+ return data;
69
+ }
70
+
71
+ function deriveSkillName(rawName, directoryName, { debug, skillFilePath }) {
72
+ const candidate = rawName || directoryName;
73
+ if (isValidSkillName(candidate)) return candidate;
74
+
75
+ if (rawName && debug) {
76
+ console.warn(`[skills] Invalid skill name '${rawName}' in ${skillFilePath}; falling back to directory name`);
77
+ }
78
+
79
+ if (isValidSkillName(directoryName)) {
80
+ return directoryName;
81
+ }
82
+
83
+ if (debug) {
84
+ console.warn(`[skills] Invalid directory name '${directoryName}' for skill at ${skillFilePath}; skipping`);
85
+ }
86
+
87
+ return null;
88
+ }
89
+
90
+ function deriveDescription(rawDescription, body) {
91
+ let description = rawDescription || '';
92
+ if (!description) {
93
+ description = getFirstParagraph(body);
94
+ }
95
+ return truncateDescription(description);
96
+ }
97
+
98
+ export function stripFrontmatter(content) {
99
+ const { body } = extractFrontmatter(content);
100
+ return body.trim();
101
+ }
102
+
103
+ function createError(code, message) {
104
+ return { code, message };
105
+ }
106
+
107
+ export async function parseSkillFile(skillFilePath, directoryName) {
108
+ let content;
109
+ try {
110
+ content = await readFile(skillFilePath, 'utf8');
111
+ } catch (error) {
112
+ return {
113
+ skill: null,
114
+ error: createError('read_failed', error.message)
115
+ };
116
+ }
117
+
118
+ const { hasFrontmatter, frontmatterText, body, invalid } = extractFrontmatter(content);
119
+ if (invalid) {
120
+ return {
121
+ skill: null,
122
+ error: createError('invalid_frontmatter', 'Missing closing frontmatter delimiter')
123
+ };
124
+ }
125
+
126
+ let data = {};
127
+ if (hasFrontmatter) {
128
+ try {
129
+ data = YAML.parse(frontmatterText, { schema: 'failsafe' }) || {};
130
+ } catch (error) {
131
+ return {
132
+ skill: null,
133
+ error: createError('invalid_yaml', error.message)
134
+ };
135
+ }
136
+ }
137
+
138
+ data = normalizeFrontmatter(data);
139
+
140
+ const rawName = typeof data.name === 'string' ? data.name.trim() : '';
141
+ const name = deriveSkillName(rawName, directoryName, { debug: false, skillFilePath });
142
+ if (!name) {
143
+ return {
144
+ skill: null,
145
+ error: createError('invalid_name', 'Skill name is invalid')
146
+ };
147
+ }
148
+
149
+ const rawDescription = typeof data.description === 'string' ? data.description.trim() : '';
150
+ const description = deriveDescription(rawDescription, body);
151
+
152
+ return {
153
+ skill: {
154
+ name,
155
+ description,
156
+ skillFilePath,
157
+ directoryName,
158
+ sourceDir: dirname(skillFilePath)
159
+ },
160
+ error: null
161
+ };
162
+ }
@@ -0,0 +1,185 @@
1
+ import { existsSync } from 'fs';
2
+ import { readdir, readFile, realpath, lstat } from 'fs/promises';
3
+ import { resolve, join, isAbsolute, sep, relative } from 'path';
4
+ import { parseSkillFile, stripFrontmatter } from './parser.js';
5
+
6
+ const DEFAULT_SKILL_DIRS = ['.claude/skills', '.codex/skills', 'skills', '.skills'];
7
+ const SKILL_FILE_NAME = 'SKILL.md';
8
+
9
+ function isPathInside(basePath, targetPath) {
10
+ const base = resolve(basePath);
11
+ const target = resolve(targetPath);
12
+ const rel = relative(base, target);
13
+ if (rel === '') return true;
14
+ if (rel === '..' || rel.startsWith(`..${sep}`)) return false;
15
+ if (isAbsolute(rel)) return false;
16
+ return true;
17
+ }
18
+
19
+ function isSafeEntryName(name) {
20
+ if (!name || name === '.' || name === '..') return false;
21
+ if (name.includes('\0')) return false;
22
+ return !name.includes('/') && !name.includes('\\');
23
+ }
24
+
25
+ export class SkillRegistry {
26
+ constructor({ repoRoot, skillDirs = DEFAULT_SKILL_DIRS, debug = false } = {}) {
27
+ this.repoRoot = repoRoot ? resolve(repoRoot) : process.cwd();
28
+ this.repoRootReal = null;
29
+ this.skillDirs = Array.isArray(skillDirs) && skillDirs.length > 0 ? skillDirs : DEFAULT_SKILL_DIRS;
30
+ this.debug = debug;
31
+ this.skills = [];
32
+ this.skillsByName = new Map();
33
+ this.loadErrors = [];
34
+ this.loaded = false;
35
+ }
36
+
37
+ async loadSkills() {
38
+ if (this.loaded) return this.skills;
39
+
40
+ this.loadErrors = [];
41
+ this.repoRootReal = await this._resolveRealPath(this.repoRoot);
42
+ if (!this.repoRootReal) {
43
+ if (this.debug) {
44
+ console.warn(`[skills] Failed to resolve repo root: ${this.repoRoot}`);
45
+ }
46
+ this.loaded = true;
47
+ return this.skills;
48
+ }
49
+
50
+ const discovered = [];
51
+ for (const skillDir of this.skillDirs) {
52
+ const resolvedDir = await this._resolveSkillDir(skillDir);
53
+ if (!resolvedDir) continue;
54
+ const skillsInDir = await this._scanSkillDir(resolvedDir);
55
+ discovered.push(...skillsInDir);
56
+ }
57
+
58
+ this.skills = discovered;
59
+ this.loaded = true;
60
+ return this.skills;
61
+ }
62
+
63
+ getSkills() {
64
+ return this.skills;
65
+ }
66
+
67
+ getLoadErrors() {
68
+ return this.loadErrors;
69
+ }
70
+
71
+ getSkill(name) {
72
+ return this.skillsByName.get(name);
73
+ }
74
+
75
+ async loadSkillInstructions(name) {
76
+ const skill = this.skillsByName.get(name);
77
+ if (!skill) return null;
78
+
79
+ const content = await readFile(skill.skillFilePath, 'utf8');
80
+ return stripFrontmatter(content);
81
+ }
82
+
83
+ async _resolveRealPath(target) {
84
+ try {
85
+ return await realpath(target);
86
+ } catch (_error) {
87
+ return null;
88
+ }
89
+ }
90
+
91
+ async _resolveSkillDir(skillDir) {
92
+ const resolved = isAbsolute(skillDir) ? resolve(skillDir) : resolve(this.repoRoot, skillDir);
93
+ const repoRoot = this.repoRootReal || resolve(this.repoRoot);
94
+ const resolvedReal = await this._resolveRealPath(resolved);
95
+ if (!resolvedReal) return null;
96
+
97
+ if (!isPathInside(repoRoot, resolvedReal)) {
98
+ if (this.debug) {
99
+ console.warn(`[skills] Skipping skill dir outside repo: ${resolvedReal}`);
100
+ }
101
+ return null;
102
+ }
103
+
104
+ return resolvedReal;
105
+ }
106
+
107
+ async _scanSkillDir(dirPath) {
108
+ if (!existsSync(dirPath)) return [];
109
+
110
+ let entries;
111
+ try {
112
+ entries = await readdir(dirPath, { withFileTypes: true });
113
+ } catch (error) {
114
+ if (this.debug) {
115
+ console.warn(`[skills] Failed to read skill dir ${dirPath}: ${error.message}`);
116
+ }
117
+ return [];
118
+ }
119
+
120
+ const results = [];
121
+ for (const entry of entries) {
122
+ if (!entry.isDirectory()) continue;
123
+ if (!isSafeEntryName(entry.name)) {
124
+ if (this.debug) {
125
+ console.warn(`[skills] Skipping unsafe skill directory name: ${entry.name}`);
126
+ }
127
+ continue;
128
+ }
129
+
130
+ const skillFolder = join(dirPath, entry.name);
131
+ const skillFilePath = join(skillFolder, SKILL_FILE_NAME);
132
+ let skillStat;
133
+ try {
134
+ skillStat = await lstat(skillFilePath);
135
+ } catch (_error) {
136
+ continue;
137
+ }
138
+
139
+ if (skillStat.isSymbolicLink()) {
140
+ if (this.debug) {
141
+ console.warn(`[skills] Skipping symlinked SKILL.md: ${skillFilePath}`);
142
+ }
143
+ continue;
144
+ }
145
+
146
+ const resolvedSkillPath = await this._resolveRealPath(skillFilePath);
147
+ if (!resolvedSkillPath || !isPathInside(dirPath, resolvedSkillPath)) {
148
+ if (this.debug) {
149
+ console.warn(`[skills] Skipping skill path outside directory: ${resolvedSkillPath || skillFilePath}`);
150
+ }
151
+ continue;
152
+ }
153
+ if (!existsSync(skillFilePath)) continue;
154
+
155
+ const { skill, error } = await parseSkillFile(skillFilePath, entry.name);
156
+ if (!skill) {
157
+ if (error) {
158
+ this.loadErrors.push({
159
+ path: skillFilePath,
160
+ code: error.code,
161
+ message: error.message
162
+ });
163
+ }
164
+ if (this.debug && error) {
165
+ console.warn(`[skills] Skipping ${skillFilePath}: ${error.code} (${error.message})`);
166
+ }
167
+ continue;
168
+ }
169
+
170
+ if (this.skillsByName.has(skill.name)) {
171
+ if (this.debug) {
172
+ console.warn(`[skills] Duplicate skill name '${skill.name}' at ${skillFolder}, skipping`);
173
+ }
174
+ continue;
175
+ }
176
+
177
+ this.skillsByName.set(skill.name, skill);
178
+ results.push(skill);
179
+ }
180
+
181
+ return results;
182
+ }
183
+ }
184
+
185
+ export { DEFAULT_SKILL_DIRS };
@@ -0,0 +1,65 @@
1
+ import { wrapToolWithEmitter } from '../probeTool.js';
2
+
3
+ function normalizeSkillName(name) {
4
+ return typeof name === 'string' ? name.trim() : '';
5
+ }
6
+
7
+ export function createSkillToolInstances({ registry, activeSkills }) {
8
+ const listSkillsTool = {
9
+ execute: async (params = {}) => {
10
+ const filter = typeof params.filter === 'string' ? params.filter.trim().toLowerCase() : '';
11
+ const skills = await registry.loadSkills();
12
+ const filtered = filter
13
+ ? skills.filter(skill =>
14
+ skill.name.toLowerCase().includes(filter) ||
15
+ (skill.description || '').toLowerCase().includes(filter)
16
+ )
17
+ : skills;
18
+
19
+ return {
20
+ skills: filtered.map(skill => ({
21
+ name: skill.name,
22
+ description: skill.description
23
+ }))
24
+ };
25
+ }
26
+ };
27
+
28
+ const useSkillTool = {
29
+ execute: async (params = {}) => {
30
+ const rawName = normalizeSkillName(params.name);
31
+ if (!rawName) {
32
+ throw new Error('Skill name is required');
33
+ }
34
+
35
+ await registry.loadSkills();
36
+ let skill = registry.getSkill(rawName);
37
+ if (!skill) {
38
+ skill = registry.getSkill(rawName.toLowerCase());
39
+ }
40
+
41
+ if (!skill) {
42
+ const available = registry.getSkills().map(s => s.name).join(', ') || 'None';
43
+ throw new Error(`Skill '${rawName}' not found. Available skills: ${available}`);
44
+ }
45
+
46
+ const instructions = await registry.loadSkillInstructions(skill.name);
47
+ if (!instructions) {
48
+ throw new Error(`Skill '${skill.name}' has no instructions`);
49
+ }
50
+
51
+ activeSkills.set(skill.name, { ...skill, instructions });
52
+
53
+ return {
54
+ name: skill.name,
55
+ description: skill.description,
56
+ instructions
57
+ };
58
+ }
59
+ };
60
+
61
+ return {
62
+ listSkillsToolInstance: wrapToolWithEmitter(listSkillsTool, 'listSkills', listSkillsTool.execute),
63
+ useSkillToolInstance: wrapToolWithEmitter(useSkillTool, 'useSkill', useSkillTool.execute)
64
+ };
65
+ }
@@ -172,6 +172,50 @@ User: Find all markdown files in the docs directory, but only at the top level.
172
172
  </examples>
173
173
  `;
174
174
 
175
+ // Define the listSkills tool XML definition
176
+ export const listSkillsToolDefinition = `
177
+ ## listSkills
178
+ Description: List available agent skills discovered in the repository.
179
+
180
+ Parameters:
181
+ - filter: (optional) Substring filter to match skill names or descriptions.
182
+
183
+ Usage Example:
184
+
185
+ <examples>
186
+
187
+ User: What skills are available?
188
+ <listSkills>
189
+ </listSkills>
190
+
191
+ User: Show me skills related to docs
192
+ <listSkills>
193
+ <filter>docs</filter>
194
+ </listSkills>
195
+
196
+ </examples>
197
+ `;
198
+
199
+ // Define the useSkill tool XML definition
200
+ export const useSkillToolDefinition = `
201
+ ## useSkill
202
+ Description: Load and activate a specific skill's instructions. Use this before following a skill's guidance.
203
+
204
+ Parameters:
205
+ - name: (required) The skill name to activate.
206
+
207
+ Usage Example:
208
+
209
+ <examples>
210
+
211
+ User: Use the onboarding skill
212
+ <useSkill>
213
+ <name>onboarding</name>
214
+ </useSkill>
215
+
216
+ </examples>
217
+ `;
218
+
175
219
  // Define the readImage tool XML definition
176
220
  export const readImageToolDefinition = `
177
221
  ## readImage