toolpack-cli 0.1.0-SNAPSHOT

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 (99) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +131 -0
  3. package/dist/app.d.ts +1 -0
  4. package/dist/app.js +15 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +29 -0
  7. package/dist/commands/clear.d.ts +3 -0
  8. package/dist/commands/clear.js +15 -0
  9. package/dist/commands/help.d.ts +3 -0
  10. package/dist/commands/help.js +29 -0
  11. package/dist/commands/index.d.ts +15 -0
  12. package/dist/commands/index.js +16 -0
  13. package/dist/commands/info.d.ts +3 -0
  14. package/dist/commands/info.js +24 -0
  15. package/dist/commands/mode.d.ts +3 -0
  16. package/dist/commands/mode.js +51 -0
  17. package/dist/commands/model.d.ts +3 -0
  18. package/dist/commands/model.js +14 -0
  19. package/dist/commands/registry.d.ts +32 -0
  20. package/dist/commands/registry.js +86 -0
  21. package/dist/commands/tool-log.d.ts +3 -0
  22. package/dist/commands/tool-log.js +17 -0
  23. package/dist/commands/tool-search.d.ts +3 -0
  24. package/dist/commands/tool-search.js +57 -0
  25. package/dist/commands/tools.d.ts +3 -0
  26. package/dist/commands/tools.js +45 -0
  27. package/dist/commands/types.d.ts +25 -0
  28. package/dist/commands/types.js +4 -0
  29. package/dist/commands/version.d.ts +3 -0
  30. package/dist/commands/version.js +25 -0
  31. package/dist/components/AppInfo.d.ts +1 -0
  32. package/dist/components/AppInfo.js +10 -0
  33. package/dist/components/HomeInput.d.ts +11 -0
  34. package/dist/components/HomeInput.js +328 -0
  35. package/dist/components/Logo.d.ts +1 -0
  36. package/dist/components/Logo.js +15 -0
  37. package/dist/components/Markdown.d.ts +5 -0
  38. package/dist/components/Markdown.js +121 -0
  39. package/dist/components/ProviderBar.d.ts +12 -0
  40. package/dist/components/ProviderBar.js +32 -0
  41. package/dist/components/ShimmerText.d.ts +8 -0
  42. package/dist/components/ShimmerText.js +20 -0
  43. package/dist/components/ToolLogPopup.d.ts +7 -0
  44. package/dist/components/ToolLogPopup.js +87 -0
  45. package/dist/components/common/HistorySelect.d.ts +6 -0
  46. package/dist/components/common/HistorySelect.js +57 -0
  47. package/dist/components/common/Modal.d.ts +10 -0
  48. package/dist/components/common/Modal.js +13 -0
  49. package/dist/components/common/ModeSelect.d.ts +6 -0
  50. package/dist/components/common/ModeSelect.js +13 -0
  51. package/dist/components/common/ModelSelect.d.ts +9 -0
  52. package/dist/components/common/ModelSelect.js +45 -0
  53. package/dist/context/ConversationContext.d.ts +44 -0
  54. package/dist/context/ConversationContext.js +113 -0
  55. package/dist/context/ToolpackContext.d.ts +55 -0
  56. package/dist/context/ToolpackContext.js +221 -0
  57. package/dist/custom-providers/AnthropicCustomAdapter.d.ts +49 -0
  58. package/dist/custom-providers/AnthropicCustomAdapter.js +297 -0
  59. package/dist/custom-providers/XAIAdapter.d.ts +40 -0
  60. package/dist/custom-providers/XAIAdapter.js +295 -0
  61. package/dist/custom-tools/skill-tools/index.d.ts +33 -0
  62. package/dist/custom-tools/skill-tools/index.js +63 -0
  63. package/dist/custom-tools/skill-tools/tools/create/index.d.ts +2 -0
  64. package/dist/custom-tools/skill-tools/tools/create/index.js +93 -0
  65. package/dist/custom-tools/skill-tools/tools/create/schema.d.ts +6 -0
  66. package/dist/custom-tools/skill-tools/tools/create/schema.js +41 -0
  67. package/dist/custom-tools/skill-tools/tools/list/index.d.ts +2 -0
  68. package/dist/custom-tools/skill-tools/tools/list/index.js +113 -0
  69. package/dist/custom-tools/skill-tools/tools/list/schema.d.ts +6 -0
  70. package/dist/custom-tools/skill-tools/tools/list/schema.js +19 -0
  71. package/dist/custom-tools/skill-tools/tools/read/index.d.ts +2 -0
  72. package/dist/custom-tools/skill-tools/tools/read/index.js +124 -0
  73. package/dist/custom-tools/skill-tools/tools/read/schema.d.ts +6 -0
  74. package/dist/custom-tools/skill-tools/tools/read/schema.js +27 -0
  75. package/dist/custom-tools/skill-tools/tools/search/bm25.d.ts +71 -0
  76. package/dist/custom-tools/skill-tools/tools/search/bm25.js +305 -0
  77. package/dist/custom-tools/skill-tools/tools/search/index.d.ts +8 -0
  78. package/dist/custom-tools/skill-tools/tools/search/index.js +63 -0
  79. package/dist/custom-tools/skill-tools/tools/search/schema.d.ts +6 -0
  80. package/dist/custom-tools/skill-tools/tools/search/schema.js +19 -0
  81. package/dist/custom-tools/skill-tools/tools/search/skill-index.d.ts +54 -0
  82. package/dist/custom-tools/skill-tools/tools/search/skill-index.js +251 -0
  83. package/dist/custom-tools/skill-tools/tools/update/index.d.ts +2 -0
  84. package/dist/custom-tools/skill-tools/tools/update/index.js +115 -0
  85. package/dist/custom-tools/skill-tools/tools/update/schema.d.ts +6 -0
  86. package/dist/custom-tools/skill-tools/tools/update/schema.js +41 -0
  87. package/dist/screens/ChatScreen.d.ts +1 -0
  88. package/dist/screens/ChatScreen.js +327 -0
  89. package/dist/screens/HomeScreen.d.ts +1 -0
  90. package/dist/screens/HomeScreen.js +68 -0
  91. package/dist/screens/SettingsScreen.d.ts +1 -0
  92. package/dist/screens/SettingsScreen.js +35 -0
  93. package/dist/services/db.d.ts +31 -0
  94. package/dist/services/db.js +108 -0
  95. package/dist/theme/ThemeContext.d.ts +11 -0
  96. package/dist/theme/ThemeContext.js +31 -0
  97. package/dist/theme/theme.d.ts +17 -0
  98. package/dist/theme/theme.js +82 -0
  99. package/package.json +101 -0
@@ -0,0 +1,124 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { name, displayName, description, parameters, category, } from './schema.js';
4
+ const SKILLS_DIR = '.skills';
5
+ function parseSkillFile(content) {
6
+ // Parse frontmatter
7
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
8
+ const metadata = {
9
+ name: '',
10
+ title: '',
11
+ version: '1.0.0',
12
+ created: '',
13
+ updated: '',
14
+ tags: [],
15
+ };
16
+ if (frontmatterMatch && frontmatterMatch[1]) {
17
+ const frontmatter = frontmatterMatch[1];
18
+ const lines = frontmatter.split('\n');
19
+ for (const line of lines) {
20
+ const [key, ...valueParts] = line.split(':');
21
+ const value = valueParts.join(':').trim();
22
+ if (key === 'name')
23
+ metadata.name = value;
24
+ if (key === 'title')
25
+ metadata.title = value;
26
+ if (key === 'version')
27
+ metadata.version = value;
28
+ if (key === 'created')
29
+ metadata.created = value;
30
+ if (key === 'updated')
31
+ metadata.updated = value;
32
+ if (key === 'tags') {
33
+ const tagsMatch = value.match(/\[(.*)\]/);
34
+ if (tagsMatch) {
35
+ metadata.tags = (tagsMatch[1] || '')
36
+ .split(',')
37
+ .map(t => t.trim().replace(/"/g, ''))
38
+ .filter(Boolean);
39
+ }
40
+ }
41
+ }
42
+ }
43
+ // Parse sections
44
+ const descMatch = content.match(/## Description\n\n([\s\S]*?)(?=\n## |$)/);
45
+ const triggersMatch = content.match(/## Triggers\n\n([\s\S]*?)(?=\n## |$)/);
46
+ const instructionsMatch = content.match(/## Instructions\n\n([\s\S]*?)(?=\n## |$)/);
47
+ const examplesMatch = content.match(/## Examples\n\n([\s\S]*?)(?=\n## |$)/);
48
+ const triggers = [];
49
+ if (triggersMatch && triggersMatch[1]) {
50
+ const triggerLines = triggersMatch[1]
51
+ .split('\n')
52
+ .filter(l => l.startsWith('- '));
53
+ for (const line of triggerLines) {
54
+ const match = line.match(/- "(.*)"/);
55
+ if (match && match[1])
56
+ triggers.push(match[1]);
57
+ }
58
+ }
59
+ const examples = [];
60
+ if (examplesMatch && examplesMatch[1]) {
61
+ const exampleBlocks = (examplesMatch[1] || '')
62
+ .split(/### Example \d+\n\n/)
63
+ .filter(Boolean);
64
+ examples.push(...exampleBlocks.map(e => e.trim()));
65
+ }
66
+ return {
67
+ metadata,
68
+ description: descMatch && descMatch[1] ? descMatch[1].trim() : '',
69
+ triggers,
70
+ instructions: instructionsMatch && instructionsMatch[1]
71
+ ? instructionsMatch[1].trim()
72
+ : '',
73
+ examples,
74
+ };
75
+ }
76
+ async function execute(args) {
77
+ const skillName = args['name'];
78
+ const section = args['section'] || 'all';
79
+ const skillPath = path.join(process.cwd(), SKILLS_DIR, `${skillName}.skill.md`);
80
+ // Check if skill exists
81
+ try {
82
+ await fs.access(skillPath);
83
+ }
84
+ catch {
85
+ return JSON.stringify({
86
+ error: 'skill_not_found',
87
+ message: `Skill "${skillName}" not found. Use skill.list to see available skills.`,
88
+ }, null, 2);
89
+ }
90
+ // Read and parse the skill file
91
+ const content = await fs.readFile(skillPath, 'utf-8');
92
+ const parsed = parseSkillFile(content);
93
+ // Return requested section
94
+ if (section === 'metadata') {
95
+ return JSON.stringify({ skill: skillName, metadata: parsed.metadata }, null, 2);
96
+ }
97
+ else if (section === 'description') {
98
+ return JSON.stringify({ skill: skillName, description: parsed.description }, null, 2);
99
+ }
100
+ else if (section === 'triggers') {
101
+ return JSON.stringify({ skill: skillName, triggers: parsed.triggers }, null, 2);
102
+ }
103
+ else if (section === 'instructions') {
104
+ return JSON.stringify({ skill: skillName, instructions: parsed.instructions }, null, 2);
105
+ }
106
+ else if (section === 'examples') {
107
+ return JSON.stringify({ skill: skillName, examples: parsed.examples }, null, 2);
108
+ }
109
+ else {
110
+ return JSON.stringify({
111
+ skill: skillName,
112
+ path: skillPath,
113
+ ...parsed,
114
+ }, null, 2);
115
+ }
116
+ }
117
+ export const skillReadTool = {
118
+ name,
119
+ displayName,
120
+ description,
121
+ parameters,
122
+ category,
123
+ execute,
124
+ };
@@ -0,0 +1,6 @@
1
+ import { ToolParameters } from 'toolpack-sdk';
2
+ export declare const name = "skill.read";
3
+ export declare const displayName = "Read Skill";
4
+ export declare const description = "Read and parse a SKILL.md file to retrieve skill documentation including metadata, triggers, instructions, and examples.";
5
+ export declare const category = "productivity";
6
+ export declare const parameters: ToolParameters;
@@ -0,0 +1,27 @@
1
+ export const name = 'skill.read';
2
+ export const displayName = 'Read Skill';
3
+ export const description = 'Read and parse a SKILL.md file to retrieve skill documentation including metadata, triggers, instructions, and examples.';
4
+ export const category = 'productivity';
5
+ export const parameters = {
6
+ type: 'object',
7
+ properties: {
8
+ name: {
9
+ type: 'string',
10
+ description: 'The skill name to read (e.g., "code-review")',
11
+ },
12
+ section: {
13
+ type: 'string',
14
+ description: 'Optional: specific section to retrieve ("metadata", "description", "triggers", "instructions", "examples"). Default: returns all.',
15
+ enum: [
16
+ 'metadata',
17
+ 'description',
18
+ 'triggers',
19
+ 'instructions',
20
+ 'examples',
21
+ 'all',
22
+ ],
23
+ default: 'all',
24
+ },
25
+ },
26
+ required: ['name'],
27
+ };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * BM25 (Best Matching 25) Search Implementation
3
+ *
4
+ * A lightweight, zero-dependency implementation of the BM25 ranking algorithm
5
+ * for searching skill documents. BM25 is a bag-of-words retrieval function that
6
+ * ranks documents based on query term frequency and inverse document frequency.
7
+ */
8
+ export interface BM25Document {
9
+ id: string;
10
+ content: string;
11
+ fields: Record<string, string | string[]>;
12
+ }
13
+ export interface BM25SearchResult {
14
+ id: string;
15
+ score: number;
16
+ fields: Record<string, string | string[]>;
17
+ }
18
+ export interface BM25Options {
19
+ k1?: number;
20
+ b?: number;
21
+ }
22
+ export declare class BM25Index {
23
+ private documents;
24
+ private termFrequencies;
25
+ private documentLengths;
26
+ private avgDocLength;
27
+ private k1;
28
+ private b;
29
+ constructor(options?: BM25Options);
30
+ /**
31
+ * Tokenize text into searchable terms.
32
+ * Handles camelCase, snake_case, kebab-case, and common word boundaries.
33
+ */
34
+ private tokenize;
35
+ /**
36
+ * Common stop words to filter out
37
+ */
38
+ private isStopWord;
39
+ /**
40
+ * Add a document to the index
41
+ */
42
+ addDocument(doc: BM25Document): void;
43
+ /**
44
+ * Remove a document from the index
45
+ */
46
+ removeDocument(docId: string): void;
47
+ /**
48
+ * Clear the entire index
49
+ */
50
+ clear(): void;
51
+ /**
52
+ * Update average document length
53
+ */
54
+ private updateAvgDocLength;
55
+ /**
56
+ * Calculate IDF (Inverse Document Frequency) for a term
57
+ */
58
+ private idf;
59
+ /**
60
+ * Search the index with a query
61
+ */
62
+ search(query: string, limit?: number): BM25SearchResult[];
63
+ /**
64
+ * Get the number of indexed documents
65
+ */
66
+ get size(): number;
67
+ /**
68
+ * Check if a document exists in the index
69
+ */
70
+ has(docId: string): boolean;
71
+ }
@@ -0,0 +1,305 @@
1
+ /**
2
+ * BM25 (Best Matching 25) Search Implementation
3
+ *
4
+ * A lightweight, zero-dependency implementation of the BM25 ranking algorithm
5
+ * for searching skill documents. BM25 is a bag-of-words retrieval function that
6
+ * ranks documents based on query term frequency and inverse document frequency.
7
+ */
8
+ export class BM25Index {
9
+ constructor(options = {}) {
10
+ Object.defineProperty(this, "documents", {
11
+ enumerable: true,
12
+ configurable: true,
13
+ writable: true,
14
+ value: new Map()
15
+ });
16
+ Object.defineProperty(this, "termFrequencies", {
17
+ enumerable: true,
18
+ configurable: true,
19
+ writable: true,
20
+ value: new Map()
21
+ }); // term -> docId -> frequency
22
+ Object.defineProperty(this, "documentLengths", {
23
+ enumerable: true,
24
+ configurable: true,
25
+ writable: true,
26
+ value: new Map()
27
+ });
28
+ Object.defineProperty(this, "avgDocLength", {
29
+ enumerable: true,
30
+ configurable: true,
31
+ writable: true,
32
+ value: 0
33
+ });
34
+ Object.defineProperty(this, "k1", {
35
+ enumerable: true,
36
+ configurable: true,
37
+ writable: true,
38
+ value: void 0
39
+ });
40
+ Object.defineProperty(this, "b", {
41
+ enumerable: true,
42
+ configurable: true,
43
+ writable: true,
44
+ value: void 0
45
+ });
46
+ this.k1 = options.k1 ?? 1.5;
47
+ this.b = options.b ?? 0.75;
48
+ }
49
+ /**
50
+ * Tokenize text into searchable terms.
51
+ * Handles camelCase, snake_case, kebab-case, and common word boundaries.
52
+ */
53
+ tokenize(text) {
54
+ if (!text)
55
+ return [];
56
+ // Convert to lowercase
57
+ let normalized = text.toLowerCase();
58
+ // Split camelCase and PascalCase
59
+ normalized = normalized.replace(/([a-z])([A-Z])/g, '$1 $2');
60
+ // Replace non-alphanumeric with spaces
61
+ normalized = normalized.replace(/[^a-z0-9]/g, ' ');
62
+ // Split and filter
63
+ const tokens = normalized
64
+ .split(/\s+/)
65
+ .filter(token => token.length > 1) // Remove single chars
66
+ .filter(token => !this.isStopWord(token));
67
+ return tokens;
68
+ }
69
+ /**
70
+ * Common stop words to filter out
71
+ */
72
+ isStopWord(word) {
73
+ const stopWords = new Set([
74
+ 'a',
75
+ 'an',
76
+ 'the',
77
+ 'and',
78
+ 'or',
79
+ 'but',
80
+ 'in',
81
+ 'on',
82
+ 'at',
83
+ 'to',
84
+ 'for',
85
+ 'of',
86
+ 'with',
87
+ 'by',
88
+ 'from',
89
+ 'as',
90
+ 'is',
91
+ 'was',
92
+ 'are',
93
+ 'were',
94
+ 'been',
95
+ 'be',
96
+ 'have',
97
+ 'has',
98
+ 'had',
99
+ 'do',
100
+ 'does',
101
+ 'did',
102
+ 'will',
103
+ 'would',
104
+ 'could',
105
+ 'should',
106
+ 'may',
107
+ 'might',
108
+ 'must',
109
+ 'shall',
110
+ 'can',
111
+ 'need',
112
+ 'it',
113
+ 'its',
114
+ 'this',
115
+ 'that',
116
+ 'these',
117
+ 'those',
118
+ 'i',
119
+ 'you',
120
+ 'he',
121
+ 'she',
122
+ 'we',
123
+ 'they',
124
+ 'what',
125
+ 'which',
126
+ 'who',
127
+ 'when',
128
+ 'where',
129
+ 'why',
130
+ 'how',
131
+ 'all',
132
+ 'each',
133
+ 'every',
134
+ 'both',
135
+ 'few',
136
+ 'more',
137
+ 'most',
138
+ 'other',
139
+ 'some',
140
+ 'such',
141
+ 'no',
142
+ 'nor',
143
+ 'not',
144
+ 'only',
145
+ 'own',
146
+ 'same',
147
+ 'so',
148
+ 'than',
149
+ 'too',
150
+ 'very',
151
+ 'just',
152
+ 'also',
153
+ 'now',
154
+ 'here',
155
+ 'there',
156
+ 'then',
157
+ ]);
158
+ return stopWords.has(word);
159
+ }
160
+ /**
161
+ * Add a document to the index
162
+ */
163
+ addDocument(doc) {
164
+ this.documents.set(doc.id, doc);
165
+ // Build searchable content from all fields
166
+ let fullContent = doc.content;
167
+ for (const [, value] of Object.entries(doc.fields)) {
168
+ if (Array.isArray(value)) {
169
+ fullContent += ' ' + value.join(' ');
170
+ }
171
+ else {
172
+ fullContent += ' ' + value;
173
+ }
174
+ }
175
+ const tokens = this.tokenize(fullContent);
176
+ this.documentLengths.set(doc.id, tokens.length);
177
+ // Count term frequencies for this document
178
+ const termCounts = {};
179
+ for (const token of tokens) {
180
+ termCounts[token] = (termCounts[token] || 0) + 1;
181
+ }
182
+ // Update global term frequencies
183
+ for (const term of Object.keys(termCounts)) {
184
+ const count = termCounts[term] || 0;
185
+ if (!this.termFrequencies.has(term)) {
186
+ this.termFrequencies.set(term, new Map());
187
+ }
188
+ this.termFrequencies.get(term).set(doc.id, count);
189
+ }
190
+ // Recalculate average document length
191
+ this.updateAvgDocLength();
192
+ }
193
+ /**
194
+ * Remove a document from the index
195
+ */
196
+ removeDocument(docId) {
197
+ if (!this.documents.has(docId))
198
+ return;
199
+ // Remove from term frequencies
200
+ const termsToDelete = [];
201
+ this.termFrequencies.forEach((docFreqs, term) => {
202
+ docFreqs.delete(docId);
203
+ if (docFreqs.size === 0) {
204
+ termsToDelete.push(term);
205
+ }
206
+ });
207
+ for (const term of termsToDelete) {
208
+ this.termFrequencies.delete(term);
209
+ }
210
+ this.documents.delete(docId);
211
+ this.documentLengths.delete(docId);
212
+ this.updateAvgDocLength();
213
+ }
214
+ /**
215
+ * Clear the entire index
216
+ */
217
+ clear() {
218
+ this.documents.clear();
219
+ this.termFrequencies.clear();
220
+ this.documentLengths.clear();
221
+ this.avgDocLength = 0;
222
+ }
223
+ /**
224
+ * Update average document length
225
+ */
226
+ updateAvgDocLength() {
227
+ if (this.documentLengths.size === 0) {
228
+ this.avgDocLength = 0;
229
+ return;
230
+ }
231
+ let total = 0;
232
+ this.documentLengths.forEach(length => {
233
+ total += length;
234
+ });
235
+ this.avgDocLength = total / this.documentLengths.size;
236
+ }
237
+ /**
238
+ * Calculate IDF (Inverse Document Frequency) for a term
239
+ */
240
+ idf(term) {
241
+ const N = this.documents.size;
242
+ const docFreq = this.termFrequencies.get(term)?.size || 0;
243
+ if (docFreq === 0)
244
+ return 0;
245
+ // Standard BM25 IDF formula
246
+ return Math.log((N - docFreq + 0.5) / (docFreq + 0.5) + 1);
247
+ }
248
+ /**
249
+ * Search the index with a query
250
+ */
251
+ search(query, limit = 10) {
252
+ const queryTokens = this.tokenize(query);
253
+ if (queryTokens.length === 0) {
254
+ return [];
255
+ }
256
+ const scores = {};
257
+ // Calculate BM25 score for each document
258
+ this.documents.forEach((_, docId) => {
259
+ let score = 0;
260
+ const docLength = this.documentLengths.get(docId) || 0;
261
+ for (const term of queryTokens) {
262
+ const termDocFreqs = this.termFrequencies.get(term);
263
+ if (!termDocFreqs)
264
+ continue;
265
+ const tf = termDocFreqs.get(docId) || 0;
266
+ if (tf === 0)
267
+ continue;
268
+ const idf = this.idf(term);
269
+ // BM25 scoring formula
270
+ const numerator = tf * (this.k1 + 1);
271
+ const denominator = tf +
272
+ this.k1 * (1 - this.b + this.b * (docLength / this.avgDocLength));
273
+ score += idf * (numerator / denominator);
274
+ }
275
+ if (score > 0) {
276
+ scores[docId] = score;
277
+ }
278
+ });
279
+ // Sort by score descending
280
+ const results = [];
281
+ for (const docId of Object.keys(scores)) {
282
+ const score = scores[docId] || 0;
283
+ const doc = this.documents.get(docId);
284
+ results.push({
285
+ id: docId,
286
+ score,
287
+ fields: doc.fields,
288
+ });
289
+ }
290
+ results.sort((a, b) => b.score - a.score);
291
+ return results.slice(0, limit);
292
+ }
293
+ /**
294
+ * Get the number of indexed documents
295
+ */
296
+ get size() {
297
+ return this.documents.size;
298
+ }
299
+ /**
300
+ * Check if a document exists in the index
301
+ */
302
+ has(docId) {
303
+ return this.documents.has(docId);
304
+ }
305
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Skill Search Tool
3
+ *
4
+ * Uses BM25 search to find relevant skills based on a natural language query.
5
+ * Searches across skill names, titles, descriptions, tags, keywords, and triggers.
6
+ */
7
+ import { ToolDefinition } from 'toolpack-sdk';
8
+ export declare const skillSearchTool: ToolDefinition;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Skill Search Tool
3
+ *
4
+ * Uses BM25 search to find relevant skills based on a natural language query.
5
+ * Searches across skill names, titles, descriptions, tags, keywords, and triggers.
6
+ */
7
+ import { name, displayName, description, parameters, category, } from './schema.js';
8
+ import { skillIndex } from './skill-index.js';
9
+ async function execute(args) {
10
+ const query = args['query'];
11
+ const limit = args['limit'] || 5;
12
+ if (!query || query.trim().length === 0) {
13
+ return JSON.stringify({
14
+ error: 'invalid_query',
15
+ message: 'Search query cannot be empty.',
16
+ }, null, 2);
17
+ }
18
+ try {
19
+ const results = await skillIndex.search(query, limit);
20
+ if (results.length === 0) {
21
+ return JSON.stringify({
22
+ query,
23
+ results: [],
24
+ count: 0,
25
+ message: 'No skills found matching your query. Try different keywords or use skill.list to see all available skills.',
26
+ }, null, 2);
27
+ }
28
+ // Format results for AI consumption
29
+ const formattedResults = results.map((result, index) => ({
30
+ rank: index + 1,
31
+ name: result.skill.name,
32
+ title: result.skill.title,
33
+ score: Math.round(result.score * 100) / 100,
34
+ description: result.skill.description.length > 200
35
+ ? result.skill.description.substring(0, 200) + '...'
36
+ : result.skill.description,
37
+ tags: result.skill.tags,
38
+ triggers: result.skill.triggers.slice(0, 3), // Limit triggers shown
39
+ path: result.skill.path,
40
+ }));
41
+ return JSON.stringify({
42
+ query,
43
+ results: formattedResults,
44
+ count: results.length,
45
+ indexed_skills: skillIndex.size,
46
+ hint: 'Use skill.read with the skill name to get full details including instructions and examples.',
47
+ }, null, 2);
48
+ }
49
+ catch (error) {
50
+ return JSON.stringify({
51
+ error: 'search_failed',
52
+ message: error.message || 'Failed to search skills.',
53
+ }, null, 2);
54
+ }
55
+ }
56
+ export const skillSearchTool = {
57
+ name,
58
+ displayName,
59
+ description,
60
+ parameters,
61
+ category,
62
+ execute,
63
+ };
@@ -0,0 +1,6 @@
1
+ import { ToolParameters } from 'toolpack-sdk';
2
+ export declare const name = "skill.search";
3
+ export declare const displayName = "Search Skills";
4
+ export declare const description = "Search for relevant skills using BM25 text search. Searches across skill names, titles, descriptions, tags, keywords, and triggers to find the most relevant skills for a given query.";
5
+ export declare const category = "productivity";
6
+ export declare const parameters: ToolParameters;
@@ -0,0 +1,19 @@
1
+ export const name = 'skill.search';
2
+ export const displayName = 'Search Skills';
3
+ export const description = 'Search for relevant skills using BM25 text search. Searches across skill names, titles, descriptions, tags, keywords, and triggers to find the most relevant skills for a given query.';
4
+ export const category = 'productivity';
5
+ export const parameters = {
6
+ type: 'object',
7
+ properties: {
8
+ query: {
9
+ type: 'string',
10
+ description: 'The search query to find relevant skills. Can be natural language like "how to deploy to AWS" or keywords like "react testing".',
11
+ },
12
+ limit: {
13
+ type: 'number',
14
+ description: 'Maximum number of results to return. Default: 5',
15
+ default: 5,
16
+ },
17
+ },
18
+ required: ['query'],
19
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Skill Index Manager
3
+ *
4
+ * Manages the BM25 index for skill files, handling indexing, caching,
5
+ * and automatic re-indexing when skills change.
6
+ */
7
+ import { BM25SearchResult } from './bm25.js';
8
+ export interface SkillDocument {
9
+ name: string;
10
+ title: string;
11
+ description: string;
12
+ tags: string[];
13
+ triggers: string[];
14
+ keywords: string[];
15
+ path: string;
16
+ lastModified: number;
17
+ }
18
+ export interface SkillSearchResult extends BM25SearchResult {
19
+ skill: SkillDocument;
20
+ }
21
+ declare class SkillIndexManager {
22
+ private index;
23
+ private skillCache;
24
+ private lastIndexTime;
25
+ private indexedDir;
26
+ constructor();
27
+ /**
28
+ * Parse a skill file and extract all searchable content
29
+ */
30
+ private parseSkillFile;
31
+ /**
32
+ * Build searchable content for BM25 indexing
33
+ * Weights different fields by repeating important terms
34
+ */
35
+ private buildSearchableContent;
36
+ /**
37
+ * Index all skills in the .skills directory
38
+ */
39
+ indexSkills(workspaceRoot?: string): Promise<number>;
40
+ /**
41
+ * Search for skills matching the query
42
+ */
43
+ search(query: string, limit?: number, workspaceRoot?: string): Promise<SkillSearchResult[]>;
44
+ /**
45
+ * Get the number of indexed skills
46
+ */
47
+ get size(): number;
48
+ /**
49
+ * Force a full re-index
50
+ */
51
+ reindex(workspaceRoot?: string): Promise<number>;
52
+ }
53
+ export declare const skillIndex: SkillIndexManager;
54
+ export {};