@skillsmith/cli 0.1.0

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 (110) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/src/commands/author.d.ts +33 -0
  3. package/dist/src/commands/author.d.ts.map +1 -0
  4. package/dist/src/commands/author.js +337 -0
  5. package/dist/src/commands/author.js.map +1 -0
  6. package/dist/src/commands/index.d.ts +9 -0
  7. package/dist/src/commands/index.d.ts.map +1 -0
  8. package/dist/src/commands/index.js +12 -0
  9. package/dist/src/commands/index.js.map +1 -0
  10. package/dist/src/commands/manage.d.ts +37 -0
  11. package/dist/src/commands/manage.d.ts.map +1 -0
  12. package/dist/src/commands/manage.js +308 -0
  13. package/dist/src/commands/manage.js.map +1 -0
  14. package/dist/src/commands/search.d.ts +12 -0
  15. package/dist/src/commands/search.d.ts.map +1 -0
  16. package/dist/src/commands/search.js +316 -0
  17. package/dist/src/commands/search.js.map +1 -0
  18. package/dist/src/config.d.ts +19 -0
  19. package/dist/src/config.d.ts.map +1 -0
  20. package/dist/src/config.js +21 -0
  21. package/dist/src/config.js.map +1 -0
  22. package/dist/src/import.d.ts +29 -0
  23. package/dist/src/import.d.ts.map +1 -0
  24. package/dist/src/import.js +255 -0
  25. package/dist/src/import.js.map +1 -0
  26. package/dist/src/index.d.ts +16 -0
  27. package/dist/src/index.d.ts.map +1 -0
  28. package/dist/src/index.js +65 -0
  29. package/dist/src/index.js.map +1 -0
  30. package/dist/src/templates/index.d.ts +8 -0
  31. package/dist/src/templates/index.d.ts.map +1 -0
  32. package/dist/src/templates/index.js +8 -0
  33. package/dist/src/templates/index.js.map +1 -0
  34. package/dist/src/templates/readme.md.template.d.ts +8 -0
  35. package/dist/src/templates/readme.md.template.d.ts.map +1 -0
  36. package/dist/src/templates/readme.md.template.js +86 -0
  37. package/dist/src/templates/readme.md.template.js.map +1 -0
  38. package/dist/src/templates/skill.md.template.d.ts +8 -0
  39. package/dist/src/templates/skill.md.template.d.ts.map +1 -0
  40. package/dist/src/templates/skill.md.template.js +99 -0
  41. package/dist/src/templates/skill.md.template.js.map +1 -0
  42. package/dist/src/utils/license.d.ts +82 -0
  43. package/dist/src/utils/license.d.ts.map +1 -0
  44. package/dist/src/utils/license.js +282 -0
  45. package/dist/src/utils/license.js.map +1 -0
  46. package/dist/src/utils/license.test.d.ts +7 -0
  47. package/dist/src/utils/license.test.d.ts.map +1 -0
  48. package/dist/src/utils/license.test.js +303 -0
  49. package/dist/src/utils/license.test.js.map +1 -0
  50. package/dist/src/utils/sanitize.d.ts +30 -0
  51. package/dist/src/utils/sanitize.d.ts.map +1 -0
  52. package/dist/src/utils/sanitize.js +45 -0
  53. package/dist/src/utils/sanitize.js.map +1 -0
  54. package/dist/tests/author.test.d.ts +5 -0
  55. package/dist/tests/author.test.d.ts.map +1 -0
  56. package/dist/tests/author.test.js +149 -0
  57. package/dist/tests/author.test.js.map +1 -0
  58. package/dist/tests/e2e/author.e2e.test.d.ts +9 -0
  59. package/dist/tests/e2e/author.e2e.test.d.ts.map +1 -0
  60. package/dist/tests/e2e/author.e2e.test.js +346 -0
  61. package/dist/tests/e2e/author.e2e.test.js.map +1 -0
  62. package/dist/tests/e2e/import.e2e.test.d.ts +10 -0
  63. package/dist/tests/e2e/import.e2e.test.d.ts.map +1 -0
  64. package/dist/tests/e2e/import.e2e.test.js +213 -0
  65. package/dist/tests/e2e/import.e2e.test.js.map +1 -0
  66. package/dist/tests/e2e/manage.e2e.test.d.ts +9 -0
  67. package/dist/tests/e2e/manage.e2e.test.d.ts.map +1 -0
  68. package/dist/tests/e2e/manage.e2e.test.js +277 -0
  69. package/dist/tests/e2e/manage.e2e.test.js.map +1 -0
  70. package/dist/tests/e2e/search.e2e.test.d.ts +10 -0
  71. package/dist/tests/e2e/search.e2e.test.d.ts.map +1 -0
  72. package/dist/tests/e2e/search.e2e.test.js +267 -0
  73. package/dist/tests/e2e/search.e2e.test.js.map +1 -0
  74. package/dist/tests/e2e/test-config.d.ts +39 -0
  75. package/dist/tests/e2e/test-config.d.ts.map +1 -0
  76. package/dist/tests/e2e/test-config.js +44 -0
  77. package/dist/tests/e2e/test-config.js.map +1 -0
  78. package/dist/tests/e2e/utils/baseline-collector.d.ts +107 -0
  79. package/dist/tests/e2e/utils/baseline-collector.d.ts.map +1 -0
  80. package/dist/tests/e2e/utils/baseline-collector.js +211 -0
  81. package/dist/tests/e2e/utils/baseline-collector.js.map +1 -0
  82. package/dist/tests/e2e/utils/hardcoded-detector.d.ts +46 -0
  83. package/dist/tests/e2e/utils/hardcoded-detector.d.ts.map +1 -0
  84. package/dist/tests/e2e/utils/hardcoded-detector.js +197 -0
  85. package/dist/tests/e2e/utils/hardcoded-detector.js.map +1 -0
  86. package/dist/tests/e2e/utils/index.d.ts +8 -0
  87. package/dist/tests/e2e/utils/index.d.ts.map +1 -0
  88. package/dist/tests/e2e/utils/index.js +8 -0
  89. package/dist/tests/e2e/utils/index.js.map +1 -0
  90. package/dist/tests/e2e/utils/linear-reporter.d.ts +60 -0
  91. package/dist/tests/e2e/utils/linear-reporter.d.ts.map +1 -0
  92. package/dist/tests/e2e/utils/linear-reporter.js +223 -0
  93. package/dist/tests/e2e/utils/linear-reporter.js.map +1 -0
  94. package/dist/tests/import.test.d.ts +5 -0
  95. package/dist/tests/import.test.d.ts.map +1 -0
  96. package/dist/tests/import.test.js +11 -0
  97. package/dist/tests/import.test.js.map +1 -0
  98. package/dist/tests/manage.test.d.ts +5 -0
  99. package/dist/tests/manage.test.d.ts.map +1 -0
  100. package/dist/tests/manage.test.js +135 -0
  101. package/dist/tests/manage.test.js.map +1 -0
  102. package/dist/tests/search.test.d.ts +5 -0
  103. package/dist/tests/search.test.d.ts.map +1 -0
  104. package/dist/tests/search.test.js +91 -0
  105. package/dist/tests/search.test.js.map +1 -0
  106. package/dist/vitest.config.d.ts +3 -0
  107. package/dist/vitest.config.d.ts.map +1 -0
  108. package/dist/vitest.config.js +9 -0
  109. package/dist/vitest.config.js.map +1 -0
  110. package/package.json +62 -0
@@ -0,0 +1,308 @@
1
+ /**
2
+ * SMI-745: Skill Management Commands
3
+ *
4
+ * Provides CLI commands for listing, updating, and removing installed skills.
5
+ */
6
+ import { Command } from 'commander';
7
+ import { confirm } from '@inquirer/prompts';
8
+ import chalk from 'chalk';
9
+ import Table from 'cli-table3';
10
+ import ora from 'ora';
11
+ import { readdir, readFile, rm, stat } from 'fs/promises';
12
+ import { join } from 'path';
13
+ import { homedir } from 'os';
14
+ import { createDatabase, SkillRepository, SkillParser, } from '@skillsmith/core';
15
+ import { DEFAULT_DB_PATH } from '../config.js';
16
+ import { sanitizeError } from '../utils/sanitize.js';
17
+ const TRUST_TIER_COLORS = {
18
+ verified: chalk.green,
19
+ community: chalk.yellow,
20
+ experimental: chalk.red,
21
+ unknown: chalk.gray,
22
+ };
23
+ const SKILLS_DIR = join(homedir(), '.claude', 'skills');
24
+ /**
25
+ * Get list of installed skills from ~/.claude/skills
26
+ */
27
+ async function getInstalledSkills() {
28
+ const skills = [];
29
+ try {
30
+ const entries = await readdir(SKILLS_DIR, { withFileTypes: true });
31
+ for (const entry of entries) {
32
+ if (entry.isDirectory()) {
33
+ const skillPath = join(SKILLS_DIR, entry.name);
34
+ const skillMdPath = join(skillPath, 'SKILL.md');
35
+ try {
36
+ const skillMdStat = await stat(skillMdPath);
37
+ const content = await readFile(skillMdPath, 'utf-8');
38
+ const parser = new SkillParser();
39
+ const parsed = parser.parse(content);
40
+ skills.push({
41
+ name: parsed?.name || entry.name,
42
+ path: skillPath,
43
+ version: parsed?.version || null,
44
+ trustTier: parsed ? parser.inferTrustTier(parsed) : 'unknown',
45
+ installDate: skillMdStat.mtime.toISOString().split('T')[0] || 'Unknown',
46
+ hasUpdates: false, // Would check remote for updates
47
+ });
48
+ }
49
+ catch {
50
+ // No SKILL.md, treat as unknown skill
51
+ const dirStat = await stat(skillPath);
52
+ skills.push({
53
+ name: entry.name,
54
+ path: skillPath,
55
+ version: null,
56
+ trustTier: 'unknown',
57
+ installDate: dirStat.mtime.toISOString().split('T')[0] || 'Unknown',
58
+ hasUpdates: false,
59
+ });
60
+ }
61
+ }
62
+ }
63
+ }
64
+ catch (error) {
65
+ if (error.code !== 'ENOENT') {
66
+ throw error;
67
+ }
68
+ }
69
+ return skills;
70
+ }
71
+ /**
72
+ * Display skills in a table format
73
+ */
74
+ function displaySkillsTable(skills) {
75
+ if (skills.length === 0) {
76
+ console.log(chalk.yellow('\nNo skills installed.\n'));
77
+ console.log(chalk.dim('Install skills with: skillsmith install <skill-name>\n'));
78
+ return;
79
+ }
80
+ const table = new Table({
81
+ head: [
82
+ chalk.bold('Name'),
83
+ chalk.bold('Version'),
84
+ chalk.bold('Trust Tier'),
85
+ chalk.bold('Install Date'),
86
+ chalk.bold('Updates'),
87
+ ],
88
+ colWidths: [30, 15, 15, 15, 12],
89
+ });
90
+ for (const skill of skills) {
91
+ const colorFn = TRUST_TIER_COLORS[skill.trustTier];
92
+ table.push([
93
+ colorFn(skill.name),
94
+ skill.version || chalk.dim('N/A'),
95
+ colorFn(skill.trustTier),
96
+ skill.installDate,
97
+ skill.hasUpdates ? chalk.green('Available') : chalk.dim('Up to date'),
98
+ ]);
99
+ }
100
+ console.log('\n' + chalk.bold.blue('Installed Skills') + '\n');
101
+ console.log(table.toString());
102
+ console.log(chalk.dim(`\n${skills.length} skill(s) installed in ${SKILLS_DIR}\n`));
103
+ }
104
+ /**
105
+ * Get skill diff from database
106
+ */
107
+ async function getSkillDiff(skillName, dbPath) {
108
+ const db = createDatabase(dbPath);
109
+ const skillRepo = new SkillRepository(db);
110
+ try {
111
+ // Find skill in database by name (case-insensitive search)
112
+ const allSkills = skillRepo.findAll(1000, 0);
113
+ const skill = allSkills.items.find((s) => s.name.toLowerCase() === skillName.toLowerCase());
114
+ if (!skill) {
115
+ return null;
116
+ }
117
+ // Get installed version
118
+ const installed = (await getInstalledSkills()).find((s) => s.name.toLowerCase() === skillName.toLowerCase());
119
+ const changes = [];
120
+ if (installed?.version !== skill.version) {
121
+ changes.push(`Version: ${installed?.version || 'N/A'} -> ${skill.version || 'N/A'}`);
122
+ }
123
+ if (installed?.trustTier !== skill.trustTier) {
124
+ changes.push(`Trust Tier: ${installed?.trustTier || 'unknown'} -> ${skill.trustTier}`);
125
+ }
126
+ return {
127
+ oldVersion: installed?.version || null,
128
+ newVersion: skill.version || null,
129
+ changes,
130
+ };
131
+ }
132
+ finally {
133
+ db.close();
134
+ }
135
+ }
136
+ /**
137
+ * Update a single skill
138
+ */
139
+ async function updateSkill(skillName, dbPath) {
140
+ const spinner = ora(`Checking updates for ${skillName}...`).start();
141
+ try {
142
+ const diff = await getSkillDiff(skillName, dbPath);
143
+ if (!diff) {
144
+ spinner.fail(`Skill "${skillName}" not found in registry`);
145
+ return false;
146
+ }
147
+ if (diff.changes.length === 0) {
148
+ spinner.succeed(`${skillName} is already up to date`);
149
+ return true;
150
+ }
151
+ spinner.stop();
152
+ console.log(chalk.bold(`\nChanges for ${skillName}:`));
153
+ for (const change of diff.changes) {
154
+ console.log(chalk.cyan(` - ${change}`));
155
+ }
156
+ console.log();
157
+ const proceed = await confirm({
158
+ message: `Update ${skillName}?`,
159
+ default: true,
160
+ });
161
+ if (!proceed) {
162
+ console.log(chalk.yellow('Update cancelled'));
163
+ return false;
164
+ }
165
+ const updateSpinner = ora(`Updating ${skillName}...`).start();
166
+ // In a real implementation, this would fetch and install the new version
167
+ // For now, we just simulate success
168
+ await new Promise((resolve) => setTimeout(resolve, 1000));
169
+ updateSpinner.succeed(`Successfully updated ${skillName}`);
170
+ return true;
171
+ }
172
+ catch (error) {
173
+ spinner.fail(`Failed to update ${skillName}: ${sanitizeError(error)}`);
174
+ return false;
175
+ }
176
+ }
177
+ /**
178
+ * Update all installed skills
179
+ */
180
+ async function updateAllSkills(dbPath) {
181
+ const skills = await getInstalledSkills();
182
+ if (skills.length === 0) {
183
+ console.log(chalk.yellow('No skills installed'));
184
+ return;
185
+ }
186
+ console.log(chalk.bold(`\nChecking updates for ${skills.length} skill(s)...\n`));
187
+ let updated = 0;
188
+ let failed = 0;
189
+ for (const skill of skills) {
190
+ const success = await updateSkill(skill.name, dbPath);
191
+ if (success) {
192
+ updated++;
193
+ }
194
+ else {
195
+ failed++;
196
+ }
197
+ }
198
+ console.log(chalk.bold('\nUpdate Summary:'));
199
+ console.log(chalk.green(` Updated: ${updated}`));
200
+ if (failed > 0) {
201
+ console.log(chalk.red(` Failed: ${failed}`));
202
+ }
203
+ console.log();
204
+ }
205
+ /**
206
+ * Remove a skill
207
+ */
208
+ async function removeSkill(skillName, force) {
209
+ const installed = await getInstalledSkills();
210
+ const skill = installed.find((s) => s.name.toLowerCase() === skillName.toLowerCase());
211
+ if (!skill) {
212
+ console.log(chalk.red(`Skill "${skillName}" is not installed`));
213
+ return false;
214
+ }
215
+ if (!force) {
216
+ console.log(chalk.bold(`\nSkill to remove:`));
217
+ console.log(` Name: ${skill.name}`);
218
+ console.log(` Version: ${skill.version || 'N/A'}`);
219
+ console.log(` Path: ${skill.path}`);
220
+ console.log();
221
+ const proceed = await confirm({
222
+ message: `Are you sure you want to remove ${skill.name}?`,
223
+ default: false,
224
+ });
225
+ if (!proceed) {
226
+ console.log(chalk.yellow('Removal cancelled'));
227
+ return false;
228
+ }
229
+ }
230
+ const spinner = ora(`Removing ${skill.name}...`).start();
231
+ try {
232
+ await rm(skill.path, { recursive: true, force: true });
233
+ spinner.succeed(`Successfully removed ${skill.name}`);
234
+ return true;
235
+ }
236
+ catch (error) {
237
+ spinner.fail(`Failed to remove ${skill.name}: ${sanitizeError(error)}`);
238
+ return false;
239
+ }
240
+ }
241
+ /**
242
+ * Create list command
243
+ */
244
+ export function createListCommand() {
245
+ return new Command('list')
246
+ .alias('ls')
247
+ .description('List all installed skills')
248
+ .action(async () => {
249
+ try {
250
+ const skills = await getInstalledSkills();
251
+ displaySkillsTable(skills);
252
+ }
253
+ catch (error) {
254
+ console.error(chalk.red('Error listing skills:'), sanitizeError(error));
255
+ process.exit(1);
256
+ }
257
+ });
258
+ }
259
+ /**
260
+ * Create update command
261
+ */
262
+ export function createUpdateCommand() {
263
+ return new Command('update')
264
+ .description('Update installed skills')
265
+ .argument('[skill]', 'Skill name to update (omit for all)')
266
+ .option('-d, --db <path>', 'Database file path', DEFAULT_DB_PATH)
267
+ .option('-a, --all', 'Update all installed skills')
268
+ .action(async (skillName, opts) => {
269
+ const dbPath = opts['db'];
270
+ const updateAll = opts['all'];
271
+ try {
272
+ if (updateAll || !skillName) {
273
+ await updateAllSkills(dbPath);
274
+ }
275
+ else {
276
+ await updateSkill(skillName, dbPath);
277
+ }
278
+ }
279
+ catch (error) {
280
+ console.error(chalk.red('Error updating skills:'), sanitizeError(error));
281
+ process.exit(1);
282
+ }
283
+ });
284
+ }
285
+ /**
286
+ * Create remove command
287
+ */
288
+ export function createRemoveCommand() {
289
+ return new Command('remove')
290
+ .alias('rm')
291
+ .alias('uninstall')
292
+ .description('Remove an installed skill')
293
+ .argument('<skill>', 'Skill name to remove')
294
+ .option('-f, --force', 'Skip confirmation prompt')
295
+ .action(async (skillName, opts) => {
296
+ const force = opts['force'] ?? false;
297
+ try {
298
+ const success = await removeSkill(skillName, force);
299
+ process.exit(success ? 0 : 1);
300
+ }
301
+ catch (error) {
302
+ console.error(chalk.red('Error removing skill:'), sanitizeError(error));
303
+ process.exit(1);
304
+ }
305
+ });
306
+ }
307
+ export { getInstalledSkills, displaySkillsTable };
308
+ //# sourceMappingURL=manage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manage.js","sourceRoot":"","sources":["../../../src/commands/manage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * SMI-744: Interactive Search Mode
3
+ *
4
+ * Provides interactive CLI for searching skills with filters and pagination.
5
+ */
6
+ import { Command } from 'commander';
7
+ /**
8
+ * Create search command
9
+ */
10
+ export declare function createSearchCommand(): Command;
11
+ export default createSearchCommand;
12
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../src/commands/search.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAwUnC;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CA0D7C;AAED,eAAe,mBAAmB,CAAA"}
@@ -0,0 +1,316 @@
1
+ /**
2
+ * SMI-744: Interactive Search Mode
3
+ *
4
+ * Provides interactive CLI for searching skills with filters and pagination.
5
+ */
6
+ import { Command } from 'commander';
7
+ import { input, checkbox, number, select } from '@inquirer/prompts';
8
+ import chalk from 'chalk';
9
+ import Table from 'cli-table3';
10
+ import { createDatabase, SearchService, } from '@skillsmith/core';
11
+ import { DEFAULT_DB_PATH } from '../config.js';
12
+ import { sanitizeError } from '../utils/sanitize.js';
13
+ const TRUST_TIER_COLORS = {
14
+ verified: chalk.green,
15
+ community: chalk.yellow,
16
+ experimental: chalk.red,
17
+ unknown: chalk.gray,
18
+ };
19
+ const PAGE_SIZE = 10;
20
+ /**
21
+ * Format a skill result for display with color coding
22
+ */
23
+ function formatSkillRow(result) {
24
+ const { skill } = result;
25
+ const colorFn = TRUST_TIER_COLORS[skill.trustTier];
26
+ const score = skill.qualityScore !== null ? (skill.qualityScore * 100).toFixed(0) + '%' : 'N/A';
27
+ return [
28
+ colorFn(skill.name),
29
+ skill.description?.slice(0, 50) || 'No description',
30
+ skill.author || 'Unknown',
31
+ colorFn(skill.trustTier),
32
+ score,
33
+ ];
34
+ }
35
+ /**
36
+ * Display search results in a table format
37
+ */
38
+ function displayResults(results, total, offset, pageSize) {
39
+ if (results.length === 0) {
40
+ console.log(chalk.yellow('\nNo skills found matching your criteria.\n'));
41
+ return;
42
+ }
43
+ const table = new Table({
44
+ head: [
45
+ chalk.bold('Name'),
46
+ chalk.bold('Description'),
47
+ chalk.bold('Author'),
48
+ chalk.bold('Trust Tier'),
49
+ chalk.bold('Quality'),
50
+ ],
51
+ colWidths: [25, 52, 20, 15, 10],
52
+ wordWrap: true,
53
+ });
54
+ for (const result of results) {
55
+ table.push(formatSkillRow(result));
56
+ }
57
+ console.log(table.toString());
58
+ const currentPage = Math.floor(offset / pageSize) + 1;
59
+ const totalPages = Math.ceil(total / pageSize);
60
+ console.log(chalk.dim(`\nShowing ${offset + 1}-${offset + results.length} of ${total} results (Page ${currentPage}/${totalPages})`));
61
+ console.log(chalk.dim('Legend: ') +
62
+ chalk.green('verified') +
63
+ ' | ' +
64
+ chalk.yellow('community') +
65
+ ' | ' +
66
+ chalk.red('experimental'));
67
+ }
68
+ /**
69
+ * Display detailed skill information
70
+ */
71
+ function displaySkillDetails(result) {
72
+ const { skill } = result;
73
+ console.log('\n' + chalk.bold.underline(skill.name) + '\n');
74
+ const colorFn = TRUST_TIER_COLORS[skill.trustTier];
75
+ console.log(chalk.bold('Description: ') + (skill.description || 'No description'));
76
+ console.log(chalk.bold('Author: ') + (skill.author || 'Unknown'));
77
+ console.log(chalk.bold('Trust Tier: ') + colorFn(skill.trustTier));
78
+ console.log(chalk.bold('Quality Score: ') +
79
+ (skill.qualityScore !== null ? (skill.qualityScore * 100).toFixed(0) + '%' : 'N/A'));
80
+ console.log(chalk.bold('Tags: ') + (skill.tags.length > 0 ? skill.tags.join(', ') : 'None'));
81
+ console.log(chalk.bold('Repository: ') + (skill.repoUrl || 'N/A'));
82
+ console.log(chalk.bold('Created: ') + skill.createdAt);
83
+ console.log(chalk.bold('Updated: ') + skill.updatedAt);
84
+ console.log();
85
+ }
86
+ /**
87
+ * Run interactive search loop using state machine pattern (SMI-759)
88
+ * Uses iterative while loop instead of recursion for new searches.
89
+ */
90
+ async function runInteractiveSearch(dbPath) {
91
+ const db = createDatabase(dbPath);
92
+ const searchService = new SearchService(db);
93
+ console.log(chalk.bold.blue('\n=== Skillsmith Interactive Search ===\n'));
94
+ try {
95
+ // State machine: phase controls the loop behavior
96
+ let phase = 'collect_query';
97
+ let state = null;
98
+ // Main state machine loop - replaces recursive calls
99
+ while (phase !== 'exit') {
100
+ // Phase: Collect search query and filters
101
+ if (phase === 'collect_query') {
102
+ // Step 1: Enter search query
103
+ const query = await input({
104
+ message: 'Enter search query:',
105
+ default: '',
106
+ validate: (value) => value.trim().length > 0 || 'Please enter a search query',
107
+ });
108
+ // Step 2: Filter by trust tier
109
+ const trustTiers = await checkbox({
110
+ message: 'Filter by trust tier (select with space, enter to continue):',
111
+ choices: [
112
+ { name: chalk.green('Verified'), value: 'verified' },
113
+ { name: chalk.yellow('Community'), value: 'community' },
114
+ { name: chalk.red('Experimental'), value: 'experimental' },
115
+ { name: chalk.gray('Unknown'), value: 'unknown' },
116
+ ],
117
+ });
118
+ // Step 3: Minimum quality score
119
+ const minQualityScore = await number({
120
+ message: 'Minimum quality score (0-100, leave empty for no filter):',
121
+ default: 0,
122
+ min: 0,
123
+ max: 100,
124
+ });
125
+ state = {
126
+ query,
127
+ trustTiers,
128
+ minQualityScore: (minQualityScore || 0) / 100,
129
+ offset: 0,
130
+ };
131
+ phase = 'searching';
132
+ continue;
133
+ }
134
+ // Phase: Search and display results
135
+ if (phase === 'searching' && state !== null) {
136
+ // Build search options - only add optional properties when they have values
137
+ const searchOptions = {
138
+ query: state.query,
139
+ limit: PAGE_SIZE,
140
+ offset: state.offset,
141
+ };
142
+ // Add optional filters only when they have values (exactOptionalPropertyTypes)
143
+ if (state.minQualityScore > 0) {
144
+ searchOptions.minQualityScore = state.minQualityScore;
145
+ }
146
+ // Filter by first selected trust tier (API only supports one)
147
+ if (state.trustTiers.length === 1 && state.trustTiers[0] !== undefined) {
148
+ searchOptions.trustTier = state.trustTiers[0];
149
+ }
150
+ // Execute search
151
+ const results = searchService.search(searchOptions);
152
+ // If filtering by multiple trust tiers, filter client-side
153
+ let filteredItems = results.items;
154
+ const trustTiersForFilter = state.trustTiers;
155
+ if (trustTiersForFilter.length > 1) {
156
+ filteredItems = results.items.filter((r) => trustTiersForFilter.includes(r.skill.trustTier));
157
+ }
158
+ displayResults(filteredItems, results.total, state.offset, PAGE_SIZE);
159
+ if (results.items.length === 0) {
160
+ phase = 'exit';
161
+ continue;
162
+ }
163
+ // Build action choices
164
+ const choices = [];
165
+ // Add skill selection options
166
+ for (let i = 0; i < filteredItems.length; i++) {
167
+ const skill = filteredItems[i].skill;
168
+ const colorFn = TRUST_TIER_COLORS[skill.trustTier];
169
+ choices.push({
170
+ name: `${i + 1}. ${colorFn(skill.name)} - View details`,
171
+ value: `view_${i}`,
172
+ });
173
+ }
174
+ // Add navigation options
175
+ choices.push({ name: chalk.dim('---'), value: 'separator' });
176
+ if (state.offset > 0) {
177
+ choices.push({ name: chalk.cyan('<< Previous page'), value: 'prev' });
178
+ }
179
+ if (results.hasMore) {
180
+ choices.push({ name: chalk.cyan('Next page >>'), value: 'next' });
181
+ }
182
+ choices.push({ name: chalk.magenta('New search'), value: 'new' });
183
+ choices.push({ name: chalk.red('Exit'), value: 'exit' });
184
+ const action = await select({
185
+ message: 'Select a skill to view or navigate:',
186
+ choices,
187
+ });
188
+ if (action === 'separator') {
189
+ continue;
190
+ }
191
+ else if (action === 'exit') {
192
+ phase = 'exit';
193
+ }
194
+ else if (action === 'new') {
195
+ // SMI-759: Reset to collect_query phase instead of recursive call
196
+ phase = 'collect_query';
197
+ console.log(chalk.bold.blue('\n=== New Search ===\n'));
198
+ }
199
+ else if (action === 'prev') {
200
+ state.offset = Math.max(0, state.offset - PAGE_SIZE);
201
+ }
202
+ else if (action === 'next') {
203
+ state.offset += PAGE_SIZE;
204
+ }
205
+ else if (action.startsWith('view_')) {
206
+ const index = parseInt(action.replace('view_', ''), 10);
207
+ const selectedResult = filteredItems[index];
208
+ if (selectedResult) {
209
+ displaySkillDetails(selectedResult);
210
+ // Ask what to do next
211
+ const nextAction = await select({
212
+ message: 'What would you like to do?',
213
+ choices: [
214
+ { name: 'Back to results', value: 'back' },
215
+ { name: 'Install this skill', value: 'install' },
216
+ { name: 'Exit', value: 'exit' },
217
+ ],
218
+ });
219
+ if (nextAction === 'install') {
220
+ console.log(chalk.green(`\nTo install this skill, run:`));
221
+ console.log(chalk.cyan(` skillsmith install ${selectedResult.skill.id}\n`));
222
+ }
223
+ else if (nextAction === 'exit') {
224
+ phase = 'exit';
225
+ }
226
+ }
227
+ }
228
+ }
229
+ }
230
+ }
231
+ finally {
232
+ db.close();
233
+ }
234
+ console.log(chalk.dim('\nGoodbye!\n'));
235
+ }
236
+ /**
237
+ * Run non-interactive search
238
+ */
239
+ async function runSearch(query, options) {
240
+ const db = createDatabase(options.db);
241
+ const searchService = new SearchService(db);
242
+ try {
243
+ // Build search options - only add optional properties when they have values
244
+ const searchOptions = {
245
+ query,
246
+ limit: options.limit,
247
+ };
248
+ // Add optional filters only when they have values (exactOptionalPropertyTypes)
249
+ if (options.tier !== undefined) {
250
+ searchOptions.trustTier = options.tier;
251
+ }
252
+ if (options.minScore !== undefined) {
253
+ searchOptions.minQualityScore = options.minScore / 100;
254
+ }
255
+ const results = searchService.search(searchOptions);
256
+ displayResults(results.items, results.total, 0, options.limit);
257
+ }
258
+ finally {
259
+ db.close();
260
+ }
261
+ }
262
+ /**
263
+ * Create search command
264
+ */
265
+ export function createSearchCommand() {
266
+ const cmd = new Command('search')
267
+ .description('Search for skills')
268
+ .argument('[query]', 'Search query')
269
+ .option('-i, --interactive', 'Launch interactive search mode')
270
+ .option('-d, --db <path>', 'Database file path', DEFAULT_DB_PATH)
271
+ .option('-l, --limit <number>', 'Maximum results to show', '20')
272
+ .option('-t, --tier <tier>', 'Filter by trust tier (verified, community, experimental, unknown)')
273
+ .option('-s, --min-score <number>', 'Minimum quality score (0-100)')
274
+ .action(async (query, opts) => {
275
+ try {
276
+ const interactive = opts['interactive'];
277
+ const dbPath = opts['db'];
278
+ const limit = parseInt(opts['limit'], 10);
279
+ const tier = opts['tier'];
280
+ const minScore = opts['min-score'] ? parseInt(opts['min-score'], 10) : undefined;
281
+ if (interactive) {
282
+ await runInteractiveSearch(dbPath);
283
+ }
284
+ else if (query) {
285
+ // Validate minimum query length
286
+ if (query.length < 2) {
287
+ console.error(chalk.red('Error: Search query must be at least 2 characters'));
288
+ process.exit(1);
289
+ }
290
+ // Build options object - only include defined values
291
+ const searchOpts = {
292
+ db: dbPath,
293
+ limit,
294
+ };
295
+ if (tier !== undefined) {
296
+ searchOpts.tier = tier;
297
+ }
298
+ if (minScore !== undefined) {
299
+ searchOpts.minScore = minScore;
300
+ }
301
+ await runSearch(query, searchOpts);
302
+ }
303
+ else {
304
+ console.log(chalk.yellow('Please provide a search query or use -i for interactive mode'));
305
+ console.log(chalk.dim('Example: skillsmith search "authentication" or skillsmith search -i'));
306
+ }
307
+ }
308
+ catch (error) {
309
+ console.error(chalk.red('Search error:'), sanitizeError(error));
310
+ process.exit(1);
311
+ }
312
+ });
313
+ return cmd;
314
+ }
315
+ export default createSearchCommand;
316
+ //# sourceMappingURL=search.js.map