@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 +10 -18
- package/README.md +89 -35
- package/dist/auth.d.ts +1 -0
- package/dist/auth.js +54 -14
- package/dist/backends/gdrive.d.ts +2 -1
- package/dist/backends/gdrive.js +15 -4
- package/dist/backends/github.d.ts +27 -0
- package/dist/backends/github.js +407 -0
- package/dist/backends/interface.d.ts +2 -0
- package/dist/backends/local.d.ts +2 -0
- package/dist/backends/local.js +11 -0
- package/dist/backends/resolve.d.ts +2 -0
- package/dist/backends/resolve.js +11 -0
- package/dist/commands/add.d.ts +3 -0
- package/dist/commands/add.js +209 -7
- package/dist/commands/collection.d.ts +5 -1
- package/dist/commands/collection.js +99 -25
- package/dist/commands/fetch.js +7 -6
- package/dist/commands/list.js +5 -3
- package/dist/commands/logout.d.ts +4 -0
- package/dist/commands/logout.js +35 -0
- package/dist/commands/refresh.js +78 -25
- package/dist/commands/registry.d.ts +6 -0
- package/dist/commands/registry.js +200 -92
- package/dist/commands/setup/github.d.ts +4 -0
- package/dist/commands/setup/github.js +91 -0
- 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 +65 -0
- package/dist/commands/update.js +6 -4
- package/dist/index.js +57 -9
- package/dist/registry.js +11 -3
- package/dist/types.d.ts +1 -0
- package/dist/types.js +2 -0
- package/package.json +2 -2
- package/skills/skillsmanager/SKILL.md +49 -4
package/LICENSE
CHANGED
|
@@ -1,21 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
Copyright 2026 Ajay Prakash
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
+
[](https://www.npmjs.com/package/@skillsmanager/cli)
|
|
2
|
+
[](LICENSE)
|
|
3
|
+
[](https://nodejs.org)
|
|
4
|
+
[](https://github.com/talktoajayprakash/skillsmanager/actions)
|
|
5
|
+
[](https://talktoajayprakash.github.io/skillsmanager)
|
|
6
|
+
|
|
1
7
|
# Skills Manager
|
|
2
8
|
|
|
3
|
-
|
|
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
|
-
##
|
|
26
|
+
## Supported Agents
|
|
6
27
|
|
|
7
|
-
|
|
28
|
+
`claude` · `codex` · `cursor` · `windsurf` · `copilot` · `gemini` · `roo` · `openclaw` · `agents`
|
|
8
29
|
|
|
9
|
-
|
|
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
|
-
##
|
|
34
|
+
## Quick Start
|
|
12
35
|
|
|
13
|
-
|
|
36
|
+
### 1. Install
|
|
14
37
|
|
|
15
|
-
|
|
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
|
-
|
|
45
|
+
skillsmanager install
|
|
19
46
|
```
|
|
20
47
|
|
|
21
|
-
|
|
48
|
+
This installs the bundled `skillsmanager` skill into all detected agents so your AI assistant can manage skills on your behalf.
|
|
22
49
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
59
|
+
Then authenticate and discover your registries:
|
|
29
60
|
|
|
30
61
|
```bash
|
|
31
|
-
#
|
|
32
|
-
skillsmanager
|
|
62
|
+
skillsmanager setup google # walks you through OAuth
|
|
63
|
+
skillsmanager refresh # discovers collections in your Drive
|
|
64
|
+
```
|
|
33
65
|
|
|
34
|
-
|
|
35
|
-
skillsmanager list
|
|
66
|
+
## Commands
|
|
36
67
|
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
41
|
-
skillsmanager fetch <skill-name> --agent claude
|
|
80
|
+
## Local Development
|
|
42
81
|
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
47
|
-
skillsmanager update <skill-name>
|
|
90
|
+
Run tests:
|
|
48
91
|
|
|
49
|
-
|
|
50
|
-
|
|
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 `
|
|
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
|
|
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 `
|
|
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
|
-
##
|
|
136
|
+
## License
|
|
83
137
|
|
|
84
|
-
|
|
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
|
|
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 = [
|
|
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,
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return new Promise((resolve) => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
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>;
|
package/dist/backends/gdrive.js
CHANGED
|
@@ -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.
|
|
17
|
-
return res.data.
|
|
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
|
+
}
|