@adonis0123/commit 1.0.4 → 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/README.md +1 -0
- package/install-skill.js +479 -26
- package/package.json +1 -1
- package/uninstall-skill.js +134 -10
package/README.md
CHANGED
|
@@ -62,6 +62,7 @@ npm install -g @adonis0123/commit
|
|
|
62
62
|
- [@adonis0123/staged-changes-review](https://www.npmjs.com/package/@adonis0123/staged-changes-review) - 代码审查
|
|
63
63
|
- [@adonis0123/create-skill](https://www.npmjs.com/package/@adonis0123/create-skill) - 创建新技能包
|
|
64
64
|
- [@adonis0123/code-doc-generator](https://www.npmjs.com/package/@adonis0123/code-doc-generator) - 代码文档生成
|
|
65
|
+
- [@adonis0123/css-tailwind-styling](https://www.npmjs.com/package/@adonis0123/css-tailwind-styling) - CSS 和 Tailwind 样式规范
|
|
65
66
|
## License
|
|
66
67
|
|
|
67
68
|
MIT
|
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;
|
|
@@ -23,9 +24,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
24
|
));
|
|
24
25
|
|
|
25
26
|
// shared/src/install-skill.ts
|
|
26
|
-
var
|
|
27
|
-
var
|
|
28
|
-
var
|
|
27
|
+
var import_fs3 = __toESM(require("fs"));
|
|
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,8 +116,432 @@ 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
|
+
}
|
|
160
|
+
|
|
161
|
+
// shared/src/claude-settings.ts
|
|
162
|
+
var import_fs2 = __toESM(require("fs"));
|
|
163
|
+
var import_path2 = __toESM(require("path"));
|
|
164
|
+
var import_os2 = __toESM(require("os"));
|
|
165
|
+
var CLAUDE_SETTINGS_PATH_ENV = "CLAUDE_CODE_SETTINGS_PATH";
|
|
166
|
+
function getClaudeSettingsPath() {
|
|
167
|
+
const overridePath = process.env[CLAUDE_SETTINGS_PATH_ENV];
|
|
168
|
+
if (overridePath && overridePath.trim()) {
|
|
169
|
+
return overridePath.trim();
|
|
170
|
+
}
|
|
171
|
+
return import_path2.default.join(import_os2.default.homedir(), ".claude", "settings.json");
|
|
172
|
+
}
|
|
173
|
+
function readClaudeSettings() {
|
|
174
|
+
const settingsPath = getClaudeSettingsPath();
|
|
175
|
+
if (!import_fs2.default.existsSync(settingsPath)) {
|
|
176
|
+
return {};
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
const content = import_fs2.default.readFileSync(settingsPath, "utf-8");
|
|
180
|
+
return JSON.parse(content);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.warn(" \u26A0 Warning: Could not parse settings.json, treating as empty");
|
|
183
|
+
return {};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function writeClaudeSettings(settings) {
|
|
187
|
+
const settingsPath = getClaudeSettingsPath();
|
|
188
|
+
const settingsDir = import_path2.default.dirname(settingsPath);
|
|
189
|
+
if (!import_fs2.default.existsSync(settingsDir)) {
|
|
190
|
+
import_fs2.default.mkdirSync(settingsDir, { recursive: true });
|
|
191
|
+
}
|
|
192
|
+
import_fs2.default.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
193
|
+
}
|
|
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);
|
|
237
|
+
}
|
|
238
|
+
function addClaudeHooks(hooksConfig, skillName) {
|
|
239
|
+
const settings = readClaudeSettings();
|
|
240
|
+
let modified = false;
|
|
241
|
+
if (!settings.hooks || typeof settings.hooks !== "object") {
|
|
242
|
+
settings.hooks = {};
|
|
243
|
+
}
|
|
244
|
+
const hooks = settings.hooks;
|
|
245
|
+
for (const [hookType, hookMatchers] of Object.entries(hooksConfig)) {
|
|
246
|
+
if (!hookMatchers || !Array.isArray(hookMatchers)) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (!hooks[hookType] || !Array.isArray(hooks[hookType])) {
|
|
250
|
+
hooks[hookType] = [];
|
|
251
|
+
}
|
|
252
|
+
const existingHooks = hooks[hookType];
|
|
253
|
+
for (const matcher of hookMatchers) {
|
|
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
|
+
}
|
|
265
|
+
} else {
|
|
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
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
hooks[hookType] = existingHooks;
|
|
281
|
+
}
|
|
282
|
+
if (modified) {
|
|
283
|
+
writeClaudeSettings(settings);
|
|
284
|
+
}
|
|
285
|
+
return modified;
|
|
286
|
+
}
|
|
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
|
+
}
|
|
118
501
|
|
|
119
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
|
+
}
|
|
120
545
|
function fetchFromRemote(tempDir, remoteSource) {
|
|
121
546
|
try {
|
|
122
547
|
console.log(` \u{1F310} Fetching latest from ${remoteSource}...`);
|
|
@@ -124,7 +549,7 @@ function fetchFromRemote(tempDir, remoteSource) {
|
|
|
124
549
|
stdio: "pipe",
|
|
125
550
|
timeout: 6e4
|
|
126
551
|
});
|
|
127
|
-
if (
|
|
552
|
+
if (import_fs3.default.existsSync(import_path4.default.join(tempDir, "SKILL.md"))) {
|
|
128
553
|
console.log(" \u2713 Fetched latest version from remote");
|
|
129
554
|
return true;
|
|
130
555
|
}
|
|
@@ -146,14 +571,14 @@ function getSourceDir(config, packageDir) {
|
|
|
146
571
|
isRemote: false
|
|
147
572
|
};
|
|
148
573
|
}
|
|
149
|
-
const tempDir =
|
|
574
|
+
const tempDir = import_path4.default.join(import_os4.default.tmpdir(), `skill-fetch-${Date.now()}`);
|
|
150
575
|
const remoteSuccess = fetchFromRemote(tempDir, config.remoteSource);
|
|
151
576
|
if (remoteSuccess) {
|
|
152
577
|
return {
|
|
153
578
|
sourceDir: tempDir,
|
|
154
579
|
cleanup: () => {
|
|
155
580
|
try {
|
|
156
|
-
|
|
581
|
+
import_fs3.default.rmSync(tempDir, { recursive: true, force: true });
|
|
157
582
|
} catch {
|
|
158
583
|
}
|
|
159
584
|
},
|
|
@@ -161,7 +586,7 @@ function getSourceDir(config, packageDir) {
|
|
|
161
586
|
};
|
|
162
587
|
}
|
|
163
588
|
try {
|
|
164
|
-
|
|
589
|
+
import_fs3.default.rmSync(tempDir, { recursive: true, force: true });
|
|
165
590
|
} catch {
|
|
166
591
|
}
|
|
167
592
|
return {
|
|
@@ -172,11 +597,11 @@ function getSourceDir(config, packageDir) {
|
|
|
172
597
|
};
|
|
173
598
|
}
|
|
174
599
|
function updateManifest(skillsDir, config, targetName, isRemote) {
|
|
175
|
-
const manifestPath =
|
|
600
|
+
const manifestPath = import_path4.default.join(skillsDir, ".skills-manifest.json");
|
|
176
601
|
let manifest = { skills: {} };
|
|
177
|
-
if (
|
|
602
|
+
if (import_fs3.default.existsSync(manifestPath)) {
|
|
178
603
|
try {
|
|
179
|
-
manifest = JSON.parse(
|
|
604
|
+
manifest = JSON.parse(import_fs3.default.readFileSync(manifestPath, "utf-8"));
|
|
180
605
|
} catch {
|
|
181
606
|
console.warn(" Warning: Could not parse existing manifest, creating new one");
|
|
182
607
|
manifest = { skills: {} };
|
|
@@ -187,55 +612,83 @@ function updateManifest(skillsDir, config, targetName, isRemote) {
|
|
|
187
612
|
version: config.version,
|
|
188
613
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
189
614
|
package: config.package || config.name,
|
|
190
|
-
path:
|
|
615
|
+
path: import_path4.default.join(skillsDir, skillName),
|
|
191
616
|
target: targetName,
|
|
192
617
|
...config.remoteSource && { source: config.remoteSource }
|
|
193
618
|
};
|
|
194
|
-
|
|
619
|
+
import_fs3.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
195
620
|
}
|
|
196
621
|
function installToTarget(target, config, sourceDir, isRemote) {
|
|
197
|
-
var _a;
|
|
622
|
+
var _a, _b;
|
|
198
623
|
console.log(`
|
|
199
624
|
\u{1F4E6} Installing to ${target.name}...`);
|
|
200
625
|
const isGlobal = isGlobalInstall();
|
|
201
626
|
const location = detectInstallLocation(target.paths, isGlobal);
|
|
202
627
|
const skillName = extractSkillName(config.name);
|
|
203
|
-
const targetDir =
|
|
204
|
-
const altTargetDir =
|
|
628
|
+
const targetDir = import_path4.default.join(location.base, skillName);
|
|
629
|
+
const altTargetDir = import_path4.default.join(location.base, config.name);
|
|
205
630
|
console.log(` Type: ${location.type}${isGlobal ? " (global)" : " (project)"}`);
|
|
206
631
|
console.log(` Directory: ${targetDir}`);
|
|
207
|
-
if (
|
|
632
|
+
if (import_fs3.default.existsSync(altTargetDir) && altTargetDir !== targetDir) {
|
|
208
633
|
console.log(" \u{1F9F9} Cleaning up alternative path format...");
|
|
209
634
|
removeDir(altTargetDir);
|
|
210
635
|
console.log(` \u2713 Removed directory: ${config.name}`);
|
|
211
636
|
}
|
|
212
637
|
ensureDir(targetDir);
|
|
213
|
-
const skillMdSource =
|
|
214
|
-
if (!
|
|
638
|
+
const skillMdSource = import_path4.default.join(sourceDir, "SKILL.md");
|
|
639
|
+
if (!import_fs3.default.existsSync(skillMdSource)) {
|
|
215
640
|
throw new Error("SKILL.md is required but not found");
|
|
216
641
|
}
|
|
217
|
-
|
|
642
|
+
import_fs3.default.copyFileSync(skillMdSource, import_path4.default.join(targetDir, "SKILL.md"));
|
|
218
643
|
console.log(" \u2713 Copied SKILL.md");
|
|
644
|
+
if (isRemote && config.remoteSource) {
|
|
645
|
+
patchSkillMdName(import_path4.default.join(targetDir, "SKILL.md"), config.name);
|
|
646
|
+
}
|
|
219
647
|
const filesToCopy = config.files || {};
|
|
220
648
|
for (const [source, dest] of Object.entries(filesToCopy)) {
|
|
221
|
-
|
|
222
|
-
|
|
649
|
+
if (source === "SKILL.md") {
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
const sourcePath = import_path4.default.join(sourceDir, source);
|
|
653
|
+
if (!import_fs3.default.existsSync(sourcePath)) {
|
|
223
654
|
console.warn(` \u26A0 Warning: ${source} not found, skipping`);
|
|
224
655
|
continue;
|
|
225
656
|
}
|
|
226
|
-
const destPath =
|
|
227
|
-
if (
|
|
657
|
+
const destPath = import_path4.default.join(targetDir, dest);
|
|
658
|
+
if (import_fs3.default.statSync(sourcePath).isDirectory()) {
|
|
228
659
|
copyDir(sourcePath, destPath);
|
|
229
660
|
console.log(` \u2713 Copied directory: ${source}`);
|
|
230
661
|
} else {
|
|
231
|
-
const destDir =
|
|
662
|
+
const destDir = import_path4.default.dirname(destPath);
|
|
232
663
|
ensureDir(destDir);
|
|
233
|
-
|
|
664
|
+
import_fs3.default.copyFileSync(sourcePath, destPath);
|
|
234
665
|
console.log(` \u2713 Copied file: ${source}`);
|
|
235
666
|
}
|
|
236
667
|
}
|
|
237
668
|
updateManifest(location.base, config, target.name, isRemote);
|
|
238
|
-
if (
|
|
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
|
+
}
|
|
678
|
+
if (target.name === "claude-code" && ((_a = config.claudeSettings) == null ? void 0 : _a.hooks)) {
|
|
679
|
+
console.log(" \u{1F527} \u914D\u7F6E Claude Code \u94A9\u5B50...");
|
|
680
|
+
try {
|
|
681
|
+
const skillName2 = extractSkillName(config.name);
|
|
682
|
+
const modified = addClaudeHooks(config.claudeSettings.hooks, skillName2);
|
|
683
|
+
if (modified) {
|
|
684
|
+
console.log(" \u2705 \u94A9\u5B50\u5DF2\u914D\u7F6E\u5230 ~/.claude/settings.json");
|
|
685
|
+
}
|
|
686
|
+
} catch (error) {
|
|
687
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
688
|
+
console.warn(` \u26A0 \u8B66\u544A: \u65E0\u6CD5\u914D\u7F6E\u94A9\u5B50: ${message}`);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
if ((_b = config.hooks) == null ? void 0 : _b.postinstall) {
|
|
239
692
|
console.log(" \u{1F527} Running postinstall hook...");
|
|
240
693
|
try {
|
|
241
694
|
(0, import_child_process.execSync)(config.hooks.postinstall, {
|
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;
|
|
@@ -23,8 +24,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
24
|
));
|
|
24
25
|
|
|
25
26
|
// shared/src/uninstall-skill.ts
|
|
26
|
-
var
|
|
27
|
-
var
|
|
27
|
+
var import_fs3 = __toESM(require("fs"));
|
|
28
|
+
var import_path3 = __toESM(require("path"));
|
|
28
29
|
|
|
29
30
|
// shared/src/utils.ts
|
|
30
31
|
var import_fs = __toESM(require("fs"));
|
|
@@ -96,17 +97,127 @@ function readSkillConfig(dir) {
|
|
|
96
97
|
return JSON.parse(import_fs.default.readFileSync(configPath, "utf-8"));
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
// shared/src/claude-settings.ts
|
|
101
|
+
var import_fs2 = __toESM(require("fs"));
|
|
102
|
+
var import_path2 = __toESM(require("path"));
|
|
103
|
+
var import_os2 = __toESM(require("os"));
|
|
104
|
+
var CLAUDE_SETTINGS_PATH_ENV = "CLAUDE_CODE_SETTINGS_PATH";
|
|
105
|
+
function getClaudeSettingsPath() {
|
|
106
|
+
const overridePath = process.env[CLAUDE_SETTINGS_PATH_ENV];
|
|
107
|
+
if (overridePath && overridePath.trim()) {
|
|
108
|
+
return overridePath.trim();
|
|
109
|
+
}
|
|
110
|
+
return import_path2.default.join(import_os2.default.homedir(), ".claude", "settings.json");
|
|
111
|
+
}
|
|
112
|
+
function readClaudeSettings() {
|
|
113
|
+
const settingsPath = getClaudeSettingsPath();
|
|
114
|
+
if (!import_fs2.default.existsSync(settingsPath)) {
|
|
115
|
+
return {};
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
const content = import_fs2.default.readFileSync(settingsPath, "utf-8");
|
|
119
|
+
return JSON.parse(content);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.warn(" \u26A0 Warning: Could not parse settings.json, treating as empty");
|
|
122
|
+
return {};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function writeClaudeSettings(settings) {
|
|
126
|
+
const settingsPath = getClaudeSettingsPath();
|
|
127
|
+
const settingsDir = import_path2.default.dirname(settingsPath);
|
|
128
|
+
if (!import_fs2.default.existsSync(settingsDir)) {
|
|
129
|
+
import_fs2.default.mkdirSync(settingsDir, { recursive: true });
|
|
130
|
+
}
|
|
131
|
+
import_fs2.default.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
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
|
+
}
|
|
159
|
+
function removeClaudeHooks(hooksConfig, skillName) {
|
|
160
|
+
const settings = readClaudeSettings();
|
|
161
|
+
let modified = false;
|
|
162
|
+
if (!settings.hooks || typeof settings.hooks !== "object") {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
const hooks = settings.hooks;
|
|
166
|
+
for (const [hookType, hookMatchers] of Object.entries(hooksConfig)) {
|
|
167
|
+
if (!hookMatchers || !Array.isArray(hookMatchers)) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (!hooks[hookType] || !Array.isArray(hooks[hookType])) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const existingHooks = hooks[hookType];
|
|
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
|
+
}
|
|
201
|
+
}
|
|
202
|
+
hooks[hookType] = existingHooks;
|
|
203
|
+
}
|
|
204
|
+
if (modified) {
|
|
205
|
+
writeClaudeSettings(settings);
|
|
206
|
+
}
|
|
207
|
+
return modified;
|
|
208
|
+
}
|
|
209
|
+
|
|
99
210
|
// shared/src/uninstall-skill.ts
|
|
100
211
|
function updateManifest(skillsDir, config) {
|
|
101
|
-
const manifestPath =
|
|
102
|
-
if (!
|
|
212
|
+
const manifestPath = import_path3.default.join(skillsDir, ".skills-manifest.json");
|
|
213
|
+
if (!import_fs3.default.existsSync(manifestPath)) {
|
|
103
214
|
return;
|
|
104
215
|
}
|
|
105
216
|
try {
|
|
106
|
-
const manifest = JSON.parse(
|
|
217
|
+
const manifest = JSON.parse(import_fs3.default.readFileSync(manifestPath, "utf-8"));
|
|
107
218
|
if (manifest.skills && manifest.skills[config.name]) {
|
|
108
219
|
delete manifest.skills[config.name];
|
|
109
|
-
|
|
220
|
+
import_fs3.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
110
221
|
console.log(" \u2713 Updated manifest");
|
|
111
222
|
}
|
|
112
223
|
} catch (error) {
|
|
@@ -115,25 +226,38 @@ function updateManifest(skillsDir, config) {
|
|
|
115
226
|
}
|
|
116
227
|
}
|
|
117
228
|
function uninstallFromTarget(target, config) {
|
|
229
|
+
var _a;
|
|
118
230
|
console.log(`
|
|
119
231
|
\u{1F5D1}\uFE0F Uninstalling from ${target.name}...`);
|
|
120
232
|
const isGlobal = isGlobalInstall();
|
|
121
233
|
const location = detectInstallLocation(target.paths, isGlobal);
|
|
122
234
|
const skillName = extractSkillName(config.name);
|
|
123
|
-
const skillNameTargetDir =
|
|
124
|
-
const fullPackageNameTargetDir =
|
|
235
|
+
const skillNameTargetDir = import_path3.default.join(location.base, skillName);
|
|
236
|
+
const fullPackageNameTargetDir = import_path3.default.join(location.base, config.name);
|
|
125
237
|
let removed = false;
|
|
126
|
-
if (
|
|
238
|
+
if (import_fs3.default.existsSync(skillNameTargetDir)) {
|
|
127
239
|
removeDir(skillNameTargetDir);
|
|
128
240
|
console.log(` \u2713 Removed skill directory: ${skillName}`);
|
|
129
241
|
removed = true;
|
|
130
242
|
}
|
|
131
|
-
if (
|
|
243
|
+
if (import_fs3.default.existsSync(fullPackageNameTargetDir) && fullPackageNameTargetDir !== skillNameTargetDir) {
|
|
132
244
|
removeDir(fullPackageNameTargetDir);
|
|
133
245
|
console.log(` \u2713 Removed skill directory: ${config.name}`);
|
|
134
246
|
removed = true;
|
|
135
247
|
}
|
|
136
248
|
updateManifest(location.base, config);
|
|
249
|
+
if (target.name === "claude-code" && removed && ((_a = config.claudeSettings) == null ? void 0 : _a.hooks)) {
|
|
250
|
+
try {
|
|
251
|
+
console.log(" \u{1F527} \u79FB\u9664 Claude Code \u94A9\u5B50...");
|
|
252
|
+
const skillName2 = extractSkillName(config.name);
|
|
253
|
+
const modified = removeClaudeHooks(config.claudeSettings.hooks, skillName2);
|
|
254
|
+
if (modified) {
|
|
255
|
+
console.log(" \u2705 \u94A9\u5B50\u5DF2\u4ECE ~/.claude/settings.json \u79FB\u9664");
|
|
256
|
+
}
|
|
257
|
+
} catch (error) {
|
|
258
|
+
console.warn(" \u26A0 \u8B66\u544A: \u65E0\u6CD5\u79FB\u9664\u94A9\u5B50\uFF08\u53EF\u5B89\u5168\u5FFD\u7565\uFF09");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
137
261
|
if (removed) {
|
|
138
262
|
console.log(` \u2705 Uninstalled from ${target.name}`);
|
|
139
263
|
return true;
|