@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
|
@@ -4,9 +4,10 @@ import os from "os";
|
|
|
4
4
|
import readline from "readline";
|
|
5
5
|
import { execSync, spawnSync } from "child_process";
|
|
6
6
|
import chalk from "chalk";
|
|
7
|
-
import { CREDENTIALS_PATH, ensureConfigDir } from "../../config.js";
|
|
8
|
-
import { runAuthFlow, hasToken } from "../../auth.js";
|
|
7
|
+
import { CREDENTIALS_PATH, ensureConfigDir, CONFIG_PATH, readConfig } from "../../config.js";
|
|
8
|
+
import { runAuthFlow, hasToken, getAuthedEmail } from "../../auth.js";
|
|
9
9
|
import { credentialsExist } from "../../config.js";
|
|
10
|
+
import { LocalBackend } from "../../backends/local.js";
|
|
10
11
|
// ─── Prompt helpers ──────────────────────────────────────────────────────────
|
|
11
12
|
function ask(question) {
|
|
12
13
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -123,6 +124,26 @@ function openUrl(url) {
|
|
|
123
124
|
}
|
|
124
125
|
catch { /* ignore */ }
|
|
125
126
|
}
|
|
127
|
+
// ─── Post-auth nudge ─────────────────────────────────────────────────────────
|
|
128
|
+
async function printPushNudgeIfNeeded() {
|
|
129
|
+
if (!fs.existsSync(CONFIG_PATH))
|
|
130
|
+
return;
|
|
131
|
+
try {
|
|
132
|
+
const config = readConfig();
|
|
133
|
+
const localReg = config.registries.find((r) => r.backend === "local");
|
|
134
|
+
if (!localReg)
|
|
135
|
+
return;
|
|
136
|
+
const local = new LocalBackend();
|
|
137
|
+
const localData = await local.readRegistry(localReg);
|
|
138
|
+
const localCollections = localData.collections.filter((c) => c.backend === "local");
|
|
139
|
+
if (localCollections.length === 0)
|
|
140
|
+
return;
|
|
141
|
+
const names = localCollections.map((c) => chalk.cyan(c.name)).join(", ");
|
|
142
|
+
console.log(chalk.yellow(`\n Found local registry with ${localCollections.length} collection(s): ${names}`));
|
|
143
|
+
console.log(chalk.dim(` Run ${chalk.white("skillsmanager registry push --backend gdrive")} to back them up to Google Drive.\n`));
|
|
144
|
+
}
|
|
145
|
+
catch { /* unreadable config, skip hint */ }
|
|
146
|
+
}
|
|
126
147
|
// ─── Main command ─────────────────────────────────────────────────────────────
|
|
127
148
|
export async function setupGoogleCommand() {
|
|
128
149
|
console.log(chalk.bold("\nSkills Manager — Google Drive Setup\n"));
|
|
@@ -131,13 +152,16 @@ export async function setupGoogleCommand() {
|
|
|
131
152
|
console.log(chalk.green(" ✓ credentials.json found"));
|
|
132
153
|
if (hasToken()) {
|
|
133
154
|
console.log(chalk.green(" ✓ Already authenticated — nothing to do."));
|
|
134
|
-
|
|
155
|
+
await printPushNudgeIfNeeded();
|
|
156
|
+
console.log(`Run ${chalk.bold("skillsmanager refresh")} to discover registries.\n`);
|
|
135
157
|
return;
|
|
136
158
|
}
|
|
137
159
|
console.log(chalk.yellow(" ✗ Not yet authenticated — starting OAuth flow...\n"));
|
|
138
|
-
await runAuthFlow();
|
|
139
|
-
|
|
140
|
-
console.log(`\
|
|
160
|
+
const client = await runAuthFlow();
|
|
161
|
+
const authedEmail = await getAuthedEmail(client);
|
|
162
|
+
console.log(chalk.green(`\n ✓ Authenticated successfully${authedEmail ? ` as ${chalk.white(authedEmail)}` : ""}.`));
|
|
163
|
+
await printPushNudgeIfNeeded();
|
|
164
|
+
console.log(`Run ${chalk.bold("skillsmanager refresh")} to discover registries.\n`);
|
|
141
165
|
return;
|
|
142
166
|
}
|
|
143
167
|
// ── Case 2: No credentials.json ───────────────────────────────────────────
|
|
@@ -189,33 +213,54 @@ export async function setupGoogleCommand() {
|
|
|
189
213
|
const projectId = await selectOrCreateProject();
|
|
190
214
|
if (!projectId)
|
|
191
215
|
return;
|
|
192
|
-
// ── Enable
|
|
193
|
-
console.log(chalk.bold("\nStep 4 — Enable
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if (ok) {
|
|
201
|
-
console.log(chalk.green(" ✓ Google Drive API enabled"));
|
|
216
|
+
// ── Enable APIs ───────────────────────────────────────────────────────────
|
|
217
|
+
console.log(chalk.bold("\nStep 4 — Enable Required APIs\n"));
|
|
218
|
+
const REQUIRED_APIS = [
|
|
219
|
+
{ api: "drive.googleapis.com", label: "Google Drive API" },
|
|
220
|
+
];
|
|
221
|
+
for (const { api, label } of REQUIRED_APIS) {
|
|
222
|
+
if (apiEnabled(projectId, api)) {
|
|
223
|
+
console.log(chalk.green(` ✓ ${label} already enabled`));
|
|
202
224
|
}
|
|
203
225
|
else {
|
|
204
|
-
|
|
205
|
-
|
|
226
|
+
const ok = enableApi(projectId, api);
|
|
227
|
+
if (ok) {
|
|
228
|
+
console.log(chalk.green(` ✓ ${label} enabled`));
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
console.log(chalk.red(` Failed to enable ${label}. Check that billing is set up for the project.`));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
206
234
|
}
|
|
207
235
|
}
|
|
236
|
+
// ── Configure OAuth Consent Screen ────────────────────────────────────────
|
|
237
|
+
console.log(chalk.bold("\nStep 5 — Configure OAuth Consent Screen\n"));
|
|
238
|
+
console.log(" Before creating credentials, Google requires you to configure the");
|
|
239
|
+
console.log(" OAuth consent screen (the login screen your users will see).\n");
|
|
240
|
+
console.log(` ${chalk.yellow("Note:")} Personal Google accounts can only create ${chalk.white("External")} apps.`);
|
|
241
|
+
console.log(` External apps start in Testing mode — only test users you add can sign in.\n`);
|
|
242
|
+
const consentUrl = `https://console.cloud.google.com/apis/credentials/consent?project=${projectId}`;
|
|
243
|
+
console.log(` URL: ${chalk.cyan(consentUrl)}\n`);
|
|
244
|
+
console.log(chalk.dim(" Instructions:"));
|
|
245
|
+
console.log(chalk.dim(` 1. Audience → select ${chalk.white("External")} (required for personal accounts)`));
|
|
246
|
+
console.log(chalk.dim(` 2. App name → ${chalk.white("Skills Manager")}`));
|
|
247
|
+
console.log(chalk.dim(` 3. User support email → ${chalk.white(account)}`));
|
|
248
|
+
console.log(chalk.dim(` 4. Contact email → ${chalk.white(account)}`));
|
|
249
|
+
console.log(chalk.dim(` 5. Click "Create"`));
|
|
250
|
+
console.log(chalk.dim(` 6. Scroll to "Test users" → "Add users" → enter ${chalk.white(account)} → Save\n`));
|
|
251
|
+
openUrl(consentUrl);
|
|
252
|
+
await ask("Press Enter once you have configured the consent screen and added yourself as a test user...");
|
|
208
253
|
// ── OAuth credentials (browser) ───────────────────────────────────────────
|
|
209
|
-
console.log(chalk.bold("\nStep
|
|
210
|
-
console.log("
|
|
211
|
-
console.log(" Opening the Google Cloud Console
|
|
254
|
+
console.log(chalk.bold("\nStep 6 — Create OAuth 2.0 Credentials\n"));
|
|
255
|
+
console.log(" Now create the OAuth client ID that Skills Manager will use to authenticate.");
|
|
256
|
+
console.log(" Opening the Google Cloud Console...\n");
|
|
212
257
|
const credentialsUrl = `https://console.cloud.google.com/apis/credentials/oauthclient?project=${projectId}`;
|
|
213
258
|
console.log(` URL: ${chalk.cyan(credentialsUrl)}\n`);
|
|
214
259
|
console.log(chalk.dim(" Instructions:"));
|
|
215
|
-
console.log(chalk.dim(
|
|
216
|
-
console.log(chalk.dim(
|
|
217
|
-
console.log(chalk.dim(
|
|
218
|
-
console.log(chalk.dim(
|
|
260
|
+
console.log(chalk.dim(` 1. Application type → ${chalk.white("Desktop app")}`));
|
|
261
|
+
console.log(chalk.dim(` 2. Name → ${chalk.white("Skills Manager")} (or anything)`));
|
|
262
|
+
console.log(chalk.dim(` 3. Click "Create" → then "Download JSON"`));
|
|
263
|
+
console.log(chalk.dim(` 4. Save the file (it will land in your Downloads folder)\n`));
|
|
219
264
|
openUrl(credentialsUrl);
|
|
220
265
|
await ask("Press Enter once you have downloaded the credentials JSON file...");
|
|
221
266
|
// ── Locate and copy the file ──────────────────────────────────────────────
|
|
@@ -248,23 +293,13 @@ export async function setupGoogleCommand() {
|
|
|
248
293
|
ensureConfigDir();
|
|
249
294
|
fs.copyFileSync(credSrc, CREDENTIALS_PATH);
|
|
250
295
|
console.log(chalk.green(`\n ✓ Credentials saved to ~/.skillsmanager/credentials.json`));
|
|
251
|
-
// ── Add test user ─────────────────────────────────────────────────────────
|
|
252
|
-
console.log(chalk.bold("\nStep 6 — Add Test User\n"));
|
|
253
|
-
console.log(" Your app is in Testing mode. You must add your Google account as a test user.");
|
|
254
|
-
console.log(" Opening the OAuth consent screen...\n");
|
|
255
|
-
console.log(chalk.dim(" Instructions:"));
|
|
256
|
-
console.log(chalk.dim(" 1. Scroll down to \"Test users\""));
|
|
257
|
-
console.log(chalk.dim(` 2. Click \"Add users\" → enter ${chalk.white(account)}`));
|
|
258
|
-
console.log(chalk.dim(" 3. Click \"Save\"\n"));
|
|
259
|
-
const consentUrl = `https://console.cloud.google.com/apis/credentials/consent?project=${projectId}`;
|
|
260
|
-
console.log(` URL: ${chalk.cyan(consentUrl)}\n`);
|
|
261
|
-
openUrl(consentUrl);
|
|
262
|
-
await ask("Press Enter once you have added your email as a test user...");
|
|
263
296
|
// ── OAuth flow ────────────────────────────────────────────────────────────
|
|
264
297
|
console.log(chalk.bold("\nStep 7 — Authorize Skills Manager\n"));
|
|
265
|
-
await runAuthFlow();
|
|
266
|
-
|
|
267
|
-
console.log(`\
|
|
298
|
+
const client = await runAuthFlow();
|
|
299
|
+
const authedEmail = await getAuthedEmail(client);
|
|
300
|
+
console.log(chalk.green(`\n ✓ Setup complete! Authenticated as ${chalk.white(authedEmail ?? account)}`));
|
|
301
|
+
await printPushNudgeIfNeeded();
|
|
302
|
+
console.log(`Run ${chalk.bold("skillsmanager refresh")} to discover your registries.\n`);
|
|
268
303
|
}
|
|
269
304
|
function printManualInstructions() {
|
|
270
305
|
console.log(chalk.bold("\nManual Setup Instructions\n"));
|
|
@@ -272,10 +307,15 @@ function printManualInstructions() {
|
|
|
272
307
|
console.log(" 2. Create a project (or select an existing one)");
|
|
273
308
|
console.log(" 3. Enable the Google Drive API:");
|
|
274
309
|
console.log(" APIs & Services → Library → search \"Google Drive API\" → Enable");
|
|
275
|
-
console.log(" 4.
|
|
310
|
+
console.log(" 4. Configure the OAuth consent screen:");
|
|
311
|
+
console.log(" APIs & Services → OAuth consent screen");
|
|
312
|
+
console.log(" Audience: External (required for personal Google accounts)");
|
|
313
|
+
console.log(" Fill in App name, support email, and contact email");
|
|
314
|
+
console.log(" Under \"Test users\", add your own Google account email");
|
|
315
|
+
console.log(" 5. Create OAuth credentials:");
|
|
276
316
|
console.log(" APIs & Services → Credentials → Create Credentials → OAuth 2.0 Client ID");
|
|
277
317
|
console.log(" Application type: Desktop app");
|
|
278
|
-
console.log("
|
|
318
|
+
console.log(" 6. Download the JSON and save it:");
|
|
279
319
|
console.log(chalk.cyan(` ~/.skillsmanager/credentials.json`));
|
|
280
320
|
console.log(`\n Then run: ${chalk.bold("skillsmanager setup google")}\n`);
|
|
281
321
|
}
|
|
@@ -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,65 @@
|
|
|
1
|
+
import os from "os";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { credentialsExist } from "../config.js";
|
|
4
|
+
import { hasToken, getAuthClient } from "../auth.js";
|
|
5
|
+
import { GDriveBackend } from "../backends/gdrive.js";
|
|
6
|
+
import { ghInstalled, ghAuthed, ghGetLogin } from "./setup/github.js";
|
|
7
|
+
async function getGdriveStatus() {
|
|
8
|
+
if (!credentialsExist()) {
|
|
9
|
+
return { name: "gdrive", loggedIn: false, identity: "", hint: "run: skillsmanager setup google" };
|
|
10
|
+
}
|
|
11
|
+
if (!hasToken()) {
|
|
12
|
+
return { name: "gdrive", loggedIn: false, identity: "", hint: "run: skillsmanager setup google" };
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const client = getAuthClient();
|
|
16
|
+
const backend = new GDriveBackend(client);
|
|
17
|
+
const email = await backend.getOwner();
|
|
18
|
+
return { name: "gdrive", loggedIn: true, identity: email };
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return { name: "gdrive", loggedIn: false, identity: "", hint: "run: skillsmanager setup google" };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function getGithubStatus() {
|
|
25
|
+
if (!ghInstalled()) {
|
|
26
|
+
return { name: "github", loggedIn: false, identity: "", hint: "install gh CLI first" };
|
|
27
|
+
}
|
|
28
|
+
if (!ghAuthed()) {
|
|
29
|
+
return { name: "github", loggedIn: false, identity: "", hint: "run: skillsmanager setup github" };
|
|
30
|
+
}
|
|
31
|
+
const login = ghGetLogin();
|
|
32
|
+
return { name: "github", loggedIn: true, identity: login };
|
|
33
|
+
}
|
|
34
|
+
export async function statusCommand() {
|
|
35
|
+
const localStatus = {
|
|
36
|
+
name: "local",
|
|
37
|
+
loggedIn: true,
|
|
38
|
+
identity: os.userInfo().username,
|
|
39
|
+
};
|
|
40
|
+
const [gdriveStatus, githubStatus] = await Promise.all([
|
|
41
|
+
getGdriveStatus(),
|
|
42
|
+
Promise.resolve(getGithubStatus()),
|
|
43
|
+
]);
|
|
44
|
+
const rows = [localStatus, gdriveStatus, githubStatus];
|
|
45
|
+
const col1 = 8;
|
|
46
|
+
const col2 = 24;
|
|
47
|
+
const header = chalk.bold("Backend".padEnd(col1)) + " " +
|
|
48
|
+
chalk.bold("Status".padEnd(col2)) + " " +
|
|
49
|
+
chalk.bold("Identity");
|
|
50
|
+
const divider = "─".repeat(col1) + " " + "─".repeat(col2) + " " + "─".repeat(30);
|
|
51
|
+
console.log();
|
|
52
|
+
console.log(header);
|
|
53
|
+
console.log(chalk.dim(divider));
|
|
54
|
+
for (const row of rows) {
|
|
55
|
+
const status = row.loggedIn
|
|
56
|
+
? chalk.green("✓ logged in")
|
|
57
|
+
: chalk.red("✗ not logged in");
|
|
58
|
+
const identity = row.loggedIn
|
|
59
|
+
? chalk.white(row.identity)
|
|
60
|
+
: chalk.dim(row.hint ?? "");
|
|
61
|
+
const statusLabel = row.loggedIn ? "✓ logged in" : "✗ not logged in";
|
|
62
|
+
console.log(row.name.padEnd(col1) + " " + status + " ".repeat(Math.max(0, col2 - statusLabel.length)) + " " + identity);
|
|
63
|
+
}
|
|
64
|
+
console.log();
|
|
65
|
+
}
|
package/dist/commands/update.js
CHANGED
|
@@ -3,7 +3,8 @@ import ora from "ora";
|
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import YAML from "yaml";
|
|
5
5
|
import path from "path";
|
|
6
|
-
import {
|
|
6
|
+
import { readConfig } from "../config.js";
|
|
7
|
+
import { resolveBackend } from "../backends/resolve.js";
|
|
7
8
|
import { getCachePath, ensureCachePath } from "../cache.js";
|
|
8
9
|
export async function updateCommand(skillPath, options) {
|
|
9
10
|
const absPath = path.resolve(skillPath);
|
|
@@ -28,7 +29,7 @@ export async function updateCommand(skillPath, options) {
|
|
|
28
29
|
console.log(chalk.red("SKILL.md frontmatter is missing 'name' field."));
|
|
29
30
|
return;
|
|
30
31
|
}
|
|
31
|
-
const
|
|
32
|
+
const config = readConfig();
|
|
32
33
|
// Find the collection — --collection override, or look up by installedAt path, or by name
|
|
33
34
|
let collection = config.collections.find((c) => c.name === options.collection) ?? null;
|
|
34
35
|
if (!collection) {
|
|
@@ -59,7 +60,8 @@ export async function updateCommand(skillPath, options) {
|
|
|
59
60
|
return;
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
|
-
const
|
|
63
|
+
const backend = await resolveBackend(collection.backend);
|
|
64
|
+
const spinner = ora(`Updating ${chalk.bold(skillName)} in ${collection.backend}:${collection.name}...`).start();
|
|
63
65
|
try {
|
|
64
66
|
await backend.uploadSkill(collection, absPath, skillName);
|
|
65
67
|
// Sync updated files into the local cache so symlinks reflect the change immediately
|
|
@@ -75,7 +77,7 @@ export async function updateCommand(skillPath, options) {
|
|
|
75
77
|
await backend.writeCollection(collection, col);
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
|
-
spinner.succeed(`${chalk.bold(skillName)} updated in
|
|
80
|
+
spinner.succeed(`${chalk.bold(skillName)} updated in ${collection.backend}:${collection.name}`);
|
|
79
81
|
}
|
|
80
82
|
catch (err) {
|
|
81
83
|
spinner.fail(`Failed: ${err.message}`);
|
package/dist/index.js
CHANGED
|
@@ -13,9 +13,13 @@ import { addCommand } from "./commands/add.js";
|
|
|
13
13
|
import { updateCommand } from "./commands/update.js";
|
|
14
14
|
import { refreshCommand } from "./commands/refresh.js";
|
|
15
15
|
import { setupGoogleCommand } from "./commands/setup/google.js";
|
|
16
|
+
import { setupGithubCommand } from "./commands/setup/github.js";
|
|
17
|
+
import { logoutGoogleCommand, logoutGithubCommand } from "./commands/logout.js";
|
|
16
18
|
import { collectionCreateCommand } from "./commands/collection.js";
|
|
19
|
+
import { skillDeleteCommand } from "./commands/skill.js";
|
|
17
20
|
import { installCommand, uninstallCommand } from "./commands/install.js";
|
|
18
|
-
import {
|
|
21
|
+
import { statusCommand } from "./commands/status.js";
|
|
22
|
+
import { registryCreateCommand, registryListCommand, registryDiscoverCommand, registryAddCollectionCommand, registryRemoveCollectionCommand, registryPushCommand, } from "./commands/registry.js";
|
|
19
23
|
const supportedAgents = Object.keys(AGENT_PATHS).join(", ");
|
|
20
24
|
// Read the bundled SKILL.md as the CLI help — single source of truth
|
|
21
25
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -39,7 +43,28 @@ setup
|
|
|
39
43
|
.command("google")
|
|
40
44
|
.description("One-time Google Drive setup (human-facing, not for agents)")
|
|
41
45
|
.action(setupGoogleCommand);
|
|
46
|
+
setup
|
|
47
|
+
.command("github")
|
|
48
|
+
.description("One-time GitHub setup — checks gh CLI and runs gh auth login")
|
|
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);
|
|
42
63
|
// ── Core commands ────────────────────────────────────────────────────────────
|
|
64
|
+
program
|
|
65
|
+
.command("status")
|
|
66
|
+
.description("Show login status and identity for each backend")
|
|
67
|
+
.action(statusCommand);
|
|
43
68
|
program
|
|
44
69
|
.command("init")
|
|
45
70
|
.description("Authenticate and discover collections (runs automatically when needed)")
|
|
@@ -59,10 +84,13 @@ program
|
|
|
59
84
|
.option("--scope <scope>", "global (~/.agent/skills/) or project (./.agent/skills/)", "global")
|
|
60
85
|
.action((names, options) => fetchCommand(names, options));
|
|
61
86
|
program
|
|
62
|
-
.command("add
|
|
63
|
-
.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")
|
|
64
89
|
.option("--collection <name>", "Target collection (default: first available)")
|
|
65
|
-
.
|
|
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));
|
|
66
94
|
program
|
|
67
95
|
.command("update <path>")
|
|
68
96
|
.description("Push local edits to a skill back to storage and refresh cache")
|
|
@@ -72,6 +100,15 @@ program
|
|
|
72
100
|
.command("refresh")
|
|
73
101
|
.description("Re-discover collections from storage")
|
|
74
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));
|
|
75
112
|
// ── Collection ───────────────────────────────────────────────────────────────
|
|
76
113
|
const collection = program
|
|
77
114
|
.command("collection")
|
|
@@ -79,15 +116,19 @@ const collection = program
|
|
|
79
116
|
collection
|
|
80
117
|
.command("create [name]")
|
|
81
118
|
.description("Create a new collection (defaults to SKILLS_MY_SKILLS)")
|
|
82
|
-
.
|
|
119
|
+
.option("--backend <backend>", "gdrive (default) or github", "gdrive")
|
|
120
|
+
.option("--repo <owner/repo>", "GitHub repo to use (required for --backend github)")
|
|
121
|
+
.option("--skills-repo <owner/repo>", "GitHub repo where skills live (github backend only; defaults to --repo)")
|
|
122
|
+
.action((name, options) => collectionCreateCommand(name, options));
|
|
83
123
|
// ── Registry ─────────────────────────────────────────────────────────────────
|
|
84
124
|
const registry = program
|
|
85
125
|
.command("registry")
|
|
86
126
|
.description("Manage registries (root indexes pointing to collections)");
|
|
87
127
|
registry
|
|
88
128
|
.command("create")
|
|
89
|
-
.description("Create a new registry (default: local, --backend gdrive
|
|
90
|
-
.option("--backend <backend>", "local (default) or
|
|
129
|
+
.description("Create a new registry (default: local, --backend gdrive or --backend github)")
|
|
130
|
+
.option("--backend <backend>", "local (default), gdrive, or github", "local")
|
|
131
|
+
.option("--repo <owner/repo>", "GitHub repo to use (required for --backend github)")
|
|
91
132
|
.action((options) => registryCreateCommand(options));
|
|
92
133
|
registry
|
|
93
134
|
.command("list")
|
|
@@ -96,7 +137,7 @@ registry
|
|
|
96
137
|
registry
|
|
97
138
|
.command("discover")
|
|
98
139
|
.description("Search a backend for registries owned by the current user")
|
|
99
|
-
.option("--backend <backend>", "local (default) or
|
|
140
|
+
.option("--backend <backend>", "local (default), gdrive, or github", "local")
|
|
100
141
|
.action((options) => registryDiscoverCommand(options));
|
|
101
142
|
registry
|
|
102
143
|
.command("add-collection <name>")
|
|
@@ -104,10 +145,17 @@ registry
|
|
|
104
145
|
.option("--backend <backend>", "Backend where the collection lives")
|
|
105
146
|
.option("--ref <ref>", "Backend-specific reference (folder name, repo path)")
|
|
106
147
|
.action((name, options) => registryAddCollectionCommand(name, options));
|
|
148
|
+
registry
|
|
149
|
+
.command("remove-collection <name>")
|
|
150
|
+
.description("Remove a collection reference from the registry")
|
|
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)")
|
|
153
|
+
.action((name, options) => registryRemoveCollectionCommand(name, options));
|
|
107
154
|
registry
|
|
108
155
|
.command("push")
|
|
109
156
|
.description("Push local registry and collections to a remote backend")
|
|
110
|
-
.option("--backend <backend>", "Target backend (default
|
|
157
|
+
.option("--backend <backend>", "Target backend: gdrive (default) or github", "gdrive")
|
|
158
|
+
.option("--repo <owner/repo>", "GitHub repo to push into (required when --backend github)")
|
|
111
159
|
.action((options) => registryPushCommand(options));
|
|
112
160
|
// ── Install/Uninstall ────────────────────────────────────────────────────────
|
|
113
161
|
program
|
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,9 +15,13 @@ export function parseCollection(content) {
|
|
|
15
15
|
description: s.description ?? "",
|
|
16
16
|
})),
|
|
17
17
|
};
|
|
18
|
+
if (data.metadata && typeof data.metadata === "object") {
|
|
19
|
+
col.metadata = data.metadata;
|
|
20
|
+
}
|
|
21
|
+
return col;
|
|
18
22
|
}
|
|
19
23
|
export function serializeCollection(collection) {
|
|
20
|
-
|
|
24
|
+
const obj = {
|
|
21
25
|
name: collection.name,
|
|
22
26
|
owner: collection.owner,
|
|
23
27
|
skills: collection.skills.map((s) => ({
|
|
@@ -25,7 +29,11 @@ export function serializeCollection(collection) {
|
|
|
25
29
|
path: s.path,
|
|
26
30
|
description: s.description,
|
|
27
31
|
})),
|
|
28
|
-
}
|
|
32
|
+
};
|
|
33
|
+
if (collection.metadata && Object.keys(collection.metadata).length > 0) {
|
|
34
|
+
obj.metadata = collection.metadata;
|
|
35
|
+
}
|
|
36
|
+
return YAML.stringify(obj);
|
|
29
37
|
}
|
|
30
38
|
// Backwards-compat aliases
|
|
31
39
|
export const parseRegistry = parseCollection;
|
package/dist/types.d.ts
CHANGED
package/dist/types.js
CHANGED
|
@@ -10,4 +10,6 @@ export const AGENT_PATHS = {
|
|
|
10
10
|
copilot: path.join(os.homedir(), ".copilot", "skills"),
|
|
11
11
|
gemini: path.join(os.homedir(), ".gemini", "skills"),
|
|
12
12
|
roo: path.join(os.homedir(), ".roo", "skills"),
|
|
13
|
+
openclaw: path.join(os.homedir(), ".openclaw", "skills"),
|
|
14
|
+
antigravity: path.join(os.homedir(), ".gemini", "antigravity", "skills"),
|
|
13
15
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skillsmanager/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
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",
|