Compare commits

...

3 Commits

Author SHA1 Message Date
Paolo Ricciuti
b29e265f01 chore: add example 2026-03-10 17:35:36 +01:00
paoloricciuti
463068a157 fix: update schema when skills change 2026-03-07 13:09:21 +01:00
paoloricciuti
19c45f1c11 feat: allow enabling a specific skill in opencode plugin 2026-03-07 13:01:10 +01:00
7 changed files with 125 additions and 31 deletions

View File

@@ -0,0 +1,5 @@
---
'@sveltejs/opencode': patch
---
feat: allow enabling a specific skill in opencode plugin

View File

@@ -53,7 +53,7 @@ jobs:
run: pnpm sync-cursor-plugin run: pnpm sync-cursor-plugin
- name: Sync OpenCode plugin - name: Sync OpenCode plugin
run: pnpm sync-opencode-plugin run: pnpm sync-opencode-plugin && pnpm generate-opencode-jsonschema
- name: Generate skills documentation - name: Generate skills documentation
run: pnpm generate-skill-docs run: pnpm generate-skill-docs
@@ -69,6 +69,7 @@ jobs:
plugins/cursor/svelte/ \ plugins/cursor/svelte/ \
packages/opencode/skills/ \ packages/opencode/skills/ \
packages/opencode/instructions/ \ packages/opencode/instructions/ \
packages/opencode/schema.json \
documentation/docs/ \ documentation/docs/ \
|| echo "changed=true" >> $GITHUB_OUTPUT || echo "changed=true" >> $GITHUB_OUTPUT
@@ -90,7 +91,7 @@ jobs:
## Changes ## Changes
- Synced `plugins/claude/svelte/` (skills, agents with `permissionMode`) - Synced `plugins/claude/svelte/` (skills, agents with `permissionMode`)
- Synced `plugins/cursor/svelte/` (skills, agents, rules) - Synced `plugins/cursor/svelte/` (skills, agents, rules)
- Synced `packages/opencode/` (skills, instructions) - Synced `packages/opencode/` (skills, instructions, schema)
- Updated documentation - Updated documentation
## Generated by ## Generated by

View File

@@ -32,7 +32,7 @@ The default configuration for the Svelte OpenCode plugin looks like this...
"enabled": true "enabled": true
}, },
"skills": { "skills": {
"enabled": true "enabled": true // it can also be an array of all the skills to enable like ['svelte-core-bestpractices']
}, },
"instructions": { "instructions": {
"enabled": true "enabled": true
@@ -40,6 +40,6 @@ The default configuration for the Svelte OpenCode plugin looks like this...
} }
``` ```
...but if you prefer, you can enable only the subagent, only the MCP, only the skills, or configure the kind of MCP server you want to use (`local` or `remote`). ...but if you prefer, you can enable only the subagent, only the MCP, only the skills (`enabled` supports both a boolean or an array containing the name of all the skills to enable), or configure the kind of MCP server you want to use (`local` or `remote`).
You can place this file in `./.opencode/svelte.json` (in your project), in `~/.config/opencode/svelte.json` or, if you have an `OPENCODE_CONFIG_DIR` environment variable specified, at `$OPENCODE_CONFIG_DIR/svelte.json`. You can place this file in `./.opencode/svelte.json` (in your project), in `~/.config/opencode/svelte.json` or, if you have an `OPENCODE_CONFIG_DIR` environment variable specified, at `$OPENCODE_CONFIG_DIR/svelte.json`.

View File

@@ -16,31 +16,57 @@ const default_config = {
enabled: true, enabled: true,
}, },
skills: { skills: {
enabled: true, enabled: true as boolean | string[],
}, },
}; };
export const config_schema = v.object({ export const config_schema = v.object({
mcp: v.optional( mcp: v.pipe(
v.object({ v.optional(
type: v.optional(v.picklist(['remote', 'local'])), v.object({
enabled: v.optional(v.boolean()), type: v.optional(v.picklist(['remote', 'local'])),
}), enabled: v.optional(v.boolean()),
}),
),
v.description(
"Configuration for the MCP. You can chose if it should be enabled or not and the transport to use 'remote' (default) and 'local'.",
),
), ),
subagent: v.optional( subagent: v.pipe(
v.object({ v.optional(
enabled: v.optional(v.boolean()), v.object({
}), enabled: v.optional(v.boolean()),
}),
),
v.description('Configuration for the subagent. You can choose if it should be enabled or not.'),
), ),
instructions: v.optional( instructions: v.pipe(
v.object({ v.optional(
enabled: v.optional(v.boolean()), v.object({
}), enabled: v.optional(v.boolean()),
}),
),
v.description(
'Configuration for the automatic AGENTS.md injection. You can choose if it should be enabled or not.',
),
v.description(
'Configuration for the automatic AGENTS.md injection. You can choose if it should be enabled or not.',
),
), ),
skills: v.optional( skills: v.pipe(
v.object({ v.optional(
enabled: v.optional(v.boolean()), v.object({
}), enabled: v.pipe(
v.optional(v.union([v.boolean(), v.array(v.string())])),
v.description(
'It can be either a boolean or an array containing the skills that you want to enable',
),
),
}),
),
v.description(
'Configuration for the skills. You can choose if it they should be enabled or not, or specify an array of skill names to enable only specific skills.',
),
), ),
}); });

View File

@@ -39,10 +39,20 @@ export const svelte_plugin: Plugin = async (ctx) => {
input.instructions.push(...instructions_paths.map((file) => join(instructions_dir, file))); input.instructions.push(...instructions_paths.map((file) => join(instructions_dir, file)));
} }
if (mcp_config.skills?.enabled !== false) { const skills_enabled = mcp_config.skills?.enabled;
if (skills_enabled !== false) {
const skills_dir = join(current_dir, 'skills'); const skills_dir = join(current_dir, 'skills');
// @ts-expect-error -- skills is a new opencode feature if (Array.isArray(skills_enabled)) {
input.skills.paths.push(skills_dir); // only add specific skill directories by name
for (const skill_name of skills_enabled) {
const skill_path = join(skills_dir, skill_name);
// @ts-expect-error -- skills is a new opencode feature
input.skills.paths.push(skill_path);
}
} else {
// @ts-expect-error -- skills is a new opencode feature
input.skills.paths.push(skills_dir);
}
} }
// if the user doesn't have the MCP server already we add one based on config // if the user doesn't have the MCP server already we add one based on config

View File

@@ -14,7 +14,8 @@
"type": "boolean" "type": "boolean"
} }
}, },
"required": [] "required": [],
"description": "Configuration for the MCP. You can chose if it should be enabled or not and the transport to use 'remote' (default) and 'local'."
}, },
"subagent": { "subagent": {
"type": "object", "type": "object",
@@ -23,7 +24,8 @@
"type": "boolean" "type": "boolean"
} }
}, },
"required": [] "required": [],
"description": "Configuration for the subagent. You can choose if it should be enabled or not."
}, },
"instructions": { "instructions": {
"type": "object", "type": "object",
@@ -32,16 +34,39 @@
"type": "boolean" "type": "boolean"
} }
}, },
"required": [] "required": [],
"description": "Configuration for the automatic AGENTS.md injection. You can choose if it should be enabled or not."
}, },
"skills": { "skills": {
"type": "object", "type": "object",
"properties": { "properties": {
"enabled": { "enabled": {
"type": "boolean" "anyOf": [
{
"type": "boolean"
},
{
"type": "array",
"items": {
"anyOf": [
{
"enum": [
"svelte-code-writer",
"svelte-core-bestpractices"
]
},
{
"type": "string"
}
]
}
}
],
"description": "It can be either a boolean or an array containing the skills that you want to enable"
} }
}, },
"required": [] "required": [],
"description": "Configuration for the skills. You can choose if it they should be enabled or not, or specify an array of skill names to enable only specific skills."
} }
}, },
"required": [], "required": [],

View File

@@ -3,6 +3,33 @@ import { config_schema } from '../config.js';
import fs from 'node:fs'; import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
const json_schema = toJsonSchema(config_schema); function get_skill_names(skills_dir: string) {
if (!fs.existsSync(skills_dir)) return [];
return fs
.readdirSync(skills_dir, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name);
}
const skills_dir = path.resolve('./skills');
const skill_names = get_skill_names(skills_dir);
const schema = config_schema;
const json_schema = toJsonSchema(schema);
// Post-process: inject skill name suggestions into the items schema.
// This is the JSON Schema equivalent of `"a" | "b" | (string & {})` —
// editors will autocomplete the known names but any string is still valid.
if (skill_names.length > 0) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const enabled = (json_schema as any).properties?.skills?.properties?.enabled;
if (enabled?.anyOf) {
const array_branch = enabled.anyOf.find((s: Record<string, unknown>) => s.type === 'array');
if (array_branch) {
array_branch.items = {
anyOf: [{ enum: skill_names }, { type: 'string' }],
};
}
}
}
fs.writeFileSync(path.resolve('./schema.json'), JSON.stringify(json_schema, null, '\t')); fs.writeFileSync(path.resolve('./schema.json'), JSON.stringify(json_schema, null, '\t'));