@skillsmanager/cli 0.0.1

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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -0
  3. package/dist/auth.d.ts +5 -0
  4. package/dist/auth.js +68 -0
  5. package/dist/backends/gdrive.d.ts +24 -0
  6. package/dist/backends/gdrive.js +371 -0
  7. package/dist/backends/interface.d.ts +14 -0
  8. package/dist/backends/interface.js +1 -0
  9. package/dist/backends/local.d.ts +20 -0
  10. package/dist/backends/local.js +159 -0
  11. package/dist/bm25.d.ts +20 -0
  12. package/dist/bm25.js +65 -0
  13. package/dist/cache.d.ts +21 -0
  14. package/dist/cache.js +59 -0
  15. package/dist/commands/add.d.ts +3 -0
  16. package/dist/commands/add.js +62 -0
  17. package/dist/commands/collection.d.ts +1 -0
  18. package/dist/commands/collection.js +42 -0
  19. package/dist/commands/fetch.d.ts +5 -0
  20. package/dist/commands/fetch.js +46 -0
  21. package/dist/commands/init.d.ts +1 -0
  22. package/dist/commands/init.js +45 -0
  23. package/dist/commands/install.d.ts +8 -0
  24. package/dist/commands/install.js +89 -0
  25. package/dist/commands/list.d.ts +3 -0
  26. package/dist/commands/list.js +38 -0
  27. package/dist/commands/refresh.d.ts +1 -0
  28. package/dist/commands/refresh.js +46 -0
  29. package/dist/commands/registry.d.ts +14 -0
  30. package/dist/commands/registry.js +258 -0
  31. package/dist/commands/search.d.ts +1 -0
  32. package/dist/commands/search.js +37 -0
  33. package/dist/commands/setup/google.d.ts +1 -0
  34. package/dist/commands/setup/google.js +281 -0
  35. package/dist/commands/update.d.ts +3 -0
  36. package/dist/commands/update.js +83 -0
  37. package/dist/config.d.ts +28 -0
  38. package/dist/config.js +136 -0
  39. package/dist/index.d.ts +2 -0
  40. package/dist/index.js +128 -0
  41. package/dist/ready.d.ts +15 -0
  42. package/dist/ready.js +39 -0
  43. package/dist/registry.d.ts +10 -0
  44. package/dist/registry.js +58 -0
  45. package/dist/types.d.ts +54 -0
  46. package/dist/types.js +13 -0
  47. package/package.json +56 -0
  48. package/skills/skillsmanager/SKILL.md +139 -0
@@ -0,0 +1,38 @@
1
+ import chalk from "chalk";
2
+ import ora from "ora";
3
+ import { ensureReady } from "../ready.js";
4
+ export async function getAllSkills() {
5
+ const { config, backend } = await ensureReady();
6
+ const allSkills = [];
7
+ for (const collection of config.collections) {
8
+ const col = await backend.readCollection(collection);
9
+ for (const entry of col.skills) {
10
+ allSkills.push({ entry, collection });
11
+ }
12
+ }
13
+ return allSkills;
14
+ }
15
+ export async function listCommand() {
16
+ const spinner = ora("Fetching skills...").start();
17
+ try {
18
+ const skills = await getAllSkills();
19
+ spinner.stop();
20
+ if (skills.length === 0) {
21
+ console.log(chalk.yellow("No skills found across any collections."));
22
+ console.log(chalk.dim('Run "skillsmanager collection create <name>" to create a collection, then "skillsmanager add <path>" to add skills.'));
23
+ return;
24
+ }
25
+ const maxName = Math.max(...skills.map((s) => s.entry.name.length), 4);
26
+ const maxDesc = Math.max(...skills.map((s) => s.entry.description.length), 11);
27
+ console.log(`\n ${chalk.dim("NAME".padEnd(maxName + 2))}${chalk.dim("DESCRIPTION".padEnd(maxDesc + 2))}${chalk.dim("SOURCE")}`);
28
+ console.log(` ${chalk.dim("-".repeat(maxName + maxDesc + 30))}`);
29
+ for (const s of skills.sort((a, b) => a.entry.name.localeCompare(b.entry.name))) {
30
+ console.log(` ${chalk.cyan(s.entry.name.padEnd(maxName + 2))}${s.entry.description.padEnd(maxDesc + 2)}${chalk.dim(`gdrive:${s.collection.name}`)}`);
31
+ }
32
+ console.log();
33
+ }
34
+ catch (err) {
35
+ spinner.stop();
36
+ throw err;
37
+ }
38
+ }
@@ -0,0 +1 @@
1
+ export declare function refreshCommand(): Promise<void>;
@@ -0,0 +1,46 @@
1
+ import chalk from "chalk";
2
+ import ora from "ora";
3
+ import { writeConfig, mergeCollections, readConfig } from "../config.js";
4
+ import { ensureAuth } from "../auth.js";
5
+ import { GDriveBackend } from "../backends/gdrive.js";
6
+ export async function refreshCommand() {
7
+ const spinner = ora("Discovering collections...").start();
8
+ try {
9
+ const auth = await ensureAuth();
10
+ const backend = new GDriveBackend(auth);
11
+ const fresh = await backend.discoverCollections();
12
+ let existing = [];
13
+ try {
14
+ existing = readConfig().collections;
15
+ }
16
+ catch { /* no existing config */ }
17
+ const collections = mergeCollections(fresh, existing);
18
+ let existingSkills = {};
19
+ try {
20
+ existingSkills = readConfig().skills ?? {};
21
+ }
22
+ catch { /* ok */ }
23
+ let existingRegistries = [];
24
+ try {
25
+ existingRegistries = readConfig().registries ?? [];
26
+ }
27
+ catch { /* ok */ }
28
+ writeConfig({ registries: existingRegistries, collections, skills: existingSkills, discoveredAt: new Date().toISOString() });
29
+ spinner.stop();
30
+ if (collections.length === 0) {
31
+ console.log(chalk.yellow("No collections found."));
32
+ console.log(chalk.dim(" Run: skillsmanager collection create <name>"));
33
+ }
34
+ else {
35
+ console.log(chalk.green(`Found ${collections.length} collection(s):`));
36
+ for (const c of collections) {
37
+ console.log(` gdrive:${c.name}`);
38
+ }
39
+ }
40
+ console.log();
41
+ }
42
+ catch (err) {
43
+ spinner.stop();
44
+ throw err;
45
+ }
46
+ }
@@ -0,0 +1,14 @@
1
+ export declare function registryCreateCommand(options: {
2
+ backend?: string;
3
+ }): Promise<void>;
4
+ export declare function registryListCommand(): Promise<void>;
5
+ export declare function registryDiscoverCommand(options: {
6
+ backend?: string;
7
+ }): Promise<void>;
8
+ export declare function registryAddCollectionCommand(collectionName: string, options: {
9
+ backend?: string;
10
+ ref?: string;
11
+ }): Promise<void>;
12
+ export declare function registryPushCommand(options: {
13
+ backend?: string;
14
+ }): Promise<void>;
@@ -0,0 +1,258 @@
1
+ import chalk from "chalk";
2
+ import ora from "ora";
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";
7
+ import { LocalBackend } from "../backends/local.js";
8
+ export async function registryCreateCommand(options) {
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);
25
+ }
26
+ catch (err) {
27
+ spinner.fail(`Failed: ${err.message}`);
28
+ }
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 */ }
43
+ }
44
+ config.registries.push(registry);
45
+ writeConfig(config);
46
+ }
47
+ catch (err) {
48
+ spinner.fail(`Failed: ${err.message}`);
49
+ }
50
+ }
51
+ else {
52
+ console.log(chalk.red(`Unknown backend "${backend}". Supported: local, gdrive`));
53
+ }
54
+ }
55
+ export async function registryListCommand() {
56
+ let config;
57
+ try {
58
+ config = readConfig();
59
+ }
60
+ catch {
61
+ console.log(chalk.yellow("No config found. Run: skillsmanager registry create"));
62
+ return;
63
+ }
64
+ if (config.registries.length === 0) {
65
+ console.log(chalk.yellow("No registries configured."));
66
+ console.log(chalk.dim(" Run: skillsmanager registry create"));
67
+ return;
68
+ }
69
+ for (const reg of config.registries) {
70
+ console.log(`\n${chalk.bold(reg.name)} ${chalk.dim(`(${reg.backend})`)}`);
71
+ try {
72
+ const backend = reg.backend === "gdrive"
73
+ ? new GDriveBackend(await ensureAuth())
74
+ : new LocalBackend();
75
+ const data = await backend.readRegistry(reg);
76
+ if (data.collections.length === 0) {
77
+ console.log(chalk.dim(" No collections"));
78
+ }
79
+ else {
80
+ for (const ref of data.collections) {
81
+ console.log(` ${chalk.cyan(ref.name)} ${chalk.dim(`${ref.backend}:${ref.ref}`)}`);
82
+ }
83
+ }
84
+ }
85
+ catch (err) {
86
+ console.log(chalk.red(` Error reading: ${err.message}`));
87
+ }
88
+ }
89
+ console.log();
90
+ }
91
+ export async function registryDiscoverCommand(options) {
92
+ const backendName = options.backend ?? "local";
93
+ const spinner = ora(`Discovering registries in ${backendName}...`).start();
94
+ try {
95
+ const backend = backendName === "gdrive"
96
+ ? new GDriveBackend(await ensureAuth())
97
+ : new LocalBackend();
98
+ const fresh = await backend.discoverRegistries();
99
+ let config = { registries: [], collections: [], skills: {}, discoveredAt: new Date().toISOString() };
100
+ if (fs.existsSync(CONFIG_PATH)) {
101
+ try {
102
+ config = readConfig();
103
+ }
104
+ catch { /* use default */ }
105
+ }
106
+ config.registries = mergeRegistries(fresh, config.registries);
107
+ writeConfig(config);
108
+ spinner.stop();
109
+ if (fresh.length === 0) {
110
+ console.log(chalk.yellow(`No registries found in ${backendName}.`));
111
+ }
112
+ else {
113
+ console.log(chalk.green(`Found ${fresh.length} registry(ies) in ${backendName}:`));
114
+ for (const r of fresh) {
115
+ console.log(` ${chalk.cyan(r.name)}`);
116
+ }
117
+ }
118
+ }
119
+ catch (err) {
120
+ spinner.fail(`Failed: ${err.message}`);
121
+ }
122
+ }
123
+ export async function registryAddCollectionCommand(collectionName, options) {
124
+ let config;
125
+ try {
126
+ config = readConfig();
127
+ }
128
+ catch {
129
+ console.log(chalk.red("No config found. Run: skillsmanager registry create"));
130
+ return;
131
+ }
132
+ if (config.registries.length === 0) {
133
+ console.log(chalk.red("No registries configured. Run: skillsmanager registry create"));
134
+ return;
135
+ }
136
+ // Use first registry
137
+ const reg = config.registries[0];
138
+ const backend = reg.backend === "gdrive"
139
+ ? new GDriveBackend(await ensureAuth())
140
+ : new LocalBackend();
141
+ const data = await backend.readRegistry(reg);
142
+ const existing = data.collections.find((c) => c.name === collectionName);
143
+ if (existing) {
144
+ console.log(chalk.yellow(`Collection "${collectionName}" already in registry.`));
145
+ return;
146
+ }
147
+ data.collections.push({
148
+ name: collectionName,
149
+ backend: options.backend ?? reg.backend,
150
+ ref: options.ref ?? collectionName,
151
+ });
152
+ await backend.writeRegistry(reg, data);
153
+ console.log(chalk.green(`Added "${collectionName}" to registry "${reg.name}".`));
154
+ }
155
+ export async function registryPushCommand(options) {
156
+ const targetBackend = options.backend ?? "gdrive";
157
+ if (targetBackend !== "gdrive") {
158
+ console.log(chalk.red(`Push to "${targetBackend}" not yet supported. Use: --backend gdrive`));
159
+ return;
160
+ }
161
+ let config;
162
+ try {
163
+ config = readConfig();
164
+ }
165
+ catch {
166
+ console.log(chalk.red("No config found."));
167
+ return;
168
+ }
169
+ // Find local registry
170
+ const localReg = config.registries.find((r) => r.backend === "local");
171
+ if (!localReg) {
172
+ console.log(chalk.yellow("No local registry to push."));
173
+ return;
174
+ }
175
+ const local = new LocalBackend();
176
+ const localData = await local.readRegistry(localReg);
177
+ const localCollectionRefs = localData.collections.filter((c) => c.backend === "local");
178
+ if (localCollectionRefs.length === 0) {
179
+ console.log(chalk.yellow("No local collections to push."));
180
+ return;
181
+ }
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
194
+ const pushed = [];
195
+ try {
196
+ for (const ref of localCollectionRefs) {
197
+ spinner.text = `Uploading collection "${ref.name}"...`;
198
+ const collInfo = await local.resolveCollectionRef(ref);
199
+ if (!collInfo) {
200
+ throw new Error(`Collection "${ref.name}" not found locally`);
201
+ }
202
+ const PREFIX = "SKILLS_";
203
+ const folderName = `${PREFIX}${ref.name.toUpperCase()}`;
204
+ const driveCol = await gdrive.createCollection(folderName);
205
+ const colData = await local.readCollection({ ...collInfo, id: "temp" });
206
+ for (const skill of colData.skills) {
207
+ const localSkillPath = path.join(collInfo.folderId, skill.name);
208
+ if (fs.existsSync(localSkillPath)) {
209
+ spinner.text = `Uploading ${ref.name}/${skill.name}...`;
210
+ await gdrive.uploadSkill(driveCol, localSkillPath, skill.name);
211
+ }
212
+ }
213
+ await gdrive.writeCollection(driveCol, colData);
214
+ pushed.push({ ref, driveCol, folderName });
215
+ }
216
+ }
217
+ catch (err) {
218
+ spinner.fail(`Push failed: ${err.message}`);
219
+ console.log(chalk.dim(" No changes were committed. Local state is unchanged."));
220
+ return;
221
+ }
222
+ // ── Phase 2: Commit — update registry and config atomically ────────────
223
+ spinner.text = "Updating registry...";
224
+ try {
225
+ // Read current gdrive registry (may already have entries)
226
+ let gdriveData;
227
+ try {
228
+ gdriveData = await gdrive.readRegistry(gdriveReg);
229
+ }
230
+ catch {
231
+ gdriveData = { name: gdriveReg.name, owner: await gdrive.getOwner(), source: "gdrive", collections: [] };
232
+ }
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 });
236
+ }
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);
241
+ }
242
+ for (const { driveCol } of pushed) {
243
+ config.collections.push(driveCol);
244
+ }
245
+ writeConfig(config);
246
+ spinner.succeed(`Pushed ${pushed.length} collection(s) to Google Drive`);
247
+ for (const { ref } of pushed) {
248
+ console.log(chalk.dim(` ${ref.name} → gdrive`));
249
+ }
250
+ }
251
+ catch (err) {
252
+ spinner.fail(`Failed to update registry: ${err.message}`);
253
+ 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."));
255
+ }
256
+ }
257
+ // Need path import for push command
258
+ import path from "path";
@@ -0,0 +1 @@
1
+ export declare function searchCommand(query: string): Promise<void>;
@@ -0,0 +1,37 @@
1
+ import chalk from "chalk";
2
+ import ora from "ora";
3
+ import { getAllSkills } from "./list.js";
4
+ import { bm25Search } from "../bm25.js";
5
+ export async function searchCommand(query) {
6
+ const spinner = ora("Searching skills...").start();
7
+ try {
8
+ const skills = await getAllSkills();
9
+ spinner.stop();
10
+ // Build BM25 documents — name is weighted 3x by repeating it
11
+ // so a name match outranks a description-only match
12
+ const docs = skills.map((s) => ({
13
+ id: s.entry.name,
14
+ text: `${s.entry.name} ${s.entry.name} ${s.entry.name} ${s.entry.description}`,
15
+ }));
16
+ const results = bm25Search(docs, query);
17
+ if (results.length === 0) {
18
+ console.log(chalk.yellow(`No skills matching "${query}".`));
19
+ return;
20
+ }
21
+ const ranked = results
22
+ .map((r) => skills.find((s) => s.entry.name === r.id))
23
+ .filter(Boolean);
24
+ const maxName = Math.max(...ranked.map((s) => s.entry.name.length), 4);
25
+ const maxDesc = Math.max(...ranked.map((s) => s.entry.description.length), 11);
26
+ console.log(`\n ${chalk.dim("NAME".padEnd(maxName + 2))}${chalk.dim("DESCRIPTION".padEnd(maxDesc + 2))}${chalk.dim("SOURCE")}`);
27
+ console.log(` ${chalk.dim("-".repeat(maxName + maxDesc + 30))}`);
28
+ for (const s of ranked) {
29
+ console.log(` ${chalk.cyan(s.entry.name.padEnd(maxName + 2))}${s.entry.description.padEnd(maxDesc + 2)}${chalk.dim(`gdrive:${s.collection.name}`)}`);
30
+ }
31
+ console.log();
32
+ }
33
+ catch (err) {
34
+ spinner.stop();
35
+ throw err;
36
+ }
37
+ }
@@ -0,0 +1 @@
1
+ export declare function setupGoogleCommand(): Promise<void>;