@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.
Files changed (42) hide show
  1. package/LICENSE +10 -18
  2. package/README.md +93 -36
  3. package/dist/auth.d.ts +1 -0
  4. package/dist/auth.js +19 -2
  5. package/dist/backends/gdrive.d.ts +6 -4
  6. package/dist/backends/gdrive.js +27 -13
  7. package/dist/backends/github.d.ts +10 -9
  8. package/dist/backends/github.js +71 -50
  9. package/dist/backends/interface.d.ts +19 -2
  10. package/dist/backends/local.d.ts +6 -4
  11. package/dist/backends/local.js +18 -13
  12. package/dist/backends/resolve.d.ts +2 -0
  13. package/dist/backends/resolve.js +25 -4
  14. package/dist/backends/routing.d.ts +38 -0
  15. package/dist/backends/routing.js +124 -0
  16. package/dist/commands/add.d.ts +3 -0
  17. package/dist/commands/add.js +130 -26
  18. package/dist/commands/collection.d.ts +1 -0
  19. package/dist/commands/collection.js +43 -37
  20. package/dist/commands/init.js +3 -3
  21. package/dist/commands/list.js +78 -8
  22. package/dist/commands/logout.d.ts +4 -0
  23. package/dist/commands/logout.js +35 -0
  24. package/dist/commands/refresh.js +1 -1
  25. package/dist/commands/registry.d.ts +1 -0
  26. package/dist/commands/registry.js +74 -36
  27. package/dist/commands/search.js +1 -1
  28. package/dist/commands/setup/github.d.ts +3 -0
  29. package/dist/commands/setup/github.js +8 -2
  30. package/dist/commands/setup/google.js +82 -42
  31. package/dist/commands/skill.d.ts +3 -0
  32. package/dist/commands/skill.js +76 -0
  33. package/dist/commands/status.d.ts +1 -0
  34. package/dist/commands/status.js +35 -0
  35. package/dist/config.js +6 -1
  36. package/dist/index.js +37 -3
  37. package/dist/registry.js +20 -8
  38. package/dist/types.d.ts +2 -0
  39. package/dist/utils/git.d.ts +10 -0
  40. package/dist/utils/git.js +27 -0
  41. package/package.json +2 -2
  42. 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
- return fresh.map((c) => {
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 <path>")
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
- .action((skillPath, options) => addCommand(skillPath, options));
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
- return {
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
- return YAML.stringify({
26
+ const obj = {
21
27
  name: collection.name,
22
28
  owner: collection.owner,
23
- skills: collection.skills.map((s) => ({
24
- name: s.name,
25
- path: s.path,
26
- description: s.description,
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
@@ -6,7 +6,9 @@ export interface SkillEntry {
6
6
  export interface CollectionFile {
7
7
  name: string;
8
8
  owner: string;
9
+ type?: string;
9
10
  skills: SkillEntry[];
11
+ metadata?: Record<string, unknown>;
10
12
  }
11
13
  export interface CollectionInfo {
12
14
  id: string;
@@ -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.4",
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": "MIT",
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/`) and `gdrive` (Google Drive)
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