@johpaz/hive-skills 1.0.6 → 1.0.8

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 (2) hide show
  1. package/package.json +2 -3
  2. package/src/loader.ts +111 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johpaz/hive-skills",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Hive Skills — Official bundled skills for Hive",
5
5
  "main": "./src/index.ts",
6
6
  "license": "MIT",
@@ -11,8 +11,7 @@
11
11
  "test": "bun test"
12
12
  },
13
13
  "dependencies": {
14
- "@johpaz/hive-core": "^1.0.6",
15
- "zod": "latest"
14
+ "js-yaml": "latest"
16
15
  },
17
16
  "devDependencies": {
18
17
  "typescript": "latest",
package/src/loader.ts CHANGED
@@ -1,8 +1,35 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import * as yaml from "js-yaml";
4
- import type { Config } from "../config/loader.ts";
5
- import { logger } from "../utils/logger.ts";
4
+
5
+ export interface SkillsConfig {
6
+ allowBundled?: string[];
7
+ managedDir?: string;
8
+ extraDirs?: string[];
9
+ hotReload?: boolean;
10
+ maxSkillSizeKB?: number;
11
+ }
12
+
13
+ export interface Config {
14
+ skills?: SkillsConfig;
15
+ workspacePath?: string;
16
+ }
17
+
18
+ interface Logger {
19
+ debug: (msg: string, ...args: unknown[]) => void;
20
+ info: (msg: string, ...args: unknown[]) => void;
21
+ warn: (msg: string, ...args: unknown[]) => void;
22
+ error: (msg: string, ...args: unknown[]) => void;
23
+ }
24
+
25
+ function createLogger(): Logger {
26
+ return {
27
+ debug: (msg, ...args) => console.debug(`[skills] ${msg}`, ...args),
28
+ info: (msg, ...args) => console.info(`[skills] ${msg}`, ...args),
29
+ warn: (msg, ...args) => console.warn(`[skills] ${msg}`, ...args),
30
+ error: (msg, ...args) => console.error(`[skills] ${msg}`, ...args),
31
+ };
32
+ }
6
33
 
7
34
  export interface SkillMetadata {
8
35
  name: string;
@@ -30,6 +57,7 @@ export interface Skill {
30
57
  name: string;
31
58
  description: string;
32
59
  content: string;
60
+ raw: string;
33
61
  metadata: SkillMetadata["metadata"];
34
62
  source: "bundled" | "managed" | "workspace";
35
63
  path: string;
@@ -37,7 +65,7 @@ export interface Skill {
37
65
 
38
66
  function parseFrontmatter(content: string): { frontmatter: Record<string, unknown>; body: string } {
39
67
  const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
40
-
68
+
41
69
  if (!match) {
42
70
  return { frontmatter: {}, body: content };
43
71
  }
@@ -53,11 +81,14 @@ function parseFrontmatter(content: string): { frontmatter: Record<string, unknow
53
81
 
54
82
  export class SkillLoader {
55
83
  private config: Config;
56
- private log = logger.child("skills");
84
+ private log: Logger;
57
85
  private cache: Map<string, Skill> = new Map();
86
+ private bundledDir: string;
58
87
 
59
88
  constructor(config: Config) {
60
89
  this.config = config;
90
+ this.log = createLogger();
91
+ this.bundledDir = path.join(__dirname, "bundled");
61
92
  }
62
93
 
63
94
  private expandPath(p: string): string {
@@ -70,7 +101,7 @@ export class SkillLoader {
70
101
  loadSkill(skillPath: string, source: Skill["source"]): Skill | null {
71
102
  try {
72
103
  const skillMdPath = path.join(skillPath, "SKILL.md");
73
-
104
+
74
105
  if (!fs.existsSync(skillMdPath)) {
75
106
  return null;
76
107
  }
@@ -86,6 +117,7 @@ export class SkillLoader {
86
117
  name,
87
118
  description,
88
119
  content: body,
120
+ raw: content,
89
121
  metadata,
90
122
  source,
91
123
  path: skillPath,
@@ -99,9 +131,41 @@ export class SkillLoader {
99
131
  }
100
132
  }
101
133
 
134
+ loadBundledSkills(): Skill[] {
135
+ const skills: Map<string, Skill> = new Map();
136
+
137
+ if (!fs.existsSync(this.bundledDir)) {
138
+ this.log.debug(`Bundled skills directory not found: ${this.bundledDir}`);
139
+ return [];
140
+ }
141
+
142
+ for (const entry of fs.readdirSync(this.bundledDir, { withFileTypes: true })) {
143
+ if (entry.isDirectory()) {
144
+ const skill = this.loadSkill(path.join(this.bundledDir, entry.name), "bundled");
145
+ if (skill) {
146
+ skills.set(skill.name, skill);
147
+ }
148
+ }
149
+ }
150
+
151
+ this.log.debug(`Loaded ${skills.size} bundled skills`);
152
+ return Array.from(skills.values());
153
+ }
154
+
102
155
  loadAllSkills(): Skill[] {
103
156
  const skills: Map<string, Skill> = new Map();
157
+ const allowBundled = this.config.skills?.allowBundled;
158
+
159
+ // 1. Load bundled skills (lowest priority)
160
+ const bundledSkills = this.loadBundledSkills();
161
+ for (const skill of bundledSkills) {
162
+ // If allowBundled is undefined, empty, or contains the skill name, include it
163
+ if (!allowBundled || allowBundled.length === 0 || allowBundled.includes(skill.name)) {
164
+ skills.set(skill.name, skill);
165
+ }
166
+ }
104
167
 
168
+ // 2. Load managed skills (medium priority)
105
169
  const managedDir = this.expandPath(this.config.skills?.managedDir ?? "~/.hive/skills");
106
170
  if (fs.existsSync(managedDir)) {
107
171
  for (const entry of fs.readdirSync(managedDir, { withFileTypes: true })) {
@@ -114,11 +178,29 @@ export class SkillLoader {
114
178
  }
115
179
  }
116
180
 
117
- const workspaceDir = "workspace/skills";
118
- if (fs.existsSync(workspaceDir)) {
119
- for (const entry of fs.readdirSync(workspaceDir, { withFileTypes: true })) {
181
+ // 3. Load extra directories
182
+ const extraDirs = this.config.skills?.extraDirs ?? [];
183
+ for (const extraDir of extraDirs) {
184
+ const expandedDir = this.expandPath(extraDir);
185
+ if (fs.existsSync(expandedDir)) {
186
+ for (const entry of fs.readdirSync(expandedDir, { withFileTypes: true })) {
187
+ if (entry.isDirectory()) {
188
+ const skill = this.loadSkill(path.join(expandedDir, entry.name), "managed");
189
+ if (skill) {
190
+ skills.set(skill.name, skill);
191
+ }
192
+ }
193
+ }
194
+ }
195
+ }
196
+
197
+ // 4. Load workspace skills (highest priority)
198
+ const workspacePath = this.config.workspacePath ?? process.cwd();
199
+ const workspaceSkillsDir = path.join(workspacePath, "skills");
200
+ if (fs.existsSync(workspaceSkillsDir)) {
201
+ for (const entry of fs.readdirSync(workspaceSkillsDir, { withFileTypes: true })) {
120
202
  if (entry.isDirectory()) {
121
- const skill = this.loadSkill(path.join(workspaceDir, entry.name), "workspace");
203
+ const skill = this.loadSkill(path.join(workspaceSkillsDir, entry.name), "workspace");
122
204
  if (skill) {
123
205
  skills.set(skill.name, skill);
124
206
  }
@@ -126,6 +208,7 @@ export class SkillLoader {
126
208
  }
127
209
  }
128
210
 
211
+ this.log.info(`Loaded ${skills.size} total skills`);
129
212
  return Array.from(skills.values());
130
213
  }
131
214
 
@@ -133,6 +216,10 @@ export class SkillLoader {
133
216
  return this.cache.get(name);
134
217
  }
135
218
 
219
+ listSkills(): string[] {
220
+ return Array.from(this.cache.keys());
221
+ }
222
+
136
223
  clearCache(): void {
137
224
  this.cache.clear();
138
225
  }
@@ -141,3 +228,18 @@ export class SkillLoader {
141
228
  export function createSkillLoader(config: Config): SkillLoader {
142
229
  return new SkillLoader(config);
143
230
  }
231
+
232
+ // Export all bundled skill names for convenience
233
+ export const BUNDLED_SKILL_NAMES = [
234
+ "web_search",
235
+ "shell",
236
+ "file_manager",
237
+ "http_client",
238
+ "memory",
239
+ "cron_manager",
240
+ "system_notify",
241
+ "browser_automation",
242
+ "context_compact",
243
+ ] as const;
244
+
245
+ export type BundledSkillName = typeof BUNDLED_SKILL_NAMES[number];