opencode-architect 0.2.0

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.
@@ -0,0 +1,269 @@
1
+ TEMPLATE INSTRUCTIONS
2
+ ====================
3
+ Replace the following placeholders before use:
4
+
5
+ EXTENSION NAME
6
+ "myextension" → your extension identifier (e.g., "mytool")
7
+
8
+ SKILL NAME
9
+ "myextension" → must match the folder name in assets/skills/
10
+
11
+ COMMAND NAME
12
+ "my-command.md" → your command file name in assets/commands/
13
+
14
+ PACKAGE NAME
15
+ "opencode-myextension" → your npm package name
16
+
17
+ ---
18
+ import { exists, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
19
+ import { homedir } from "node:os";
20
+ import { join } from "node:path";
21
+
22
+ export type Scope = "local" | "global";
23
+
24
+ export interface InstallOptions {
25
+ configurePermission?: boolean;
26
+ configureMcp?: boolean;
27
+ addPluginConfig?: boolean;
28
+ }
29
+
30
+ export interface InstallResult {
31
+ scope: Scope;
32
+ skillPath: string;
33
+ commandPath: string;
34
+ configPath: string;
35
+ migrated: boolean;
36
+ permissionConfigured: boolean;
37
+ mcpConfigured: boolean;
38
+ pluginAdded: boolean;
39
+ }
40
+
41
+ export interface UninstallResult {
42
+ scope: Scope;
43
+ removed: string[];
44
+ pluginRemoved: boolean;
45
+ }
46
+
47
+ export interface StatusResult {
48
+ local: { installed: boolean; version: string | null; pluginInConfig: boolean } | null;
49
+ global: { installed: boolean; version: string | null; pluginInConfig: boolean } | null;
50
+ }
51
+
52
+ const SKILL_NAME = "myextension";
53
+ const COMMAND_NAME = "my-command.md";
54
+ const PLUGIN_NAME = "opencode-myextension";
55
+
56
+ export async function getPackageVersion(): Promise<string> {
57
+ const content = await Bun.file(`${import.meta.dirname}/../package.json`).text();
58
+ return JSON.parse(content).version;
59
+ }
60
+
61
+ export async function getPackageName(): Promise<string> {
62
+ const content = await Bun.file(`${import.meta.dirname}/../package.json`).text();
63
+ return JSON.parse(content).name;
64
+ }
65
+
66
+ function getPackageDir(): string {
67
+ return join(import.meta.dirname, "..");
68
+ }
69
+
70
+ export function getGlobalConfigPath(): string {
71
+ const xdgConfig = process.env.XDG_CONFIG_HOME;
72
+ if (xdgConfig) {
73
+ return join(xdgConfig, "opencode");
74
+ }
75
+ return join(homedir(), ".config", "opencode");
76
+ }
77
+
78
+ export function getLocalConfigPath(projectDir: string): string {
79
+ return join(projectDir, ".opencode");
80
+ }
81
+
82
+ async function copyDir(src: string, dest: string): Promise<void> {
83
+ await mkdir(dest, { recursive: true });
84
+ for (const entry of await readdir(src, { withFileTypes: true })) {
85
+ const s = join(src, entry.name);
86
+ const d = join(dest, entry.name);
87
+ if (entry.isDirectory()) {
88
+ await copyDir(s, d);
89
+ } else {
90
+ await Bun.write(d, Bun.file(s));
91
+ }
92
+ }
93
+ }
94
+
95
+ async function readJsonConfig(path: string): Promise<Record<string, unknown>> {
96
+ try {
97
+ const content = await readFile(path, "utf-8");
98
+ return JSON.parse(content);
99
+ } catch {
100
+ return {};
101
+ }
102
+ }
103
+
104
+ async function writeJsonConfig(path: string, config: Record<string, unknown>): Promise<void> {
105
+ await mkdir(join(path, ".."), { recursive: true });
106
+ await writeFile(path, JSON.stringify(config, null, 2));
107
+ }
108
+
109
+ async function ensureSkillPermission(configPath: string): Promise<void> {
110
+ const config = await readJsonConfig(configPath);
111
+ if (!config.permission) config.permission = {};
112
+ if (!(config.permission as Record<string, unknown>).skill) (config.permission as Record<string, unknown>).skill = {};
113
+ const skillPerms = (config.permission as Record<string, unknown>).skill as Record<string, unknown>;
114
+ if (skillPerms[SKILL_NAME] !== "allow") {
115
+ skillPerms[SKILL_NAME] = "allow";
116
+ await mkdir(join(configPath, ".."), { recursive: true });
117
+ await writeFile(configPath, JSON.stringify(config, null, 2));
118
+ }
119
+ }
120
+
121
+ async function removeSkillPermission(configPath: string): Promise<void> {
122
+ const config = await readJsonConfig(configPath);
123
+ if (config.permission && (config.permission as Record<string, unknown>).skill) {
124
+ const skillPerms = (config.permission as Record<string, unknown>).skill as Record<string, unknown>;
125
+ delete skillPerms[SKILL_NAME];
126
+ if (Object.keys(skillPerms).length === 0) delete (config.permission as Record<string, unknown>).skill;
127
+ if (Object.keys(config.permission as Record<string, unknown>).length === 0) delete config.permission;
128
+ await mkdir(join(configPath, ".."), { recursive: true });
129
+ await writeFile(configPath, JSON.stringify(config, null, 2));
130
+ }
131
+ }
132
+
133
+ async function addPluginToConfig(configPath: string, pluginName: string): Promise<boolean> {
134
+ const config = await readJsonConfig(configPath);
135
+ if (!config.plugin) config.plugin = [];
136
+ const plugins = config.plugin as string[];
137
+ if (plugins.includes(pluginName)) return false;
138
+ plugins.push(pluginName);
139
+ config.plugin = plugins;
140
+ await mkdir(join(configPath, ".."), { recursive: true });
141
+ await writeFile(configPath, JSON.stringify(config, null, 2));
142
+ return true;
143
+ }
144
+
145
+ async function removePluginFromConfig(configPath: string, pluginName: string): Promise<boolean> {
146
+ const config = await readJsonConfig(configPath);
147
+ if (!config.plugin) return false;
148
+ const plugins = config.plugin as string[];
149
+ const index = plugins.indexOf(pluginName);
150
+ if (index === -1) return false;
151
+ plugins.splice(index, 1);
152
+ if (plugins.length === 0) delete config.plugin;
153
+ else config.plugin = plugins;
154
+ await mkdir(join(configPath, ".."), { recursive: true });
155
+ await writeFile(configPath, JSON.stringify(config, null, 2));
156
+ return true;
157
+ }
158
+
159
+ async function isPluginInConfig(configPath: string, pluginName: string): Promise<boolean> {
160
+ const config = await readJsonConfig(configPath);
161
+ if (!config.plugin) return false;
162
+ return (config.plugin as string[]).includes(pluginName);
163
+ }
164
+
165
+ async function addMcpServer(configPath: string): Promise<boolean> {
166
+ return false;
167
+ }
168
+
169
+ export async function checkMigrationNeeded(projectDir: string): Promise<{
170
+ needed: boolean;
171
+ rootConfigPath: string;
172
+ dotOpenencodeConfigPath: string;
173
+ rootConfig: Record<string, unknown> | null;
174
+ dotOpenencodeConfig: Record<string, unknown> | null;
175
+ }> {
176
+ const rootConfigPath = join(projectDir, "opencode.json");
177
+ const dotOpenencodeConfigPath = join(projectDir, ".opencode", "opencode.json");
178
+ const rootExists = await exists(rootConfigPath);
179
+ const dotOpenencodeExists = await exists(dotOpenencodeConfigPath);
180
+ if (!rootExists) return { needed: false, rootConfigPath, dotOpenencodeConfigPath, rootConfig: null, dotOpenencodeConfig: null };
181
+ const rootConfig = await readJsonConfig(rootConfigPath);
182
+ const dotOpenencodeConfig = dotOpenencodeExists ? await readJsonConfig(dotOpenencodeConfigPath) : null;
183
+ return { needed: rootExists, rootConfigPath, dotOpenencodeConfigPath, rootConfig, dotOpenencodeConfig };
184
+ }
185
+
186
+ export async function migrateRootConfig(
187
+ projectDir: string,
188
+ onConflict?: (root: Record<string, unknown>, dot: Record<string, unknown>) => Promise<boolean>
189
+ ): Promise<boolean> {
190
+ const { needed, rootConfigPath, dotOpenencodeConfigPath, rootConfig, dotOpenencodeConfig } = await checkMigrationNeeded(projectDir);
191
+ if (!needed || !rootConfig) return false;
192
+ if (dotOpenencodeConfig) {
193
+ const hasConflict = Object.keys(rootConfig).some(key => key in dotOpenencodeConfig);
194
+ if (hasConflict && onConflict) {
195
+ const shouldContinue = await onConflict(rootConfig, dotOpenencodeConfig);
196
+ if (!shouldContinue) return false;
197
+ }
198
+ const merged = { ...rootConfig, ...dotOpenencodeConfig };
199
+ await writeJsonConfig(dotOpenencodeConfigPath, merged);
200
+ } else {
201
+ await mkdir(join(projectDir, ".opencode"), { recursive: true });
202
+ await writeJsonConfig(dotOpenencodeConfigPath, rootConfig);
203
+ }
204
+ await rm(rootConfigPath);
205
+ return true;
206
+ }
207
+
208
+ export async function install(
209
+ scope: Scope,
210
+ projectDir: string = process.cwd(),
211
+ options: InstallOptions = {}
212
+ ): Promise<InstallResult> {
213
+ const version = await getPackageVersion();
214
+ const packageName = await getPackageName();
215
+ const pkgDir = getPackageDir();
216
+ const { configurePermission = true, configureMcp = true, addPluginConfig = true } = options;
217
+ const configBase = scope === "global" ? getGlobalConfigPath() : getLocalConfigPath(projectDir);
218
+ const skillPath = join(configBase, "skills", SKILL_NAME);
219
+ const commandPath = join(configBase, "commands", COMMAND_NAME);
220
+ const configPath = join(configBase, "opencode.json");
221
+ const versionMarker = join(skillPath, ".version");
222
+ let migrated = false;
223
+ if (scope === "local") migrated = await migrateRootConfig(projectDir);
224
+ await copyDir(join(pkgDir, "assets", "skills", SKILL_NAME), skillPath);
225
+ await mkdir(join(configBase, "commands"), { recursive: true });
226
+ await Bun.write(commandPath, Bun.file(join(pkgDir, "assets", "commands", COMMAND_NAME)));
227
+ await Bun.write(versionMarker, version);
228
+ let permissionConfigured = false;
229
+ if (configurePermission) { await ensureSkillPermission(configPath); permissionConfigured = true; }
230
+ let mcpConfigured = false;
231
+ if (configureMcp) mcpConfigured = await addMcpServer(configPath);
232
+ let pluginAdded = false;
233
+ if (addPluginConfig) pluginAdded = await addPluginToConfig(configPath, packageName);
234
+ return { scope, skillPath, commandPath, configPath, migrated, permissionConfigured, mcpConfigured, pluginAdded };
235
+ }
236
+
237
+ export async function uninstall(scope: Scope, projectDir: string = process.cwd()): Promise<UninstallResult> {
238
+ const packageName = await getPackageName();
239
+ const configBase = scope === "global" ? getGlobalConfigPath() : getLocalConfigPath(projectDir);
240
+ const skillPath = join(configBase, "skills", SKILL_NAME);
241
+ const commandPath = join(configBase, "commands", COMMAND_NAME);
242
+ const configPath = join(configBase, "opencode.json");
243
+ const removed: string[] = [];
244
+ if (await exists(skillPath)) { await rm(skillPath, { recursive: true }); removed.push(skillPath); }
245
+ if ( await exists(commandPath)) { await rm(commandPath); removed.push(commandPath); }
246
+ let pluginRemoved = false;
247
+ if (await exists(configPath)) { await removeSkillPermission(configPath); pluginRemoved = await removePluginFromConfig(configPath, packageName); }
248
+ const skillsDir = join(configBase, "skills");
249
+ const commandsDir = join(configBase, "commands");
250
+ try { const skillsContents = await readdir(skillsDir); if (skillsContents.length === 0) await rm(skillsDir, { recursive: true }); } catch {}
251
+ try { const commandsContents = await readdir(commandsDir); if (commandsContents.length === 0) await rm(commandsDir, { recursive: true }); } catch {}
252
+ return { scope, removed, pluginRemoved };
253
+ }
254
+
255
+ export async function status(projectDir: string = process.cwd()): Promise<StatusResult> {
256
+ const version = await getPackageVersion();
257
+ const packageName = await getPackageName();
258
+ const localConfigPath = getLocalConfigPath(projectDir);
259
+ const localVersionMarker = join(localConfigPath, "skills", SKILL_NAME, ".version");
260
+ const localConfigFile = join(localConfigPath, "opencode.json");
261
+ const globalConfigPath = getGlobalConfigPath();
262
+ const globalVersionMarker = join(globalConfigPath, "skills", SKILL_NAME, ".version");
263
+ const globalConfigFile = join(globalConfigPath, "opencode.json");
264
+ let localStatus: { installed: boolean; version: string | null; pluginInConfig: boolean } | null = null;
265
+ let globalStatus: { installed: boolean; version: string | null; pluginInConfig: boolean } | null = null;
266
+ try { const localVersion = (await readFile(localVersionMarker, "utf-8")).trim(); const localPluginInConfig = await isPluginInConfig(localConfigFile, packageName); localStatus = { installed: true, version: localVersion, pluginInConfig: localPluginInConfig }; } catch { const skillPath = join(localConfigPath, "skills", SKILL_NAME); if (await exists(skillPath)) { const localPluginInConfig = await isPluginInConfig(localConfigFile, packageName); localStatus = { installed: true, version: null, pluginInConfig: localPluginInConfig }; } }
267
+ try { const globalVersion = (await readFile(globalVersionMarker, "utf-8")).trim(); const globalPluginInConfig = await isPluginInConfig(globalConfigFile, packageName); globalStatus = { installed: true, version: globalVersion, pluginInConfig: globalPluginInConfig }; } catch { const skillPath = join(globalConfigPath, "skills", SKILL_NAME); if (await exists(skillPath)) { const globalPluginInConfig = await isPluginInConfig(globalConfigFile, packageName); globalStatus = { installed: true, version: null, pluginInConfig: globalPluginInConfig }; } }
268
+ return { local: localStatus, global: globalStatus };
269
+ }
@@ -0,0 +1,60 @@
1
+ # Package Analysis Template
2
+
3
+ Use this template to document extension analysis before packaging.
4
+
5
+ ## Source Information
6
+
7
+ - **Source Path**: [path to .opencode/ or existing package]
8
+ - **Source Type**: [project-local | existing-package]
9
+ - **Target Package Name**: opencode-[name]
10
+
11
+ ## Extension Inventory
12
+
13
+ ### Skills
14
+ | Name | Description | Dependencies | Issues |
15
+ | ---- | ----------- | ------------ | ------ |
16
+ | | | | |
17
+
18
+ ### Commands
19
+ | Name | Description | Template Vars | Issues |
20
+ | ---- | ----------- | ------------- | ------ |
21
+ | | | | |
22
+
23
+ ### Agents
24
+ | Name | Description | Mode | Issues |
25
+ | ---- | ----------- | ---- | ------ |
26
+ | | | | |
27
+
28
+ ### Plugins
29
+ | File | Hooks Used | Merge Strategy | Issues |
30
+ | ---- | ---------- | -------------- | ------ |
31
+ | | | | |
32
+
33
+ ### Tools
34
+ | File | Tool Name | Merge Strategy | Issues |
35
+ | ---- | --------- | -------------- | ------ |
36
+ | | | | |
37
+
38
+ ## Dependencies
39
+
40
+ From `.opencode/package.json`:
41
+ - [list dependencies]
42
+
43
+ ## Packaging Assessment
44
+
45
+ - **Complexity**: [simple | medium | complex]
46
+ - **Requires Guidance**: [yes | no]
47
+ - **Guidance Needed For**: [plugins | tools | both | none]
48
+
49
+ ## Recommendations
50
+
51
+ 1. [recommendation 1]
52
+ 2. [recommendation 2]
53
+
54
+ ## Merge Decisions (if applicable)
55
+
56
+ ### Plugin Merge
57
+ - [decision and rationale]
58
+
59
+ ### Tool Merge
60
+ - [decision and rationale]
@@ -0,0 +1,28 @@
1
+ TEMPLATE INSTRUCTIONS
2
+ ====================
3
+ Replace the following placeholders before use:
4
+
5
+ PACKAGE NAME
6
+ "opencode-myextension" → your extension name (e.g., "opencode-mytool")
7
+ "myextension" → skill/extension identifier (e.g., "mytool")
8
+
9
+ VERSION
10
+ "1.0.0" → initial version
11
+
12
+ PATHS
13
+ "skills/myextension" → path to your skill assets
14
+ "commands/my-command.md" → path to your command file
15
+
16
+ METADATA
17
+ "Publisher Name" → your name or organization
18
+ "email@example.com" → your email
19
+ "https://github.com/org/opencode-myextension.git" → your repo URL
20
+
21
+ ---
22
+ {
23
+ "name": "opencode-myextension",
24
+ "version": "1.0.0",
25
+ "type": "module",
26
+ "module": "index.ts",
27
+ "files": ["index.ts", "plugin.ts", "assets"]
28
+ }
@@ -0,0 +1,56 @@
1
+ TEMPLATE INSTRUCTIONS
2
+ ====================
3
+ Replace the following placeholders before use:
4
+
5
+ PACKAGE NAME
6
+ "opencode-myextension" → your extension name (e.g., "opencode-mytool")
7
+ "myextension" → skill/extension identifier (e.g., "mytool")
8
+
9
+ VERSION
10
+ "1.0.0" → initial version
11
+
12
+ PATHS
13
+ "skills/myextension" → path to your skill assets
14
+ "commands/my-command.md" → path to your command file
15
+
16
+ METADATA
17
+ "Publisher Name" → your name or organization
18
+ "Author Name <email@example.com>" → your name and email
19
+ "email@example.com" → your support email
20
+ "https://github.com/org/opencode-myextension.git" → your repo URL
21
+
22
+ ---
23
+ {
24
+ "name": "opencode-myextension",
25
+ "version": "1.0.0",
26
+ "type": "module",
27
+ "module": "index.ts",
28
+ "bin": {
29
+ "opencode-myextension": "./src/cli.ts"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/org/opencode-myextension.git"
34
+ },
35
+ "publisher": "Publisher Name",
36
+ "author": "Author Name <email@example.com>",
37
+ "bugs": {
38
+ "url": "https://github.com/org/opencode-myextension/issues",
39
+ "email": "support@example.com"
40
+ },
41
+ "license": "MIT",
42
+ "files": ["index.ts", "plugin.ts", "src", "assets"],
43
+ "scripts": {
44
+ "check": "tsc --noEmit",
45
+ "test": "bun test"
46
+ },
47
+ "dependencies": {
48
+ "@inquirer/prompts": "^8.3.0",
49
+ "@opencode-ai/plugin": "*",
50
+ "@opencode-ai/sdk": "^1.2.20"
51
+ },
52
+ "devDependencies": {
53
+ "@types/bun": "latest",
54
+ "typescript": "^5.7.0"
55
+ }
56
+ }
@@ -0,0 +1,69 @@
1
+ TEMPLATE INSTRUCTIONS
2
+ ====================
3
+ Replace the following placeholders before use:
4
+
5
+ EXTENSION NAME
6
+ "myextension" → your extension identifier (e.g., "mytool")
7
+
8
+ SKILL NAME
9
+ "myextension" → must match the folder name in assets/skills/
10
+
11
+ COMMAND NAME
12
+ "my-command.md" → your command file name in assets/commands/
13
+
14
+ PACKAGE NAME
15
+ "./src/installer.ts" → path to installer module (for published packages)
16
+ Or inline install functions (for local packages)
17
+
18
+ ---
19
+ import type { Plugin } from "@opencode-ai/plugin";
20
+ import { install, getGlobalConfigPath, getPackageVersion, type Scope } from "./src/installer.ts";
21
+ import { join } from "node:path";
22
+
23
+ const plugin: Plugin = async ({ directory }) => ({
24
+ config: async () => {
25
+ const version = await getPackageVersion();
26
+ const globalConfigPath = getGlobalConfigPath();
27
+ const globalVersionMarker = join(globalConfigPath, "skills", "myextension", ".version");
28
+
29
+ const isGlobalInstall = directory === globalConfigPath ||
30
+ directory.startsWith(globalConfigPath + "/") ||
31
+ directory.startsWith(globalConfigPath + "\\");
32
+
33
+ let scope: Scope;
34
+
35
+ if (isGlobalInstall) {
36
+ scope = "global";
37
+ } else {
38
+ try {
39
+ const globalVersion = (await Bun.file(globalVersionMarker).text()).trim();
40
+ if (globalVersion === version) {
41
+ return;
42
+ }
43
+ } catch {}
44
+ scope = "local";
45
+ }
46
+
47
+ const marker = scope === "global"
48
+ ? globalVersionMarker
49
+ : join(directory, ".opencode", "skills", "myextension", ".version");
50
+
51
+ try {
52
+ const installedVersion = (await Bun.file(marker).text()).trim();
53
+ if (installedVersion === version) {
54
+ return;
55
+ }
56
+ } catch {}
57
+
58
+ const result = await install(scope, directory);
59
+
60
+ console.log(`\nOpenCode MyExtension installed ${scope === "global" ? "globally" : "locally"}:`);
61
+ console.log(` Skill: ${result.skillPath}`);
62
+ console.log(` Command: ${result.commandPath}`);
63
+ if (result.migrated) {
64
+ console.log(` Migrated: opencode.json → .opencode/opencode.json`);
65
+ }
66
+ },
67
+ });
68
+
69
+ export default plugin;
@@ -0,0 +1,28 @@
1
+ TEMPLATE INSTRUCTIONS
2
+ ====================
3
+ Replace the following placeholders before use:
4
+
5
+ EXTENSION NAME
6
+ "myextension" → your extension identifier
7
+
8
+ SKILL NAME
9
+ "myextension" → must match the skill folder name
10
+
11
+ ---
12
+ import { confirm } from "@inquirer/prompts";
13
+
14
+ export async function confirmOverwrite(message: string): Promise<boolean> {
15
+ return confirm({ message, default: false });
16
+ }
17
+
18
+ export async function confirmPermissionConfig(): Promise<boolean> {
19
+ return confirm({ message: "Configure skill permission (allow myextension)?", default: true });
20
+ }
21
+
22
+ export async function confirmMcpConfig(): Promise<boolean> {
23
+ return confirm({ message: "Configure MCP server?", default: true });
24
+ }
25
+
26
+ export async function confirmPluginConfig(): Promise<boolean> {
27
+ return confirm({ message: "Add plugin to opencode.json config?", default: true });
28
+ }
@@ -0,0 +1,46 @@
1
+ TEMPLATE INSTRUCTIONS
2
+ ====================
3
+ Replace the following placeholders before use:
4
+
5
+ EXTENSION NAME
6
+ "myextension" → your extension identifier
7
+
8
+ VERSION
9
+ "1.0.0" → initial version
10
+
11
+ TOPICS
12
+ "topic1" → your skill topics
13
+ "topic2"
14
+
15
+ DESCRIPTION
16
+ "Use this skill when the user asks about..." → your skill description
17
+
18
+ ---
19
+ ---
20
+ name: myextension
21
+ description: Use this skill when the user asks about...
22
+ license: MIT
23
+ compatibility: opencode
24
+ metadata:
25
+ version: 1.0.0
26
+ audience: agents
27
+ topic: [topic1, topic2]
28
+ ---
29
+
30
+ ## Activation Triggers
31
+
32
+ **USE this skill when user asks about:**
33
+ - Category: "Example query"
34
+
35
+ **DO NOT USE for:**
36
+ - Non-technical topics
37
+ - Known specific repos
38
+
39
+ ## Workflow Summary
40
+
41
+ <workflow>
42
+ <phase name="detect">Check available tools</phase>
43
+ <phase name="search">Execute search</phase>
44
+ <phase name="query">Query sources</phase>
45
+ <phase name="synthesize">Return answer</phase>
46
+ </workflow>
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["ESNext"],
4
+ "module": "esnext",
5
+ "target": "esnext",
6
+ "moduleResolution": "bundler",
7
+ "moduleDetection": "force",
8
+ "allowImportingTsExtensions": true,
9
+ "noEmit": true,
10
+ "strict": true,
11
+ "skipLibCheck": true,
12
+ "types": ["bun-types"]
13
+ },
14
+ "include": ["*.ts", "src/**/*.ts", "tests/**/*.ts"]
15
+ }
@@ -0,0 +1,9 @@
1
+ import type { Config } from "@opencode-ai/sdk";
2
+
3
+ export const command: NonNullable<Config["command"]>[string] = {
4
+ description: "Sync OpenCode docs into ~/.cache/opencode/opencode-architect/docs",
5
+ template:
6
+ "Use the sync-docs tool to fetch the latest OpenCode documentation into `~/.cache/opencode/opencode-architect/docs`.",
7
+ };
8
+
9
+ export default command;