@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 +17 -3
- package/dist/auth.d.ts +8 -0
- package/dist/auth.js +67 -0
- package/dist/index.js +48 -6
- package/dist/installer.js +99 -39
- package/dist/registry.d.ts +2 -1
- package/dist/registry.js +15 -0
- package/dist/remote-registry.d.ts +4 -0
- package/dist/remote-registry.js +152 -0
- package/dist/utils.d.ts +5 -1
- package/dist/utils.js +1 -1
- package/package.json +1 -1
- package/skills/feature-analysis/SKILL.md +290 -0
- package/skills/feature-analysis/skill.json +15 -0
- package/skills/mermaid-validator/SKILL.md +163 -0
- package/skills/mermaid-validator/skill.json +15 -0
- package/skills/nano-brain/AGENTS_SNIPPET.md +46 -0
- package/skills/nano-brain/SKILL.md +77 -0
- package/skills/nano-brain/skill.json +15 -0
- package/skills/rtk/SKILL.md +103 -0
- package/skills/rtk/skill.json +15 -0
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
|
-
| `
|
|
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
|
|
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
|
|
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
|
|
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
|
|
77
|
-
await (0, installer_1.installSkill)(
|
|
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
|
-
|
|
17
|
-
|
|
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
|
|
33
|
-
|
|
34
|
-
await
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
await (0, config_1.
|
|
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
|
-
|
|
77
|
-
|
|
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
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
}
|
package/dist/registry.d.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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