@skillsmanager/cli 0.0.2 → 0.0.5

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 CHANGED
@@ -1,21 +1,13 @@
1
- MIT License
1
+ Copyright 2026 Ajay Prakash
2
2
 
3
- Copyright (c) 2026 Ajay Prakash
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
4
6
 
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
7
+ http://www.apache.org/licenses/LICENSE-2.0
11
8
 
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
package/README.md CHANGED
@@ -1,58 +1,107 @@
1
+ [![npm](https://img.shields.io/npm/v/%40skillsmanager%2Fcli)](https://www.npmjs.com/package/@skillsmanager/cli)
2
+ [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
3
+ [![Node >=18](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
4
+ [![CI](https://github.com/talktoajayprakash/skillsmanager/actions/workflows/release.yml/badge.svg)](https://github.com/talktoajayprakash/skillsmanager/actions)
5
+ [![Docs](https://img.shields.io/badge/docs-skillsmanager-blue)](https://talktoajayprakash.github.io/skillsmanager)
6
+
1
7
  # Skills Manager
2
8
 
3
- A CLI for AI agents to discover, fetch, and share agent skills stored in Google Drive.
9
+ **One place to manage, sync, and share all your AI agent skills across every agent you use.**
10
+
11
+ You build skills for your AI agents, but keeping track of them is a mess. They're scattered across GitHub repos, local folders, and machines. Each agent has its own directory. Nothing is searchable. Nothing is shared.
12
+
13
+ Skills Manager fixes this. It gives every skill a home — in Google Drive, GitHub, or any storage backend you choose — and makes them instantly available to any agent via a single CLI command. Your agents can search, fetch, and use any skill regardless of where it lives.
14
+
15
+ Build skills confidently, store them where you want, and sync them across every device and agent you work with — Claude, Cursor, OpenAI Codex, OpenClaw, and beyond.
16
+
17
+ ## Why Skills Manager?
18
+
19
+ - **Unified skill library** — one searchable index across all your skills, wherever they're stored
20
+ - **Cross-agent** — install any skill into Claude, Cursor, Windsurf, Copilot, Gemini, and more
21
+ - **Backend-agnostic** — store in Google Drive, GitHub, Dropbox, AWS S3, or local filesystem
22
+ - **Sync across devices** — skills follow you, not your machine
23
+ - **No duplication** — cached once locally, symlinked into each agent's directory
24
+ - **Git-friendly** — plain Markdown files, easy to version-control and review
4
25
 
5
- ## What is it?
26
+ ## Supported Agents
6
27
 
7
- Skills Manager lets you maintain a personal library of agent skills in Google Drive and install them into any supported AI agent (Claude, Cursor, Windsurf, Copilot, etc.) with a single command.
28
+ `claude` · `codex` · `cursor` · `windsurf` · `copilot` · `gemini` · `roo` · `openclaw` · `agents`
8
29
 
9
- Skills are downloaded to a local cache (`~/.skillsmanager/cache/`) and symlinked into the agent's skills directory. No duplicationone copy, many agents.
30
+ > **OpenClaw users:** OpenClaw's skill system uses the same `SKILL.md` format and directory-based loading that Skills Manager is built around. Your OpenClaw skills are first-class citizens store them in any backend, search them, and sync them across devices just like any other skill.
31
+ >
32
+ > Any agent that reads from a skills directory works with Skills Manager. If your agent can read a file, it can use your skills.
10
33
 
11
- ## Supported agents
34
+ ## Quick Start
12
35
 
13
- `claude`, `codex`, `cursor`, `windsurf`, `copilot`, `gemini`, `roo`, `agents`
36
+ ### 1. Install
14
37
 
15
- ## Installation
38
+ ```bash
39
+ npm install -g @skillsmanager/cli
40
+ ```
41
+
42
+ ### 2. Install the skillsmanager skill (lets your agent drive Skills Manager)
16
43
 
17
44
  ```bash
18
- npm install -g skillsmanager
45
+ skillsmanager install
19
46
  ```
20
47
 
21
- ## Google Drive setup
48
+ This installs the bundled `skillsmanager` skill into all detected agents so your AI assistant can manage skills on your behalf.
22
49
 
23
- 1. Go to [Google Cloud Console](https://console.cloud.google.com/)
24
- 2. Create a project → enable the **Google Drive API**
25
- 3. Create OAuth 2.0 credentials (Desktop app)
50
+ ### 3. One-time Google Drive setup
51
+
52
+ Skills Manager uses Google Drive as a remote registry. To connect it:
53
+
54
+ 1. Go to [Google Cloud Console](https://console.cloud.google.com/) and create a project
55
+ 2. Enable the **Google Drive API** for that project
56
+ 3. Create **OAuth 2.0 credentials** (Desktop app type)
26
57
  4. Download `credentials.json` and save it to `~/.skillsmanager/credentials.json`
27
58
 
28
- ## Usage
59
+ Then authenticate and discover your registries:
29
60
 
30
61
  ```bash
31
- # Authenticate and discover registries
32
- skillsmanager init
62
+ skillsmanager setup google # walks you through OAuth
63
+ skillsmanager refresh # discovers collections in your Drive
64
+ ```
33
65
 
34
- # List all available skills
35
- skillsmanager list
66
+ ## Commands
36
67
 
37
- # Search skills by name or description
38
- skillsmanager search <query>
68
+ | Command | Description |
69
+ |---|---|
70
+ | `skillsmanager install` | Install the skillsmanager skill to all agents |
71
+ | `skillsmanager list` | List all available skills |
72
+ | `skillsmanager search <query>` | Search skills by name or description |
73
+ | `skillsmanager fetch <name> --agent <agent>` | Download and install a skill for an agent |
74
+ | `skillsmanager add <path>` | Upload a local skill to a collection |
75
+ | `skillsmanager update <path>` | Push local changes back to remote storage |
76
+ | `skillsmanager refresh` | Re-discover collections from remote |
77
+ | `skillsmanager collection create` | Create a new skill collection |
78
+ | `skillsmanager registry push --backend gdrive` | Push local registry to Google Drive |
39
79
 
40
- # Download a skill and install it for an agent
41
- skillsmanager fetch <skill-name> --agent claude
80
+ ## Local Development
42
81
 
43
- # Add a local skill to your registry
44
- skillsmanager add ./my-skill
82
+ ```bash
83
+ git clone https://github.com/talktoajayprakash/skillsmanager.git
84
+ cd skillsmanager
85
+ npm install
86
+ npm run build # compiles TypeScript to dist/
87
+ npm link # makes `skillsmanager` available globally from source
88
+ ```
45
89
 
46
- # Push local changes to an existing skill back to Drive
47
- skillsmanager update <skill-name>
90
+ Run tests:
48
91
 
49
- # Re-scan Drive for new registries
50
- skillsmanager refresh
92
+ ```bash
93
+ npm test
94
+ ```
95
+
96
+ To run without installing globally:
97
+
98
+ ```bash
99
+ node dist/index.js <command>
51
100
  ```
52
101
 
53
102
  ## Registry format
54
103
 
55
- Skills are indexed by a `SKILLS_SYNC.yaml` file inside any Google Drive folder you own:
104
+ Skills are indexed by a `SKILLS_REGISTRY.yaml` file inside any Google Drive folder you own:
56
105
 
57
106
  ```yaml
58
107
  name: my-skills
@@ -61,12 +110,9 @@ skills:
61
110
  - name: code-review
62
111
  path: code-review/
63
112
  description: Reviews code for bugs, style, and security issues
64
- - name: write-tests
65
- path: write-tests/
66
- description: Generates unit tests for a given function or module
67
113
  ```
68
114
 
69
- Each skill is a directory containing a `SKILL.md` file with YAML frontmatter:
115
+ Each skill is a directory with a `SKILL.md` file:
70
116
 
71
117
  ```markdown
72
118
  ---
@@ -77,8 +123,16 @@ description: Reviews code for bugs, style, and security issues
77
123
  ... skill instructions ...
78
124
  ```
79
125
 
80
- Skills Manager auto-discovers any `SKILLS_SYNC.yaml` file owned by your Google account, so registries are found automatically on `skillsmanager init` or `skillsmanager refresh`.
126
+ Skills Manager auto-discovers any `SKILLS_REGISTRY.yaml` in your Google account on `refresh`.
127
+
128
+ ## Contributing
129
+
130
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) — PRs welcome.
131
+
132
+ ## Security
133
+
134
+ See [SECURITY.md](./SECURITY.md) for how to report vulnerabilities.
81
135
 
82
- ## Design doc
136
+ ## License
83
137
 
84
- See [WRITEUP.md](./WRITEUP.md) for the full design.
138
+ [Apache 2.0](LICENSE)
package/dist/auth.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { OAuth2Client } from "google-auth-library";
2
2
  export declare function runAuthFlow(): Promise<OAuth2Client>;
3
+ export declare function getAuthedEmail(client: OAuth2Client): Promise<string | null>;
3
4
  export declare function getAuthClient(): OAuth2Client;
4
5
  export declare function hasToken(): boolean;
5
6
  export declare function ensureAuth(): Promise<OAuth2Client>;
package/dist/auth.js CHANGED
@@ -1,26 +1,56 @@
1
1
  import fs from "fs";
2
- import readline from "readline";
2
+ import http from "http";
3
3
  import { google } from "googleapis";
4
4
  import { TOKEN_PATH, ensureConfigDir, readCredentials, credentialsExist } from "./config.js";
5
- const SCOPES = ["https://www.googleapis.com/auth/drive"];
5
+ const SCOPES = [
6
+ "https://www.googleapis.com/auth/drive",
7
+ "openid",
8
+ "https://www.googleapis.com/auth/userinfo.email",
9
+ ];
10
+ const LOOPBACK_PORT = 3847;
11
+ const REDIRECT_URI = `http://localhost:${LOOPBACK_PORT}`;
6
12
  function createOAuth2Client() {
7
13
  const { client_id, client_secret } = readCredentials();
8
- return new google.auth.OAuth2(client_id, client_secret, "urn:ietf:wg:oauth:2.0:oob");
14
+ return new google.auth.OAuth2(client_id, client_secret, REDIRECT_URI);
9
15
  }
10
16
  function saveToken(client) {
11
17
  ensureConfigDir();
12
18
  fs.writeFileSync(TOKEN_PATH, JSON.stringify(client.credentials, null, 2));
13
19
  }
14
- function prompt(question) {
15
- const rl = readline.createInterface({
16
- input: process.stdin,
17
- output: process.stdout,
18
- });
19
- return new Promise((resolve) => {
20
- rl.question(question, (answer) => {
21
- rl.close();
22
- resolve(answer.trim());
20
+ /**
21
+ * Starts a temporary local HTTP server to receive the OAuth redirect,
22
+ * extracts the authorization code, and resolves the promise.
23
+ */
24
+ function waitForAuthCode() {
25
+ return new Promise((resolve, reject) => {
26
+ const server = http.createServer((req, res) => {
27
+ const url = new URL(req.url ?? "/", `http://localhost:${LOOPBACK_PORT}`);
28
+ const code = url.searchParams.get("code");
29
+ const error = url.searchParams.get("error");
30
+ if (error) {
31
+ res.writeHead(200, { "Content-Type": "text/html" });
32
+ res.end("<h2>Authorization failed.</h2><p>You can close this tab.</p>");
33
+ res.socket?.destroy();
34
+ server.closeAllConnections?.();
35
+ server.close();
36
+ reject(new Error(`OAuth error: ${error}`));
37
+ return;
38
+ }
39
+ if (code) {
40
+ res.writeHead(200, { "Content-Type": "text/html" });
41
+ res.end("<h2>Authorization successful!</h2><p>You can close this tab and return to the terminal.</p>");
42
+ res.socket?.destroy();
43
+ server.closeAllConnections?.();
44
+ server.close();
45
+ resolve(code);
46
+ }
47
+ else {
48
+ res.writeHead(400, { "Content-Type": "text/html" });
49
+ res.end("<h2>Missing authorization code.</h2>");
50
+ }
23
51
  });
52
+ server.listen(LOOPBACK_PORT, () => { });
53
+ server.on("error", (err) => reject(new Error(`Could not start auth server on port ${LOOPBACK_PORT}: ${err.message}`)));
24
54
  });
25
55
  }
26
56
  export async function runAuthFlow() {
@@ -32,13 +62,23 @@ export async function runAuthFlow() {
32
62
  });
33
63
  console.log("\nOpen this URL in your browser to authorize Skills Manager:\n");
34
64
  console.log(authUrl);
35
- console.log();
36
- const code = await prompt("Paste the authorization code here: ");
65
+ console.log("\nWaiting for authorization...");
66
+ const code = await waitForAuthCode();
37
67
  const { tokens } = await client.getToken(code);
38
68
  client.setCredentials(tokens);
39
69
  saveToken(client);
40
70
  return client;
41
71
  }
72
+ export async function getAuthedEmail(client) {
73
+ try {
74
+ const oauth2 = google.oauth2({ version: "v2", auth: client });
75
+ const res = await oauth2.userinfo.get();
76
+ return res.data.email ?? null;
77
+ }
78
+ catch {
79
+ return null;
80
+ }
81
+ }
42
82
  export function getAuthClient() {
43
83
  if (!fs.existsSync(TOKEN_PATH)) {
44
84
  throw new Error(`Not authenticated. Run "skillsmanager init" first.`);
@@ -3,7 +3,6 @@ import type { StorageBackend } from "./interface.js";
3
3
  import type { CollectionFile, CollectionInfo, RegistryCollectionRef, RegistryFile, RegistryInfo } from "../types.js";
4
4
  export declare class GDriveBackend implements StorageBackend {
5
5
  private drive;
6
- private oauth2;
7
6
  constructor(auth: OAuth2Client);
8
7
  getOwner(): Promise<string>;
9
8
  getOwnerEmail(): Promise<string>;
@@ -13,6 +12,8 @@ export declare class GDriveBackend implements StorageBackend {
13
12
  downloadSkill(collection: CollectionInfo, skillName: string, destDir: string): Promise<void>;
14
13
  private downloadFolder;
15
14
  createCollection(folderName: string): Promise<CollectionInfo>;
15
+ deleteCollection(collection: CollectionInfo): Promise<void>;
16
+ deleteSkill(collection: CollectionInfo, skillName: string): Promise<void>;
16
17
  uploadSkill(collection: CollectionInfo, localPath: string, skillName: string): Promise<void>;
17
18
  discoverRegistries(): Promise<Omit<RegistryInfo, "id">[]>;
18
19
  readRegistry(registry: RegistryInfo): Promise<RegistryFile>;
@@ -7,14 +7,12 @@ import { randomUUID } from "crypto";
7
7
  const FOLDER_MIME = "application/vnd.google-apps.folder";
8
8
  export class GDriveBackend {
9
9
  drive;
10
- oauth2;
11
10
  constructor(auth) {
12
11
  this.drive = google.drive({ version: "v3", auth });
13
- this.oauth2 = google.oauth2({ version: "v2", auth });
14
12
  }
15
13
  async getOwner() {
16
- const res = await this.oauth2.userinfo.get();
17
- return res.data.email ?? "";
14
+ const res = await this.drive.about.get({ fields: "user(emailAddress)" });
15
+ return res.data.user?.emailAddress ?? "";
18
16
  }
19
17
  // Alias for backwards compat
20
18
  async getOwnerEmail() {
@@ -180,6 +178,19 @@ export class GDriveBackend {
180
178
  registryFileId: fileRes.data.id ?? undefined,
181
179
  };
182
180
  }
181
+ async deleteCollection(collection) {
182
+ // Trash the collection folder on Drive (moves to trash, not permanent delete)
183
+ await this.drive.files.update({
184
+ fileId: collection.folderId,
185
+ requestBody: { trashed: true },
186
+ });
187
+ }
188
+ async deleteSkill(collection, skillName) {
189
+ const folderId = await this.findFolder(skillName, collection.folderId);
190
+ if (folderId) {
191
+ await this.drive.files.update({ fileId: folderId, requestBody: { trashed: true } });
192
+ }
193
+ }
183
194
  async uploadSkill(collection, localPath, skillName) {
184
195
  let folderId = await this.findFolder(skillName, collection.folderId);
185
196
  if (!folderId) {
@@ -0,0 +1,27 @@
1
+ import type { StorageBackend } from "./interface.js";
2
+ import type { CollectionFile, CollectionInfo, RegistryCollectionRef, RegistryFile, RegistryInfo } from "../types.js";
3
+ export declare class GithubBackend implements StorageBackend {
4
+ getOwner(): Promise<string>;
5
+ ensureRepo(repo: string): Promise<void>;
6
+ private ensureWorkdir;
7
+ private gitPushOrPR;
8
+ private commitAndPush;
9
+ discoverCollections(): Promise<Omit<CollectionInfo, "id">[]>;
10
+ readCollection(collection: CollectionInfo): Promise<CollectionFile>;
11
+ writeCollection(collection: CollectionInfo, data: CollectionFile): Promise<void>;
12
+ downloadSkill(collection: CollectionInfo, skillName: string, destDir: string): Promise<void>;
13
+ uploadSkill(collection: CollectionInfo, localPath: string, skillName: string): Promise<void>;
14
+ deleteCollection(collection: CollectionInfo): Promise<void>;
15
+ deleteSkill(collection: CollectionInfo, skillName: string): Promise<void>;
16
+ discoverRegistries(): Promise<Omit<RegistryInfo, "id">[]>;
17
+ readRegistry(registry: RegistryInfo): Promise<RegistryFile>;
18
+ writeRegistry(registry: RegistryInfo, data: RegistryFile): Promise<void>;
19
+ resolveCollectionRef(ref: RegistryCollectionRef): Promise<Omit<CollectionInfo, "id"> | null>;
20
+ createRegistry(name?: string, repoRef?: string): Promise<RegistryInfo>;
21
+ createCollection(collectionName: string, repoRef?: string, skillsRepoRef?: string): Promise<CollectionInfo>;
22
+ static detectRepoContext(absPath: string): {
23
+ repo: string;
24
+ repoRoot: string;
25
+ relPath: string;
26
+ } | null;
27
+ }