@skillsmanager/cli 0.0.4 → 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.
- package/LICENSE +10 -18
- package/README.md +93 -36
- package/dist/auth.d.ts +1 -0
- package/dist/auth.js +19 -2
- package/dist/backends/gdrive.d.ts +6 -4
- package/dist/backends/gdrive.js +27 -13
- package/dist/backends/github.d.ts +10 -9
- package/dist/backends/github.js +71 -50
- package/dist/backends/interface.d.ts +19 -2
- package/dist/backends/local.d.ts +6 -4
- package/dist/backends/local.js +18 -13
- package/dist/backends/resolve.d.ts +2 -0
- package/dist/backends/resolve.js +25 -4
- package/dist/backends/routing.d.ts +38 -0
- package/dist/backends/routing.js +124 -0
- package/dist/commands/add.d.ts +3 -0
- package/dist/commands/add.js +130 -26
- package/dist/commands/collection.d.ts +1 -0
- package/dist/commands/collection.js +43 -37
- package/dist/commands/init.js +3 -3
- package/dist/commands/list.js +78 -8
- package/dist/commands/logout.d.ts +4 -0
- package/dist/commands/logout.js +35 -0
- package/dist/commands/refresh.js +1 -1
- package/dist/commands/registry.d.ts +1 -0
- package/dist/commands/registry.js +74 -36
- package/dist/commands/search.js +1 -1
- package/dist/commands/setup/github.d.ts +3 -0
- package/dist/commands/setup/github.js +8 -2
- package/dist/commands/setup/google.js +82 -42
- package/dist/commands/skill.d.ts +3 -0
- package/dist/commands/skill.js +76 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +35 -0
- package/dist/config.js +6 -1
- package/dist/index.js +37 -3
- package/dist/registry.js +20 -8
- package/dist/types.d.ts +2 -0
- package/dist/utils/git.d.ts +10 -0
- package/dist/utils/git.js +27 -0
- package/package.json +2 -2
- package/skills/skillsmanager/SKILL.md +109 -6
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { readConfig, writeConfig, CACHE_DIR } from "../config.js";
|
|
6
|
+
import { resolveBackend } from "../backends/resolve.js";
|
|
7
|
+
export async function skillDeleteCommand(skillName, options) {
|
|
8
|
+
let config;
|
|
9
|
+
try {
|
|
10
|
+
config = readConfig();
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
console.log(chalk.red("No config found. Run `skillsmanager refresh` first."));
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
// Resolve which collection to target
|
|
17
|
+
let collection = config.collections.find((c) => c.name === options.collection);
|
|
18
|
+
if (options.collection && !collection) {
|
|
19
|
+
console.log(chalk.red(`Collection "${options.collection}" not found.`));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (!collection) {
|
|
23
|
+
const locations = config.skills[skillName] ?? [];
|
|
24
|
+
if (locations.length === 0) {
|
|
25
|
+
console.log(chalk.red(`Skill "${skillName}" not found in any collection.`));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (locations.length > 1) {
|
|
29
|
+
const names = locations
|
|
30
|
+
.map((l) => config.collections.find((c) => c.id === l.collectionId)?.name ?? l.collectionId)
|
|
31
|
+
.join(", ");
|
|
32
|
+
console.log(chalk.red(`Skill "${skillName}" exists in multiple collections: ${names}`));
|
|
33
|
+
console.log(chalk.dim(` Use --collection <name> to specify which one.`));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
collection = config.collections.find((c) => c.id === locations[0].collectionId);
|
|
37
|
+
if (!collection) {
|
|
38
|
+
console.log(chalk.red(`Collection for skill "${skillName}" not found in config.`));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const backend = await resolveBackend(collection.backend);
|
|
43
|
+
// Delete from backend storage
|
|
44
|
+
const spinner = ora(`Deleting skill "${skillName}" from ${collection.backend}...`).start();
|
|
45
|
+
try {
|
|
46
|
+
await backend.deleteSkill(collection, skillName);
|
|
47
|
+
spinner.succeed(`Deleted "${skillName}" from ${collection.backend}`);
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
spinner.fail(`Failed to delete from backend: ${err.message}`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// Remove from collection YAML
|
|
54
|
+
try {
|
|
55
|
+
const col = await backend.readCollection(collection);
|
|
56
|
+
col.skills = col.skills.filter((s) => s.name !== skillName);
|
|
57
|
+
await backend.writeCollection(collection, col);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Non-fatal: backend data already removed
|
|
61
|
+
}
|
|
62
|
+
// Clean up local cache
|
|
63
|
+
const cachePath = path.join(CACHE_DIR, collection.id, skillName);
|
|
64
|
+
if (fs.existsSync(cachePath)) {
|
|
65
|
+
fs.rmSync(cachePath, { recursive: true, force: true });
|
|
66
|
+
}
|
|
67
|
+
// Update config skills index
|
|
68
|
+
if (config.skills[skillName]) {
|
|
69
|
+
config.skills[skillName] = config.skills[skillName].filter((l) => l.collectionId !== collection.id);
|
|
70
|
+
if (config.skills[skillName].length === 0) {
|
|
71
|
+
delete config.skills[skillName];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
writeConfig(config);
|
|
75
|
+
console.log(chalk.green(`\n ✓ Skill "${skillName}" removed from collection "${collection.name}".\n`));
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function statusCommand(): Promise<void>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { tryResolveBackend } from "../backends/resolve.js";
|
|
3
|
+
const BACKENDS = [
|
|
4
|
+
{ name: "local", hint: "" },
|
|
5
|
+
{ name: "gdrive", hint: "run: skillsmanager setup google" },
|
|
6
|
+
{ name: "github", hint: "run: skillsmanager setup github" },
|
|
7
|
+
];
|
|
8
|
+
export async function statusCommand() {
|
|
9
|
+
const rows = await Promise.all(BACKENDS.map(async ({ name, hint }) => {
|
|
10
|
+
const backend = await tryResolveBackend(name);
|
|
11
|
+
if (!backend)
|
|
12
|
+
return { name, loggedIn: false, identity: "", hint };
|
|
13
|
+
return { name, ...(await backend.getStatus()) };
|
|
14
|
+
}));
|
|
15
|
+
const col1 = 8;
|
|
16
|
+
const col2 = 24;
|
|
17
|
+
const header = chalk.bold("Backend".padEnd(col1)) + " " +
|
|
18
|
+
chalk.bold("Status".padEnd(col2)) + " " +
|
|
19
|
+
chalk.bold("Identity");
|
|
20
|
+
const divider = "─".repeat(col1) + " " + "─".repeat(col2) + " " + "─".repeat(30);
|
|
21
|
+
console.log();
|
|
22
|
+
console.log(header);
|
|
23
|
+
console.log(chalk.dim(divider));
|
|
24
|
+
for (const row of rows) {
|
|
25
|
+
const statusLabel = row.loggedIn ? "✓ logged in" : "✗ not logged in";
|
|
26
|
+
const status = row.loggedIn
|
|
27
|
+
? chalk.green(statusLabel)
|
|
28
|
+
: chalk.red(statusLabel);
|
|
29
|
+
const identity = row.loggedIn
|
|
30
|
+
? chalk.white(row.identity)
|
|
31
|
+
: chalk.dim(row.hint ?? "");
|
|
32
|
+
console.log(row.name.padEnd(col1) + " " + status + " ".repeat(Math.max(0, col2 - statusLabel.length)) + " " + identity);
|
|
33
|
+
}
|
|
34
|
+
console.log();
|
|
35
|
+
}
|
package/dist/config.js
CHANGED
|
@@ -70,10 +70,15 @@ export function readConfig() {
|
|
|
70
70
|
* fresh UUID. This keeps cache paths stable across refreshes.
|
|
71
71
|
*/
|
|
72
72
|
export function mergeCollections(fresh, existing) {
|
|
73
|
-
|
|
73
|
+
const freshFolderIds = new Set(fresh.map((c) => c.folderId));
|
|
74
|
+
// Fresh collections, preserving UUIDs for already-known ones
|
|
75
|
+
const updated = fresh.map((c) => {
|
|
74
76
|
const prev = existing.find((e) => e.folderId === c.folderId);
|
|
75
77
|
return { ...c, id: prev?.id ?? randomUUID() };
|
|
76
78
|
});
|
|
79
|
+
// Keep existing collections that weren't re-discovered (temporarily unavailable, etc.)
|
|
80
|
+
const kept = existing.filter((e) => !freshFolderIds.has(e.folderId));
|
|
81
|
+
return [...updated, ...kept];
|
|
77
82
|
}
|
|
78
83
|
/**
|
|
79
84
|
* Merges freshly discovered registries with existing ones, preserving UUIDs
|
package/dist/index.js
CHANGED
|
@@ -14,8 +14,11 @@ import { updateCommand } from "./commands/update.js";
|
|
|
14
14
|
import { refreshCommand } from "./commands/refresh.js";
|
|
15
15
|
import { setupGoogleCommand } from "./commands/setup/google.js";
|
|
16
16
|
import { setupGithubCommand } from "./commands/setup/github.js";
|
|
17
|
+
import { logoutGoogleCommand, logoutGithubCommand } from "./commands/logout.js";
|
|
17
18
|
import { collectionCreateCommand } from "./commands/collection.js";
|
|
19
|
+
import { skillDeleteCommand } from "./commands/skill.js";
|
|
18
20
|
import { installCommand, uninstallCommand } from "./commands/install.js";
|
|
21
|
+
import { statusCommand } from "./commands/status.js";
|
|
19
22
|
import { registryCreateCommand, registryListCommand, registryDiscoverCommand, registryAddCollectionCommand, registryRemoveCollectionCommand, registryPushCommand, } from "./commands/registry.js";
|
|
20
23
|
const supportedAgents = Object.keys(AGENT_PATHS).join(", ");
|
|
21
24
|
// Read the bundled SKILL.md as the CLI help — single source of truth
|
|
@@ -44,7 +47,24 @@ setup
|
|
|
44
47
|
.command("github")
|
|
45
48
|
.description("One-time GitHub setup — checks gh CLI and runs gh auth login")
|
|
46
49
|
.action(setupGithubCommand);
|
|
50
|
+
// ── Logout ───────────────────────────────────────────────────────────────────
|
|
51
|
+
const logout = program
|
|
52
|
+
.command("logout")
|
|
53
|
+
.description("Log out of a storage backend");
|
|
54
|
+
logout
|
|
55
|
+
.command("google")
|
|
56
|
+
.description("Clear Google OAuth session (and optionally credentials)")
|
|
57
|
+
.option("--all", "Also remove credentials.json to start setup from scratch")
|
|
58
|
+
.action((options) => logoutGoogleCommand(options));
|
|
59
|
+
logout
|
|
60
|
+
.command("github")
|
|
61
|
+
.description("Log out of GitHub (runs gh auth logout)")
|
|
62
|
+
.action(logoutGithubCommand);
|
|
47
63
|
// ── Core commands ────────────────────────────────────────────────────────────
|
|
64
|
+
program
|
|
65
|
+
.command("status")
|
|
66
|
+
.description("Show login status and identity for each backend")
|
|
67
|
+
.action(statusCommand);
|
|
48
68
|
program
|
|
49
69
|
.command("init")
|
|
50
70
|
.description("Authenticate and discover collections (runs automatically when needed)")
|
|
@@ -64,10 +84,13 @@ program
|
|
|
64
84
|
.option("--scope <scope>", "global (~/.agent/skills/) or project (./.agent/skills/)", "global")
|
|
65
85
|
.action((names, options) => fetchCommand(names, options));
|
|
66
86
|
program
|
|
67
|
-
.command("add
|
|
68
|
-
.description("Upload a local skill directory to a collection")
|
|
87
|
+
.command("add [path]")
|
|
88
|
+
.description("Upload a local skill directory to a collection, or register a remote path")
|
|
69
89
|
.option("--collection <name>", "Target collection (default: first available)")
|
|
70
|
-
.
|
|
90
|
+
.option("--remote-path <rel/path>", "Register a skill path from the collection's skills-source repo (no local files needed)")
|
|
91
|
+
.option("--name <name>", "Skill name (required with --remote-path)")
|
|
92
|
+
.option("--description <desc>", "Skill description (used with --remote-path)")
|
|
93
|
+
.action((skillPath, options) => addCommand(skillPath ?? "", options));
|
|
71
94
|
program
|
|
72
95
|
.command("update <path>")
|
|
73
96
|
.description("Push local edits to a skill back to storage and refresh cache")
|
|
@@ -77,6 +100,15 @@ program
|
|
|
77
100
|
.command("refresh")
|
|
78
101
|
.description("Re-discover collections from storage")
|
|
79
102
|
.action(refreshCommand);
|
|
103
|
+
// ── Skill ────────────────────────────────────────────────────────────────────
|
|
104
|
+
const skill = program
|
|
105
|
+
.command("skill")
|
|
106
|
+
.description("Manage individual skills");
|
|
107
|
+
skill
|
|
108
|
+
.command("delete <name>")
|
|
109
|
+
.description("Delete a single skill from a collection")
|
|
110
|
+
.option("--collection <name>", "Collection to delete from (required if skill is in multiple)")
|
|
111
|
+
.action((name, options) => skillDeleteCommand(name, options));
|
|
80
112
|
// ── Collection ───────────────────────────────────────────────────────────────
|
|
81
113
|
const collection = program
|
|
82
114
|
.command("collection")
|
|
@@ -86,6 +118,7 @@ collection
|
|
|
86
118
|
.description("Create a new collection (defaults to SKILLS_MY_SKILLS)")
|
|
87
119
|
.option("--backend <backend>", "gdrive (default) or github", "gdrive")
|
|
88
120
|
.option("--repo <owner/repo>", "GitHub repo to use (required for --backend github)")
|
|
121
|
+
.option("--skills-repo <owner/repo>", "GitHub repo where skills live; collection YAML stays in the declared backend")
|
|
89
122
|
.action((name, options) => collectionCreateCommand(name, options));
|
|
90
123
|
// ── Registry ─────────────────────────────────────────────────────────────────
|
|
91
124
|
const registry = program
|
|
@@ -116,6 +149,7 @@ registry
|
|
|
116
149
|
.command("remove-collection <name>")
|
|
117
150
|
.description("Remove a collection reference from the registry")
|
|
118
151
|
.option("--delete", "Also delete the collection and all its skills from the backend")
|
|
152
|
+
.option("--backend <backend>", "Backend the collection lives on (local, gdrive, github)")
|
|
119
153
|
.action((name, options) => registryRemoveCollectionCommand(name, options));
|
|
120
154
|
registry
|
|
121
155
|
.command("push")
|
package/dist/registry.js
CHANGED
|
@@ -6,7 +6,7 @@ export const LEGACY_COLLECTION_FILENAME = "SKILLS_SYNC.yaml";
|
|
|
6
6
|
// ── Collection (formerly "registry") parsing ─────────────────────────────────
|
|
7
7
|
export function parseCollection(content) {
|
|
8
8
|
const data = YAML.parse(content);
|
|
9
|
-
|
|
9
|
+
const col = {
|
|
10
10
|
name: data.name ?? "",
|
|
11
11
|
owner: data.owner ?? "",
|
|
12
12
|
skills: (data.skills ?? []).map((s) => ({
|
|
@@ -15,17 +15,29 @@ export function parseCollection(content) {
|
|
|
15
15
|
description: s.description ?? "",
|
|
16
16
|
})),
|
|
17
17
|
};
|
|
18
|
+
if (data.type)
|
|
19
|
+
col.type = data.type;
|
|
20
|
+
if (data.metadata && typeof data.metadata === "object") {
|
|
21
|
+
col.metadata = data.metadata;
|
|
22
|
+
}
|
|
23
|
+
return col;
|
|
18
24
|
}
|
|
19
25
|
export function serializeCollection(collection) {
|
|
20
|
-
|
|
26
|
+
const obj = {
|
|
21
27
|
name: collection.name,
|
|
22
28
|
owner: collection.owner,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
};
|
|
30
|
+
if (collection.type)
|
|
31
|
+
obj.type = collection.type;
|
|
32
|
+
obj.skills = collection.skills.map((s) => ({
|
|
33
|
+
name: s.name,
|
|
34
|
+
path: s.path,
|
|
35
|
+
description: s.description,
|
|
36
|
+
}));
|
|
37
|
+
if (collection.metadata && Object.keys(collection.metadata).length > 0) {
|
|
38
|
+
obj.metadata = collection.metadata;
|
|
39
|
+
}
|
|
40
|
+
return YAML.stringify(obj);
|
|
29
41
|
}
|
|
30
42
|
// Backwards-compat aliases
|
|
31
43
|
export const parseRegistry = parseCollection;
|
package/dist/types.d.ts
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface GitRepoContext {
|
|
2
|
+
repo: string;
|
|
3
|
+
repoRoot: string;
|
|
4
|
+
relPath: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Returns git repo context for a path if it belongs to a GitHub-tracked repo,
|
|
8
|
+
* or null otherwise.
|
|
9
|
+
*/
|
|
10
|
+
export declare function detectRepoContext(absPath: string): GitRepoContext | null;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { spawnSync } from "child_process";
|
|
2
|
+
import path from "path";
|
|
3
|
+
/**
|
|
4
|
+
* Returns git repo context for a path if it belongs to a GitHub-tracked repo,
|
|
5
|
+
* or null otherwise.
|
|
6
|
+
*/
|
|
7
|
+
export function detectRepoContext(absPath) {
|
|
8
|
+
const rootResult = spawnSync("git", ["-C", absPath, "rev-parse", "--show-toplevel"], {
|
|
9
|
+
encoding: "utf-8", stdio: "pipe",
|
|
10
|
+
});
|
|
11
|
+
if (rootResult.status !== 0)
|
|
12
|
+
return null;
|
|
13
|
+
const repoRoot = rootResult.stdout.trim();
|
|
14
|
+
const remoteResult = spawnSync("git", ["-C", repoRoot, "remote", "get-url", "origin"], {
|
|
15
|
+
encoding: "utf-8", stdio: "pipe",
|
|
16
|
+
});
|
|
17
|
+
if (remoteResult.status !== 0)
|
|
18
|
+
return null;
|
|
19
|
+
const remoteUrl = remoteResult.stdout.trim();
|
|
20
|
+
const match = remoteUrl.match(/github\.com[/:]([^/]+\/[^/]+?)(?:\.git)?$/) ??
|
|
21
|
+
remoteUrl.match(/github\.com\/([^/]+\/[^/]+)/);
|
|
22
|
+
if (!match)
|
|
23
|
+
return null;
|
|
24
|
+
const repo = match[1].replace(/\.git$/, "");
|
|
25
|
+
const relPath = path.relative(repoRoot, absPath).replace(/\\/g, "/");
|
|
26
|
+
return { repo, repoRoot, relPath };
|
|
27
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skillsmanager/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Discover, fetch, and manage agent skills from local or remote storage",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"url": "git+https://github.com/talktoajayprakash/skillsmanager.git"
|
|
36
36
|
},
|
|
37
37
|
"author": "Ajay Prakash",
|
|
38
|
-
"license": "
|
|
38
|
+
"license": "Apache-2.0",
|
|
39
39
|
"homepage": "https://github.com/talktoajayprakash/skillsmanager#readme",
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"chalk": "^4.1.2",
|
|
@@ -5,12 +5,13 @@ description: Discover, fetch, add, and update agent skills from local or remote
|
|
|
5
5
|
|
|
6
6
|
# Skills Manager
|
|
7
7
|
|
|
8
|
-
Skills Manager is a CLI tool for managing agent skills stored locally or in remote storage (Google Drive). Use it to find, install, share, and update skills. Works offline by default — no setup needed for local use.
|
|
8
|
+
Skills Manager is a CLI tool for managing agent skills stored locally or in remote storage (Google Drive, GitHub). Use it to find, install, share, and update skills. Works offline by default — no setup needed for local use.
|
|
9
9
|
|
|
10
10
|
## Prerequisites
|
|
11
11
|
|
|
12
12
|
- Local storage works out of the box — no setup needed.
|
|
13
13
|
- For Google Drive: a human must run `skillsmanager setup google` once to configure credentials.
|
|
14
|
+
- For GitHub: requires the `gh` CLI to be installed and authenticated (`gh auth login`). No additional skillsmanager setup needed.
|
|
14
15
|
- All commands except `setup google` are non-interactive and designed for agent use.
|
|
15
16
|
|
|
16
17
|
## Commands
|
|
@@ -33,7 +34,7 @@ skillsmanager list
|
|
|
33
34
|
|
|
34
35
|
Supported agents: `claude`, `codex`, `agents`, `cursor`, `windsurf`, `copilot`, `gemini`, `roo`, `openclaw`, `antigravity`
|
|
35
36
|
|
|
36
|
-
### Share a skill
|
|
37
|
+
### Share a skill you own
|
|
37
38
|
|
|
38
39
|
```bash
|
|
39
40
|
# Upload a local skill directory to a collection
|
|
@@ -44,6 +45,18 @@ skillsmanager add <path>
|
|
|
44
45
|
skillsmanager add <path> --collection <name>
|
|
45
46
|
```
|
|
46
47
|
|
|
48
|
+
### Register a skill path without uploading files (cross-repo / curated collections)
|
|
49
|
+
|
|
50
|
+
Use `--remote-path` when the skill files already exist in a remote backend and you just want to register a pointer to them. You cannot `add` local files to a cross-backend collection — you must use this flag instead.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Register a skill entry by path — no file upload
|
|
54
|
+
skillsmanager add --remote-path <backend-path> --name <skill-name> --description "<description>" --collection <name>
|
|
55
|
+
|
|
56
|
+
# Example: register a skill that lives in a GitHub repo
|
|
57
|
+
skillsmanager add --remote-path skills/write-tests/ --name write-tests --description "Generate unit tests" --collection my-col
|
|
58
|
+
```
|
|
59
|
+
|
|
47
60
|
### Update a skill
|
|
48
61
|
|
|
49
62
|
```bash
|
|
@@ -57,6 +70,16 @@ skillsmanager update <path> --collection <name>
|
|
|
57
70
|
|
|
58
71
|
After updating, the local cache is refreshed so all symlinks on this machine reflect the change immediately.
|
|
59
72
|
|
|
73
|
+
### Delete a skill
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Delete a skill from its collection (removes from backend, cache, and index)
|
|
77
|
+
skillsmanager skill delete <name>
|
|
78
|
+
|
|
79
|
+
# If the skill exists in multiple collections, specify which one
|
|
80
|
+
skillsmanager skill delete <name> --collection <collection-name>
|
|
81
|
+
```
|
|
82
|
+
|
|
60
83
|
### Registry and collection management
|
|
61
84
|
|
|
62
85
|
```bash
|
|
@@ -66,27 +89,44 @@ skillsmanager registry create
|
|
|
66
89
|
# Create a registry in Google Drive
|
|
67
90
|
skillsmanager registry create --backend gdrive
|
|
68
91
|
|
|
92
|
+
# Create a registry in a GitHub repo (creates repo if it doesn't exist)
|
|
93
|
+
skillsmanager registry create --backend github --repo <owner/repo>
|
|
94
|
+
|
|
69
95
|
# Show all registries and their collection references
|
|
70
96
|
skillsmanager registry list
|
|
71
97
|
|
|
72
98
|
# Search a backend for registries owned by the current user
|
|
73
99
|
skillsmanager registry discover --backend gdrive
|
|
100
|
+
skillsmanager registry discover --backend github
|
|
74
101
|
|
|
75
102
|
# Add a collection reference to the registry
|
|
76
103
|
skillsmanager registry add-collection <name>
|
|
77
104
|
|
|
78
|
-
# Push local registry and collections to Google Drive
|
|
105
|
+
# Push local registry and collections to Google Drive (safe to re-run — skips already-synced collections)
|
|
79
106
|
skillsmanager registry push --backend gdrive
|
|
80
107
|
|
|
108
|
+
# Push local registry and collections to GitHub (safe to re-run — skips already-synced collections)
|
|
109
|
+
skillsmanager registry push --backend github --repo <owner/repo>
|
|
110
|
+
|
|
81
111
|
# Remove a collection reference from the registry (keeps data)
|
|
82
112
|
skillsmanager registry remove-collection <name>
|
|
83
113
|
|
|
84
114
|
# Remove and permanently delete the collection and all its skills
|
|
85
115
|
skillsmanager registry remove-collection <name> --delete
|
|
86
116
|
|
|
87
|
-
# Create a new collection
|
|
117
|
+
# Create a new collection (local by default)
|
|
88
118
|
skillsmanager collection create [name]
|
|
89
119
|
|
|
120
|
+
# Create a collection in a GitHub repo (skills stored in that repo)
|
|
121
|
+
skillsmanager collection create [name] --backend github --repo <owner/repo>
|
|
122
|
+
|
|
123
|
+
# Create a collection in Google Drive
|
|
124
|
+
skillsmanager collection create [name] --backend gdrive
|
|
125
|
+
|
|
126
|
+
# Create a collection whose skills live in a specific GitHub repo (cross-backend)
|
|
127
|
+
skillsmanager collection create [name] --backend gdrive --skills-repo <owner/repo>
|
|
128
|
+
skillsmanager collection create [name] --backend github --repo <owner/registry-repo> --skills-repo <owner/skills-repo>
|
|
129
|
+
|
|
90
130
|
# Re-discover collections from storage
|
|
91
131
|
skillsmanager refresh
|
|
92
132
|
```
|
|
@@ -107,15 +147,37 @@ skillsmanager install --path <dir>
|
|
|
107
147
|
skillsmanager uninstall
|
|
108
148
|
```
|
|
109
149
|
|
|
150
|
+
## Cross-backend collections (curated skill libraries)
|
|
151
|
+
|
|
152
|
+
A collection can declare that its skill files live in a different GitHub repo than the collection YAML. This is indicated by `type: github` in `SKILLS_COLLECTION.yaml`.
|
|
153
|
+
|
|
154
|
+
**When you encounter a cross-backend collection:**
|
|
155
|
+
- `skillsmanager add <local-path> --collection <name>` → **will fail** with an error like `skills source type is "github"`. This is expected — you cannot upload local files to a foreign repo.
|
|
156
|
+
- **Do this instead:** `skillsmanager add --remote-path <path-in-repo> --name <n> --description "<d>" --collection <name>`
|
|
157
|
+
- `skillsmanager fetch <skill> --agent claude` → **works normally** — files are automatically pulled from the declared GitHub repo
|
|
158
|
+
|
|
159
|
+
**How to identify a cross-backend collection:**
|
|
160
|
+
- `skillsmanager registry list` — collections with a `--skills-repo` are shown with their skills repo
|
|
161
|
+
- Reading the `SKILLS_COLLECTION.yaml` directly — look for `type: github` + `metadata.repo`
|
|
162
|
+
|
|
163
|
+
**Quick rule:**
|
|
164
|
+
- Own the skill files? → `skillsmanager add <path>`
|
|
165
|
+
- Files already in a GitHub repo? → `skillsmanager add --remote-path <path> --name <n> --description "<d>"`
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
110
169
|
## Common Workflows
|
|
111
170
|
|
|
112
171
|
**User asks to find a skill:**
|
|
113
172
|
1. `skillsmanager search <relevant terms>`
|
|
114
173
|
2. `skillsmanager fetch <skill-name> --agent claude`
|
|
115
174
|
|
|
116
|
-
**User asks to share a skill they created:**
|
|
175
|
+
**User asks to share a skill they created locally:**
|
|
117
176
|
1. Ensure the skill directory has a `SKILL.md` with `name` and `description` in YAML frontmatter
|
|
118
177
|
2. `skillsmanager add <path-to-skill-directory>`
|
|
178
|
+
3. Fetch the skill to make it immediately available to the agent:
|
|
179
|
+
- For all projects: `skillsmanager fetch <skill-name> --agent claude`
|
|
180
|
+
- For current project only: `skillsmanager fetch <skill-name> --agent claude --scope project`
|
|
119
181
|
|
|
120
182
|
**User asks to update a skill:**
|
|
121
183
|
1. Edit the skill files locally
|
|
@@ -128,20 +190,61 @@ skillsmanager uninstall
|
|
|
128
190
|
1. `skillsmanager setup google` (one-time, human-only)
|
|
129
191
|
2. `skillsmanager registry push --backend gdrive`
|
|
130
192
|
|
|
193
|
+
**User wants to store skills in a GitHub repo:**
|
|
194
|
+
1. `skillsmanager collection create <name> --backend github --repo <owner/repo>` — creates the GitHub repo if needed, and auto-registers the collection
|
|
195
|
+
2. `skillsmanager add <path> --collection <name>` — upload the skill into that collection
|
|
196
|
+
|
|
197
|
+
**User wants to create a curated collection of skills from a public GitHub repo (cross-backend):**
|
|
198
|
+
|
|
199
|
+
Use this when you want to expose skills from an external GitHub repo (e.g. `anthropics/skills`) via a collection the user can fetch from, without copying the files.
|
|
200
|
+
|
|
201
|
+
1. `skillsmanager collection create <name> --backend gdrive --skills-repo <owner/skills-repo>`
|
|
202
|
+
- This creates the collection YAML in the user's Google Drive with `type: github` + `metadata.repo` pointing to the skills repo
|
|
203
|
+
2. Register each skill by its path in the skills repo (no file upload needed):
|
|
204
|
+
```bash
|
|
205
|
+
skillsmanager add --remote-path skills/write-tests/ --name write-tests --description "Generate unit tests" --collection <name>
|
|
206
|
+
```
|
|
207
|
+
3. Users fetch skills normally — `skillsmanager fetch write-tests --agent claude` — and the files are pulled from the skills repo
|
|
208
|
+
|
|
209
|
+
**User wants to add a skill from a public GitHub repo without uploading files:**
|
|
210
|
+
1. Create or identify a collection with `--skills-repo <owner/repo>`
|
|
211
|
+
2. `skillsmanager add --remote-path <path-in-repo> --name <skill-name> --description "<desc>" --collection <name>`
|
|
212
|
+
|
|
213
|
+
**User wants to discover GitHub-hosted collections:**
|
|
214
|
+
1. `skillsmanager registry discover --backend github`
|
|
215
|
+
|
|
131
216
|
**User wants to see what registries and collections exist:**
|
|
132
217
|
1. `skillsmanager registry list`
|
|
133
218
|
|
|
219
|
+
**User asks to delete/remove a single skill:**
|
|
220
|
+
1. `skillsmanager skill delete <skill-name>`
|
|
221
|
+
2. If the skill lives in multiple collections, add `--collection <name>` to target the right one
|
|
222
|
+
|
|
134
223
|
**User wants to remove a collection:**
|
|
135
224
|
1. `skillsmanager registry remove-collection <name>` (removes reference only, data is kept)
|
|
136
225
|
2. `skillsmanager registry remove-collection <name> --delete` (permanently deletes collection and skills)
|
|
137
226
|
|
|
227
|
+
## Collection types
|
|
228
|
+
|
|
229
|
+
Most collections store skill files directly in their backend. But a collection can also declare that skill files live in a different GitHub repo — this is useful for curating public skills or pointing to a shared library repo.
|
|
230
|
+
|
|
231
|
+
| Collection backend | Skills repo | What `add` does | What `fetch` does |
|
|
232
|
+
|---|---|---|---|
|
|
233
|
+
| `gdrive` or `local` | (none) | Uploads files to Drive/local | Downloads from Drive/local |
|
|
234
|
+
| `github` | (same repo as collection) | Commits files to the repo | Clones/pulls from repo |
|
|
235
|
+
| `gdrive` or `local` | `--skills-repo owner/repo` | **Requires `--remote-path`** — registers a path pointer only | Downloads files from the GitHub repo |
|
|
236
|
+
| `github` (registry repo) | `--skills-repo owner/skills-repo` | **Requires `--remote-path`** — registers a path pointer only | Downloads files from the skills repo |
|
|
237
|
+
|
|
238
|
+
When you try to `skillsmanager add <local-path>` to a collection with a cross-backend skills repo, the command will fail with a clear error pointing you to `--remote-path`.
|
|
239
|
+
|
|
138
240
|
## Architecture
|
|
139
241
|
|
|
140
242
|
- **Registry** (`SKILLS_REGISTRY.yaml`): root index pointing to all collections across backends
|
|
141
243
|
- **Collection** (`SKILLS_COLLECTION.yaml`): folder of skills with an index file
|
|
142
|
-
- **Backends**: `local` (default, `~/.skillsmanager/`)
|
|
244
|
+
- **Backends**: `local` (default, `~/.skillsmanager/`), `gdrive` (Google Drive), and `github` (GitHub repo via `gh` CLI)
|
|
143
245
|
- **Cache**: skills are cached at `~/.skillsmanager/cache/<uuid>/` and symlinked to agent directories
|
|
144
246
|
- **Symlinks**: all agents share one cached copy — updating the cache updates all agents
|
|
247
|
+
- **RoutingBackend**: transparent middleware that intercepts skill-file operations and dispatches to the right backend based on the collection's declared `type` field
|
|
145
248
|
|
|
146
249
|
## Scope
|
|
147
250
|
|