@adonis0123/commit 1.0.5 → 1.0.6
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/install-skill.js +397 -20
- package/package.json +1 -1
- package/uninstall-skill.js +60 -9
package/install-skill.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
4
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -24,8 +25,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
25
|
|
|
25
26
|
// shared/src/install-skill.ts
|
|
26
27
|
var import_fs3 = __toESM(require("fs"));
|
|
27
|
-
var
|
|
28
|
-
var
|
|
28
|
+
var import_path4 = __toESM(require("path"));
|
|
29
|
+
var import_os4 = __toESM(require("os"));
|
|
29
30
|
var import_child_process = require("child_process");
|
|
30
31
|
|
|
31
32
|
// shared/src/utils.ts
|
|
@@ -115,12 +116,58 @@ function readSkillConfig(dir) {
|
|
|
115
116
|
}
|
|
116
117
|
return JSON.parse(import_fs.default.readFileSync(configPath, "utf-8"));
|
|
117
118
|
}
|
|
119
|
+
function parseYamlFrontmatter(content) {
|
|
120
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
121
|
+
if (!match) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const [, frontmatterStr, body] = match;
|
|
125
|
+
const frontmatter = {};
|
|
126
|
+
const lines = frontmatterStr.split("\n");
|
|
127
|
+
for (const line of lines) {
|
|
128
|
+
const match2 = line.match(/^(\w+):\s*(.*)$/);
|
|
129
|
+
if (match2) {
|
|
130
|
+
const [, key, value] = match2;
|
|
131
|
+
frontmatter[key] = value.replace(/^["']|["']$/g, "").trim();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return { frontmatter, body };
|
|
135
|
+
}
|
|
136
|
+
function patchSkillMdName(skillMdPath, name) {
|
|
137
|
+
try {
|
|
138
|
+
const content = import_fs.default.readFileSync(skillMdPath, "utf-8");
|
|
139
|
+
const parsed = parseYamlFrontmatter(content);
|
|
140
|
+
if (!parsed) {
|
|
141
|
+
console.warn(" \u26A0 Warning: SKILL.md has no frontmatter, skipping name patch");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const { frontmatter, body } = parsed;
|
|
145
|
+
frontmatter.name = name;
|
|
146
|
+
const frontmatterLines = Object.entries(frontmatter).map(
|
|
147
|
+
([key, value]) => `${key}: ${value}`
|
|
148
|
+
);
|
|
149
|
+
const newContent = `---
|
|
150
|
+
${frontmatterLines.join("\n")}
|
|
151
|
+
---
|
|
152
|
+
${body}`;
|
|
153
|
+
import_fs.default.writeFileSync(skillMdPath, newContent, "utf-8");
|
|
154
|
+
console.log(` \u2713 Patched SKILL.md name: ${name}`);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
157
|
+
console.warn(` \u26A0 Warning: Failed to patch SKILL.md name: ${message}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
118
160
|
|
|
119
161
|
// shared/src/claude-settings.ts
|
|
120
162
|
var import_fs2 = __toESM(require("fs"));
|
|
121
163
|
var import_path2 = __toESM(require("path"));
|
|
122
164
|
var import_os2 = __toESM(require("os"));
|
|
165
|
+
var CLAUDE_SETTINGS_PATH_ENV = "CLAUDE_CODE_SETTINGS_PATH";
|
|
123
166
|
function getClaudeSettingsPath() {
|
|
167
|
+
const overridePath = process.env[CLAUDE_SETTINGS_PATH_ENV];
|
|
168
|
+
if (overridePath && overridePath.trim()) {
|
|
169
|
+
return overridePath.trim();
|
|
170
|
+
}
|
|
124
171
|
return import_path2.default.join(import_os2.default.homedir(), ".claude", "settings.json");
|
|
125
172
|
}
|
|
126
173
|
function readClaudeSettings() {
|
|
@@ -144,8 +191,49 @@ function writeClaudeSettings(settings) {
|
|
|
144
191
|
}
|
|
145
192
|
import_fs2.default.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
146
193
|
}
|
|
147
|
-
function
|
|
148
|
-
|
|
194
|
+
function normalizeHook(hook) {
|
|
195
|
+
if (!hook || typeof hook !== "object") return null;
|
|
196
|
+
const command = hook.command;
|
|
197
|
+
if (typeof command !== "string" || !command.trim()) return null;
|
|
198
|
+
return { type: "command", command };
|
|
199
|
+
}
|
|
200
|
+
function normalizeHooks(hooks) {
|
|
201
|
+
if (!Array.isArray(hooks)) return [];
|
|
202
|
+
const normalized = [];
|
|
203
|
+
for (const h of hooks) {
|
|
204
|
+
const nh = normalizeHook(h);
|
|
205
|
+
if (nh) normalized.push(nh);
|
|
206
|
+
}
|
|
207
|
+
return normalized;
|
|
208
|
+
}
|
|
209
|
+
function getHookKey(hook) {
|
|
210
|
+
return hook.command;
|
|
211
|
+
}
|
|
212
|
+
function mergeHooks(existing, incoming) {
|
|
213
|
+
const merged = [];
|
|
214
|
+
const seen = /* @__PURE__ */ new Set();
|
|
215
|
+
let didChange = false;
|
|
216
|
+
for (const hook of existing) {
|
|
217
|
+
const key = getHookKey(hook);
|
|
218
|
+
if (!seen.has(key)) {
|
|
219
|
+
merged.push(hook);
|
|
220
|
+
seen.add(key);
|
|
221
|
+
} else {
|
|
222
|
+
didChange = true;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
for (const hook of incoming) {
|
|
226
|
+
const key = getHookKey(hook);
|
|
227
|
+
if (!seen.has(key)) {
|
|
228
|
+
merged.push(hook);
|
|
229
|
+
seen.add(key);
|
|
230
|
+
didChange = true;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return { merged, didChange };
|
|
234
|
+
}
|
|
235
|
+
function findMatcherIndex(existingHooks, matcher) {
|
|
236
|
+
return existingHooks.findIndex((hook) => hook && hook.matcher === matcher);
|
|
149
237
|
}
|
|
150
238
|
function addClaudeHooks(hooksConfig, skillName) {
|
|
151
239
|
const settings = readClaudeSettings();
|
|
@@ -163,12 +251,30 @@ function addClaudeHooks(hooksConfig, skillName) {
|
|
|
163
251
|
}
|
|
164
252
|
const existingHooks = hooks[hookType];
|
|
165
253
|
for (const matcher of hookMatchers) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
254
|
+
const idx = findMatcherIndex(existingHooks, matcher.matcher);
|
|
255
|
+
if (idx === -1) {
|
|
256
|
+
const normalized = {
|
|
257
|
+
matcher: matcher.matcher,
|
|
258
|
+
hooks: normalizeHooks(matcher.hooks)
|
|
259
|
+
};
|
|
260
|
+
if (normalized.hooks.length > 0) {
|
|
261
|
+
existingHooks.push(normalized);
|
|
262
|
+
modified = true;
|
|
263
|
+
console.log(` \u2713 Added ${hookType} hook for ${skillName}`);
|
|
264
|
+
}
|
|
170
265
|
} else {
|
|
171
|
-
|
|
266
|
+
const existingMatcher = existingHooks[idx];
|
|
267
|
+
const existingMatcherHooks = normalizeHooks(existingMatcher.hooks);
|
|
268
|
+
const incomingHooks = normalizeHooks(matcher.hooks);
|
|
269
|
+
const { merged, didChange } = mergeHooks(existingMatcherHooks, incomingHooks);
|
|
270
|
+
const normalizedChanged = Array.isArray(existingMatcher.hooks) && JSON.stringify(existingMatcher.hooks) !== JSON.stringify(merged);
|
|
271
|
+
if (didChange || normalizedChanged) {
|
|
272
|
+
existingHooks[idx] = { ...existingMatcher, hooks: merged };
|
|
273
|
+
modified = true;
|
|
274
|
+
console.log(` \u2713 Merged ${hookType} hooks for ${skillName} (matcher: ${matcher.matcher})`);
|
|
275
|
+
} else {
|
|
276
|
+
console.log(` \u2139 ${hookType} hook already exists, skipping`);
|
|
277
|
+
}
|
|
172
278
|
}
|
|
173
279
|
}
|
|
174
280
|
hooks[hookType] = existingHooks;
|
|
@@ -179,7 +285,263 @@ function addClaudeHooks(hooksConfig, skillName) {
|
|
|
179
285
|
return modified;
|
|
180
286
|
}
|
|
181
287
|
|
|
288
|
+
// shared/src/remote-update-checker-script.ts
|
|
289
|
+
var import_path3 = __toESM(require("path"));
|
|
290
|
+
var import_os3 = __toESM(require("os"));
|
|
291
|
+
var REMOTE_UPDATE_CHECKER_VERSION = "1";
|
|
292
|
+
function getRemoteUpdateCheckerScriptContents() {
|
|
293
|
+
const version = REMOTE_UPDATE_CHECKER_VERSION;
|
|
294
|
+
return `#!/usr/bin/env node
|
|
295
|
+
/* remote-skill-update-checker v${version} */
|
|
296
|
+
|
|
297
|
+
const fs = require('fs');
|
|
298
|
+
const path = require('path');
|
|
299
|
+
const os = require('os');
|
|
300
|
+
const https = require('https');
|
|
301
|
+
|
|
302
|
+
const DEFAULT_COOLDOWN_MS = 24 * 60 * 60 * 1000; // 1 day
|
|
303
|
+
|
|
304
|
+
function parseArgs(argv) {
|
|
305
|
+
const args = new Set(argv.slice(2));
|
|
306
|
+
return {
|
|
307
|
+
force: args.has('--force') || args.has('-f'),
|
|
308
|
+
verbose: args.has('--verbose') || args.has('-v'),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function readJson(filePath) {
|
|
313
|
+
try {
|
|
314
|
+
if (!fs.existsSync(filePath)) return null;
|
|
315
|
+
const txt = fs.readFileSync(filePath, 'utf-8');
|
|
316
|
+
return JSON.parse(txt);
|
|
317
|
+
} catch {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function writeJson(filePath, obj) {
|
|
323
|
+
try {
|
|
324
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
325
|
+
fs.writeFileSync(filePath, JSON.stringify(obj, null, 2), 'utf-8');
|
|
326
|
+
} catch {
|
|
327
|
+
// ignore
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function getManifestPaths() {
|
|
332
|
+
const cwd = process.cwd();
|
|
333
|
+
return Array.from(
|
|
334
|
+
new Set([
|
|
335
|
+
path.join(os.homedir(), '.claude', 'skills', '.skills-manifest.json'),
|
|
336
|
+
path.join(cwd, '.claude', 'skills', '.skills-manifest.json'),
|
|
337
|
+
])
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function getRemoteSourcesFromManifest(manifest) {
|
|
342
|
+
const skills = manifest && typeof manifest.skills === 'object' ? manifest.skills : {};
|
|
343
|
+
const sources = [];
|
|
344
|
+
for (const v of Object.values(skills || {})) {
|
|
345
|
+
if (!v || typeof v !== 'object') continue;
|
|
346
|
+
const s = v.source;
|
|
347
|
+
if (typeof s === 'string' && s.trim()) sources.push(s.trim());
|
|
348
|
+
}
|
|
349
|
+
return Array.from(new Set(sources));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function parseRemoteSource(remoteSource) {
|
|
353
|
+
const trimmed = String(remoteSource || '')
|
|
354
|
+
.trim()
|
|
355
|
+
.replace(/^https?:\\/\\/github\\.com\\//, '');
|
|
356
|
+
const parts = trimmed.split('/').filter(Boolean);
|
|
357
|
+
if (parts.length < 2) return null;
|
|
358
|
+
const owner = parts[0];
|
|
359
|
+
const repo = parts[1];
|
|
360
|
+
const repoPath = parts.slice(2).join('/') || null;
|
|
361
|
+
return { owner, repo, repoPath };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function buildCommitsUrl(remoteSource) {
|
|
365
|
+
const parsed = parseRemoteSource(remoteSource);
|
|
366
|
+
if (!parsed) return null;
|
|
367
|
+
const base =
|
|
368
|
+
'https://api.github.com/repos/' +
|
|
369
|
+
encodeURIComponent(parsed.owner) +
|
|
370
|
+
'/' +
|
|
371
|
+
encodeURIComponent(parsed.repo) +
|
|
372
|
+
'/commits?per_page=1';
|
|
373
|
+
if (!parsed.repoPath) return base;
|
|
374
|
+
return base + '&path=' + encodeURIComponent(parsed.repoPath);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function shouldSkipByCooldown(lastCheckedAtIso, now, cooldownMs) {
|
|
378
|
+
if (!lastCheckedAtIso) return false;
|
|
379
|
+
const last = new Date(lastCheckedAtIso);
|
|
380
|
+
if (Number.isNaN(last.getTime())) return false;
|
|
381
|
+
return now.getTime() - last.getTime() < cooldownMs;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function fetchText(url) {
|
|
385
|
+
return new Promise((resolve, reject) => {
|
|
386
|
+
const req = https.request(
|
|
387
|
+
url,
|
|
388
|
+
{
|
|
389
|
+
method: 'GET',
|
|
390
|
+
headers: {
|
|
391
|
+
'user-agent': 'claude-skills-remote-update-checker',
|
|
392
|
+
accept: 'application/vnd.github+json',
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
(res) => {
|
|
396
|
+
let data = '';
|
|
397
|
+
res.setEncoding('utf8');
|
|
398
|
+
res.on('data', (chunk) => (data += chunk));
|
|
399
|
+
res.on('end', () => {
|
|
400
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
401
|
+
resolve(data);
|
|
402
|
+
} else {
|
|
403
|
+
reject(new Error('HTTP ' + (res.statusCode || 0)));
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
);
|
|
408
|
+
req.on('error', reject);
|
|
409
|
+
req.end();
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function extractLatestSha(jsonText) {
|
|
414
|
+
try {
|
|
415
|
+
const data = JSON.parse(jsonText);
|
|
416
|
+
if (Array.isArray(data) && data[0] && typeof data[0] === 'object' && typeof data[0].sha === 'string') {
|
|
417
|
+
return data[0].sha || null;
|
|
418
|
+
}
|
|
419
|
+
return null;
|
|
420
|
+
} catch {
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async function main() {
|
|
426
|
+
const { force, verbose } = parseArgs(process.argv);
|
|
427
|
+
const now = new Date();
|
|
428
|
+
const cooldownMs = DEFAULT_COOLDOWN_MS;
|
|
429
|
+
|
|
430
|
+
const manifestPaths = getManifestPaths();
|
|
431
|
+
const updates = [];
|
|
432
|
+
|
|
433
|
+
for (const manifestPath of manifestPaths) {
|
|
434
|
+
const manifest = readJson(manifestPath) || {};
|
|
435
|
+
const sources = getRemoteSourcesFromManifest(manifest);
|
|
436
|
+
if (!sources.length) continue;
|
|
437
|
+
|
|
438
|
+
manifest.remoteCache = manifest.remoteCache && typeof manifest.remoteCache === 'object' ? manifest.remoteCache : {};
|
|
439
|
+
|
|
440
|
+
for (const remoteSource of sources) {
|
|
441
|
+
const prevEntry = manifest.remoteCache[remoteSource] || {};
|
|
442
|
+
const prevSha = prevEntry.lastSeenSha || null;
|
|
443
|
+
|
|
444
|
+
const url = buildCommitsUrl(remoteSource);
|
|
445
|
+
if (!url) continue;
|
|
446
|
+
|
|
447
|
+
if (!force && shouldSkipByCooldown(prevEntry.lastCheckedAt, now, cooldownMs)) {
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
try {
|
|
452
|
+
const text = await fetchText(url);
|
|
453
|
+
const latestSha = extractLatestSha(text);
|
|
454
|
+
|
|
455
|
+
manifest.remoteCache[remoteSource] = {
|
|
456
|
+
...prevEntry,
|
|
457
|
+
lastCheckedAt: now.toISOString(),
|
|
458
|
+
...(latestSha ? { lastSeenSha: latestSha } : {}),
|
|
459
|
+
};
|
|
460
|
+
writeJson(manifestPath, manifest);
|
|
461
|
+
|
|
462
|
+
// Baseline: first time seeing sha => do not notify
|
|
463
|
+
if (!prevSha && latestSha) continue;
|
|
464
|
+
|
|
465
|
+
if (prevSha && latestSha && prevSha !== latestSha) {
|
|
466
|
+
updates.push({ remoteSource, prevSha, latestSha });
|
|
467
|
+
}
|
|
468
|
+
} catch (e) {
|
|
469
|
+
manifest.remoteCache[remoteSource] = {
|
|
470
|
+
...prevEntry,
|
|
471
|
+
lastCheckedAt: now.toISOString(),
|
|
472
|
+
};
|
|
473
|
+
writeJson(manifestPath, manifest);
|
|
474
|
+
if (verbose) {
|
|
475
|
+
console.log('[remote-skill-check] error', remoteSource, String(e && e.message ? e.message : e));
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (updates.length > 0) {
|
|
482
|
+
console.log('\\n\u{1F4E1} \u68C0\u6D4B\u5230\u8FDC\u7A0B skill \u6709\u66F4\u65B0\uFF1A');
|
|
483
|
+
for (const u of updates) {
|
|
484
|
+
console.log(' -', u.remoteSource);
|
|
485
|
+
console.log(' ', (u.prevSha || '').slice(0, 7), '->', (u.latestSha || '').slice(0, 7));
|
|
486
|
+
}
|
|
487
|
+
console.log('\\n\u{1F4A1} \u5EFA\u8BAE\u91CD\u65B0\u5B89\u88C5\u5BF9\u5E94 skill \u4EE5\u62C9\u53D6\u6700\u65B0\u7248\u672C\uFF08remoteSource \u6A21\u5F0F\u4E0D\u4F1A\u81EA\u52A8\u66F4\u65B0\uFF09\u3002\\n');
|
|
488
|
+
process.exitCode = 0;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
main().catch(() => {
|
|
493
|
+
// ignore
|
|
494
|
+
});
|
|
495
|
+
`;
|
|
496
|
+
}
|
|
497
|
+
function getRemoteUpdateCheckerScriptInstallPath() {
|
|
498
|
+
const scriptsDir = import_path3.default.join(import_os3.default.homedir(), ".claude", "scripts");
|
|
499
|
+
return import_path3.default.join(scriptsDir, "remote-skill-update-check.js");
|
|
500
|
+
}
|
|
501
|
+
|
|
182
502
|
// shared/src/install-skill.ts
|
|
503
|
+
function ensureRemoteUpdateCheckerInstalled() {
|
|
504
|
+
const scriptPath = getRemoteUpdateCheckerScriptInstallPath();
|
|
505
|
+
const scriptsDir = import_path4.default.dirname(scriptPath);
|
|
506
|
+
ensureDir(scriptsDir);
|
|
507
|
+
const marker = `/* remote-skill-update-checker v${REMOTE_UPDATE_CHECKER_VERSION} */`;
|
|
508
|
+
if (import_fs3.default.existsSync(scriptPath)) {
|
|
509
|
+
try {
|
|
510
|
+
const existing = import_fs3.default.readFileSync(scriptPath, "utf-8");
|
|
511
|
+
if (existing.includes(marker)) {
|
|
512
|
+
return scriptPath;
|
|
513
|
+
}
|
|
514
|
+
} catch {
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
const contents = getRemoteUpdateCheckerScriptContents();
|
|
518
|
+
import_fs3.default.writeFileSync(scriptPath, contents, { encoding: "utf-8" });
|
|
519
|
+
try {
|
|
520
|
+
import_fs3.default.chmodSync(scriptPath, 493);
|
|
521
|
+
} catch {
|
|
522
|
+
}
|
|
523
|
+
return scriptPath;
|
|
524
|
+
}
|
|
525
|
+
function ensureRemoteUpdateSessionEndHook(scriptPath) {
|
|
526
|
+
const command = `node "${scriptPath}"`;
|
|
527
|
+
addClaudeHooks(
|
|
528
|
+
{
|
|
529
|
+
SessionEnd: [
|
|
530
|
+
{
|
|
531
|
+
matcher: "*",
|
|
532
|
+
hooks: [
|
|
533
|
+
{
|
|
534
|
+
command,
|
|
535
|
+
// Claude Code settings schema expects "command"
|
|
536
|
+
type: "command"
|
|
537
|
+
}
|
|
538
|
+
]
|
|
539
|
+
}
|
|
540
|
+
]
|
|
541
|
+
},
|
|
542
|
+
"remote-update-checker"
|
|
543
|
+
);
|
|
544
|
+
}
|
|
183
545
|
function fetchFromRemote(tempDir, remoteSource) {
|
|
184
546
|
try {
|
|
185
547
|
console.log(` \u{1F310} Fetching latest from ${remoteSource}...`);
|
|
@@ -187,7 +549,7 @@ function fetchFromRemote(tempDir, remoteSource) {
|
|
|
187
549
|
stdio: "pipe",
|
|
188
550
|
timeout: 6e4
|
|
189
551
|
});
|
|
190
|
-
if (import_fs3.default.existsSync(
|
|
552
|
+
if (import_fs3.default.existsSync(import_path4.default.join(tempDir, "SKILL.md"))) {
|
|
191
553
|
console.log(" \u2713 Fetched latest version from remote");
|
|
192
554
|
return true;
|
|
193
555
|
}
|
|
@@ -209,7 +571,7 @@ function getSourceDir(config, packageDir) {
|
|
|
209
571
|
isRemote: false
|
|
210
572
|
};
|
|
211
573
|
}
|
|
212
|
-
const tempDir =
|
|
574
|
+
const tempDir = import_path4.default.join(import_os4.default.tmpdir(), `skill-fetch-${Date.now()}`);
|
|
213
575
|
const remoteSuccess = fetchFromRemote(tempDir, config.remoteSource);
|
|
214
576
|
if (remoteSuccess) {
|
|
215
577
|
return {
|
|
@@ -235,7 +597,7 @@ function getSourceDir(config, packageDir) {
|
|
|
235
597
|
};
|
|
236
598
|
}
|
|
237
599
|
function updateManifest(skillsDir, config, targetName, isRemote) {
|
|
238
|
-
const manifestPath =
|
|
600
|
+
const manifestPath = import_path4.default.join(skillsDir, ".skills-manifest.json");
|
|
239
601
|
let manifest = { skills: {} };
|
|
240
602
|
if (import_fs3.default.existsSync(manifestPath)) {
|
|
241
603
|
try {
|
|
@@ -250,7 +612,7 @@ function updateManifest(skillsDir, config, targetName, isRemote) {
|
|
|
250
612
|
version: config.version,
|
|
251
613
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
252
614
|
package: config.package || config.name,
|
|
253
|
-
path:
|
|
615
|
+
path: import_path4.default.join(skillsDir, skillName),
|
|
254
616
|
target: targetName,
|
|
255
617
|
...config.remoteSource && { source: config.remoteSource }
|
|
256
618
|
};
|
|
@@ -263,8 +625,8 @@ function installToTarget(target, config, sourceDir, isRemote) {
|
|
|
263
625
|
const isGlobal = isGlobalInstall();
|
|
264
626
|
const location = detectInstallLocation(target.paths, isGlobal);
|
|
265
627
|
const skillName = extractSkillName(config.name);
|
|
266
|
-
const targetDir =
|
|
267
|
-
const altTargetDir =
|
|
628
|
+
const targetDir = import_path4.default.join(location.base, skillName);
|
|
629
|
+
const altTargetDir = import_path4.default.join(location.base, config.name);
|
|
268
630
|
console.log(` Type: ${location.type}${isGlobal ? " (global)" : " (project)"}`);
|
|
269
631
|
console.log(` Directory: ${targetDir}`);
|
|
270
632
|
if (import_fs3.default.existsSync(altTargetDir) && altTargetDir !== targetDir) {
|
|
@@ -273,31 +635,46 @@ function installToTarget(target, config, sourceDir, isRemote) {
|
|
|
273
635
|
console.log(` \u2713 Removed directory: ${config.name}`);
|
|
274
636
|
}
|
|
275
637
|
ensureDir(targetDir);
|
|
276
|
-
const skillMdSource =
|
|
638
|
+
const skillMdSource = import_path4.default.join(sourceDir, "SKILL.md");
|
|
277
639
|
if (!import_fs3.default.existsSync(skillMdSource)) {
|
|
278
640
|
throw new Error("SKILL.md is required but not found");
|
|
279
641
|
}
|
|
280
|
-
import_fs3.default.copyFileSync(skillMdSource,
|
|
642
|
+
import_fs3.default.copyFileSync(skillMdSource, import_path4.default.join(targetDir, "SKILL.md"));
|
|
281
643
|
console.log(" \u2713 Copied SKILL.md");
|
|
644
|
+
if (isRemote && config.remoteSource) {
|
|
645
|
+
patchSkillMdName(import_path4.default.join(targetDir, "SKILL.md"), config.name);
|
|
646
|
+
}
|
|
282
647
|
const filesToCopy = config.files || {};
|
|
283
648
|
for (const [source, dest] of Object.entries(filesToCopy)) {
|
|
284
|
-
|
|
649
|
+
if (source === "SKILL.md") {
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
const sourcePath = import_path4.default.join(sourceDir, source);
|
|
285
653
|
if (!import_fs3.default.existsSync(sourcePath)) {
|
|
286
654
|
console.warn(` \u26A0 Warning: ${source} not found, skipping`);
|
|
287
655
|
continue;
|
|
288
656
|
}
|
|
289
|
-
const destPath =
|
|
657
|
+
const destPath = import_path4.default.join(targetDir, dest);
|
|
290
658
|
if (import_fs3.default.statSync(sourcePath).isDirectory()) {
|
|
291
659
|
copyDir(sourcePath, destPath);
|
|
292
660
|
console.log(` \u2713 Copied directory: ${source}`);
|
|
293
661
|
} else {
|
|
294
|
-
const destDir =
|
|
662
|
+
const destDir = import_path4.default.dirname(destPath);
|
|
295
663
|
ensureDir(destDir);
|
|
296
664
|
import_fs3.default.copyFileSync(sourcePath, destPath);
|
|
297
665
|
console.log(` \u2713 Copied file: ${source}`);
|
|
298
666
|
}
|
|
299
667
|
}
|
|
300
668
|
updateManifest(location.base, config, target.name, isRemote);
|
|
669
|
+
if (target.name === "claude-code" && config.remoteSource) {
|
|
670
|
+
try {
|
|
671
|
+
const checkerPath = ensureRemoteUpdateCheckerInstalled();
|
|
672
|
+
ensureRemoteUpdateSessionEndHook(checkerPath);
|
|
673
|
+
} catch (error) {
|
|
674
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
675
|
+
console.warn(` \u26A0 \u8B66\u544A: \u65E0\u6CD5\u914D\u7F6E\u8FDC\u7A0B\u66F4\u65B0\u68C0\u67E5\uFF08\u53EF\u5B89\u5168\u5FFD\u7565\uFF09: ${message}`);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
301
678
|
if (target.name === "claude-code" && ((_a = config.claudeSettings) == null ? void 0 : _a.hooks)) {
|
|
302
679
|
console.log(" \u{1F527} \u914D\u7F6E Claude Code \u94A9\u5B50...");
|
|
303
680
|
try {
|
package/package.json
CHANGED
package/uninstall-skill.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
4
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -100,7 +101,12 @@ function readSkillConfig(dir) {
|
|
|
100
101
|
var import_fs2 = __toESM(require("fs"));
|
|
101
102
|
var import_path2 = __toESM(require("path"));
|
|
102
103
|
var import_os2 = __toESM(require("os"));
|
|
104
|
+
var CLAUDE_SETTINGS_PATH_ENV = "CLAUDE_CODE_SETTINGS_PATH";
|
|
103
105
|
function getClaudeSettingsPath() {
|
|
106
|
+
const overridePath = process.env[CLAUDE_SETTINGS_PATH_ENV];
|
|
107
|
+
if (overridePath && overridePath.trim()) {
|
|
108
|
+
return overridePath.trim();
|
|
109
|
+
}
|
|
104
110
|
return import_path2.default.join(import_os2.default.homedir(), ".claude", "settings.json");
|
|
105
111
|
}
|
|
106
112
|
function readClaudeSettings() {
|
|
@@ -124,6 +130,32 @@ function writeClaudeSettings(settings) {
|
|
|
124
130
|
}
|
|
125
131
|
import_fs2.default.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
126
132
|
}
|
|
133
|
+
function normalizeHook(hook) {
|
|
134
|
+
if (!hook || typeof hook !== "object") return null;
|
|
135
|
+
const command = hook.command;
|
|
136
|
+
if (typeof command !== "string" || !command.trim()) return null;
|
|
137
|
+
return { type: "command", command };
|
|
138
|
+
}
|
|
139
|
+
function normalizeHooks(hooks) {
|
|
140
|
+
if (!Array.isArray(hooks)) return [];
|
|
141
|
+
const normalized = [];
|
|
142
|
+
for (const h of hooks) {
|
|
143
|
+
const nh = normalizeHook(h);
|
|
144
|
+
if (nh) normalized.push(nh);
|
|
145
|
+
}
|
|
146
|
+
return normalized;
|
|
147
|
+
}
|
|
148
|
+
function getHookKey(hook) {
|
|
149
|
+
return hook.command;
|
|
150
|
+
}
|
|
151
|
+
function removeHooks(existing, toRemove) {
|
|
152
|
+
const removeKeys = new Set(toRemove.map(getHookKey));
|
|
153
|
+
const remaining = existing.filter((h) => !removeKeys.has(getHookKey(h)));
|
|
154
|
+
return { remaining, didChange: remaining.length !== existing.length };
|
|
155
|
+
}
|
|
156
|
+
function findMatcherIndex(existingHooks, matcher) {
|
|
157
|
+
return existingHooks.findIndex((hook) => hook && hook.matcher === matcher);
|
|
158
|
+
}
|
|
127
159
|
function removeClaudeHooks(hooksConfig, skillName) {
|
|
128
160
|
const settings = readClaudeSettings();
|
|
129
161
|
let modified = false;
|
|
@@ -139,16 +171,35 @@ function removeClaudeHooks(hooksConfig, skillName) {
|
|
|
139
171
|
continue;
|
|
140
172
|
}
|
|
141
173
|
const existingHooks = hooks[hookType];
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
174
|
+
for (const matcher of hookMatchers) {
|
|
175
|
+
const idx = findMatcherIndex(existingHooks, matcher.matcher);
|
|
176
|
+
if (idx === -1) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const existingMatcher = existingHooks[idx];
|
|
180
|
+
const existingMatcherHooks = normalizeHooks(existingMatcher.hooks);
|
|
181
|
+
const toRemoveHooks = normalizeHooks(matcher.hooks);
|
|
182
|
+
const { remaining, didChange } = removeHooks(existingMatcherHooks, toRemoveHooks);
|
|
183
|
+
if (didChange) {
|
|
184
|
+
modified = true;
|
|
185
|
+
if (remaining.length === 0) {
|
|
186
|
+
existingHooks.splice(idx, 1);
|
|
187
|
+
console.log(
|
|
188
|
+
` \u2713 Removed ${hookType} matcher for ${skillName} (matcher: ${matcher.matcher})`
|
|
189
|
+
);
|
|
190
|
+
} else {
|
|
191
|
+
existingHooks[idx] = { ...existingMatcher, hooks: remaining };
|
|
192
|
+
console.log(` \u2713 Removed ${hookType} hooks for ${skillName} (matcher: ${matcher.matcher})`);
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
const normalizedChanged = Array.isArray(existingMatcher.hooks) && JSON.stringify(existingMatcher.hooks) !== JSON.stringify(existingMatcherHooks);
|
|
196
|
+
if (normalizedChanged) {
|
|
197
|
+
existingHooks[idx] = { ...existingMatcher, hooks: existingMatcherHooks };
|
|
198
|
+
modified = true;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
151
201
|
}
|
|
202
|
+
hooks[hookType] = existingHooks;
|
|
152
203
|
}
|
|
153
204
|
if (modified) {
|
|
154
205
|
writeClaudeSettings(settings);
|