@sveltejs/opencode 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/config.ts ADDED
@@ -0,0 +1,143 @@
1
+ import type { PluginInput } from '@opencode-ai/plugin';
2
+ import { existsSync, readFileSync } from 'fs';
3
+ import { homedir } from 'os';
4
+ import { join } from 'path';
5
+ import * as v from 'valibot';
6
+
7
+ const default_config = {
8
+ mcp: {
9
+ type: 'remote' as 'remote' | 'local',
10
+ enabled: true,
11
+ },
12
+ subagent: {
13
+ enabled: true,
14
+ },
15
+ instructions: {
16
+ enabled: true,
17
+ },
18
+ };
19
+
20
+ export const config_schema = v.object({
21
+ mcp: v.optional(
22
+ v.object({
23
+ type: v.optional(v.picklist(['remote', 'local'])),
24
+ enabled: v.optional(v.boolean()),
25
+ }),
26
+ ),
27
+ subagent: v.optional(
28
+ v.object({
29
+ enabled: v.optional(v.boolean()),
30
+ }),
31
+ ),
32
+ instructions: v.optional(
33
+ v.object({
34
+ enabled: v.optional(v.boolean()),
35
+ }),
36
+ ),
37
+ });
38
+
39
+ export type McpConfig = v.InferInput<typeof config_schema>;
40
+
41
+ const GLOBAL_CONFIG_DIR = join(homedir(), '.config', 'opencode');
42
+ const GLOBAL_CONFIG_PATH = join(GLOBAL_CONFIG_DIR, 'svelte.json');
43
+
44
+ interface ConfigLoadResult {
45
+ data: Record<string, unknown> | null;
46
+ parse_error?: string;
47
+ }
48
+
49
+ function get_config_paths() {
50
+ // Global: ~/.config/opencode/svelte.json
51
+ let global_path: string | null = null;
52
+ if (existsSync(GLOBAL_CONFIG_PATH)) {
53
+ global_path = GLOBAL_CONFIG_PATH;
54
+ }
55
+
56
+ // Custom config directory: $OPENCODE_CONFIG_DIR/svelte.json
57
+ let config_dir_path: string | null = null;
58
+ const opencode_config_dir = process.env.OPENCODE_CONFIG_DIR;
59
+ if (opencode_config_dir) {
60
+ const config_json = join(opencode_config_dir, 'svelte.json');
61
+ if (existsSync(config_json)) {
62
+ config_dir_path = config_json;
63
+ }
64
+ }
65
+
66
+ // returning config_dir first so it has higher priority
67
+ return [config_dir_path, global_path];
68
+ }
69
+
70
+ function load_config_file(config_path: string): ConfigLoadResult {
71
+ let file_content: string;
72
+ try {
73
+ file_content = readFileSync(config_path, 'utf-8');
74
+ } catch {
75
+ // File doesn't exist or can't be read
76
+ return { data: null };
77
+ }
78
+
79
+ try {
80
+ const parsed = JSON.parse(file_content);
81
+ if (parsed === undefined || parsed === null) {
82
+ return { data: null, parse_error: 'Config file is empty or invalid' };
83
+ }
84
+ return { data: parsed };
85
+ } catch (error: unknown) {
86
+ return {
87
+ data: null,
88
+ parse_error: error instanceof Error ? error.message : 'Failed to parse config',
89
+ };
90
+ }
91
+ }
92
+
93
+ function merge_with_defaults(user_config: Partial<McpConfig>): McpConfig {
94
+ return {
95
+ mcp: {
96
+ ...default_config.mcp,
97
+ ...user_config.mcp,
98
+ },
99
+ subagent: {
100
+ ...default_config.subagent,
101
+ ...user_config.subagent,
102
+ },
103
+ instructions: {
104
+ ...default_config.instructions,
105
+ ...user_config.instructions,
106
+ },
107
+ };
108
+ }
109
+
110
+ export function get_mcp_config(ctx: PluginInput) {
111
+ const config_paths = get_config_paths();
112
+ for (const path of config_paths) {
113
+ if (path && existsSync(path)) {
114
+ const result = load_config_file(path);
115
+ if (result.parse_error) {
116
+ ctx.client.tui.showToast({
117
+ body: {
118
+ title: 'Svelte: Invalid opencode plugin config',
119
+ message: `${result.parse_error}\nUsing default values`,
120
+ variant: 'warning',
121
+ duration: 7000,
122
+ },
123
+ });
124
+ return default_config;
125
+ }
126
+ const parsed = v.safeParse(config_schema, result.data);
127
+ if (parsed.success) {
128
+ return merge_with_defaults(parsed.output);
129
+ } else {
130
+ ctx.client.tui.showToast({
131
+ body: {
132
+ title: 'Svelte: Invalid opencode plugin config',
133
+ message: `${result.parse_error}\nUsing default values`,
134
+ variant: 'warning',
135
+ duration: 7000,
136
+ },
137
+ });
138
+ }
139
+ }
140
+ }
141
+
142
+ return default_config;
143
+ }
package/index.ts ADDED
@@ -0,0 +1,72 @@
1
+ import type { Plugin } from '@opencode-ai/plugin';
2
+ import { readdir } from 'node:fs/promises';
3
+ import { dirname, join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { get_mcp_config } from './config.js';
6
+
7
+ const current_dir = dirname(fileURLToPath(import.meta.url));
8
+
9
+ export const svelte_plugin: Plugin = async (ctx) => {
10
+ return {
11
+ async config(input) {
12
+ input.agent ??= {};
13
+ input.mcp ??= {};
14
+ input.instructions ??= [];
15
+ // by default we use svelte as the name for the svelte MCP server
16
+ let svelte_mcp_name = 'svelte';
17
+ // we loop over every mcp server to see if any of them is already the svelte MCP server
18
+ for (const name in input.mcp) {
19
+ const mcp = input.mcp[name];
20
+ if (
21
+ (mcp?.type === 'remote' && mcp.url.includes('https://mcp.svelte.dev/mcp')) ||
22
+ (mcp?.type === 'local' &&
23
+ mcp.command.some((cmd: string) => cmd.includes('@sveltejs/mcp')))
24
+ ) {
25
+ // if we found the svelte MCP server, we store its name and break
26
+ svelte_mcp_name = name;
27
+ break;
28
+ }
29
+ }
30
+ const mcp_config = get_mcp_config(ctx);
31
+
32
+ if (mcp_config.instructions?.enabled !== false) {
33
+ const instructions_dir = join(current_dir, 'instructions');
34
+ const instructions_paths = await readdir(instructions_dir);
35
+ input.instructions.push(...instructions_paths.map((file) => join(instructions_dir, file)));
36
+ }
37
+
38
+ // if the user doesn't have the MCP server already we add one based on config
39
+ if (!input.mcp[svelte_mcp_name] && mcp_config.mcp?.enabled !== false) {
40
+ if (mcp_config.mcp?.type === 'remote') {
41
+ input.mcp[svelte_mcp_name] = {
42
+ type: 'remote',
43
+ url: 'https://mcp.svelte.dev/mcp',
44
+ };
45
+ } else {
46
+ input.mcp[svelte_mcp_name] = {
47
+ type: 'local',
48
+ command: ['npx', '-y', '@sveltejs/mcp'],
49
+ };
50
+ }
51
+ }
52
+ if (mcp_config.subagent?.enabled !== false) {
53
+ // we add the editor subagent that will be used when editing Svelte files to prevent wasting context on the main agent
54
+ input.agent['svelte-file-editor'] = {
55
+ color: '#ff3e00',
56
+ mode: 'subagent',
57
+ prompt: `You are a specialized Svelte coder. Always use the tools from the svelte MCP server to fetch documentation with \`get_documentation\` and validating the code with \`svelte_autofixer\`. If the autofixer returns any issue or suggestions solve them before summarizing the changes for the main agent.`,
58
+ description:
59
+ 'Specialized Svelte 5 code editor. MUST BE USED PROACTIVELY when creating, editing, or reviewing any .svelte file or .svelte.ts/.svelte.js module and MUST use the tools from the MCP server. Fetches relevant documentation and validates code using the Svelte MCP server tools.',
60
+ permission: {
61
+ bash: 'ask',
62
+ edit: 'allow',
63
+ webfetch: 'ask',
64
+ },
65
+ tools: {
66
+ [`${svelte_mcp_name}_*`]: true,
67
+ },
68
+ };
69
+ }
70
+ },
71
+ };
72
+ };
@@ -0,0 +1,23 @@
1
+ You are able to use the Svelte MCP server, where you have access to comprehensive Svelte 5 and SvelteKit documentation. Here's how to use the available tools effectively:
2
+
3
+ ## Available MCP Tools:
4
+
5
+ ### 1. list-sections
6
+
7
+ Use this FIRST to discover all available documentation sections. Returns a structured list with titles, use_cases, and paths.
8
+ When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections.
9
+
10
+ ### 2. get-documentation
11
+
12
+ Retrieves full documentation content for specific sections. Accepts single or multiple sections.
13
+ After calling the list-sections tool, you MUST analyze the returned documentation sections (especially the use_cases field) and then use the get-documentation tool to fetch ALL documentation sections that are relevant for the user's task.
14
+
15
+ ### 3. svelte-autofixer
16
+
17
+ Analyzes Svelte code and returns issues and suggestions.
18
+ You MUST use this tool whenever writing Svelte code before sending it to the user. Keep calling it until no issues or suggestions are returned.
19
+
20
+ ### 4. playground-link
21
+
22
+ Generates a Svelte Playground link with the provided code.
23
+ After completing the code, ask the user if they want a playground link. Only call this tool after user confirmation and NEVER if code was written to files in their project.
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@sveltejs/opencode",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/sveltejs/mcp#readme",
7
+ "bugs": {
8
+ "url": "https://github.com/sveltejs/mcp/issues"
9
+ },
10
+ "files": [
11
+ "index.ts",
12
+ "config.ts",
13
+ "instructions"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/sveltejs/mcp.git",
18
+ "path": "packages/opencode"
19
+ },
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "dependencies": {
24
+ "valibot": "^1.2.0"
25
+ },
26
+ "devDependencies": {
27
+ "@opencode-ai/plugin": "^1.1.21",
28
+ "@valibot/to-json-schema": "^1.5.0",
29
+ "@types/node": "^24.3.1"
30
+ },
31
+ "scripts": {
32
+ "check": "tsc --noEmit",
33
+ "generate-schema": "node --import node-resolve-ts/register scripts/generate-schema.ts"
34
+ }
35
+ }