@skillsmanager/cli 0.0.1 → 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.
- package/dist/auth.js +36 -13
- package/dist/backends/gdrive.d.ts +1 -1
- package/dist/backends/gdrive.js +9 -4
- package/dist/backends/github.d.ts +26 -0
- package/dist/backends/github.js +378 -0
- package/dist/backends/interface.d.ts +1 -0
- package/dist/backends/local.d.ts +1 -0
- package/dist/backends/local.js +5 -0
- package/dist/backends/resolve.d.ts +2 -0
- package/dist/backends/resolve.js +11 -0
- package/dist/commands/add.js +89 -7
- package/dist/commands/collection.d.ts +4 -1
- package/dist/commands/collection.js +59 -25
- package/dist/commands/fetch.js +7 -6
- package/dist/commands/list.js +5 -3
- package/dist/commands/refresh.js +78 -25
- package/dist/commands/registry.d.ts +5 -0
- package/dist/commands/registry.js +145 -89
- package/dist/commands/setup/github.d.ts +1 -0
- package/dist/commands/setup/github.js +85 -0
- package/dist/commands/update.js +6 -4
- package/dist/index.js +20 -6
- package/dist/types.js +2 -0
- package/package.json +2 -2
- package/skills/skillsmanager/SKILL.md +11 -1
|
@@ -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 {
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
27
|
-
|
|
27
|
+
else {
|
|
28
|
+
registry = await (await resolveBackend(backend)).createRegistry();
|
|
28
29
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
158
|
-
|
|
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
|
|
183
|
-
const
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
|
269
|
+
await remote.uploadSkill(remoteCol, localSkillPath, skill.name);
|
|
211
270
|
}
|
|
212
271
|
}
|
|
213
|
-
await
|
|
214
|
-
pushed.push({ ref,
|
|
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
|
-
//
|
|
281
|
+
// Phase 2: Commit — update registry and config atomically
|
|
223
282
|
spinner.text = "Updating registry...";
|
|
224
283
|
try {
|
|
225
|
-
|
|
226
|
-
let gdriveData;
|
|
284
|
+
let targetData;
|
|
227
285
|
try {
|
|
228
|
-
|
|
286
|
+
targetData = await remote.readRegistry(targetReg);
|
|
229
287
|
}
|
|
230
288
|
catch {
|
|
231
|
-
|
|
289
|
+
targetData = { name: targetReg.name, owner: await remote.getOwner(), source: targetBackend, collections: [] };
|
|
232
290
|
}
|
|
233
|
-
|
|
234
|
-
|
|
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
|
|
238
|
-
|
|
239
|
-
|
|
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 {
|
|
243
|
-
config.collections.push(
|
|
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
|
|
302
|
+
spinner.succeed(`Pushed ${pushed.length} collection(s) to ${targetBackend}`);
|
|
247
303
|
for (const { ref } of pushed) {
|
|
248
|
-
console.log(chalk.dim(` ${ref.name} →
|
|
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(
|
|
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
|
+
}
|
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,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
|
-
.
|
|
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
|
|
90
|
-
.option("--backend <backend>", "local (default) or
|
|
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
|
|
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
|
|
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,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skillsmanager/cli",
|
|
3
|
-
"version": "0.0.
|
|
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": {
|
|
7
|
-
"skillsmanager": "
|
|
7
|
+
"skillsmanager": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist/",
|
|
@@ -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
|