@skillsmanager/cli 0.0.2 → 0.0.4

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.
@@ -1,55 +1,45 @@
1
1
  import chalk from "chalk";
2
2
  import ora from "ora";
3
3
  import fs from "fs";
4
- import { readConfig, writeConfig, mergeRegistries, CONFIG_PATH } from "../config.js";
5
- import { ensureAuth } from "../auth.js";
6
- import { GDriveBackend } from "../backends/gdrive.js";
4
+ import { readConfig, writeConfig, mergeRegistries, CONFIG_PATH, CACHE_DIR } from "../config.js";
5
+ import { GithubBackend } from "../backends/github.js";
7
6
  import { LocalBackend } from "../backends/local.js";
7
+ import { resolveBackend } from "../backends/resolve.js";
8
8
  export async function registryCreateCommand(options) {
9
9
  const backend = options.backend ?? "local";
10
- if (backend === "local") {
11
- const local = new LocalBackend();
12
- const spinner = ora("Creating local registry...").start();
13
- try {
14
- const registry = await local.createRegistry();
15
- spinner.succeed(`Local registry created`);
16
- let config = { registries: [], collections: [], skills: {}, discoveredAt: new Date().toISOString() };
17
- if (fs.existsSync(CONFIG_PATH)) {
18
- try {
19
- config = readConfig();
20
- }
21
- catch { /* use default */ }
22
- }
23
- config.registries.push(registry);
24
- writeConfig(config);
10
+ const supported = ["local", "gdrive", "github"];
11
+ if (!supported.includes(backend)) {
12
+ console.log(chalk.red(`Unknown backend "${backend}". Supported: ${supported.join(", ")}`));
13
+ return;
14
+ }
15
+ if (backend === "github" && !options.repo) {
16
+ console.log(chalk.red("GitHub backend requires --repo <owner/repo>"));
17
+ console.log(chalk.dim(" Example: skillsmanager registry create --backend github --repo owner/my-repo"));
18
+ return;
19
+ }
20
+ const label = backend === "local" ? "locally" : `in ${backend}`;
21
+ const spinner = ora(`Creating registry ${label}...`).start();
22
+ try {
23
+ let registry;
24
+ if (backend === "github") {
25
+ registry = await new GithubBackend().createRegistry(undefined, options.repo);
25
26
  }
26
- catch (err) {
27
- spinner.fail(`Failed: ${err.message}`);
27
+ else {
28
+ registry = await (await resolveBackend(backend)).createRegistry();
28
29
  }
29
- }
30
- else if (backend === "gdrive") {
31
- const auth = await ensureAuth();
32
- const gdrive = new GDriveBackend(auth);
33
- const spinner = ora("Creating registry in Google Drive...").start();
34
- try {
35
- const registry = await gdrive.createRegistry();
36
- spinner.succeed(`Registry created in Google Drive`);
37
- let config = { registries: [], collections: [], skills: {}, discoveredAt: new Date().toISOString() };
38
- if (fs.existsSync(CONFIG_PATH)) {
39
- try {
40
- config = readConfig();
41
- }
42
- catch { /* use default */ }
30
+ spinner.succeed(`Registry created ${label}`);
31
+ let config = { registries: [], collections: [], skills: {}, discoveredAt: new Date().toISOString() };
32
+ if (fs.existsSync(CONFIG_PATH)) {
33
+ try {
34
+ config = readConfig();
43
35
  }
44
- config.registries.push(registry);
45
- writeConfig(config);
46
- }
47
- catch (err) {
48
- spinner.fail(`Failed: ${err.message}`);
36
+ catch { /* use default */ }
49
37
  }
38
+ config.registries.push(registry);
39
+ writeConfig(config);
50
40
  }
51
- else {
52
- console.log(chalk.red(`Unknown backend "${backend}". Supported: local, gdrive`));
41
+ catch (err) {
42
+ spinner.fail(`Failed: ${err.message}`);
53
43
  }
54
44
  }
55
45
  export async function registryListCommand() {
@@ -69,9 +59,7 @@ export async function registryListCommand() {
69
59
  for (const reg of config.registries) {
70
60
  console.log(`\n${chalk.bold(reg.name)} ${chalk.dim(`(${reg.backend})`)}`);
71
61
  try {
72
- const backend = reg.backend === "gdrive"
73
- ? new GDriveBackend(await ensureAuth())
74
- : new LocalBackend();
62
+ const backend = await resolveBackend(reg.backend);
75
63
  const data = await backend.readRegistry(reg);
76
64
  if (data.collections.length === 0) {
77
65
  console.log(chalk.dim(" No collections"));
@@ -92,9 +80,7 @@ export async function registryDiscoverCommand(options) {
92
80
  const backendName = options.backend ?? "local";
93
81
  const spinner = ora(`Discovering registries in ${backendName}...`).start();
94
82
  try {
95
- const backend = backendName === "gdrive"
96
- ? new GDriveBackend(await ensureAuth())
97
- : new LocalBackend();
83
+ const backend = await resolveBackend(backendName);
98
84
  const fresh = await backend.discoverRegistries();
99
85
  let config = { registries: [], collections: [], skills: {}, discoveredAt: new Date().toISOString() };
100
86
  if (fs.existsSync(CONFIG_PATH)) {
@@ -135,9 +121,7 @@ export async function registryAddCollectionCommand(collectionName, options) {
135
121
  }
136
122
  // Use first registry
137
123
  const reg = config.registries[0];
138
- const backend = reg.backend === "gdrive"
139
- ? new GDriveBackend(await ensureAuth())
140
- : new LocalBackend();
124
+ const backend = await resolveBackend(reg.backend);
141
125
  const data = await backend.readRegistry(reg);
142
126
  const existing = data.collections.find((c) => c.name === collectionName);
143
127
  if (existing) {
@@ -152,10 +136,83 @@ export async function registryAddCollectionCommand(collectionName, options) {
152
136
  await backend.writeRegistry(reg, data);
153
137
  console.log(chalk.green(`Added "${collectionName}" to registry "${reg.name}".`));
154
138
  }
139
+ export async function registryRemoveCollectionCommand(collectionName, options) {
140
+ let config;
141
+ try {
142
+ config = readConfig();
143
+ }
144
+ catch {
145
+ console.log(chalk.red("No config found."));
146
+ return;
147
+ }
148
+ if (config.registries.length === 0) {
149
+ console.log(chalk.red("No registries configured."));
150
+ return;
151
+ }
152
+ const reg = config.registries[0];
153
+ const backend = await resolveBackend(reg.backend);
154
+ const data = await backend.readRegistry(reg);
155
+ const ref = data.collections.find((c) => c.name === collectionName);
156
+ if (!ref) {
157
+ console.log(chalk.yellow(`Collection "${collectionName}" not found in registry.`));
158
+ return;
159
+ }
160
+ // If --delete, delete the actual collection and skills from the backend
161
+ if (options.delete) {
162
+ const collectionInConfig = config.collections.find((c) => c.name === collectionName);
163
+ if (collectionInConfig) {
164
+ const collBackend = await resolveBackend(collectionInConfig.backend);
165
+ const spinner = ora(`Deleting collection "${collectionName}" from ${collectionInConfig.backend}...`).start();
166
+ try {
167
+ await collBackend.deleteCollection(collectionInConfig);
168
+ spinner.succeed(`Deleted collection "${collectionName}" from ${collectionInConfig.backend}`);
169
+ }
170
+ catch (err) {
171
+ spinner.fail(`Failed to delete: ${err.message}`);
172
+ return;
173
+ }
174
+ }
175
+ // Clean up local cache
176
+ if (collectionInConfig) {
177
+ const cachePath = path.join(CACHE_DIR, collectionInConfig.id);
178
+ if (fs.existsSync(cachePath)) {
179
+ fs.rmSync(cachePath, { recursive: true, force: true });
180
+ }
181
+ }
182
+ }
183
+ // Remove ref from registry
184
+ data.collections = data.collections.filter((c) => c.name !== collectionName);
185
+ await backend.writeRegistry(reg, data);
186
+ // Remove from local config — capture ID before removal for skills cleanup
187
+ const removedColId = config.collections.find((c) => c.name === collectionName)?.id;
188
+ config.collections = config.collections.filter((c) => c.name !== collectionName);
189
+ // Remove skills index entries for this collection
190
+ if (removedColId) {
191
+ for (const [skillName, locations] of Object.entries(config.skills)) {
192
+ config.skills[skillName] = locations.filter((l) => l.collectionId !== removedColId);
193
+ if (config.skills[skillName].length === 0)
194
+ delete config.skills[skillName];
195
+ }
196
+ }
197
+ writeConfig(config);
198
+ if (options.delete) {
199
+ console.log(chalk.green(`Removed and deleted "${collectionName}" from registry "${reg.name}".`));
200
+ }
201
+ else {
202
+ console.log(chalk.green(`Removed "${collectionName}" from registry "${reg.name}".`));
203
+ console.log(chalk.dim(` Collection data was kept. Use --delete to permanently remove it.`));
204
+ }
205
+ }
155
206
  export async function registryPushCommand(options) {
156
207
  const targetBackend = options.backend ?? "gdrive";
157
- if (targetBackend !== "gdrive") {
158
- console.log(chalk.red(`Push to "${targetBackend}" not yet supported. Use: --backend gdrive`));
208
+ const supportedPush = ["gdrive", "github"];
209
+ if (!supportedPush.includes(targetBackend)) {
210
+ console.log(chalk.red(`Push to "${targetBackend}" not yet supported. Use: --backend gdrive or --backend github`));
211
+ return;
212
+ }
213
+ if (targetBackend === "github" && !options.repo) {
214
+ console.log(chalk.red("GitHub backend requires --repo <owner/repo>"));
215
+ console.log(chalk.dim(" Example: skillsmanager registry push --backend github --repo owner/my-repo"));
159
216
  return;
160
217
  }
161
218
  let config;
@@ -166,7 +223,6 @@ export async function registryPushCommand(options) {
166
223
  console.log(chalk.red("No config found."));
167
224
  return;
168
225
  }
169
- // Find local registry
170
226
  const localReg = config.registries.find((r) => r.backend === "local");
171
227
  if (!localReg) {
172
228
  console.log(chalk.yellow("No local registry to push."));
@@ -179,39 +235,42 @@ export async function registryPushCommand(options) {
179
235
  console.log(chalk.yellow("No local collections to push."));
180
236
  return;
181
237
  }
182
- const auth = await ensureAuth();
183
- const gdrive = new GDriveBackend(auth);
184
- // ── Phase 1: Upload all collections (no state changes yet) ─────────────
185
- // If any collection fails, we abort and nothing is committed.
186
- const spinner = ora("Pushing collections to Google Drive...").start();
187
- // Discover or create gdrive registry (this is safe — an empty registry is harmless)
188
- let gdriveReg = config.registries.find((r) => r.backend === "gdrive");
189
- if (!gdriveReg) {
190
- spinner.text = "Creating registry in Google Drive...";
191
- gdriveReg = await gdrive.createRegistry();
192
- }
193
- // Accumulate results — only commit if ALL succeed
238
+ const remote = await resolveBackend(targetBackend);
239
+ const spinner = ora(`Pushing collections to ${targetBackend}...`).start();
240
+ // Find or create target registry
241
+ let targetReg = config.registries.find((r) => r.backend === targetBackend);
242
+ if (!targetReg) {
243
+ spinner.text = `Creating registry in ${targetBackend}...`;
244
+ targetReg = await remote.createRegistry();
245
+ }
246
+ // Phase 1: Upload all collections — abort on any failure
194
247
  const pushed = [];
195
248
  try {
196
249
  for (const ref of localCollectionRefs) {
197
250
  spinner.text = `Uploading collection "${ref.name}"...`;
198
251
  const collInfo = await local.resolveCollectionRef(ref);
199
- if (!collInfo) {
252
+ if (!collInfo)
200
253
  throw new Error(`Collection "${ref.name}" not found locally`);
254
+ let remoteCol;
255
+ if (targetBackend === "gdrive") {
256
+ const gdrive = remote;
257
+ const folderName = `SKILLS_${ref.name.toUpperCase()}`;
258
+ remoteCol = await gdrive.createCollection(folderName);
259
+ }
260
+ else {
261
+ const github = remote;
262
+ remoteCol = await github.createCollection(ref.name, options.repo);
201
263
  }
202
- const PREFIX = "SKILLS_";
203
- const folderName = `${PREFIX}${ref.name.toUpperCase()}`;
204
- const driveCol = await gdrive.createCollection(folderName);
205
264
  const colData = await local.readCollection({ ...collInfo, id: "temp" });
206
265
  for (const skill of colData.skills) {
207
266
  const localSkillPath = path.join(collInfo.folderId, skill.name);
208
267
  if (fs.existsSync(localSkillPath)) {
209
268
  spinner.text = `Uploading ${ref.name}/${skill.name}...`;
210
- await gdrive.uploadSkill(driveCol, localSkillPath, skill.name);
269
+ await remote.uploadSkill(remoteCol, localSkillPath, skill.name);
211
270
  }
212
271
  }
213
- await gdrive.writeCollection(driveCol, colData);
214
- pushed.push({ ref, driveCol, folderName });
272
+ await remote.writeCollection(remoteCol, colData);
273
+ pushed.push({ ref, remoteCol });
215
274
  }
216
275
  }
217
276
  catch (err) {
@@ -219,39 +278,36 @@ export async function registryPushCommand(options) {
219
278
  console.log(chalk.dim(" No changes were committed. Local state is unchanged."));
220
279
  return;
221
280
  }
222
- // ── Phase 2: Commit — update registry and config atomically ────────────
281
+ // Phase 2: Commit — update registry and config atomically
223
282
  spinner.text = "Updating registry...";
224
283
  try {
225
- // Read current gdrive registry (may already have entries)
226
- let gdriveData;
284
+ let targetData;
227
285
  try {
228
- gdriveData = await gdrive.readRegistry(gdriveReg);
286
+ targetData = await remote.readRegistry(targetReg);
229
287
  }
230
288
  catch {
231
- gdriveData = { name: gdriveReg.name, owner: await gdrive.getOwner(), source: "gdrive", collections: [] };
289
+ targetData = { name: targetReg.name, owner: await remote.getOwner(), source: targetBackend, collections: [] };
232
290
  }
233
- // Add all pushed collections at once
234
- for (const { ref, folderName } of pushed) {
235
- gdriveData.collections.push({ name: ref.name, backend: "gdrive", ref: folderName });
291
+ for (const { ref, remoteCol } of pushed) {
292
+ targetData.collections.push({ name: ref.name, backend: targetBackend, ref: remoteCol.folderId });
236
293
  }
237
- await gdrive.writeRegistry(gdriveReg, gdriveData);
238
- // Update local config
239
- if (!config.registries.find((r) => r.id === gdriveReg.id)) {
240
- config.registries.push(gdriveReg);
294
+ await remote.writeRegistry(targetReg, targetData);
295
+ if (!config.registries.find((r) => r.id === targetReg.id)) {
296
+ config.registries.push(targetReg);
241
297
  }
242
- for (const { driveCol } of pushed) {
243
- config.collections.push(driveCol);
298
+ for (const { remoteCol } of pushed) {
299
+ config.collections.push(remoteCol);
244
300
  }
245
301
  writeConfig(config);
246
- spinner.succeed(`Pushed ${pushed.length} collection(s) to Google Drive`);
302
+ spinner.succeed(`Pushed ${pushed.length} collection(s) to ${targetBackend}`);
247
303
  for (const { ref } of pushed) {
248
- console.log(chalk.dim(` ${ref.name} → gdrive`));
304
+ console.log(chalk.dim(` ${ref.name} → ${targetBackend}`));
249
305
  }
250
306
  }
251
307
  catch (err) {
252
308
  spinner.fail(`Failed to update registry: ${err.message}`);
253
309
  console.log(chalk.dim(" Collections were uploaded but the registry was not updated."));
254
- console.log(chalk.dim(" Run 'skillsmanager registry push' again to retry."));
310
+ console.log(chalk.dim(` Run 'skillsmanager registry push --backend ${targetBackend}' again to retry.`));
255
311
  }
256
312
  }
257
313
  // Need path import for push command
@@ -0,0 +1 @@
1
+ export declare function setupGithubCommand(): Promise<void>;
@@ -0,0 +1,85 @@
1
+ import { spawnSync } from "child_process";
2
+ import chalk from "chalk";
3
+ // ── helpers ───────────────────────────────────────────────────────────────────
4
+ function ghInstalled() {
5
+ return spawnSync("gh", ["--version"], { stdio: "pipe" }).status === 0;
6
+ }
7
+ function ghAuthed() {
8
+ return spawnSync("gh", ["auth", "status"], { stdio: "pipe" }).status === 0;
9
+ }
10
+ async function installGh() {
11
+ if (process.platform !== "darwin") {
12
+ console.log(chalk.yellow(" Auto-install is only supported on macOS."));
13
+ console.log(chalk.dim(" Install manually: https://cli.github.com/manual/installation"));
14
+ return false;
15
+ }
16
+ const brewCheck = spawnSync("brew", ["--version"], { stdio: "pipe" });
17
+ if (brewCheck.status !== 0) {
18
+ console.log(chalk.yellow(" Homebrew not found. Install it from https://brew.sh then re-run."));
19
+ return false;
20
+ }
21
+ console.log(chalk.dim(" Installing gh via Homebrew..."));
22
+ const r = spawnSync("brew", ["install", "gh"], { stdio: "inherit" });
23
+ if (r.status !== 0) {
24
+ console.log(chalk.red(" Install failed. Try manually: brew install gh"));
25
+ return false;
26
+ }
27
+ return ghInstalled();
28
+ }
29
+ // ── main command ──────────────────────────────────────────────────────────────
30
+ export async function setupGithubCommand() {
31
+ console.log(chalk.bold("\nSkills Manager — GitHub Setup\n"));
32
+ // Step 1: Check gh CLI
33
+ console.log(chalk.bold("Step 1 — gh CLI\n"));
34
+ if (!ghInstalled()) {
35
+ console.log(chalk.yellow(" gh CLI is not installed."));
36
+ const readline = await import("readline");
37
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
38
+ const ans = await new Promise((resolve) => {
39
+ rl.question(` Install it now via Homebrew? ${chalk.dim("[y/n]")} `, (a) => {
40
+ rl.close();
41
+ resolve(a.trim().toLowerCase());
42
+ });
43
+ });
44
+ if (!ans.startsWith("y")) {
45
+ console.log(chalk.dim(" Install manually: https://cli.github.com/manual/installation"));
46
+ console.log(chalk.dim(" Then re-run: skillsmanager setup github"));
47
+ return;
48
+ }
49
+ const ok = await installGh();
50
+ if (!ok)
51
+ return;
52
+ console.log(chalk.green(" ✓ gh installed"));
53
+ }
54
+ else {
55
+ console.log(chalk.green(" ✓ gh is installed"));
56
+ }
57
+ // Step 2: Check auth
58
+ console.log(chalk.bold("\nStep 2 — GitHub Authentication\n"));
59
+ if (ghAuthed()) {
60
+ const r = spawnSync("gh", ["api", "user", "--jq", ".login"], {
61
+ encoding: "utf-8", stdio: "pipe",
62
+ });
63
+ const login = r.stdout?.trim() ?? "";
64
+ console.log(chalk.green(` ✓ Already authenticated${login ? ` as ${chalk.white(login)}` : ""}`));
65
+ }
66
+ else {
67
+ console.log(chalk.dim(" Running gh auth login..."));
68
+ const r = spawnSync("gh", ["auth", "login"], { stdio: "inherit" });
69
+ if (r.status !== 0) {
70
+ console.log(chalk.red(" Authentication failed. Please try manually: gh auth login"));
71
+ return;
72
+ }
73
+ const r2 = spawnSync("gh", ["api", "user", "--jq", ".login"], {
74
+ encoding: "utf-8", stdio: "pipe",
75
+ });
76
+ const login = r2.stdout?.trim() ?? "";
77
+ if (!login) {
78
+ console.log(chalk.red(" Could not verify authentication."));
79
+ return;
80
+ }
81
+ console.log(chalk.green(` ✓ Authenticated as ${chalk.white(login)}`));
82
+ }
83
+ console.log(chalk.green("\n ✓ GitHub setup complete!"));
84
+ console.log(`\nRun ${chalk.bold("skillsmanager collection create --backend github")} to create a collection.\n`);
85
+ }
@@ -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 { ensureReady } from "../ready.js";
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 { config, backend } = await ensureReady();
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 spinner = ora(`Updating ${chalk.bold(skillName)} in gdrive:${collection.name}...`).start();
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 gdrive:${collection.name}`);
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,10 @@ 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";
16
17
  import { collectionCreateCommand } from "./commands/collection.js";
17
18
  import { installCommand, uninstallCommand } from "./commands/install.js";
18
- import { registryCreateCommand, registryListCommand, registryDiscoverCommand, registryAddCollectionCommand, registryPushCommand, } from "./commands/registry.js";
19
+ import { registryCreateCommand, registryListCommand, registryDiscoverCommand, registryAddCollectionCommand, registryRemoveCollectionCommand, registryPushCommand, } from "./commands/registry.js";
19
20
  const supportedAgents = Object.keys(AGENT_PATHS).join(", ");
20
21
  // Read the bundled SKILL.md as the CLI help — single source of truth
21
22
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -39,6 +40,10 @@ setup
39
40
  .command("google")
40
41
  .description("One-time Google Drive setup (human-facing, not for agents)")
41
42
  .action(setupGoogleCommand);
43
+ setup
44
+ .command("github")
45
+ .description("One-time GitHub setup — checks gh CLI and runs gh auth login")
46
+ .action(setupGithubCommand);
42
47
  // ── Core commands ────────────────────────────────────────────────────────────
43
48
  program
44
49
  .command("init")
@@ -79,15 +84,18 @@ const collection = program
79
84
  collection
80
85
  .command("create [name]")
81
86
  .description("Create a new collection (defaults to SKILLS_MY_SKILLS)")
82
- .action(collectionCreateCommand);
87
+ .option("--backend <backend>", "gdrive (default) or github", "gdrive")
88
+ .option("--repo <owner/repo>", "GitHub repo to use (required for --backend github)")
89
+ .action((name, options) => collectionCreateCommand(name, options));
83
90
  // ── Registry ─────────────────────────────────────────────────────────────────
84
91
  const registry = program
85
92
  .command("registry")
86
93
  .description("Manage registries (root indexes pointing to collections)");
87
94
  registry
88
95
  .command("create")
89
- .description("Create a new registry (default: local, --backend gdrive for Drive)")
90
- .option("--backend <backend>", "local (default) or gdrive", "local")
96
+ .description("Create a new registry (default: local, --backend gdrive or --backend github)")
97
+ .option("--backend <backend>", "local (default), gdrive, or github", "local")
98
+ .option("--repo <owner/repo>", "GitHub repo to use (required for --backend github)")
91
99
  .action((options) => registryCreateCommand(options));
92
100
  registry
93
101
  .command("list")
@@ -96,7 +104,7 @@ registry
96
104
  registry
97
105
  .command("discover")
98
106
  .description("Search a backend for registries owned by the current user")
99
- .option("--backend <backend>", "local (default) or gdrive", "local")
107
+ .option("--backend <backend>", "local (default), gdrive, or github", "local")
100
108
  .action((options) => registryDiscoverCommand(options));
101
109
  registry
102
110
  .command("add-collection <name>")
@@ -104,10 +112,16 @@ registry
104
112
  .option("--backend <backend>", "Backend where the collection lives")
105
113
  .option("--ref <ref>", "Backend-specific reference (folder name, repo path)")
106
114
  .action((name, options) => registryAddCollectionCommand(name, options));
115
+ registry
116
+ .command("remove-collection <name>")
117
+ .description("Remove a collection reference from the registry")
118
+ .option("--delete", "Also delete the collection and all its skills from the backend")
119
+ .action((name, options) => registryRemoveCollectionCommand(name, options));
107
120
  registry
108
121
  .command("push")
109
122
  .description("Push local registry and collections to a remote backend")
110
- .option("--backend <backend>", "Target backend (default: gdrive)", "gdrive")
123
+ .option("--backend <backend>", "Target backend: gdrive (default) or github", "gdrive")
124
+ .option("--repo <owner/repo>", "GitHub repo to push into (required when --backend github)")
111
125
  .action((options) => registryPushCommand(options));
112
126
  // ── Install/Uninstall ────────────────────────────────────────────────────────
113
127
  program
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.2",
3
+ "version": "0.0.4",
4
4
  "description": "Discover, fetch, and manage agent skills from local or remote storage",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,7 +31,7 @@ skillsmanager fetch <name> --agent <agent> --scope project
31
31
  skillsmanager list
32
32
  ```
33
33
 
34
- Supported agents: `claude`, `codex`, `agents`, `cursor`, `windsurf`, `copilot`, `gemini`, `roo`
34
+ Supported agents: `claude`, `codex`, `agents`, `cursor`, `windsurf`, `copilot`, `gemini`, `roo`, `openclaw`, `antigravity`
35
35
 
36
36
  ### Share a skill
37
37
 
@@ -78,6 +78,12 @@ skillsmanager registry add-collection <name>
78
78
  # Push local registry and collections to Google Drive
79
79
  skillsmanager registry push --backend gdrive
80
80
 
81
+ # Remove a collection reference from the registry (keeps data)
82
+ skillsmanager registry remove-collection <name>
83
+
84
+ # Remove and permanently delete the collection and all its skills
85
+ skillsmanager registry remove-collection <name> --delete
86
+
81
87
  # Create a new collection
82
88
  skillsmanager collection create [name]
83
89
 
@@ -125,6 +131,10 @@ skillsmanager uninstall
125
131
  **User wants to see what registries and collections exist:**
126
132
  1. `skillsmanager registry list`
127
133
 
134
+ **User wants to remove a collection:**
135
+ 1. `skillsmanager registry remove-collection <name>` (removes reference only, data is kept)
136
+ 2. `skillsmanager registry remove-collection <name> --delete` (permanently deletes collection and skills)
137
+
128
138
  ## Architecture
129
139
 
130
140
  - **Registry** (`SKILLS_REGISTRY.yaml`): root index pointing to all collections across backends