@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.
- package/index.js +50 -96
- 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
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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.
|
|
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
|
+
}
|