opencode-skills-collection 1.0.195 → 2.0.0-beta.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.
package/README.md CHANGED
@@ -14,42 +14,87 @@
14
14
 
15
15
  # OpenCode Skills Collection
16
16
 
17
- > An [OpenCode CLI](https://opencode.ai/) plugin that bundles and auto-syncs the [Antigravity Awesome Skills](https://github.com/sickn33/antigravity-awesome-skills) collection delivered instantly, with zero network latency at startup.
17
+ > An [OpenCode CLI](https://opencode.ai/) plugin that bundles and auto-syncs a universal collection of AI skills
18
+ > delivered instantly, with zero network latency at startup.
18
19
 
19
- > ⚠️ **Previously published as [`opencode-skills-antigravity`](https://www.npmjs.com/package/opencode-skills-antigravity)** — that package is now deprecated and points to this one.
20
+ > ⚠️ **Previously published
21
+ as [`opencode-skills-antigravity`](https://www.npmjs.com/package/opencode-skills-antigravity)** — that package is now
22
+ > deprecated and points to this one.
20
23
 
21
24
  ---
22
25
 
23
26
  ## Overview
24
27
 
25
- **OpenCode Skills Collection** bridges the OpenCode CLI with the Antigravity Awesome Skills repository. Instead of fetching skills on every startup, this plugin ships with a pre-bundled snapshot that gets copied directly to your local machine the moment OpenCode launches.
28
+ **OpenCode Skills Collection** ships a pre-bundled snapshot of 800+ universal skills for the OpenCode CLI.
26
29
 
27
- The result: skills are always fresh (synced hourly via GitHub Actions), always available (even offline), and always instant.
30
+ Instead of loading every skill into the AI context at startup which would consume ~80k tokens and cause compaction
31
+ loops — the plugin uses a **SkillPointer** architecture: skills are organized into categories inside a hidden vault and
32
+ only loaded into context on demand.
28
33
 
29
34
  ---
30
35
 
31
36
  ## How It Works
32
37
 
33
- The plugin operates in two phases:
38
+ The plugin operates in three phases:
34
39
 
35
- **1. Automated upstream sync (CI)**
40
+ **1. Local deployment (startup)**
36
41
 
37
- A GitHub Actions workflow runs every hour, checking the [Antigravity Awesome Skills](https://github.com/sickn33/antigravity-awesome-skills) repository for changes. When new or updated skills are detected, the workflow:
42
+ When OpenCode starts, the plugin copies the pre-bundled skills from the npm package and runs the **SkillPointer pipeline
43
+ **:
38
44
 
39
- - Re-bundles the skill files into `bundled-skills/`
40
- - Bumps the package version (`patch`)
41
- - Creates a tagged GitHub Release
42
- - Publishes the new version to npm
45
+ ```
46
+ bundled-skills/ (npm package)
47
+
48
+
49
+ ~/.config/opencode/skills/ ← OpenCode reads this
50
+
51
+ └── SkillPointer pipeline
52
+
53
+ ├─ vault-manager → moves 800+ raw skills to the vault
54
+ └─ pointer-generator → writes ~35 lightweight pointer files
55
+ ```
56
+
57
+ **2. On-demand skill loading**
58
+
59
+ Each pointer file tells the AI: *"there are N skills for this category in the vault — use `list_dir` / `view_file` to
60
+ retrieve them when needed."*
61
+ The full skill content is only injected into context when the AI actually needs it.
43
62
 
44
- **2. Local deployment (startup)**
63
+ ---
64
+
65
+ ## Disk Layout
45
66
 
46
- When OpenCode starts, the plugin runs and copies the pre-bundled skills from the npm package to:
67
+ After the first startup, your `~/.config/opencode/` directory looks like this:
47
68
 
48
69
  ```
49
- ~/.config/opencode/skills/
70
+ ~/.config/opencode/
71
+ ├── opencode.json
72
+ ├── skills/ ← ~35 pointer folders (active, read by OpenCode)
73
+ │ ├── backend-dev-category-pointer/
74
+ │ │ └── SKILL.md
75
+ │ ├── devops-category-pointer/
76
+ │ │ └── SKILL.md
77
+ │ └── ...
78
+ └── skill-libraries/ ← vault with all raw skills (hidden from startup context)
79
+ ├── backend-dev/
80
+ │ ├── laravel-expert/
81
+ │ │ └── SKILL.md
82
+ │ └── wordpress-core/
83
+ │ └── SKILL.md
84
+ ├── devops/
85
+ └── ...
50
86
  ```
51
87
 
52
- No network calls, no latency, no failures. If the copy somehow fails, a silent fallback attempts to fetch via `npx antigravity-awesome-skills` in the background.
88
+ ---
89
+
90
+ ## Context Usage
91
+
92
+ | | Without SkillPointer | With SkillPointer |
93
+ |----------------------|----------------------|---------------------|
94
+ | Folders in `skills/` | ~800 | ~35 |
95
+ | Tokens at startup | ~80,000 | ~255 |
96
+ | Skills available | All injected upfront | On-demand via vault |
97
+ | Compaction loops | ✗ frequent | ✓ none |
53
98
 
54
99
  ---
55
100
 
@@ -65,21 +110,24 @@ Add the plugin to your global OpenCode configuration file at `~/.config/opencode
65
110
  }
66
111
  ```
67
112
 
68
- That's it. OpenCode will automatically download the npm package on next startup via Bun — no manual `npm install` needed.
113
+ That's it. OpenCode will automatically download the npm package on next startup via Bun — no manual `npm install`
114
+ needed.
69
115
 
70
116
  ---
71
117
 
72
118
  ## Usage
73
119
 
74
- Once installed, all bundled skills are available in three ways:
120
+ Once installed, all skills are available in three ways:
75
121
 
76
122
  **Explicit invocation via CLI:**
123
+
77
124
  ```bash
78
125
  opencode run /brainstorming help me plan a new feature
79
126
  opencode run /refactor clean up this function
80
127
  ```
81
128
 
82
129
  **Slash commands in the OpenCode chat:**
130
+
83
131
  ```
84
132
  /brainstorming
85
133
  /refactor
@@ -87,6 +135,7 @@ opencode run /refactor clean up this function
87
135
  ```
88
136
 
89
137
  **Natural language — OpenCode picks the right skill automatically:**
138
+
90
139
  ```
91
140
  "Help me brainstorm ideas for a REST API design"
92
141
  "Refactor this function to be more readable"
@@ -94,26 +143,6 @@ opencode run /refactor clean up this function
94
143
 
95
144
  ---
96
145
 
97
- ## Project Structure
98
-
99
- ```
100
- opencode-skills-collection/
101
- ├── src/
102
- │ └── index.ts # Plugin entry point — copies bundled skills on startup
103
- ├── bundled-skills/ # Pre-bundled skills snapshot (auto-updated by CI)
104
- ├── dist/ # Compiled TypeScript output
105
- ├── .github/
106
- │ └── workflows/
107
- │ ├── sync-skills.yml # Hourly skill sync + auto-publish
108
- │ ├── release.yml # Manual version bump + GitHub Release
109
- │ ├── publish.yml # npm publish on new release
110
- │ └── merge-branch.yml # Keeps develop in sync with main
111
- ├── package.json
112
- └── tsconfig.json
113
- ```
114
-
115
- ---
116
-
117
146
  ## Development
118
147
 
119
148
  **Requirements:** Node.js ≥ 20, TypeScript ≥ 5
@@ -128,7 +157,8 @@ npm run build
128
157
  # Output is in dist/
129
158
  ```
130
159
 
131
- The plugin is written in TypeScript and compiled to ESNext with full type declarations. It targets ES2022 and uses ESM module resolution.
160
+ The plugin is written in TypeScript and compiled to ESNext with full type declarations. It targets ES2022 and uses ESM
161
+ module resolution.
132
162
 
133
163
  ---
134
164
 
@@ -150,12 +180,30 @@ The old `opencode-skills-antigravity` package on npm is deprecated and re-export
150
180
 
151
181
  ## Contributing
152
182
 
153
- Issues and pull requests are welcome at [github.com/FrancoStino/opencode-skills-collection](https://github.com/FrancoStino/opencode-skills-collection/issues).
183
+ Issues and pull requests are welcome
184
+ at [github.com/FrancoStino/opencode-skills-collection](https://github.com/FrancoStino/opencode-skills-collection/issues).
154
185
 
155
- If you'd like to contribute new skills to the upstream collection, head over to [antigravity-awesome-skills](https://github.com/sickn33/antigravity-awesome-skills) they'll be automatically picked up and bundled here within the hour.
186
+ If you'd like to contribute new skills to the collection, open a PR adding a new folder inside `bundled-skills/` it
187
+ will be automatically picked up on next sync.
156
188
 
157
189
  ---
158
190
 
191
+ ## Beta Releases
192
+
193
+ Beta versions are published from the `develop` branch for testing before official releases.
194
+
195
+ ### Installing Beta Versions
196
+
197
+ To use the latest beta version, update your `~/.config/opencode/opencode.json`:
198
+
199
+ ```json
200
+ {
201
+ "plugin": [
202
+ "opencode-skills-collection@beta"
203
+ ]
204
+ }
205
+ ```
206
+
159
207
  ## License
160
208
 
161
- MIT © [Davide Ladisa](https://www.davideladisa.it/)
209
+ [MIT ©](./LICENSE)
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "updatedAt": "2026-04-08T07:23:02.044Z",
3
+ "updatedAt": "2026-04-08T09:03:21.581Z",
4
4
  "entries": [
5
5
  "00-andruia-consultant",
6
6
  "007",
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Shared constants used across the plugin.
3
+ */
4
+ /** Suffix appended to every category pointer folder name. */
5
+ export declare const POINTER_SUFFIX: "-category-pointer";
6
+ /** Expected filename for every skill definition. */
7
+ export declare const SKILL_FILENAME: "SKILL.md";
8
+ /**
9
+ * Name of the vault directory that stores raw skills, co-located
10
+ * with the rest of OpenCode config under ~/.config/opencode/
11
+ */
12
+ export declare const VAULT_DIR_NAME: "skill-libraries";
13
+ /** Fallback category slug for skills not present in skills_index.json. */
14
+ export declare const UNCATEGORIZED_CATEGORY: "uncategorized";
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Shared constants used across the plugin.
3
+ */
4
+ /** Suffix appended to every category pointer folder name. */
5
+ export const POINTER_SUFFIX = "-category-pointer";
6
+ /** Expected filename for every skill definition. */
7
+ export const SKILL_FILENAME = "SKILL.md";
8
+ /**
9
+ * Name of the vault directory that stores raw skills, co-located
10
+ * with the rest of OpenCode config under ~/.config/opencode/
11
+ */
12
+ export const VAULT_DIR_NAME = "skill-libraries";
13
+ /** Fallback category slug for skills not present in skills_index.json. */
14
+ export const UNCATEGORIZED_CATEGORY = "uncategorized";
package/dist/index.d.ts CHANGED
@@ -1,3 +1,13 @@
1
1
  import type { Plugin } from "@opencode-ai/plugin";
2
+ /**
3
+ * OpenCode Skills Collection plugin.
4
+ *
5
+ * On every OpenCode startup:
6
+ * 1. Copies bundled skills directly into the vault
7
+ * (~/.config/opencode/skill-libraries/) organised by category.
8
+ * The active skills directory is never used as staging.
9
+ * 2. Generates lightweight category pointer SKILL.md files in
10
+ * ~/.config/opencode/skills/ without touching user custom skills.
11
+ */
2
12
  declare const OpenCodeSkillsCollection: Plugin;
3
13
  export default OpenCodeSkillsCollection;
package/dist/index.js CHANGED
@@ -1,71 +1,35 @@
1
- import fs from "fs";
2
- import path from "path";
3
1
  import os from "os";
2
+ import path from "path";
4
3
  import { fileURLToPath } from "url";
5
- const LOG_FILE = path.join(os.homedir(), ".config", "opencode", "antigravity-debug.log");
6
- function log(msg) {
7
- const line = `[${new Date().toISOString()}] ${msg}\n`;
8
- try {
9
- fs.appendFileSync(LOG_FILE, line);
10
- }
11
- catch (_) { }
12
- process.stderr.write(line);
13
- }
14
- function copyDirContents(srcDir, destDir) {
15
- fs.mkdirSync(destDir, { recursive: true });
16
- for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
17
- const srcEntry = path.join(srcDir, entry.name);
18
- const destEntry = path.join(destDir, entry.name);
19
- if (entry.isDirectory()) {
20
- copyDirContents(srcEntry, destEntry);
21
- }
22
- else {
23
- fs.copyFileSync(srcEntry, destEntry);
24
- }
25
- }
26
- }
4
+ import { ensureDir } from "./utils/fs.utils.js";
5
+ import { runSkillPointer } from "./skill-pointer/index.js";
6
+ const ACTIVE_SKILLS_PATH_SEGMENTS = [".config", "opencode", "skills"];
27
7
  function resolveBundledSkillsPath() {
28
- const fromFile = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "bundled-skills");
29
- if (fs.existsSync(fromFile))
30
- return fromFile;
31
- let dir = path.dirname(fileURLToPath(import.meta.url));
32
- for (let i = 0; i < 5; i++) {
33
- const candidate = path.join(dir, "bundled-skills");
34
- if (fs.existsSync(candidate))
35
- return candidate;
36
- const parent = path.dirname(dir);
37
- if (parent === dir)
38
- break;
39
- dir = parent;
40
- }
41
- throw new Error(`bundled-skills not found. import.meta.url=${import.meta.url}`);
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ return path.join(__dirname, "..", "bundled-skills");
10
+ }
11
+ function resolveActiveSkillsDir() {
12
+ return path.join(os.homedir(), ...ACTIVE_SKILLS_PATH_SEGMENTS);
42
13
  }
14
+ /**
15
+ * OpenCode Skills Collection plugin.
16
+ *
17
+ * On every OpenCode startup:
18
+ * 1. Copies bundled skills directly into the vault
19
+ * (~/.config/opencode/skill-libraries/) organised by category.
20
+ * The active skills directory is never used as staging.
21
+ * 2. Generates lightweight category pointer SKILL.md files in
22
+ * ~/.config/opencode/skills/ without touching user custom skills.
23
+ */
43
24
  const OpenCodeSkillsCollection = async (_ctx) => {
44
- log("plugin start");
45
25
  try {
46
26
  const bundledSkillsPath = resolveBundledSkillsPath();
47
- const skillsPath = path.join(os.homedir(), ".config", "opencode", "skills");
48
- log(`bundled-skills path: ${bundledSkillsPath}`);
49
- log(`skills dest: ${skillsPath}`);
50
- fs.mkdirSync(skillsPath, { recursive: true });
51
- const entries = fs.readdirSync(bundledSkillsPath, { withFileTypes: true });
52
- log(`entries in bundled-skills: ${entries.length}`);
53
- let installed = 0;
54
- for (const entry of entries) {
55
- if (!entry.isDirectory())
56
- continue;
57
- if (entry.name.startsWith("."))
58
- continue;
59
- const src = path.join(bundledSkillsPath, entry.name);
60
- if (!fs.existsSync(path.join(src, "SKILL.md")))
61
- continue;
62
- copyDirContents(src, path.join(skillsPath, entry.name));
63
- installed++;
64
- }
65
- log(`done — installed ${installed} skills`);
27
+ const activeSkillsDir = resolveActiveSkillsDir();
28
+ ensureDir(activeSkillsDir);
29
+ runSkillPointer({ bundledSkillsPath, activeSkillsDir });
66
30
  }
67
- catch (error) {
68
- log(`ERROR: ${String(error)}`);
31
+ catch {
32
+ // Plugin errors must never crash OpenCode.
69
33
  }
70
34
  return {};
71
35
  };
@@ -0,0 +1,23 @@
1
+ export interface SkillPointerOptions {
2
+ /** Absolute path where OpenCode looks for active skills. */
3
+ activeSkillsDir: string;
4
+ /** Absolute path to the bundled-skills snapshot inside the npm package. */
5
+ bundledSkillsPath: string;
6
+ /**
7
+ * Absolute path of the hidden vault where raw skills are stored.
8
+ * Defaults to ~/.config/opencode/skill-libraries
9
+ */
10
+ vaultDir?: string;
11
+ }
12
+ /**
13
+ * Orchestrates the full SkillPointer pipeline:
14
+ *
15
+ * 1. Reads skills_index.json bundled alongside the skills snapshot.
16
+ * 2. Copies bundled skills directly into the vault, categorised by the index.
17
+ * 3. Generates pointer SKILL.md files in activeSkillsDir with full skill
18
+ * listings so keyword searches (e.g. "laravel") resolve out of the box.
19
+ *
20
+ * The activeSkillsDir is never used as a staging area — user custom
21
+ * skills already present there are never moved or overwritten.
22
+ */
23
+ export declare function runSkillPointer(options: SkillPointerOptions): void;
@@ -0,0 +1,28 @@
1
+ import os from "os";
2
+ import path from "path";
3
+ import { VAULT_DIR_NAME } from "../constants/constants.js";
4
+ import { ensureDir } from "../utils/fs.utils.js";
5
+ import { generatePointers } from "./pointer-generator.js";
6
+ import { installSkillsToVault, loadSkillsIndex } from "./vault-installer.js";
7
+ function resolveDefaultVaultDir() {
8
+ return path.join(os.homedir(), ".config", "opencode", VAULT_DIR_NAME);
9
+ }
10
+ /**
11
+ * Orchestrates the full SkillPointer pipeline:
12
+ *
13
+ * 1. Reads skills_index.json bundled alongside the skills snapshot.
14
+ * 2. Copies bundled skills directly into the vault, categorised by the index.
15
+ * 3. Generates pointer SKILL.md files in activeSkillsDir with full skill
16
+ * listings so keyword searches (e.g. "laravel") resolve out of the box.
17
+ *
18
+ * The activeSkillsDir is never used as a staging area — user custom
19
+ * skills already present there are never moved or overwritten.
20
+ */
21
+ export function runSkillPointer(options) {
22
+ const vaultDir = options.vaultDir ?? resolveDefaultVaultDir();
23
+ ensureDir(options.activeSkillsDir);
24
+ ensureDir(vaultDir);
25
+ const index = loadSkillsIndex(options.bundledSkillsPath);
26
+ installSkillsToVault(options.bundledSkillsPath, vaultDir, index);
27
+ generatePointers(options.activeSkillsDir, vaultDir, index);
28
+ }
@@ -0,0 +1,10 @@
1
+ import type { SkillIndexEntry } from "./vault-installer.js";
2
+ /**
3
+ * Scans every category directory in the vault and writes
4
+ * a lightweight pointer SKILL.md into the active skills directory.
5
+ *
6
+ * Each pointer includes the full list of skill names + descriptions
7
+ * so keyword searches (e.g. "laravel", "wordpress") resolve correctly
8
+ * via get_available_skills without loading every SKILL.md.
9
+ */
10
+ export declare function generatePointers(activeSkillsDir: string, vaultDir: string, index?: SkillIndexEntry[]): void;
@@ -0,0 +1,69 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { POINTER_SUFFIX, SKILL_FILENAME, UNCATEGORIZED_CATEGORY } from "../constants/constants.js";
4
+ import { ensureDir, listSubdirectories } from "../utils/fs.utils.js";
5
+ function buildPointerContent(category, skills, libraryPath) {
6
+ const title = category
7
+ .replace(/-/g, " ")
8
+ .replace(/\b\w/g, (char) => char.toUpperCase());
9
+ const normalizedPath = libraryPath.replace(/\\/g, "/");
10
+ const skillCount = skills.length;
11
+ const skillList = skills
12
+ .map((s) => `- **${s.id}** — ${s.description || s.name}`)
13
+ .join("\n");
14
+ return `---
15
+ name: ${category}${POINTER_SUFFIX}
16
+ description: "Pointer to a library of ${skillCount} specialized ${title} skills. Use when working on ${category}-related tasks."
17
+ risk: safe
18
+ ---
19
+
20
+ # ${title} Capability Library 🎯
21
+
22
+ This is a **pointer skill**. The ${skillCount} specialized ${title} skills are stored in a hidden vault to keep your startup context minimal.
23
+
24
+ ## Available skills in this category
25
+
26
+ ${skillList}
27
+
28
+ ## How to load a skill
29
+
30
+ 1. Identify the skill name above matching your task.
31
+ 2. Use \`view_file\` to read its \`SKILL.md\` from the vault:
32
+ \`${normalizedPath}/<skill-name>/SKILL.md\`
33
+ 3. Follow those instructions to complete the request.
34
+
35
+ **Vault path:** \`${normalizedPath}\`
36
+
37
+ > Do not guess best practices — always read from the vault first.
38
+ `;
39
+ }
40
+ /**
41
+ * Scans every category directory in the vault and writes
42
+ * a lightweight pointer SKILL.md into the active skills directory.
43
+ *
44
+ * Each pointer includes the full list of skill names + descriptions
45
+ * so keyword searches (e.g. "laravel", "wordpress") resolve correctly
46
+ * via get_available_skills without loading every SKILL.md.
47
+ */
48
+ export function generatePointers(activeSkillsDir, vaultDir, index = []) {
49
+ const categoryDirs = listSubdirectories(vaultDir);
50
+ const byCategory = new Map();
51
+ for (const entry of index) {
52
+ const cat = entry.category ?? UNCATEGORIZED_CATEGORY;
53
+ if (!byCategory.has(cat))
54
+ byCategory.set(cat, []);
55
+ byCategory.get(cat).push(entry);
56
+ }
57
+ for (const categoryName of categoryDirs) {
58
+ const categoryVaultPath = path.join(vaultDir, categoryName);
59
+ const skills = byCategory.get(categoryName) ?? [];
60
+ if (skills.length === 0) {
61
+ const subDirs = fs.readdirSync(categoryVaultPath).filter((e) => fs.statSync(path.join(categoryVaultPath, e)).isDirectory());
62
+ if (subDirs.length === 0)
63
+ continue;
64
+ }
65
+ const pointerDir = path.join(activeSkillsDir, `${categoryName}${POINTER_SUFFIX}`);
66
+ ensureDir(pointerDir);
67
+ fs.writeFileSync(path.join(pointerDir, SKILL_FILENAME), buildPointerContent(categoryName, skills, categoryVaultPath), "utf-8");
68
+ }
69
+ }
@@ -0,0 +1,17 @@
1
+ export interface SkillIndexEntry {
2
+ id: string;
3
+ category: string;
4
+ name: string;
5
+ description: string;
6
+ }
7
+ /**
8
+ * Loads the pre-built skills_index.json from the project root.
9
+ * Falls back to a dynamically generated index from SKILL.md frontmatter
10
+ * when the file is missing, so the plugin always works correctly.
11
+ */
12
+ export declare function loadSkillsIndex(bundledSkillsPath: string): SkillIndexEntry[];
13
+ /**
14
+ * Copies every skill folder from bundledSkillsPath directly into
15
+ * the vault under the appropriate category sub-directory.
16
+ */
17
+ export declare function installSkillsToVault(bundledSkillsPath: string, vaultDir: string, index: SkillIndexEntry[]): void;
@@ -0,0 +1,86 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { ensureDir } from "../utils/fs.utils.js";
4
+ import { UNCATEGORIZED_CATEGORY } from "../constants/constants.js";
5
+ /**
6
+ * Extracts a frontmatter field value from a SKILL.md string.
7
+ * Handles both quoted and unquoted values.
8
+ */
9
+ function parseFrontmatterField(content, field) {
10
+ const match = content.match(new RegExp(`^${field}:\\s*["']?([^"'\\n]+)["']?`, "m"));
11
+ return match ? match[1].trim() : "";
12
+ }
13
+ /**
14
+ * Derives a category slug from a skill folder name by taking the
15
+ * first hyphen-separated segment (e.g. "laravel-expert" → "laravel",
16
+ * "wordpress-core" → "wordpress", "php-pro" → "php").
17
+ * Falls back to UNCATEGORIZED_CATEGORY for single-word names.
18
+ */
19
+ function categoryFromFolderName(folderName) {
20
+ const parts = folderName.split("-");
21
+ return parts.length > 1 ? parts[0] : UNCATEGORIZED_CATEGORY;
22
+ }
23
+ /**
24
+ * Builds a SkillIndexEntry[] by scanning every skill folder in
25
+ * bundledSkillsPath and reading its SKILL.md frontmatter.
26
+ * This is used as fallback when skills_index.json is absent.
27
+ */
28
+ function buildIndexFromBundledSkills(bundledSkillsPath) {
29
+ const index = [];
30
+ for (const entry of fs.readdirSync(bundledSkillsPath)) {
31
+ if (entry.startsWith(".") || entry === "skills_index.json" || entry === "README.md")
32
+ continue;
33
+ const skillDir = path.join(bundledSkillsPath, entry);
34
+ if (!fs.statSync(skillDir).isDirectory())
35
+ continue;
36
+ const skillMdPath = path.join(skillDir, "SKILL.md");
37
+ if (!fs.existsSync(skillMdPath))
38
+ continue;
39
+ const content = fs.readFileSync(skillMdPath, "utf-8");
40
+ const name = parseFrontmatterField(content, "name") || entry;
41
+ const description = parseFrontmatterField(content, "description") || name;
42
+ const category = parseFrontmatterField(content, "category") || categoryFromFolderName(entry);
43
+ index.push({ id: entry, category, name, description });
44
+ }
45
+ return index;
46
+ }
47
+ /**
48
+ * Loads the pre-built skills_index.json from the project root.
49
+ * Falls back to a dynamically generated index from SKILL.md frontmatter
50
+ * when the file is missing, so the plugin always works correctly.
51
+ */
52
+ export function loadSkillsIndex(bundledSkillsPath) {
53
+ const indexPath = path.join(bundledSkillsPath, "..", "skills_index.json");
54
+ if (fs.existsSync(indexPath)) {
55
+ try {
56
+ const raw = fs.readFileSync(indexPath, "utf-8");
57
+ return JSON.parse(raw);
58
+ }
59
+ catch {
60
+ // fall through to dynamic generation
61
+ }
62
+ }
63
+ return buildIndexFromBundledSkills(bundledSkillsPath);
64
+ }
65
+ /**
66
+ * Copies every skill folder from bundledSkillsPath directly into
67
+ * the vault under the appropriate category sub-directory.
68
+ */
69
+ export function installSkillsToVault(bundledSkillsPath, vaultDir, index) {
70
+ if (!fs.existsSync(bundledSkillsPath))
71
+ return;
72
+ const categoryMap = new Map(index.map((e) => [e.id, e.category ?? UNCATEGORIZED_CATEGORY]));
73
+ for (const entry of fs.readdirSync(bundledSkillsPath)) {
74
+ if (entry.startsWith(".") ||
75
+ entry === "skills_index.json" ||
76
+ entry === "README.md")
77
+ continue;
78
+ const srcPath = path.join(bundledSkillsPath, entry);
79
+ if (!fs.statSync(srcPath).isDirectory())
80
+ continue;
81
+ const category = categoryMap.get(entry) ?? UNCATEGORIZED_CATEGORY;
82
+ const destPath = path.join(vaultDir, category, entry);
83
+ ensureDir(path.join(vaultDir, category));
84
+ fs.cpSync(srcPath, destPath, { recursive: true, force: true });
85
+ }
86
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Ensures a directory exists, creating it recursively if needed.
3
+ */
4
+ export declare function ensureDir(dirPath: string): void;
5
+ /**
6
+ * Returns the names of all direct child directories inside a given path.
7
+ */
8
+ export declare function listSubdirectories(dirPath: string): string[];
@@ -0,0 +1,21 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ /**
4
+ * Ensures a directory exists, creating it recursively if needed.
5
+ */
6
+ export function ensureDir(dirPath) {
7
+ fs.mkdirSync(dirPath, { recursive: true });
8
+ }
9
+ /**
10
+ * Returns the names of all direct child directories inside a given path.
11
+ */
12
+ export function listSubdirectories(dirPath) {
13
+ if (!fs.existsSync(dirPath))
14
+ return [];
15
+ return fs
16
+ .readdirSync(dirPath)
17
+ .filter((entry) => {
18
+ const fullPath = path.join(dirPath, entry);
19
+ return fs.statSync(fullPath).isDirectory();
20
+ });
21
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-skills-collection",
3
- "version": "1.0.195",
3
+ "version": "2.0.0-beta.1",
4
4
  "description": "OpenCode CLI plugin that automatically downloads and keeps skills up to date.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -23,20 +23,21 @@
23
23
  "keywords": [
24
24
  "opencode",
25
25
  "opencode-plugin",
26
- "antigravity",
27
26
  "skills",
27
+ "skills-collection",
28
+ "skills-pointer",
28
29
  "ai",
29
30
  "agent"
30
31
  ],
31
- "author": "Davide Ladisa <rawsar@gmail.com>",
32
+ "author": "Davide Ladisa <info@davideladisa.it>",
32
33
  "license": "MIT",
33
34
  "dependencies": {
34
- "@opencode-ai/plugin": "^1.0.0"
35
+ "@opencode-ai/plugin": "^1.4.0"
35
36
  },
36
37
  "devDependencies": {
37
- "@opencode-ai/sdk": "^1.0.0",
38
+ "@opencode-ai/sdk": "^1.4.0",
38
39
  "@types/bun": "latest",
39
- "@types/node": "^25.5.0",
40
+ "@types/node": "^25.5.2",
40
41
  "typescript": "^6.0.2"
41
42
  }
42
43
  }