@ever-works/everworks-skills-plugin 1.0.0 → 1.0.3

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.
@@ -1,67 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { EverWorksSkillsPlugin } from './everworks-skills.plugin.js';
3
-
4
- describe('EverWorksSkillsPlugin (builtin fallback catalog)', () => {
5
- let plugin: EverWorksSkillsPlugin;
6
-
7
- beforeEach(async () => {
8
- plugin = new EverWorksSkillsPlugin();
9
- await plugin.onLoad({
10
- logger: { log: () => undefined, warn: () => undefined, error: () => undefined }
11
- } as any);
12
- });
13
-
14
- it('exposes the skills-provider capability', () => {
15
- expect(plugin.capabilities).toContain('skills-provider');
16
- expect(plugin.id).toBe('everworks-skills');
17
- });
18
-
19
- it('listEntries returns the builtin catalog by default', async () => {
20
- const result = await plugin.listEntries({ limit: 50, offset: 0 });
21
- expect(result.total).toBeGreaterThanOrEqual(3);
22
- expect(result.entries.map((e) => e.slug)).toEqual(
23
- expect.arrayContaining(['cron-defaults', 'secret-handling', 'commit-message-style'])
24
- );
25
- });
26
-
27
- it('listEntries paginates correctly', async () => {
28
- const page1 = await plugin.listEntries({ limit: 2, offset: 0 });
29
- const page2 = await plugin.listEntries({ limit: 2, offset: 2 });
30
- expect(page1.entries).toHaveLength(2);
31
- expect(page2.entries[0].slug).not.toEqual(page1.entries[0].slug);
32
- });
33
-
34
- it('listEntries filters by search term', async () => {
35
- const result = await plugin.listEntries({ limit: 50, offset: 0, search: 'cron' });
36
- expect(result.entries).toHaveLength(1);
37
- expect(result.entries[0].slug).toBe('cron-defaults');
38
- });
39
-
40
- it('listEntries filters by tag (case-insensitive)', async () => {
41
- const result = await plugin.listEntries({ limit: 50, offset: 0, tags: ['SECURITY'] });
42
- expect(result.entries.map((e) => e.slug)).toEqual(['secret-handling']);
43
- });
44
-
45
- it('getEntry returns one entry by slug', async () => {
46
- const entry = await plugin.getEntry('cron-defaults');
47
- expect(entry?.slug).toBe('cron-defaults');
48
- expect(entry?.body).toContain('UTC');
49
- });
50
-
51
- it('getEntry returns null for unknown slug', async () => {
52
- const entry = await plugin.getEntry('does-not-exist');
53
- expect(entry).toBeNull();
54
- });
55
-
56
- it('checkForUpdates flags rows whose installed version mismatches the catalog', async () => {
57
- const result = await plugin.checkForUpdates({
58
- 'cron-defaults': '0.9.0',
59
- 'secret-handling': '1.0.0'
60
- });
61
- expect(result.updated).toEqual([{ slug: 'cron-defaults', oldVersion: '0.9.0', newVersion: '1.0.0' }]);
62
- });
63
-
64
- it('isAvailable returns true (no required credentials)', () => {
65
- expect(plugin.isAvailable()).toBe(true);
66
- });
67
- });
@@ -1,187 +0,0 @@
1
- import type {
2
- IPlugin,
3
- ISkillsProviderPlugin,
4
- JsonSchema,
5
- PluginCategory,
6
- PluginContext,
7
- PluginSettings,
8
- SkillCatalogEntry,
9
- SkillCatalogListOptions,
10
- SkillCatalogListResult,
11
- SkillCatalogUpdate
12
- } from '@ever-works/plugin';
13
-
14
- import matter from 'gray-matter';
15
-
16
- /**
17
- * Ever Works Skills — first-party `skills-provider` plugin (ADR-012).
18
- *
19
- * Sources its catalog from the [`ever-works/skills`](https://github.com/ever-works/skills)
20
- * GitHub repo (per ADR-014). v1 ships with a small built-in fallback
21
- * catalog so the plugin works out of the box even before the
22
- * upstream repo is created — the platform self-recovers when the
23
- * repo appears.
24
- *
25
- * Future iteration: replace the built-in fallback with a real
26
- * git-clone + cache loader that pulls `.md` files from the repo's
27
- * `catalog/` subtree, parses frontmatter via `gray-matter`, and
28
- * caches in memory with a configurable TTL.
29
- */
30
- const BUILTIN_CATALOG: SkillCatalogEntry[] = [
31
- {
32
- slug: 'cron-defaults',
33
- title: 'Cron defaults to UTC',
34
- description:
35
- 'When you see a cron expression, default the timezone to UTC unless the user explicitly says otherwise.',
36
- frontmatter: {
37
- name: 'cron-defaults',
38
- description: 'Cron timezone default',
39
- tags: ['scheduling', 'time']
40
- },
41
- body: `When working with cron expressions:\n\n- Default to UTC unless the user explicitly says otherwise.\n- Match the platform's existing scheduling code which is UTC throughout.\n- When showing a cron expression to the user, also format the next 3 fire times in their local timezone for clarity.`,
42
- version: '1.0.0',
43
- tags: ['scheduling', 'time']
44
- },
45
- {
46
- slug: 'secret-handling',
47
- title: 'Never log or echo secrets',
48
- description: 'Hard rule: never write API keys, tokens, or credentials to logs, chat, or Agent files.',
49
- frontmatter: {
50
- name: 'secret-handling',
51
- description: 'Secret handling policy',
52
- tags: ['security']
53
- },
54
- body: `Secrets MUST NEVER appear in:\n\n- Log output (file, stdout, run-log rows).\n- Chat messages.\n- Agent definition files (SOUL.md, AGENTS.md, etc.).\n- Task descriptions or comments.\n\nIf you need to reference a secret, refer to its environment variable name only (e.g. \`OPENAI_API_KEY\`). The platform secret-scanner will reject writes containing values matching common secret patterns (\`sk-\`, \`ghp_\`, \`AKIA\`, \`glpat-\`, \`xoxb-\`, \`pat_\`).`,
55
- version: '1.0.0',
56
- tags: ['security']
57
- },
58
- {
59
- slug: 'commit-message-style',
60
- title: 'Conventional commit messages',
61
- description: 'Format git commit messages following the conventional-commits spec.',
62
- frontmatter: {
63
- name: 'commit-message-style',
64
- description: 'Commit message format',
65
- tags: ['git', 'style']
66
- },
67
- body: `When creating a git commit, follow conventional-commits:\n\n- Subject line: \`<type>(<scope>): <imperative description>\`\n- \`<type>\` is one of: \`feat\`, \`fix\`, \`docs\`, \`refactor\`, \`test\`, \`chore\`.\n- \`<scope>\` is optional and names the affected package or module.\n- The body explains \`why\`, not \`what\` — the diff already shows \`what\`.\n- Wrap body lines at ~72 chars.`,
68
- version: '1.0.0',
69
- tags: ['git', 'style']
70
- }
71
- ];
72
-
73
- export class EverWorksSkillsPlugin implements IPlugin, ISkillsProviderPlugin {
74
- readonly id = 'everworks-skills';
75
- readonly name = 'Ever Works Skills';
76
- readonly version = '1.0.0';
77
- readonly category: PluginCategory = 'utility' as PluginCategory;
78
- readonly capabilities: readonly string[] = ['skills-provider'];
79
- readonly providerName = 'Ever Works Skills';
80
-
81
- readonly settingsSchema: JsonSchema = {
82
- type: 'object',
83
- properties: {
84
- catalogRepo: {
85
- type: 'string',
86
- title: 'Catalog GitHub repo',
87
- description: 'Owner/repo of the Skills catalog source. Defaults to ever-works/skills (ADR-014).',
88
- default: 'ever-works/skills',
89
- 'x-envVar': 'PLUGIN_EVERWORKS_SKILLS_REPO'
90
- },
91
- catalogBranch: {
92
- type: 'string',
93
- title: 'Branch',
94
- default: 'main',
95
- 'x-envVar': 'PLUGIN_EVERWORKS_SKILLS_BRANCH'
96
- },
97
- cacheTtlSeconds: {
98
- type: 'number',
99
- title: 'Cache TTL (seconds)',
100
- description: 'How long to cache the cloned catalog locally.',
101
- default: 3600,
102
- minimum: 0,
103
- maximum: 86400
104
- }
105
- }
106
- };
107
-
108
- readonly configurationMode: 'admin-only' | 'user-required' | 'hybrid' = 'admin-only';
109
-
110
- private context?: PluginContext;
111
- private cache: { entries: SkillCatalogEntry[]; fetchedAt: number } | null = null;
112
-
113
- async onLoad(context: PluginContext): Promise<void> {
114
- this.context = context;
115
- context.logger.log('Ever Works Skills provider loaded (v1: builtin fallback catalog).');
116
- }
117
-
118
- async onUnload(): Promise<void> {
119
- this.cache = null;
120
- }
121
-
122
- isAvailable(_settings?: PluginSettings): boolean {
123
- return true;
124
- }
125
-
126
- async listEntries(options: SkillCatalogListOptions): Promise<SkillCatalogListResult> {
127
- const all = await this.loadAll(options.settings);
128
- const filtered = filterCatalog(all, options);
129
- const sliced = filtered.slice(options.offset, options.offset + options.limit);
130
- return { entries: sliced, total: filtered.length };
131
- }
132
-
133
- async getEntry(slug: string, settings?: PluginSettings): Promise<SkillCatalogEntry | null> {
134
- const all = await this.loadAll(settings);
135
- return all.find((e) => e.slug === slug) ?? null;
136
- }
137
-
138
- async checkForUpdates(
139
- installedVersions: Record<string, string>,
140
- settings?: PluginSettings
141
- ): Promise<{ updated: SkillCatalogUpdate[] }> {
142
- const all = await this.loadAll(settings);
143
- const updated: SkillCatalogUpdate[] = [];
144
- for (const entry of all) {
145
- const installed = installedVersions[entry.slug];
146
- if (installed && installed !== entry.version) {
147
- updated.push({ slug: entry.slug, oldVersion: installed, newVersion: entry.version });
148
- }
149
- }
150
- return { updated };
151
- }
152
-
153
- private async loadAll(settings?: PluginSettings): Promise<SkillCatalogEntry[]> {
154
- // Security: clamp TTL to [0, 86400] at runtime to prevent Infinity/NaN bypass of schema-only constraint
155
- const rawTtl = Number(settings?.cacheTtlSeconds ?? 3600);
156
- const ttlSeconds = isNaN(rawTtl) ? 3600 : Math.max(0, Math.min(rawTtl, 86400));
157
- const ttl = ttlSeconds * 1000;
158
- const now = Date.now();
159
- if (this.cache && now - this.cache.fetchedAt < ttl) {
160
- return this.cache.entries;
161
- }
162
-
163
- // TODO: Replace builtin fallback with a real git clone + cache
164
- // of `ever-works/skills` per ADR-014. When the upstream repo
165
- // is reachable, parse every `*.md` file via `gray-matter` into
166
- // a SkillCatalogEntry. Fall back to the builtin if the clone
167
- // fails so the plugin always returns SOMETHING.
168
- this.cache = { entries: BUILTIN_CATALOG.slice(), fetchedAt: now };
169
- void matter; // silence unused import warning until the loader wires up
170
- return this.cache.entries;
171
- }
172
- }
173
-
174
- function filterCatalog(entries: SkillCatalogEntry[], options: SkillCatalogListOptions): SkillCatalogEntry[] {
175
- let out = entries;
176
- if (options.search) {
177
- const q = options.search.toLowerCase();
178
- out = out.filter(
179
- (e) => e.slug.includes(q) || e.title.toLowerCase().includes(q) || e.description.toLowerCase().includes(q)
180
- );
181
- }
182
- if (options.tags && options.tags.length > 0) {
183
- const requested = new Set(options.tags.map((t) => t.toLowerCase()));
184
- out = out.filter((e) => e.tags.some((t) => requested.has(t.toLowerCase())));
185
- }
186
- return out;
187
- }
package/src/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export { EverWorksSkillsPlugin } from './everworks-skills.plugin.js';
2
- export { EverWorksSkillsPlugin as default } from './everworks-skills.plugin.js';
package/tsconfig.json DELETED
@@ -1,22 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "types": ["node"],
5
- "module": "NodeNext",
6
- "moduleResolution": "NodeNext",
7
- "declaration": true,
8
- "declarationMap": true,
9
- "sourceMap": true,
10
- "outDir": "./dist",
11
- "rootDir": "./src",
12
- "strict": true,
13
- "esModuleInterop": true,
14
- "skipLibCheck": true,
15
- "forceConsistentCasingInFileNames": true,
16
- "resolveJsonModule": true,
17
- "isolatedModules": true,
18
- "noEmit": true
19
- },
20
- "include": ["src/**/*"],
21
- "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
22
- }
package/tsup.config.ts DELETED
@@ -1,12 +0,0 @@
1
- import { defineConfig } from 'tsup';
2
-
3
- export default defineConfig({
4
- entry: ['src/index.ts'],
5
- noExternal: ['@ever-works/plugin'],
6
- format: ['cjs', 'esm'],
7
- dts: true,
8
- clean: true,
9
- sourcemap: false,
10
- splitting: false,
11
- treeshake: true
12
- });