@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.
- package/dist/index.cjs +1135 -0
- package/dist/index.d.cts +45 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +1122 -0
- package/package.json +74 -59
- package/LICENSE +0 -661
- package/src/everworks-skills.plugin.spec.ts +0 -67
- package/src/everworks-skills.plugin.ts +0 -187
- package/src/index.ts +0 -2
- package/tsconfig.json +0 -22
- package/tsup.config.ts +0 -12
|
@@ -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
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