@impulselab/directory 1.0.7 → 1.0.8

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 (2) hide show
  1. package/index.js +50 -96
  2. package/package.json +7 -7
package/index.js CHANGED
@@ -48,6 +48,7 @@ const MODES = {
48
48
  const INSTALLATION_TARGETS = {
49
49
  CLAUDE_SLASH: 'claude-slash',
50
50
  CLAUDE_SUB_AGENT: 'claude-sub-agent',
51
+ CLAUDE_SKILL: 'claude-skill',
51
52
  CURSOR_COMMAND: 'cursor-command',
52
53
  CURSOR_RULE: 'cursor-rule',
53
54
  };
@@ -67,6 +68,13 @@ const TARGET_CONFIG = {
67
68
  resolveDirectory: () => resolveClaudeDirectory('agents'),
68
69
  extension: '.md',
69
70
  },
71
+ [INSTALLATION_TARGETS.CLAUDE_SKILL]: {
72
+ label: 'Claude Code skill',
73
+ description: '.claude/skills (project only)',
74
+ resolveDirectory: () => resolveClaudeSkillsDirectory(),
75
+ extension: '.md',
76
+ isSkill: true,
77
+ },
70
78
  [INSTALLATION_TARGETS.CURSOR_COMMAND]: {
71
79
  label: 'Cursor command',
72
80
  description: '.cursor/commands (project only)',
@@ -170,95 +178,6 @@ function sanitizeIdentifierForFilename(identifier) {
170
178
  || 'unknown'; // fallback for empty results
171
179
  }
172
180
 
173
- function generateUniqueFilename(directory, baseName, extension) {
174
- const baseFilename = `${baseName}${extension}`;
175
-
176
- if (!checkFileExists(directory, baseFilename)) {
177
- return baseFilename;
178
- }
179
-
180
- let counter = 1;
181
- let candidate = `${baseName}-${counter}${extension}`;
182
- while (checkFileExists(directory, candidate)) {
183
- counter += 1;
184
- candidate = `${baseName}-${counter}${extension}`;
185
- }
186
-
187
- return candidate;
188
- }
189
-
190
- function downloadCommand(baseUrl, fullPath) {
191
- return new Promise((resolve, reject) => {
192
- const useHttps = shouldUseHttps(baseUrl);
193
- const protocol = useHttps ? https : http;
194
- const scheme = useHttps ? 'https' : 'http';
195
-
196
- const url = `${scheme}://${baseUrl}${RAW_PATH}${fullPath}`;
197
-
198
- protocol
199
- .get(url, (res) => {
200
- if (res.statusCode === 200) {
201
- let data = '';
202
- res.on('data', (chunk) => {
203
- data += chunk;
204
- });
205
- res.on('end', () => resolve(data));
206
- } else if (res.statusCode === 404) {
207
- reject(new Error(`Command '${fullPath}' not found (404)`));
208
- } else if (useHttps && (res.statusCode === 500 || res.statusCode >= 400)) {
209
- if (canFallbackToHttp(baseUrl)) {
210
- console.log(`HTTPS failed (${res.statusCode}), trying HTTP...`);
211
- downloadWithHttp(baseUrl, fullPath)
212
- .then(resolve)
213
- .catch(reject);
214
- } else {
215
- reject(new Error(`HTTPS failed (${res.statusCode}) and HTTP fallback is disabled`));
216
- }
217
- } else {
218
- reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`));
219
- }
220
- })
221
- .on('error', (err) => {
222
- if (useHttps) {
223
- if (canFallbackToHttp(baseUrl)) {
224
- console.log(`HTTPS failed (${err.message}), trying HTTP...`);
225
- downloadWithHttp(baseUrl, fullPath)
226
- .then(resolve)
227
- .catch(reject);
228
- } else {
229
- reject(err);
230
- }
231
- } else {
232
- reject(err);
233
- }
234
- });
235
- });
236
- }
237
-
238
- function downloadWithHttp(baseUrl, fullPath) {
239
- return new Promise((resolve, reject) => {
240
- const url = `http://${baseUrl}${RAW_PATH}${fullPath}`;
241
-
242
- console.log(`Trying HTTP: ${url}`);
243
-
244
- http
245
- .get(url, (res) => {
246
- if (res.statusCode === 200) {
247
- let data = '';
248
- res.on('data', (chunk) => {
249
- data += chunk;
250
- });
251
- res.on('end', () => resolve(data));
252
- } else if (res.statusCode === 404) {
253
- reject(new Error(`Command '${fullPath}' not found (404)`));
254
- } else {
255
- reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`));
256
- }
257
- })
258
- .on('error', reject);
259
- });
260
- }
261
-
262
181
  function downloadStack(baseUrl, stackId) {
263
182
  return new Promise((resolve, reject) => {
264
183
  const useHttps = shouldUseHttps(baseUrl);
@@ -433,6 +352,16 @@ function resolveCursorDirectory(subFolder) {
433
352
  return path.join(projectCursorDir, subFolder);
434
353
  }
435
354
 
355
+ function resolveClaudeSkillsDirectory() {
356
+ const projectClaudeDir = findClosestFolder('.claude');
357
+ if (!projectClaudeDir) {
358
+ const message =
359
+ 'Unable to find a .claude directory. Run this command from inside a project with a .claude folder.';
360
+ throw new Error(message);
361
+ }
362
+ return path.join(projectClaudeDir, 'skills');
363
+ }
364
+
436
365
  function ensureDirectoryExists(dirPath) {
437
366
  if (!fs.existsSync(dirPath)) {
438
367
  fs.mkdirSync(dirPath, { recursive: true });
@@ -452,17 +381,36 @@ function saveCommand({
452
381
  fullPath,
453
382
  target,
454
383
  }) {
455
- const filePath = path.join(directory, filename);
456
- const relativeDir = path.relative(process.cwd(), directory);
457
- const displayPath = relativeDir
458
- ? path.join(relativeDir, filename)
459
- : filename;
384
+ const targetConfig = TARGET_CONFIG[target];
385
+ const isSkill = targetConfig && targetConfig.isSkill;
386
+
387
+ let filePath;
388
+ let displayPath;
389
+ let commandName;
390
+
391
+ if (isSkill) {
392
+ const skillName = filename.replace(path.extname(filename), '');
393
+ const skillDir = path.join(directory, skillName);
394
+ ensureDirectoryExists(skillDir);
395
+ filePath = path.join(skillDir, 'SKILL.md');
396
+ const relativeDir = path.relative(process.cwd(), skillDir);
397
+ displayPath = relativeDir
398
+ ? path.join(relativeDir, 'SKILL.md')
399
+ : path.join(skillName, 'SKILL.md');
400
+ commandName = skillName;
401
+ } else {
402
+ filePath = path.join(directory, filename);
403
+ const relativeDir = path.relative(process.cwd(), directory);
404
+ displayPath = relativeDir
405
+ ? path.join(relativeDir, filename)
406
+ : filename;
407
+ commandName = filename.replace(path.extname(filename), '');
408
+ }
460
409
 
461
- console.log(`šŸ“ Saving command to ${displayPath}`);
410
+ console.log(`šŸ“ Saving ${isSkill ? 'skill' : 'command'} to ${displayPath}`);
462
411
 
463
412
  fs.writeFileSync(filePath, content, 'utf8');
464
413
 
465
- const commandName = filename.replace(path.extname(filename), '');
466
414
  logSuccessMessage(target, fullPath, commandName);
467
415
  }
468
416
 
@@ -478,6 +426,12 @@ function logSuccessMessage(target, fullPath, commandName) {
478
426
  `\nšŸŽ‰ You can now select '${commandName}' from Claude Desktop's agent menu.`
479
427
  );
480
428
  break;
429
+ case INSTALLATION_TARGETS.CLAUDE_SKILL:
430
+ console.log(`āœ… Skill '${fullPath}' installed successfully`);
431
+ console.log(
432
+ `\nšŸŽ‰ Claude Code will now automatically use the '${commandName}' skill when relevant.`
433
+ );
434
+ break;
481
435
  case INSTALLATION_TARGETS.CURSOR_COMMAND:
482
436
  console.log(`āœ… Cursor command '${fullPath}' installed successfully`);
483
437
  console.log(
package/package.json CHANGED
@@ -1,11 +1,16 @@
1
1
  {
2
2
  "name": "@impulselab/directory",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "Download and install Claude commands from Impulse Directory",
5
5
  "main": "index.js",
6
6
  "bin": {
7
7
  "directory": "./index.js"
8
8
  },
9
+ "scripts": {
10
+ "test": "node index.js --help",
11
+ "predeploy": "chmod +x index.js",
12
+ "deploy": "pnpm publish --access public --no-git-checks"
13
+ },
9
14
  "keywords": [
10
15
  "claude",
11
16
  "commands",
@@ -20,10 +25,5 @@
20
25
  },
21
26
  "publishConfig": {
22
27
  "access": "public"
23
- },
24
- "scripts": {
25
- "test": "node index.js --help",
26
- "predeploy": "chmod +x index.js",
27
- "deploy": "pnpm publish --access public --no-git-checks"
28
28
  }
29
- }
29
+ }