@skill-toolbox/filesystem-source 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.
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # @skill-toolbox/filesystem-source
2
+
3
+ Filesystem source adapter for skill-toolbox. Enables loading skills from local directories and files.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @skill-toolbox/filesystem-source
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Basic Usage
14
+
15
+ ```typescript
16
+ import { FilesystemSource } from '@skill-toolbox/filesystem-source';
17
+
18
+ const source = new FilesystemSource();
19
+
20
+ // Check if source can handle a path
21
+ const canHandle = await source.canHandle('/path/to/skills');
22
+
23
+ // Resolve path to metadata
24
+ const meta = await source.resolve('/path/to/skills');
25
+
26
+ // Fetch (validates path exists)
27
+ const localPath = await source.fetch(meta);
28
+
29
+ // Discover skills
30
+ const skills = await source.discover(localPath, meta);
31
+ ```
32
+
33
+ ### With Custom Working Directory
34
+
35
+ ```typescript
36
+ const source = new FilesystemSource({
37
+ cwd: '/base/directory'
38
+ });
39
+
40
+ // Now relative paths are resolved from cwd
41
+ const meta = await source.resolve('./skills');
42
+ ```
43
+
44
+ ## Features
45
+
46
+ - **Single Skill Loading**: Load a skill from a directory containing a SKILL.md file
47
+ - **Multi-Skill Discovery**: Discover multiple skills in subdirectories
48
+ - **Direct File Loading**: Load a skill directly from a SKILL.md file
49
+ - **Relative Path Support**: Use relative paths with custom working directory
50
+
51
+ ## API
52
+
53
+ ### `FilesystemSource`
54
+
55
+ #### Constructor
56
+
57
+ ```typescript
58
+ constructor(options?: FilesystemSourceOptions)
59
+ ```
60
+
61
+ **Options:**
62
+ - `cwd` - Base directory for relative paths (default: `process.cwd()`)
63
+
64
+ #### Methods
65
+
66
+ ##### `canHandle(source: string): Promise<boolean>`
67
+
68
+ Check if the source string is a valid filesystem path.
69
+
70
+ ##### `resolve(source: string): Promise<SkillSourceMeta>`
71
+
72
+ Resolve the source string to metadata.
73
+
74
+ ##### `fetch(meta: SkillSourceMeta, options?: FetchOptions): Promise<string>`
75
+
76
+ Validate and return the path (no actual fetching needed for filesystem).
77
+
78
+ ##### `discover(localPath: string, meta: SkillSourceMeta): Promise<DiscoveredSkill[]>`
79
+
80
+ Discover skills in the given path.
81
+
82
+ ## License
83
+
84
+ MIT
@@ -0,0 +1,39 @@
1
+ import { SkillSource, SourceCategory, SourceInfo, SourceLoadResult } from '@skill-toolbox/utils';
2
+
3
+ interface FilesystemSourceOptions {
4
+ /** Path to the skills directory or file */
5
+ path: string;
6
+ /** Display name for this source (used in skill names) */
7
+ name?: string;
8
+ /** Category override (defaults to 'global' if name is 'global', else 'local') */
9
+ category?: SourceCategory;
10
+ }
11
+ declare class FilesystemSource implements SkillSource {
12
+ private targetPath;
13
+ private name;
14
+ private category;
15
+ constructor(options: FilesystemSourceOptions);
16
+ getSourceInfo(): SourceInfo;
17
+ load(): Promise<SourceLoadResult>;
18
+ cleanup(): Promise<void>;
19
+ /**
20
+ * Load a single skill file
21
+ */
22
+ private loadSingleFile;
23
+ /**
24
+ * Load all skills from a directory
25
+ */
26
+ private loadDirectory;
27
+ /**
28
+ * Check if filename is a valid skill file name
29
+ */
30
+ private isSkillFileName;
31
+ }
32
+
33
+ /**
34
+ * @skill-toolbox/filesystem-source
35
+ * Filesystem source adapter for skill-toolbox
36
+ */
37
+ declare const VERSION = "1.0.0";
38
+
39
+ export { FilesystemSource, type FilesystemSourceOptions, VERSION };
@@ -0,0 +1,39 @@
1
+ import { SkillSource, SourceCategory, SourceInfo, SourceLoadResult } from '@skill-toolbox/utils';
2
+
3
+ interface FilesystemSourceOptions {
4
+ /** Path to the skills directory or file */
5
+ path: string;
6
+ /** Display name for this source (used in skill names) */
7
+ name?: string;
8
+ /** Category override (defaults to 'global' if name is 'global', else 'local') */
9
+ category?: SourceCategory;
10
+ }
11
+ declare class FilesystemSource implements SkillSource {
12
+ private targetPath;
13
+ private name;
14
+ private category;
15
+ constructor(options: FilesystemSourceOptions);
16
+ getSourceInfo(): SourceInfo;
17
+ load(): Promise<SourceLoadResult>;
18
+ cleanup(): Promise<void>;
19
+ /**
20
+ * Load a single skill file
21
+ */
22
+ private loadSingleFile;
23
+ /**
24
+ * Load all skills from a directory
25
+ */
26
+ private loadDirectory;
27
+ /**
28
+ * Check if filename is a valid skill file name
29
+ */
30
+ private isSkillFileName;
31
+ }
32
+
33
+ /**
34
+ * @skill-toolbox/filesystem-source
35
+ * Filesystem source adapter for skill-toolbox
36
+ */
37
+ declare const VERSION = "1.0.0";
38
+
39
+ export { FilesystemSource, type FilesystemSourceOptions, VERSION };
package/dist/index.js ADDED
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ FilesystemSource: () => FilesystemSource,
34
+ VERSION: () => VERSION
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/FilesystemSource.ts
39
+ var import_path = __toESM(require("path"));
40
+ var import_fs_extra = __toESM(require("fs-extra"));
41
+ var import_utils = require("@skill-toolbox/utils");
42
+ var FilesystemSource = class {
43
+ targetPath;
44
+ name;
45
+ category;
46
+ constructor(options) {
47
+ this.targetPath = options.path;
48
+ if (options.name) {
49
+ this.name = options.name;
50
+ } else {
51
+ const resolved = import_path.default.resolve(options.path);
52
+ const dirName = import_path.default.basename(resolved);
53
+ this.name = dirName || "local";
54
+ }
55
+ if (options.category) {
56
+ this.category = options.category;
57
+ } else {
58
+ this.category = this.name === "global" ? "global" : "local";
59
+ }
60
+ }
61
+ getSourceInfo() {
62
+ return {
63
+ type: "filesystem",
64
+ category: this.category,
65
+ identifier: this.name
66
+ };
67
+ }
68
+ async load() {
69
+ const info = {
70
+ type: "filesystem",
71
+ category: this.category,
72
+ identifier: this.name,
73
+ path: ""
74
+ // Will be set below
75
+ };
76
+ const result = {
77
+ skills: [],
78
+ errors: [],
79
+ info
80
+ };
81
+ try {
82
+ const resolvedPath = import_path.default.resolve(this.targetPath);
83
+ result.info.path = resolvedPath;
84
+ if (!await import_fs_extra.default.pathExists(resolvedPath)) {
85
+ result.errors.push({
86
+ path: this.targetPath,
87
+ error: new Error(`Path does not exist: ${this.targetPath}`)
88
+ });
89
+ return result;
90
+ }
91
+ const stat = await import_fs_extra.default.stat(resolvedPath);
92
+ const isFile = stat.isFile();
93
+ if (isFile) {
94
+ await this.loadSingleFile(resolvedPath, result);
95
+ } else {
96
+ await this.loadDirectory(resolvedPath, result);
97
+ }
98
+ } catch (error) {
99
+ result.errors.push({
100
+ path: this.targetPath,
101
+ error: error instanceof Error ? error : new Error(String(error))
102
+ });
103
+ }
104
+ return result;
105
+ }
106
+ async cleanup() {
107
+ }
108
+ /**
109
+ * Load a single skill file
110
+ */
111
+ async loadSingleFile(filePath, result) {
112
+ try {
113
+ const content = await import_fs_extra.default.readFile(filePath, "utf-8");
114
+ const dir = import_path.default.dirname(filePath);
115
+ const baseName = import_path.default.basename(dir);
116
+ result.skills.push({
117
+ name: `${this.name}/${baseName}`,
118
+ baseName,
119
+ source: this.name,
120
+ path: filePath,
121
+ directory: dir,
122
+ content
123
+ });
124
+ } catch (error) {
125
+ result.errors.push({
126
+ path: filePath,
127
+ error: error instanceof Error ? error : new Error(String(error))
128
+ });
129
+ }
130
+ }
131
+ /**
132
+ * Load all skills from a directory
133
+ */
134
+ async loadDirectory(dirPath, result) {
135
+ try {
136
+ const entries = await import_fs_extra.default.readdir(dirPath, { withFileTypes: true });
137
+ for (const entry of entries) {
138
+ if (entry.name.startsWith(".")) continue;
139
+ const entryPath = import_path.default.join(dirPath, entry.name);
140
+ if (entry.isFile() && this.isSkillFileName(entry.name)) {
141
+ await this.loadSingleFile(entryPath, result);
142
+ } else if (entry.isDirectory()) {
143
+ const skillFile = await (0, import_utils.findSkillFile)(entryPath);
144
+ if (skillFile) {
145
+ try {
146
+ const content = await import_fs_extra.default.readFile(skillFile, "utf-8");
147
+ result.skills.push({
148
+ name: `${this.name}/${entry.name}`,
149
+ baseName: entry.name,
150
+ source: this.name,
151
+ path: skillFile,
152
+ directory: entryPath,
153
+ content
154
+ });
155
+ } catch (error) {
156
+ result.errors.push({
157
+ path: skillFile,
158
+ error: error instanceof Error ? error : new Error(String(error))
159
+ });
160
+ }
161
+ }
162
+ }
163
+ }
164
+ } catch (error) {
165
+ result.errors.push({
166
+ path: dirPath,
167
+ error: error instanceof Error ? error : new Error(String(error))
168
+ });
169
+ }
170
+ }
171
+ /**
172
+ * Check if filename is a valid skill file name
173
+ */
174
+ isSkillFileName(name) {
175
+ const lower = name.toLowerCase();
176
+ return lower === "skill.md";
177
+ }
178
+ };
179
+
180
+ // src/index.ts
181
+ var VERSION = "1.0.0";
182
+ // Annotate the CommonJS export names for ESM import in node:
183
+ 0 && (module.exports = {
184
+ FilesystemSource,
185
+ VERSION
186
+ });
187
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/FilesystemSource.ts"],"sourcesContent":["/**\n * @skill-toolbox/filesystem-source\n * Filesystem source adapter for skill-toolbox\n */\n\nexport const VERSION = '1.0.0';\n\nexport { FilesystemSource } from './FilesystemSource';\nexport type { FilesystemSourceOptions } from './FilesystemSource';\n","import path from 'path';\nimport fs from 'fs-extra';\nimport type { SkillSource, SourceInfo, SourceLoadResult, SourceCategory, LoadedSourceInfo } from '@skill-toolbox/utils';\nimport { findSkillFile } from '@skill-toolbox/utils';\n\nexport interface FilesystemSourceOptions {\n /** Path to the skills directory or file */\n path: string;\n /** Display name for this source (used in skill names) */\n name?: string;\n /** Category override (defaults to 'global' if name is 'global', else 'local') */\n category?: SourceCategory;\n}\n\nexport class FilesystemSource implements SkillSource {\n private targetPath: string;\n private name: string;\n private category: SourceCategory;\n\n constructor(options: FilesystemSourceOptions) {\n this.targetPath = options.path;\n\n // Generate name for skill names\n if (options.name) {\n this.name = options.name;\n } else {\n // Use directory name or 'local' for absolute paths\n const resolved = path.resolve(options.path);\n const dirName = path.basename(resolved);\n this.name = dirName || 'local';\n }\n\n // Determine category\n if (options.category) {\n this.category = options.category;\n } else {\n this.category = this.name === 'global' ? 'global' : 'local';\n }\n }\n\n getSourceInfo(): SourceInfo {\n return {\n type: 'filesystem',\n category: this.category,\n identifier: this.name,\n };\n }\n\n async load(): Promise<SourceLoadResult> {\n // Initialize info with default path (will be updated after resolution)\n const info: LoadedSourceInfo = {\n type: 'filesystem',\n category: this.category,\n identifier: this.name,\n path: '', // Will be set below\n };\n\n const result: SourceLoadResult = {\n skills: [],\n errors: [],\n info,\n };\n\n try {\n // Resolve to absolute path\n const resolvedPath = path.resolve(this.targetPath);\n\n // Update info with resolved path\n result.info.path = resolvedPath;\n\n // Check if path exists\n if (!(await fs.pathExists(resolvedPath))) {\n result.errors.push({\n path: this.targetPath,\n error: new Error(`Path does not exist: ${this.targetPath}`),\n });\n return result;\n }\n\n const stat = await fs.stat(resolvedPath);\n const isFile = stat.isFile();\n\n if (isFile) {\n // Single skill file\n await this.loadSingleFile(resolvedPath, result);\n } else {\n // Directory with multiple skills\n await this.loadDirectory(resolvedPath, result);\n }\n } catch (error) {\n result.errors.push({\n path: this.targetPath,\n error: error instanceof Error ? error : new Error(String(error)),\n });\n }\n\n return result;\n }\n\n async cleanup(): Promise<void> {\n // Nothing to cleanup for filesystem sources\n }\n\n /**\n * Load a single skill file\n */\n private async loadSingleFile(filePath: string, result: SourceLoadResult): Promise<void> {\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n const dir = path.dirname(filePath);\n const baseName = path.basename(dir);\n\n result.skills.push({\n name: `${this.name}/${baseName}`,\n baseName,\n source: this.name,\n path: filePath,\n directory: dir,\n content,\n });\n } catch (error) {\n result.errors.push({\n path: filePath,\n error: error instanceof Error ? error : new Error(String(error)),\n });\n }\n }\n\n /**\n * Load all skills from a directory\n */\n private async loadDirectory(dirPath: string, result: SourceLoadResult): Promise<void> {\n try {\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n\n for (const entry of entries) {\n // Skip hidden files/directories\n if (entry.name.startsWith('.')) continue;\n\n const entryPath = path.join(dirPath, entry.name);\n\n if (entry.isFile() && this.isSkillFileName(entry.name)) {\n // Skill file in root directory\n await this.loadSingleFile(entryPath, result);\n } else if (entry.isDirectory()) {\n // Check if directory contains a skill file\n const skillFile = await findSkillFile(entryPath);\n if (skillFile) {\n try {\n const content = await fs.readFile(skillFile, 'utf-8');\n result.skills.push({\n name: `${this.name}/${entry.name}`,\n baseName: entry.name,\n source: this.name,\n path: skillFile,\n directory: entryPath,\n content,\n });\n } catch (error) {\n result.errors.push({\n path: skillFile,\n error: error instanceof Error ? error : new Error(String(error)),\n });\n }\n }\n }\n }\n } catch (error) {\n result.errors.push({\n path: dirPath,\n error: error instanceof Error ? error : new Error(String(error)),\n });\n }\n }\n\n /**\n * Check if filename is a valid skill file name\n */\n private isSkillFileName(name: string): boolean {\n const lower = name.toLowerCase();\n return lower === 'skill.md';\n }\n}\n"],"mappings":"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","names":["path","fs"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,149 @@
1
+ // src/FilesystemSource.ts
2
+ import path from "path";
3
+ import fs from "fs-extra";
4
+ import { findSkillFile } from "@skill-toolbox/utils";
5
+ var FilesystemSource = class {
6
+ targetPath;
7
+ name;
8
+ category;
9
+ constructor(options) {
10
+ this.targetPath = options.path;
11
+ if (options.name) {
12
+ this.name = options.name;
13
+ } else {
14
+ const resolved = path.resolve(options.path);
15
+ const dirName = path.basename(resolved);
16
+ this.name = dirName || "local";
17
+ }
18
+ if (options.category) {
19
+ this.category = options.category;
20
+ } else {
21
+ this.category = this.name === "global" ? "global" : "local";
22
+ }
23
+ }
24
+ getSourceInfo() {
25
+ return {
26
+ type: "filesystem",
27
+ category: this.category,
28
+ identifier: this.name
29
+ };
30
+ }
31
+ async load() {
32
+ const info = {
33
+ type: "filesystem",
34
+ category: this.category,
35
+ identifier: this.name,
36
+ path: ""
37
+ // Will be set below
38
+ };
39
+ const result = {
40
+ skills: [],
41
+ errors: [],
42
+ info
43
+ };
44
+ try {
45
+ const resolvedPath = path.resolve(this.targetPath);
46
+ result.info.path = resolvedPath;
47
+ if (!await fs.pathExists(resolvedPath)) {
48
+ result.errors.push({
49
+ path: this.targetPath,
50
+ error: new Error(`Path does not exist: ${this.targetPath}`)
51
+ });
52
+ return result;
53
+ }
54
+ const stat = await fs.stat(resolvedPath);
55
+ const isFile = stat.isFile();
56
+ if (isFile) {
57
+ await this.loadSingleFile(resolvedPath, result);
58
+ } else {
59
+ await this.loadDirectory(resolvedPath, result);
60
+ }
61
+ } catch (error) {
62
+ result.errors.push({
63
+ path: this.targetPath,
64
+ error: error instanceof Error ? error : new Error(String(error))
65
+ });
66
+ }
67
+ return result;
68
+ }
69
+ async cleanup() {
70
+ }
71
+ /**
72
+ * Load a single skill file
73
+ */
74
+ async loadSingleFile(filePath, result) {
75
+ try {
76
+ const content = await fs.readFile(filePath, "utf-8");
77
+ const dir = path.dirname(filePath);
78
+ const baseName = path.basename(dir);
79
+ result.skills.push({
80
+ name: `${this.name}/${baseName}`,
81
+ baseName,
82
+ source: this.name,
83
+ path: filePath,
84
+ directory: dir,
85
+ content
86
+ });
87
+ } catch (error) {
88
+ result.errors.push({
89
+ path: filePath,
90
+ error: error instanceof Error ? error : new Error(String(error))
91
+ });
92
+ }
93
+ }
94
+ /**
95
+ * Load all skills from a directory
96
+ */
97
+ async loadDirectory(dirPath, result) {
98
+ try {
99
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
100
+ for (const entry of entries) {
101
+ if (entry.name.startsWith(".")) continue;
102
+ const entryPath = path.join(dirPath, entry.name);
103
+ if (entry.isFile() && this.isSkillFileName(entry.name)) {
104
+ await this.loadSingleFile(entryPath, result);
105
+ } else if (entry.isDirectory()) {
106
+ const skillFile = await findSkillFile(entryPath);
107
+ if (skillFile) {
108
+ try {
109
+ const content = await fs.readFile(skillFile, "utf-8");
110
+ result.skills.push({
111
+ name: `${this.name}/${entry.name}`,
112
+ baseName: entry.name,
113
+ source: this.name,
114
+ path: skillFile,
115
+ directory: entryPath,
116
+ content
117
+ });
118
+ } catch (error) {
119
+ result.errors.push({
120
+ path: skillFile,
121
+ error: error instanceof Error ? error : new Error(String(error))
122
+ });
123
+ }
124
+ }
125
+ }
126
+ }
127
+ } catch (error) {
128
+ result.errors.push({
129
+ path: dirPath,
130
+ error: error instanceof Error ? error : new Error(String(error))
131
+ });
132
+ }
133
+ }
134
+ /**
135
+ * Check if filename is a valid skill file name
136
+ */
137
+ isSkillFileName(name) {
138
+ const lower = name.toLowerCase();
139
+ return lower === "skill.md";
140
+ }
141
+ };
142
+
143
+ // src/index.ts
144
+ var VERSION = "1.0.0";
145
+ export {
146
+ FilesystemSource,
147
+ VERSION
148
+ };
149
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/FilesystemSource.ts","../src/index.ts"],"sourcesContent":["import path from 'path';\nimport fs from 'fs-extra';\nimport type { SkillSource, SourceInfo, SourceLoadResult, SourceCategory, LoadedSourceInfo } from '@skill-toolbox/utils';\nimport { findSkillFile } from '@skill-toolbox/utils';\n\nexport interface FilesystemSourceOptions {\n /** Path to the skills directory or file */\n path: string;\n /** Display name for this source (used in skill names) */\n name?: string;\n /** Category override (defaults to 'global' if name is 'global', else 'local') */\n category?: SourceCategory;\n}\n\nexport class FilesystemSource implements SkillSource {\n private targetPath: string;\n private name: string;\n private category: SourceCategory;\n\n constructor(options: FilesystemSourceOptions) {\n this.targetPath = options.path;\n\n // Generate name for skill names\n if (options.name) {\n this.name = options.name;\n } else {\n // Use directory name or 'local' for absolute paths\n const resolved = path.resolve(options.path);\n const dirName = path.basename(resolved);\n this.name = dirName || 'local';\n }\n\n // Determine category\n if (options.category) {\n this.category = options.category;\n } else {\n this.category = this.name === 'global' ? 'global' : 'local';\n }\n }\n\n getSourceInfo(): SourceInfo {\n return {\n type: 'filesystem',\n category: this.category,\n identifier: this.name,\n };\n }\n\n async load(): Promise<SourceLoadResult> {\n // Initialize info with default path (will be updated after resolution)\n const info: LoadedSourceInfo = {\n type: 'filesystem',\n category: this.category,\n identifier: this.name,\n path: '', // Will be set below\n };\n\n const result: SourceLoadResult = {\n skills: [],\n errors: [],\n info,\n };\n\n try {\n // Resolve to absolute path\n const resolvedPath = path.resolve(this.targetPath);\n\n // Update info with resolved path\n result.info.path = resolvedPath;\n\n // Check if path exists\n if (!(await fs.pathExists(resolvedPath))) {\n result.errors.push({\n path: this.targetPath,\n error: new Error(`Path does not exist: ${this.targetPath}`),\n });\n return result;\n }\n\n const stat = await fs.stat(resolvedPath);\n const isFile = stat.isFile();\n\n if (isFile) {\n // Single skill file\n await this.loadSingleFile(resolvedPath, result);\n } else {\n // Directory with multiple skills\n await this.loadDirectory(resolvedPath, result);\n }\n } catch (error) {\n result.errors.push({\n path: this.targetPath,\n error: error instanceof Error ? error : new Error(String(error)),\n });\n }\n\n return result;\n }\n\n async cleanup(): Promise<void> {\n // Nothing to cleanup for filesystem sources\n }\n\n /**\n * Load a single skill file\n */\n private async loadSingleFile(filePath: string, result: SourceLoadResult): Promise<void> {\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n const dir = path.dirname(filePath);\n const baseName = path.basename(dir);\n\n result.skills.push({\n name: `${this.name}/${baseName}`,\n baseName,\n source: this.name,\n path: filePath,\n directory: dir,\n content,\n });\n } catch (error) {\n result.errors.push({\n path: filePath,\n error: error instanceof Error ? error : new Error(String(error)),\n });\n }\n }\n\n /**\n * Load all skills from a directory\n */\n private async loadDirectory(dirPath: string, result: SourceLoadResult): Promise<void> {\n try {\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n\n for (const entry of entries) {\n // Skip hidden files/directories\n if (entry.name.startsWith('.')) continue;\n\n const entryPath = path.join(dirPath, entry.name);\n\n if (entry.isFile() && this.isSkillFileName(entry.name)) {\n // Skill file in root directory\n await this.loadSingleFile(entryPath, result);\n } else if (entry.isDirectory()) {\n // Check if directory contains a skill file\n const skillFile = await findSkillFile(entryPath);\n if (skillFile) {\n try {\n const content = await fs.readFile(skillFile, 'utf-8');\n result.skills.push({\n name: `${this.name}/${entry.name}`,\n baseName: entry.name,\n source: this.name,\n path: skillFile,\n directory: entryPath,\n content,\n });\n } catch (error) {\n result.errors.push({\n path: skillFile,\n error: error instanceof Error ? error : new Error(String(error)),\n });\n }\n }\n }\n }\n } catch (error) {\n result.errors.push({\n path: dirPath,\n error: error instanceof Error ? error : new Error(String(error)),\n });\n }\n }\n\n /**\n * Check if filename is a valid skill file name\n */\n private isSkillFileName(name: string): boolean {\n const lower = name.toLowerCase();\n return lower === 'skill.md';\n }\n}\n","/**\n * @skill-toolbox/filesystem-source\n * Filesystem source adapter for skill-toolbox\n */\n\nexport const VERSION = '1.0.0';\n\nexport { FilesystemSource } from './FilesystemSource';\nexport type { FilesystemSourceOptions } from './FilesystemSource';\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@skill-toolbox/filesystem-source",
3
+ "version": "0.0.1",
4
+ "description": "Filesystem source adapter for skill-toolbox",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
20
+ "build": "tsup src/index.ts --format cjs,esm --dts",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest",
23
+ "clean": "rm -rf dist *.tsbuildinfo"
24
+ },
25
+ "dependencies": {
26
+ "@skill-toolbox/utils": "workspace:*",
27
+ "fs-extra": "^11.2.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/fs-extra": "^11.0.4",
31
+ "tsup": "^8.0.0",
32
+ "vitest": "^1.2.0"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public",
36
+ "registry": "https://registry.npmjs.org/"
37
+ },
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/Link-Reverie/skill-toolbox.git",
41
+ "directory": "packages/filesystem-source"
42
+ },
43
+ "bugs": {
44
+ "url": "https://github.com/Link-Reverie/skill-toolbox/issues"
45
+ },
46
+ "homepage": "https://github.com/Link-Reverie/skill-toolbox#readme",
47
+ "license": "MIT",
48
+ "keywords": [
49
+ "skill",
50
+ "ai-agent",
51
+ "claude",
52
+ "llm",
53
+ "typescript",
54
+ "filesystem",
55
+ "source"
56
+ ]
57
+ }