@nano-step/skill-manager 5.2.2 → 5.4.0

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 CHANGED
@@ -26,13 +26,27 @@ npx @nano-step/skill-manager install --all
26
26
  | `update [name]` | Update one or all installed skills |
27
27
  | `installed` | Show currently installed skills |
28
28
 
29
- ## Available Skills
29
+ ## Available Skills (17)
30
30
 
31
31
  | Skill | Description |
32
32
  |-------|-------------|
33
- | `skill-management` | AI skill routing isolates tool definitions in subagent context to save 80-95% tokens |
33
+ | `blog-workflow` | Generate SEO-optimized blog posts for dev.to, Medium, LinkedIn, Hashnode |
34
+ | `comprehensive-feature-builder` | Systematic 5-phase workflow for researching, designing, implementing, and testing features |
35
+ | `feature-analysis` | Deep code analysis with execution tracing, data transformation audits, and gap analysis |
34
36
  | `graphql-inspector` | GraphQL schema inspection with progressive discovery workflow |
35
-
37
+ | `idea-workflow` | Analyze source code and produce monetization strategy with execution blueprint |
38
+ | `mermaid-validator` | Validate Mermaid diagram syntax — enforces rules that prevent parse errors |
39
+ | `nano-brain` | Persistent memory for AI agents — hybrid search across sessions, codebase, and notes |
40
+ | `pdf` | PDF manipulation toolkit — extract, create, merge, split, OCR, fill forms, watermark |
41
+ | `reddit-workflow` | Draft Reddit posts optimized for subreddit rules, tone, and spam filters |
42
+ | `rri-t-testing` | RRI-T QA methodology — 5-phase testing with 7 dimensions, 5 personas, and release gates |
43
+ | `rtk` | Token optimizer — wraps CLI commands with rtk to reduce token consumption by 60-90% |
44
+ | `rtk-setup` | One-time RTK setup + ongoing enforcement across sessions and subagents |
45
+ | `security-workflow` | OWASP Top 10 security audit with CVE scanning and prioritized hardening plan |
46
+ | `skill-creator` | Create and validate AI agent skills with progressive disclosure and marketplace packaging |
47
+ | `skill-management` | AI skill routing — isolates tool definitions in subagent context to save 80-95% tokens |
48
+ | `team-workflow` | Simulate an autonomous software team — architecture, execution plan, QA strategy |
49
+ | `ui-ux-pro-max` | UI/UX design intelligence with searchable database of styles, palettes, fonts, and guidelines |
36
50
  ## What Gets Installed
37
51
 
38
52
  When you install a skill, the manager:
package/dist/auth.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ export interface AuthConfig {
2
+ token?: string;
3
+ }
4
+ export declare function loadAuthConfig(): Promise<AuthConfig>;
5
+ export declare function saveAuthConfig(config: AuthConfig): Promise<void>;
6
+ export declare function removeAuthConfig(): Promise<void>;
7
+ export declare function resolveToken(): Promise<string | null>;
8
+ export declare function getPrivateRepo(): string;
package/dist/auth.js ADDED
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loadAuthConfig = loadAuthConfig;
7
+ exports.saveAuthConfig = saveAuthConfig;
8
+ exports.removeAuthConfig = removeAuthConfig;
9
+ exports.resolveToken = resolveToken;
10
+ exports.getPrivateRepo = getPrivateRepo;
11
+ const path_1 = __importDefault(require("path"));
12
+ const os_1 = __importDefault(require("os"));
13
+ const fs_extra_1 = __importDefault(require("fs-extra"));
14
+ const child_process_1 = require("child_process");
15
+ const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), ".config", "skill-manager");
16
+ const CONFIG_FILE = path_1.default.join(CONFIG_DIR, "config.json");
17
+ const PRIVATE_REPO = "nano-step/private-skills";
18
+ async function loadAuthConfig() {
19
+ const exists = await fs_extra_1.default.pathExists(CONFIG_FILE);
20
+ if (!exists) {
21
+ return {};
22
+ }
23
+ try {
24
+ const data = await fs_extra_1.default.readFile(CONFIG_FILE, "utf8");
25
+ return JSON.parse(data);
26
+ }
27
+ catch {
28
+ return {};
29
+ }
30
+ }
31
+ async function saveAuthConfig(config) {
32
+ await fs_extra_1.default.ensureDir(CONFIG_DIR);
33
+ await fs_extra_1.default.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", {
34
+ encoding: "utf8",
35
+ mode: 0o600,
36
+ });
37
+ await fs_extra_1.default.chmod(CONFIG_FILE, 0o600);
38
+ }
39
+ async function removeAuthConfig() {
40
+ const exists = await fs_extra_1.default.pathExists(CONFIG_FILE);
41
+ if (exists) {
42
+ await fs_extra_1.default.remove(CONFIG_FILE);
43
+ }
44
+ }
45
+ function getGhCliToken() {
46
+ try {
47
+ const token = (0, child_process_1.execSync)("gh auth token", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
48
+ return token || null;
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
54
+ async function resolveToken() {
55
+ const config = await loadAuthConfig();
56
+ if (config.token) {
57
+ return config.token;
58
+ }
59
+ const envToken = process.env.GITHUB_TOKEN;
60
+ if (envToken) {
61
+ return envToken;
62
+ }
63
+ return getGhCliToken();
64
+ }
65
+ function getPrivateRepo() {
66
+ return PRIVATE_REPO;
67
+ }
package/dist/index.js CHANGED
@@ -10,6 +10,8 @@ const utils_1 = require("./utils");
10
10
  const registry_1 = require("./registry");
11
11
  const state_1 = require("./state");
12
12
  const installer_1 = require("./installer");
13
+ const remote_registry_1 = require("./remote-registry");
14
+ const auth_1 = require("./auth");
13
15
  async function run() {
14
16
  const args = process.argv.slice(2);
15
17
  if (args.includes("--update") || args.includes("--remove")) {
@@ -27,38 +29,76 @@ async function run() {
27
29
  .name("skill-manager")
28
30
  .description("Install and manage AI agent skills for OpenCode")
29
31
  .version(utils_1.MANAGER_VERSION);
32
+ program
33
+ .command("login")
34
+ .description("Authenticate with GitHub to access private skills")
35
+ .option("--token <token>", "GitHub personal access token")
36
+ .action(async (options) => {
37
+ if (!options.token) {
38
+ console.error(chalk_1.default.red("Please provide a token with --token <token>"));
39
+ console.error(chalk_1.default.gray("Create a token at: https://github.com/settings/tokens"));
40
+ console.error(chalk_1.default.gray("Required scope: repo (for private repositories)"));
41
+ process.exit(1);
42
+ }
43
+ const config = await (0, auth_1.loadAuthConfig)();
44
+ config.token = options.token;
45
+ await (0, auth_1.saveAuthConfig)(config);
46
+ console.log(chalk_1.default.green("✓ Token saved successfully"));
47
+ console.log(chalk_1.default.gray(" Config: ~/.config/skill-manager/config.json"));
48
+ });
49
+ program
50
+ .command("logout")
51
+ .description("Remove stored GitHub token")
52
+ .action(async () => {
53
+ await (0, auth_1.removeAuthConfig)();
54
+ console.log(chalk_1.default.green("✓ Token removed"));
55
+ });
30
56
  program
31
57
  .command("list")
32
58
  .description("Show available skills from the catalog")
33
59
  .action(async () => {
34
60
  const paths = await (0, utils_1.detectOpenCodePaths)();
35
61
  await (0, state_1.migrateV4State)(paths.configDir, paths.stateFilePath, paths.skillsDir);
36
- const catalog = await (0, registry_1.loadCatalog)(paths.packageSkillsDir);
62
+ const token = await (0, auth_1.resolveToken)();
63
+ const remoteSkills = token ? await (0, remote_registry_1.listRemoteSkills)() : [];
64
+ const catalog = await (0, registry_1.loadMergedCatalog)(paths.packageSkillsDir, remoteSkills);
37
65
  const state = await (0, state_1.loadState)(paths.stateFilePath);
38
66
  if (catalog.length === 0) {
39
67
  console.log(chalk_1.default.yellow("No skills found in catalog."));
68
+ if (!token) {
69
+ console.log(chalk_1.default.gray("Run 'skill-manager login' to access private skills."));
70
+ }
40
71
  return;
41
72
  }
42
73
  console.log(chalk_1.default.bold("\nAvailable Skills:\n"));
43
74
  const nameWidth = 22;
44
75
  const versionWidth = 10;
76
+ const sourceWidth = 10;
45
77
  const statusWidth = 14;
46
78
  console.log(chalk_1.default.gray(" " +
47
79
  "Name".padEnd(nameWidth) +
48
80
  "Version".padEnd(versionWidth) +
81
+ "Source".padEnd(sourceWidth) +
49
82
  "Status".padEnd(statusWidth) +
50
83
  "Description"));
51
- console.log(chalk_1.default.gray(" " + "─".repeat(nameWidth + versionWidth + statusWidth + 30)));
52
- for (const skill of catalog) {
84
+ console.log(chalk_1.default.gray(" " + "─".repeat(nameWidth + versionWidth + sourceWidth + statusWidth + 30)));
85
+ for (const entry of catalog) {
86
+ const skill = entry.manifest;
53
87
  const installed = state.skills[skill.name];
54
88
  const status = installed ? chalk_1.default.green("installed") : chalk_1.default.gray("not installed");
89
+ const sourceLabel = entry.source === "private" ? chalk_1.default.magenta("private") : chalk_1.default.blue("public");
55
90
  console.log(" " +
56
91
  chalk_1.default.cyan(skill.name.padEnd(nameWidth)) +
57
92
  skill.version.padEnd(versionWidth) +
93
+ sourceLabel.padEnd(sourceWidth + 10) +
58
94
  status.padEnd(statusWidth + 10) +
59
95
  skill.description);
60
96
  }
61
97
  console.log("");
98
+ if (!token) {
99
+ console.log(chalk_1.default.gray("Tip: Run 'skill-manager login' to access private skills."));
100
+ console.log("");
101
+ }
62
102
  });
63
103
  program
64
104
  .command("install [name]")
@@ -68,13 +108,15 @@ async function run() {
68
108
  const paths = await (0, utils_1.detectOpenCodePaths)();
69
109
  await (0, state_1.migrateV4State)(paths.configDir, paths.stateFilePath, paths.skillsDir);
70
110
  if (options.all) {
71
- const catalog = await (0, registry_1.loadCatalog)(paths.packageSkillsDir);
111
+ const token = await (0, auth_1.resolveToken)();
112
+ const remoteSkills = token ? await (0, remote_registry_1.listRemoteSkills)() : [];
113
+ const catalog = await (0, registry_1.loadMergedCatalog)(paths.packageSkillsDir, remoteSkills);
72
114
  if (catalog.length === 0) {
73
115
  console.log(chalk_1.default.yellow("No skills found in catalog."));
74
116
  return;
75
117
  }
76
- for (const skill of catalog) {
77
- await (0, installer_1.installSkill)(skill.name, paths);
118
+ for (const entry of catalog) {
119
+ await (0, installer_1.installSkill)(entry.manifest.name, paths);
78
120
  }
79
121
  }
80
122
  else if (name) {
package/dist/installer.js CHANGED
@@ -13,34 +13,54 @@ const utils_1 = require("./utils");
13
13
  const registry_1 = require("./registry");
14
14
  const state_1 = require("./state");
15
15
  const config_1 = require("./config");
16
- async function installSkill(name, paths) {
17
- const manifest = await (0, registry_1.getSkillManifest)(paths.packageSkillsDir, name);
18
- if (!manifest) {
19
- const catalog = await (0, registry_1.loadCatalog)(paths.packageSkillsDir);
20
- const available = catalog.map((s) => s.name).join(", ");
21
- console.error(chalk_1.default.red(`Skill "${name}" not found in catalog.`));
22
- console.error(chalk_1.default.yellow(`Available skills: ${available || "none"}`));
23
- process.exit(1);
24
- }
16
+ const remote_registry_1 = require("./remote-registry");
17
+ async function installFromDir(manifest, sourceDir, paths, source) {
25
18
  const state = await (0, state_1.loadState)(paths.stateFilePath);
26
- const existing = state.skills[name];
19
+ const existing = state.skills[manifest.name];
27
20
  if (existing && existing.version === manifest.version) {
28
- console.log(chalk_1.default.yellow(`Skill "${name}" is already installed at v${manifest.version}.`));
21
+ console.log(chalk_1.default.yellow(`Skill "${manifest.name}" is already installed at v${manifest.version}.`));
29
22
  return;
30
23
  }
31
24
  await (0, utils_1.ensureDirExists)(paths.skillsDir);
32
- const packageSkillDir = path_1.default.join(paths.packageSkillsDir, name);
33
- const targetSkillDir = path_1.default.join(paths.skillsDir, name);
34
- await fs_extra_1.default.copy(packageSkillDir, targetSkillDir, { overwrite: true });
35
- await (0, config_1.copyCommands)(manifest, packageSkillDir, paths.commandDir);
25
+ const targetSkillDir = path_1.default.join(paths.skillsDir, manifest.name);
26
+ await fs_extra_1.default.copy(sourceDir, targetSkillDir, { overwrite: true });
27
+ await (0, config_1.copyCommands)(manifest, sourceDir, paths.commandDir);
36
28
  await (0, config_1.mergeAgentConfig)(paths.agentConfigPath, manifest);
37
- state.skills[name] = {
29
+ state.skills[manifest.name] = {
38
30
  version: manifest.version,
39
31
  installedAt: new Date().toISOString(),
40
32
  location: paths.configDir.includes(".config/opencode") ? "global" : "project",
41
33
  };
42
34
  await (0, state_1.saveState)(paths.stateFilePath, state);
43
- console.log(chalk_1.default.green(`✓ Installed ${name} v${manifest.version}`));
35
+ const sourceLabel = source === "private" ? chalk_1.default.magenta(" (private)") : "";
36
+ console.log(chalk_1.default.green(`✓ Installed ${manifest.name} v${manifest.version}${sourceLabel}`));
37
+ }
38
+ async function installSkill(name, paths) {
39
+ const localManifest = await (0, registry_1.getSkillManifest)(paths.packageSkillsDir, name);
40
+ if (localManifest) {
41
+ const packageSkillDir = path_1.default.join(paths.packageSkillsDir, name);
42
+ await installFromDir(localManifest, packageSkillDir, paths, "public");
43
+ return;
44
+ }
45
+ const remoteManifest = await (0, remote_registry_1.getRemoteSkillManifest)(name);
46
+ if (remoteManifest) {
47
+ const tempDir = await (0, remote_registry_1.downloadSkillToTemp)(name);
48
+ if (tempDir) {
49
+ try {
50
+ await installFromDir(remoteManifest, tempDir, paths, "private");
51
+ }
52
+ finally {
53
+ await fs_extra_1.default.remove(tempDir);
54
+ }
55
+ return;
56
+ }
57
+ }
58
+ const catalog = await (0, registry_1.loadCatalog)(paths.packageSkillsDir);
59
+ const available = catalog.map((s) => s.name).join(", ");
60
+ console.error(chalk_1.default.red(`Skill "${name}" not found in catalog.`));
61
+ console.error(chalk_1.default.yellow(`Available public skills: ${available || "none"}`));
62
+ console.error(chalk_1.default.gray("Run 'skill-manager login' to access private skills."));
63
+ process.exit(1);
44
64
  }
45
65
  async function removeSkill(name, paths) {
46
66
  const state = await (0, state_1.loadState)(paths.stateFilePath);
@@ -49,13 +69,25 @@ async function removeSkill(name, paths) {
49
69
  return;
50
70
  }
51
71
  const manifest = await (0, registry_1.getSkillManifest)(paths.packageSkillsDir, name);
72
+ const installedManifestPath = path_1.default.join(paths.skillsDir, name, "skill.json");
73
+ let installedManifest = null;
74
+ if (await fs_extra_1.default.pathExists(installedManifestPath)) {
75
+ try {
76
+ const raw = await fs_extra_1.default.readFile(installedManifestPath, "utf8");
77
+ installedManifest = JSON.parse(raw);
78
+ }
79
+ catch {
80
+ installedManifest = null;
81
+ }
82
+ }
52
83
  const targetSkillDir = path_1.default.join(paths.skillsDir, name);
53
84
  if (await fs_extra_1.default.pathExists(targetSkillDir)) {
54
85
  await fs_extra_1.default.remove(targetSkillDir);
55
86
  }
56
- if (manifest) {
57
- await (0, config_1.removeCommands)(manifest, paths.commandDir);
58
- await (0, config_1.removeAgentConfig)(paths.agentConfigPath, manifest);
87
+ const effectiveManifest = manifest || installedManifest;
88
+ if (effectiveManifest) {
89
+ await (0, config_1.removeCommands)(effectiveManifest, paths.commandDir);
90
+ await (0, config_1.removeAgentConfig)(paths.agentConfigPath, effectiveManifest);
59
91
  }
60
92
  delete state.skills[name];
61
93
  await (0, state_1.saveState)(paths.stateFilePath, state);
@@ -67,26 +99,54 @@ async function updateSkill(name, paths) {
67
99
  console.error(chalk_1.default.red(`Skill "${name}" is not installed. Use 'skill-manager install ${name}' first.`));
68
100
  process.exit(1);
69
101
  }
70
- const manifest = await (0, registry_1.getSkillManifest)(paths.packageSkillsDir, name);
71
- if (!manifest) {
72
- console.error(chalk_1.default.red(`Skill "${name}" not found in catalog. It may have been removed.`));
73
- process.exit(1);
74
- }
75
102
  const installed = state.skills[name];
76
- if (installed.version === manifest.version) {
77
- console.log(chalk_1.default.yellow(`Skill "${name}" is already at v${manifest.version} (up to date).`));
103
+ const localManifest = await (0, registry_1.getSkillManifest)(paths.packageSkillsDir, name);
104
+ if (localManifest) {
105
+ if (installed.version === localManifest.version) {
106
+ console.log(chalk_1.default.yellow(`Skill "${name}" is already at v${localManifest.version} (up to date).`));
107
+ return;
108
+ }
109
+ const packageSkillDir = path_1.default.join(paths.packageSkillsDir, name);
110
+ const targetSkillDir = path_1.default.join(paths.skillsDir, name);
111
+ await fs_extra_1.default.copy(packageSkillDir, targetSkillDir, { overwrite: true });
112
+ await (0, config_1.copyCommands)(localManifest, packageSkillDir, paths.commandDir);
113
+ await (0, config_1.mergeAgentConfig)(paths.agentConfigPath, localManifest);
114
+ state.skills[name] = {
115
+ version: localManifest.version,
116
+ installedAt: new Date().toISOString(),
117
+ location: installed.location,
118
+ };
119
+ await (0, state_1.saveState)(paths.stateFilePath, state);
120
+ console.log(chalk_1.default.green(`✓ Updated ${name} from v${installed.version} to v${localManifest.version}`));
78
121
  return;
79
122
  }
80
- const packageSkillDir = path_1.default.join(paths.packageSkillsDir, name);
81
- const targetSkillDir = path_1.default.join(paths.skillsDir, name);
82
- await fs_extra_1.default.copy(packageSkillDir, targetSkillDir, { overwrite: true });
83
- await (0, config_1.copyCommands)(manifest, packageSkillDir, paths.commandDir);
84
- await (0, config_1.mergeAgentConfig)(paths.agentConfigPath, manifest);
85
- state.skills[name] = {
86
- version: manifest.version,
87
- installedAt: new Date().toISOString(),
88
- location: installed.location,
89
- };
90
- await (0, state_1.saveState)(paths.stateFilePath, state);
91
- console.log(chalk_1.default.green(`✓ Updated ${name} from v${installed.version} to v${manifest.version}`));
123
+ const remoteManifest = await (0, remote_registry_1.getRemoteSkillManifest)(name);
124
+ if (remoteManifest) {
125
+ if (installed.version === remoteManifest.version) {
126
+ console.log(chalk_1.default.yellow(`Skill "${name}" is already at v${remoteManifest.version} (up to date).`));
127
+ return;
128
+ }
129
+ const tempDir = await (0, remote_registry_1.downloadSkillToTemp)(name);
130
+ if (tempDir) {
131
+ try {
132
+ const targetSkillDir = path_1.default.join(paths.skillsDir, name);
133
+ await fs_extra_1.default.copy(tempDir, targetSkillDir, { overwrite: true });
134
+ await (0, config_1.copyCommands)(remoteManifest, tempDir, paths.commandDir);
135
+ await (0, config_1.mergeAgentConfig)(paths.agentConfigPath, remoteManifest);
136
+ state.skills[name] = {
137
+ version: remoteManifest.version,
138
+ installedAt: new Date().toISOString(),
139
+ location: installed.location,
140
+ };
141
+ await (0, state_1.saveState)(paths.stateFilePath, state);
142
+ console.log(chalk_1.default.green(`✓ Updated ${name} from v${installed.version} to v${remoteManifest.version} (private)`));
143
+ }
144
+ finally {
145
+ await fs_extra_1.default.remove(tempDir);
146
+ }
147
+ return;
148
+ }
149
+ }
150
+ console.error(chalk_1.default.red(`Skill "${name}" not found in catalog. It may have been removed.`));
151
+ process.exit(1);
92
152
  }
@@ -1,3 +1,4 @@
1
- import { SkillManifest } from "./utils";
1
+ import { SkillManifest, CatalogEntry } from "./utils";
2
2
  export declare function loadCatalog(packageSkillsDir: string): Promise<SkillManifest[]>;
3
3
  export declare function getSkillManifest(packageSkillsDir: string, name: string): Promise<SkillManifest | null>;
4
+ export declare function loadMergedCatalog(packageSkillsDir: string, remoteSkills: SkillManifest[]): Promise<CatalogEntry[]>;
package/dist/registry.js CHANGED
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.loadCatalog = loadCatalog;
7
7
  exports.getSkillManifest = getSkillManifest;
8
+ exports.loadMergedCatalog = loadMergedCatalog;
8
9
  const path_1 = __importDefault(require("path"));
9
10
  const fs_extra_1 = __importDefault(require("fs-extra"));
10
11
  const chalk_1 = __importDefault(require("chalk"));
@@ -61,3 +62,17 @@ async function getSkillManifest(packageSkillsDir, name) {
61
62
  return null;
62
63
  }
63
64
  }
65
+ async function loadMergedCatalog(packageSkillsDir, remoteSkills) {
66
+ const localCatalog = await loadCatalog(packageSkillsDir);
67
+ const localNames = new Set(localCatalog.map((s) => s.name));
68
+ const entries = localCatalog.map((manifest) => ({
69
+ manifest,
70
+ source: "public",
71
+ }));
72
+ for (const manifest of remoteSkills) {
73
+ if (!localNames.has(manifest.name)) {
74
+ entries.push({ manifest, source: "private" });
75
+ }
76
+ }
77
+ return entries.sort((a, b) => a.manifest.name.localeCompare(b.manifest.name));
78
+ }
@@ -0,0 +1,4 @@
1
+ import { SkillManifest } from "./utils";
2
+ export declare function listRemoteSkills(): Promise<SkillManifest[]>;
3
+ export declare function downloadSkillToTemp(skillName: string): Promise<string | null>;
4
+ export declare function getRemoteSkillManifest(skillName: string): Promise<SkillManifest | null>;
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.listRemoteSkills = listRemoteSkills;
7
+ exports.downloadSkillToTemp = downloadSkillToTemp;
8
+ exports.getRemoteSkillManifest = getRemoteSkillManifest;
9
+ const path_1 = __importDefault(require("path"));
10
+ const os_1 = __importDefault(require("os"));
11
+ const fs_extra_1 = __importDefault(require("fs-extra"));
12
+ const chalk_1 = __importDefault(require("chalk"));
13
+ const auth_1 = require("./auth");
14
+ async function githubFetch(url, token) {
15
+ const response = await fetch(url, {
16
+ headers: {
17
+ Authorization: `Bearer ${token}`,
18
+ Accept: "application/vnd.github.v3+json",
19
+ "User-Agent": "skill-manager",
20
+ },
21
+ });
22
+ if (response.status === 429) {
23
+ console.error(chalk_1.default.yellow("GitHub API rate limit exceeded. Please wait and try again later."));
24
+ return { data: null, status: 429 };
25
+ }
26
+ if (response.status === 403) {
27
+ console.error(chalk_1.default.yellow("Access denied to private repository. Check your token permissions."));
28
+ return { data: null, status: 403 };
29
+ }
30
+ if (response.status === 404) {
31
+ return { data: null, status: 404 };
32
+ }
33
+ if (!response.ok) {
34
+ return { data: null, status: response.status };
35
+ }
36
+ const data = (await response.json());
37
+ return { data, status: response.status };
38
+ }
39
+ function isValidManifest(data) {
40
+ if (typeof data !== "object" || data === null)
41
+ return false;
42
+ const obj = data;
43
+ return (typeof obj.name === "string" &&
44
+ typeof obj.version === "string" &&
45
+ typeof obj.description === "string");
46
+ }
47
+ async function listRemoteSkills() {
48
+ const token = await (0, auth_1.resolveToken)();
49
+ if (!token) {
50
+ return [];
51
+ }
52
+ const repo = (0, auth_1.getPrivateRepo)();
53
+ const [owner, repoName] = repo.split("/");
54
+ const url = `https://api.github.com/repos/${owner}/${repoName}/contents/skills`;
55
+ const { data: items, status } = await githubFetch(url, token);
56
+ if (!items || status !== 200) {
57
+ return [];
58
+ }
59
+ const skills = [];
60
+ for (const item of items) {
61
+ if (item.type !== "dir")
62
+ continue;
63
+ const manifestUrl = `https://api.github.com/repos/${owner}/${repoName}/contents/skills/${item.name}/skill.json`;
64
+ const { data: fileContent, status: fileStatus } = await githubFetch(manifestUrl, token);
65
+ if (!fileContent || fileStatus !== 200)
66
+ continue;
67
+ try {
68
+ const content = Buffer.from(fileContent.content, "base64").toString("utf8");
69
+ const manifest = JSON.parse(content);
70
+ if (isValidManifest(manifest)) {
71
+ skills.push(manifest);
72
+ }
73
+ }
74
+ catch {
75
+ console.error(chalk_1.default.yellow(`Warning: Failed to parse remote skill manifest for ${item.name}`));
76
+ }
77
+ }
78
+ return skills.sort((a, b) => a.name.localeCompare(b.name));
79
+ }
80
+ async function downloadSkillToTemp(skillName) {
81
+ const token = await (0, auth_1.resolveToken)();
82
+ if (!token) {
83
+ console.error(chalk_1.default.red("No GitHub token available. Run 'skill-manager login' first."));
84
+ return null;
85
+ }
86
+ const repo = (0, auth_1.getPrivateRepo)();
87
+ const [owner, repoName] = repo.split("/");
88
+ const baseUrl = `https://api.github.com/repos/${owner}/${repoName}/contents/skills/${skillName}`;
89
+ const { data: items, status } = await githubFetch(baseUrl, token);
90
+ if (!items || status !== 200) {
91
+ if (status === 404) {
92
+ console.error(chalk_1.default.red(`Remote skill "${skillName}" not found.`));
93
+ }
94
+ return null;
95
+ }
96
+ const tempDir = path_1.default.join(os_1.default.tmpdir(), `skill-manager-${skillName}-${Date.now()}`);
97
+ await fs_extra_1.default.ensureDir(tempDir);
98
+ try {
99
+ await downloadDirectory(items, tempDir, owner, repoName, `skills/${skillName}`, token);
100
+ return tempDir;
101
+ }
102
+ catch (error) {
103
+ await fs_extra_1.default.remove(tempDir);
104
+ console.error(chalk_1.default.red(`Failed to download skill "${skillName}": ${error}`));
105
+ return null;
106
+ }
107
+ }
108
+ async function downloadDirectory(items, targetDir, owner, repoName, basePath, token) {
109
+ for (const item of items) {
110
+ const targetPath = path_1.default.join(targetDir, item.name);
111
+ if (item.type === "file") {
112
+ const fileUrl = `https://api.github.com/repos/${owner}/${repoName}/contents/${item.path}`;
113
+ const { data: fileContent, status } = await githubFetch(fileUrl, token);
114
+ if (fileContent && status === 200) {
115
+ const content = Buffer.from(fileContent.content, "base64");
116
+ await fs_extra_1.default.writeFile(targetPath, content);
117
+ }
118
+ }
119
+ else if (item.type === "dir") {
120
+ await fs_extra_1.default.ensureDir(targetPath);
121
+ const dirUrl = `https://api.github.com/repos/${owner}/${repoName}/contents/${item.path}`;
122
+ const { data: subItems, status } = await githubFetch(dirUrl, token);
123
+ if (subItems && status === 200) {
124
+ await downloadDirectory(subItems, targetPath, owner, repoName, item.path, token);
125
+ }
126
+ }
127
+ }
128
+ }
129
+ async function getRemoteSkillManifest(skillName) {
130
+ const token = await (0, auth_1.resolveToken)();
131
+ if (!token) {
132
+ return null;
133
+ }
134
+ const repo = (0, auth_1.getPrivateRepo)();
135
+ const [owner, repoName] = repo.split("/");
136
+ const manifestUrl = `https://api.github.com/repos/${owner}/${repoName}/contents/skills/${skillName}/skill.json`;
137
+ const { data: fileContent, status } = await githubFetch(manifestUrl, token);
138
+ if (!fileContent || status !== 200) {
139
+ return null;
140
+ }
141
+ try {
142
+ const content = Buffer.from(fileContent.content, "base64").toString("utf8");
143
+ const manifest = JSON.parse(content);
144
+ if (isValidManifest(manifest)) {
145
+ return manifest;
146
+ }
147
+ }
148
+ catch {
149
+ return null;
150
+ }
151
+ return null;
152
+ }
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const MANAGER_VERSION = "5.2.2";
1
+ export declare const MANAGER_VERSION = "5.4.0";
2
2
  export interface SkillManifest {
3
3
  name: string;
4
4
  version: string;
@@ -11,6 +11,10 @@ export interface SkillManifest {
11
11
  commands?: string[];
12
12
  tags?: string[];
13
13
  }
14
+ export interface CatalogEntry {
15
+ manifest: SkillManifest;
16
+ source: "public" | "private";
17
+ }
14
18
  export interface InstalledSkillInfo {
15
19
  version: string;
16
20
  installedAt: string;
package/dist/utils.js CHANGED
@@ -13,7 +13,7 @@ exports.writeText = writeText;
13
13
  const path_1 = __importDefault(require("path"));
14
14
  const os_1 = __importDefault(require("os"));
15
15
  const fs_extra_1 = __importDefault(require("fs-extra"));
16
- exports.MANAGER_VERSION = "5.2.2";
16
+ exports.MANAGER_VERSION = "5.4.0";
17
17
  async function detectOpenCodePaths() {
18
18
  const homeConfig = path_1.default.join(os_1.default.homedir(), ".config", "opencode");
19
19
  const cwd = process.cwd();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nano-step/skill-manager",
3
- "version": "5.2.2",
3
+ "version": "5.4.0",
4
4
  "description": "CLI tool that installs and manages AI agent skills, MCP tool routing, and workflow configurations.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",