skills-manager 0.0.5 → 0.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.
Files changed (2) hide show
  1. package/dist/index.mjs +89 -76
  2. package/package.json +2 -4
package/dist/index.mjs CHANGED
@@ -1,113 +1,126 @@
1
1
  import { cac } from "cac";
2
- import { cwd, exit } from "node:process";
3
- import chalk from "chalk";
4
- import degit from "degit";
5
- import { access, copyFile, mkdir, readdir } from "node:fs/promises";
2
+ import { downloadTemplate } from "giget";
3
+ import { access, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
6
4
  import { homedir } from "node:os";
7
- import { join } from "node:path";
5
+ import { dirname, join } from "node:path";
6
+ import { cwd } from "node:process";
8
7
  import { rimraf } from "rimraf";
9
8
 
10
9
  //#region src/utils.ts
11
10
  const print = console.log;
12
- async function exists(path) {
11
+ const red = (...text) => `\x1B[31m${text.join(" ")}\x1B[0m`;
12
+ const green = (...text) => `\x1B[32m${text.join(" ")}\x1B[0m`;
13
+ const gray = (...text) => `\x1B[90m${text.join(" ")}\x1B[0m`;
14
+ const yellow = (...text) => `\x1B[33m${text.join(" ")}\x1B[0m`;
15
+ const info = (...text) => print(gray(...text));
16
+ const warn = (...text) => print(yellow(...text, "\n"));
17
+ const CACHE_DIR = join(homedir(), ".cache", "skills-manager");
18
+ const CACHE_FILE = join(CACHE_DIR, "repos.json");
19
+ async function getCache() {
13
20
  try {
14
- await access(path);
15
- return true;
21
+ await mkdir(CACHE_DIR, { recursive: true });
22
+ const data = await readFile(CACHE_FILE, "utf-8");
23
+ return JSON.parse(data);
16
24
  } catch {
17
- return false;
25
+ return {};
18
26
  }
19
27
  }
20
- async function copyDirectory(src, dest) {
21
- await mkdir(dest, { recursive: true });
22
- const entries = await readdir(src, { withFileTypes: true });
23
- for (const entry of entries) {
24
- const srcPath = join(src, entry.name);
25
- const destPath = join(dest, entry.name);
26
- if (entry.isDirectory()) await copyDirectory(srcPath, destPath);
27
- else try {
28
- await copyFile(srcPath, destPath);
29
- } catch {}
28
+ async function saveCache(cacheData) {
29
+ try {
30
+ await mkdir(CACHE_DIR, { recursive: true });
31
+ await writeFile(CACHE_FILE, JSON.stringify(cacheData, null, 2));
32
+ } catch (error) {
33
+ warn("Failed to save cache:", error instanceof Error ? error.message : String(error));
30
34
  }
31
35
  }
32
- async function findSkillPath(repoPath, skillName) {
33
- const skillDir = join(join(repoPath, "skills"), skillName);
34
- if (await exists(skillDir)) return skillDir;
35
- async function findDirectory(dir, target) {
36
- try {
37
- const entries = await readdir(dir, { withFileTypes: true });
38
- for (const entry of entries) {
39
- if (entry.isDirectory() && entry.name === target) return join(dir, target);
40
- if (entry.isDirectory() && !entry.name.startsWith(".")) {
41
- const result = await findDirectory(join(dir, entry.name), target);
42
- if (result) return result;
43
- }
44
- }
45
- } catch {}
36
+ async function findSkill(repo, name) {
37
+ const cacheKey = `${repo}:${name}`;
38
+ const cacheData = await getCache();
39
+ if (cacheKey in cacheData) return cacheData[cacheKey];
40
+ try {
41
+ const { repo: { defaultBranch } } = await fetch(`https://ungh.cc/repos/${repo}`).then((res) => res.json());
42
+ const { files } = await fetch(`https://ungh.cc/repos/${repo}/files/${defaultBranch}`).then((res) => res.json());
43
+ const file = files.find((it) => it.path.endsWith(`${name}/SKILL.md`));
44
+ const subdir = file ? dirname(file.path) : null;
45
+ cacheData[cacheKey] = subdir;
46
+ await saveCache(cacheData);
47
+ return subdir;
48
+ } catch {
49
+ cacheData[cacheKey] = null;
50
+ await saveCache(cacheData);
46
51
  return null;
47
52
  }
48
- const result = await findDirectory(repoPath, skillName);
49
- if (!result) print(chalk.yellow(`Warning: Skill '${skillName}' not found in skills/ directory, searching entire repo...`));
50
- return result;
51
53
  }
52
54
 
53
55
  //#endregion
54
- //#region src/manager.ts
55
- const GLOBAL_SKILLS_DIR = join(homedir(), ".claude", "skills");
56
- const LOCAL_SKILLS_DIR = join(cwd(), ".claude", "skills");
57
- const CACHE_DIR = join(homedir(), ".cache", "skills-manager");
58
- async function list(options = {}) {
59
- const skillsDir = options.global ? GLOBAL_SKILLS_DIR : LOCAL_SKILLS_DIR;
60
- const type = options.global ? "global" : "local";
61
- print(chalk.gray("→"), chalk.gray(skillsDir));
62
- print();
56
+ //#region src/action.ts
57
+ async function action(options) {
58
+ const dir = join(options.global ? homedir() : cwd(), ".claude", "skills");
59
+ if (options.type === "list") await list(dir);
60
+ else if (options.type === "remove") await remove(dir, options.name, options.global);
61
+ else if (options.type === "add") await add(dir, options.repo, options.name);
62
+ }
63
+ async function list(dir) {
63
64
  try {
64
- (await readdir(skillsDir, { withFileTypes: true })).forEach((it) => it.isDirectory() && print(chalk.gray("•"), it.name));
65
+ const skills = await readdir(dir, { withFileTypes: true });
66
+ info("→", dir, "\n");
67
+ skills.forEach((it) => it.isDirectory() && print(gray("•"), it.name));
65
68
  print();
66
69
  } catch {
67
- print(chalk.gray(`No ${type} skills found`));
70
+ warn("No skills found.");
68
71
  }
69
72
  }
70
- async function remove(name, options = {}) {
71
- const path = join(options.global ? GLOBAL_SKILLS_DIR : LOCAL_SKILLS_DIR, name);
72
- if (!await exists(path)) throw new Error(`Skill '${name}' not found`);
73
+ async function remove(dir, name, global) {
74
+ const path = join(dir, name);
75
+ try {
76
+ await access(path);
77
+ } catch {
78
+ warn(`Skill '${name}' not found`);
79
+ return;
80
+ }
73
81
  await rimraf(path);
74
- if (!options.global) {
75
- if ((await readdir(LOCAL_SKILLS_DIR)).length === 0) await rimraf(join(LOCAL_SKILLS_DIR, ".."));
82
+ if (!global) {
83
+ if ((await readdir(dir)).length === 0) await rimraf(join(dir, ".."));
76
84
  }
77
- print(chalk.gray("-", options.global ? GLOBAL_SKILLS_DIR : LOCAL_SKILLS_DIR), chalk.red(name));
78
- print();
85
+ print(gray("-", dir), red(name), "\n");
79
86
  }
80
- async function add(repo, skillName, options) {
81
- const cachePath = join(CACHE_DIR, repo.replace(/\//g, "-"));
82
- await mkdir(CACHE_DIR, { recursive: true });
83
- print(chalk.gray("✲", "Downloading repo to cache..."));
84
- await degit(repo, {
85
- cache: true,
86
- force: true
87
- }).clone(cachePath);
88
- print(chalk.green("✓", "Repo downloaded to cache"));
89
- const skillPath = await findSkillPath(cachePath, skillName);
90
- if (!skillPath) throw new Error(`Skill '${skillName}' not found in repo ${repo}`);
91
- const skillsDir = options.global ? GLOBAL_SKILLS_DIR : LOCAL_SKILLS_DIR;
92
- const targetDir = join(skillsDir, skillName);
93
- await mkdir(skillsDir, { recursive: true });
94
- if (await exists(targetDir)) await rimraf(targetDir);
95
- await copyDirectory(skillPath, targetDir);
96
- print(chalk.gray("→", options.global ? GLOBAL_SKILLS_DIR : LOCAL_SKILLS_DIR), chalk.green(skillName));
97
- print();
87
+ async function add(dir, repo, name) {
88
+ const subdir = await findSkill(repo, name);
89
+ if (!subdir) {
90
+ warn(`Skill '${name}' not found`);
91
+ return;
92
+ }
93
+ await downloadTemplate(`gh:${repo}/${subdir}`, {
94
+ forceClean: true,
95
+ preferOffline: true,
96
+ dir: join(dir, name)
97
+ });
98
+ print(gray("→", dir), green(name), "\n");
98
99
  }
99
100
 
100
101
  //#endregion
101
102
  //#region package.json
102
103
  var name = "skills-manager";
103
- var version = "0.0.5";
104
+ var version = "0.0.6";
104
105
 
105
106
  //#endregion
106
107
  //#region src/index.ts
107
108
  const cli = cac(name);
108
- cli.command("add <repo> <skill-name>", "Add skill from GitHub repo").option("-g, --global", "Install globally").action((repo, skillName, options) => add(repo, skillName, options).catch(() => exit(1)));
109
- cli.command("list", "List skills").alias("ls").option("-g, --global", "List global skills").action((options) => list(options).catch(() => exit(1)));
110
- cli.command("remove <name>", "Remove skill").alias("rm").option("-g, --global", "Remove from global skills").action((name, options) => remove(name, options).catch(() => exit(1)));
109
+ cli.command("add <repo> <name>", "Add skill from GitHub repo").option("-g, --global", "Install globally", { default: false }).action((repo, name, options) => action({
110
+ type: "add",
111
+ repo,
112
+ name,
113
+ ...options
114
+ }));
115
+ cli.command("list", "List skills").alias("ls").option("-g, --global", "List global skills").action((options) => action({
116
+ type: "list",
117
+ ...options
118
+ }));
119
+ cli.command("remove <name>", "Remove skill").alias("rm").option("-g, --global", "Remove from global skills").action((name, options) => action({
120
+ type: "remove",
121
+ name,
122
+ ...options
123
+ }));
111
124
  cli.version(version);
112
125
  cli.help();
113
126
  cli.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skills-manager",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "",
5
5
  "homepage": "https://github.com/jiakun-zhao/skills-manager#readme",
6
6
  "bugs": {
@@ -19,13 +19,11 @@
19
19
  "type": "module",
20
20
  "dependencies": {
21
21
  "cac": "^6.7.14",
22
- "chalk": "^5.6.2",
23
- "degit": "^2.8.4",
22
+ "giget": "^3.1.2",
24
23
  "rimraf": "^6.1.2"
25
24
  },
26
25
  "devDependencies": {
27
26
  "@jiakun-zhao/eslint-config": "^4.3.0",
28
- "@types/degit": "^2.8.6",
29
27
  "@types/node": "^25.2.1",
30
28
  "bumpp": "^10.4.1",
31
29
  "eslint": "^9.39.2",