opencodekit 0.15.5 → 0.15.7

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.js CHANGED
@@ -750,7 +750,7 @@ var cac = (name = "") => new CAC(name);
750
750
  // package.json
751
751
  var package_default = {
752
752
  name: "opencodekit",
753
- version: "0.15.5",
753
+ version: "0.15.7",
754
754
  description: "CLI tool for bootstrapping and managing OpenCodeKit projects",
755
755
  keywords: ["agents", "cli", "mcp", "opencode", "opencodekit", "template"],
756
756
  license: "MIT",
@@ -3866,20 +3866,22 @@ var MODEL_PRESETS = {
3866
3866
  general: "opencode/glm-4.7-free",
3867
3867
  looker: "opencode/gpt-5-nano",
3868
3868
  vision: "opencode/gpt-5-nano",
3869
- scout: "opencode/big-pickle"
3869
+ scout: "opencode/big-pickle",
3870
+ compaction: "opencode/minimax-m2.1-free"
3870
3871
  }
3871
3872
  },
3872
3873
  recommend: {
3873
3874
  model: "proxypal/gemini-claude-opus-4-5-thinking",
3874
3875
  agents: {
3875
3876
  build: "proxypal/gemini-claude-opus-4-5-thinking",
3876
- plan: "proxypal/gemini-3-flash-preview",
3877
- review: "proxypal/gemini-3-pro-preview",
3878
- explore: "opencode/grok-code",
3877
+ plan: "openai/gpt-5.2",
3878
+ review: "openai/gpt-5.2-codex",
3879
+ explore: "proxypal/gemini-3-flash-preview",
3879
3880
  general: "opencode/glm-4.7-free",
3880
3881
  looker: "proxypal/gemini-3-flash-preview",
3881
3882
  vision: "proxypal/gemini-3-pro-preview",
3882
- scout: "proxypal/gemini-claude-sonnet-4-5"
3883
+ scout: "proxypal/gemini-3-flash-preview",
3884
+ compaction: "proxypal/gemini-2.5-flash"
3883
3885
  }
3884
3886
  }
3885
3887
  };
@@ -65,6 +65,10 @@ EXA_API_KEY=your_exa_api_key_here # Web search
65
65
  # Figma integration
66
66
  FIGMA_API_KEY=your_figma_api_key_here
67
67
 
68
+ # Google Stitch (AI UI design)
69
+ GOOGLE_CLOUD_PROJECT=your_gcp_project_id
70
+ STITCH_ACCESS_TOKEN=$(gcloud auth print-access-token)
71
+
68
72
  # Cloud deployments (devops skill)
69
73
  CLOUDFLARE_API_TOKEN=your_token_here
70
74
  CLOUDFLARE_ACCOUNT_ID=your_account_id_here
@@ -76,6 +80,7 @@ GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json
76
80
  - Context7: https://context7.com
77
81
  - Exa: https://exa.ai
78
82
  - Figma: https://www.figma.com/developers/api#access-tokens
83
+ - Google Cloud: https://console.cloud.google.com (for Stitch)
79
84
 
80
85
  ---
81
86
 
@@ -200,6 +205,122 @@ Plugins run automatically in every session:
200
205
  - No API key needed
201
206
  - Use: `skill({ name: "chrome-devtools" })` then `skill_mcp()`
202
207
 
208
+ 7. **stitch** - Google Stitch AI-powered UI design generation
209
+ - Requires: Google Cloud project with Stitch API enabled
210
+ - Tools: `stitch_list_projects`, `stitch_create_project`, `stitch_generate_screen_from_text`, etc.
211
+ - See: [Stitch MCP Setup](#stitch-mcp-setup) below
212
+
213
+ ---
214
+
215
+ ## Stitch MCP Setup
216
+
217
+ Google Stitch MCP enables AI-powered UI design generation directly from OpenCode.
218
+
219
+ ### Prerequisites
220
+
221
+ 1. **Google Cloud CLI** installed: https://cloud.google.com/sdk/docs/install
222
+ 2. **Google Cloud Project** with billing enabled
223
+
224
+ ### Setup Steps
225
+
226
+ **Step 1: Authenticate with Google Cloud**
227
+
228
+ ```bash
229
+ # Login to Google Cloud
230
+ gcloud auth login
231
+
232
+ # Set your project
233
+ gcloud config set project YOUR_PROJECT_ID
234
+
235
+ # Set up Application Default Credentials
236
+ gcloud auth application-default login
237
+ ```
238
+
239
+ **Step 2: Enable Stitch MCP**
240
+
241
+ ```bash
242
+ # Enable the Stitch API
243
+ gcloud services enable stitch.googleapis.com --project=YOUR_PROJECT_ID
244
+
245
+ # Enable Stitch for MCP access (required!)
246
+ gcloud beta services mcp enable stitch.googleapis.com --project=YOUR_PROJECT_ID
247
+ ```
248
+
249
+ **Step 3: Set Environment Variables**
250
+
251
+ Add to your `.opencode/.env` or export before starting OpenCode:
252
+
253
+ ```bash
254
+ # Google Cloud Project ID
255
+ GOOGLE_CLOUD_PROJECT=your-project-id
256
+
257
+ # Access token (refresh when expired - tokens last ~1 hour)
258
+ STITCH_ACCESS_TOKEN=$(gcloud auth print-access-token)
259
+ ```
260
+
261
+ **Step 4: Verify Configuration**
262
+
263
+ The Stitch MCP is pre-configured in `opencode.json`. After setting environment variables, restart OpenCode and test:
264
+
265
+ ```bash
266
+ # In OpenCode, call Stitch tools directly:
267
+ stitch_list_projects
268
+ stitch_create_project --title "My App"
269
+ ```
270
+
271
+ ### Available Stitch Tools
272
+
273
+ | Tool | Description |
274
+ | ---------------------------------- | --------------------------------- |
275
+ | `stitch_list_projects` | List all your Stitch projects |
276
+ | `stitch_create_project` | Create a new UI design project |
277
+ | `stitch_get_project` | Get project details |
278
+ | `stitch_list_screens` | List screens in a project |
279
+ | `stitch_get_screen` | Get screen details with HTML code |
280
+ | `stitch_generate_screen_from_text` | Generate UI from text prompt |
281
+
282
+ ### Example Usage
283
+
284
+ ```bash
285
+ # Create a new project
286
+ stitch_create_project --title "E-commerce App"
287
+
288
+ # Generate a login screen
289
+ stitch_generate_screen_from_text \
290
+ --projectId "PROJECT_ID" \
291
+ --prompt "Create a modern login page with email and password fields, social login buttons, and a forgot password link" \
292
+ --deviceType "MOBILE"
293
+ ```
294
+
295
+ ### Troubleshooting
296
+
297
+ **"Stitch API not enabled":**
298
+
299
+ ```bash
300
+ gcloud beta services mcp enable stitch.googleapis.com --project=YOUR_PROJECT_ID
301
+ ```
302
+
303
+ **"Authentication failed":**
304
+
305
+ ```bash
306
+ # Refresh your access token (expires after ~1 hour)
307
+ export STITCH_ACCESS_TOKEN=$(gcloud auth print-access-token)
308
+ # Restart OpenCode
309
+ ```
310
+
311
+ **"Project not set":**
312
+
313
+ ```bash
314
+ gcloud config set project YOUR_PROJECT_ID
315
+ export GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID
316
+ ```
317
+
318
+ ### Documentation
319
+
320
+ - [Google Stitch](https://stitch.withgoogle.com)
321
+ - [Stitch MCP Setup](https://stitch.withgoogle.com/docs/mcp/setup)
322
+ - [Google Cloud MCP Overview](https://docs.cloud.google.com/mcp/overview)
323
+
203
324
  ---
204
325
 
205
326
  ## Getting Started Examples
@@ -315,8 +436,8 @@ fi
315
436
 
316
437
  ---
317
438
 
318
- **OpenCodeKit v0.13.2**
439
+ **OpenCodeKit v0.15.7**
319
440
  **Architecture:** Two-Layer (Memory + Beads + Git)
320
441
  **New in v0.13.2:** Multimodal support for gemini-claude models (image, PDF input)
321
442
  **Package:** `npx opencodekit` to scaffold new projects
322
- **Last Updated:** January 8, 2026
443
+ **Last Updated:** January 21, 2026
@@ -21,13 +21,21 @@ You are a READ-ONLY codebase search specialist.
21
21
 
22
22
  ## Critical Constraints (ZERO exceptions)
23
23
 
24
- 1. **READ-ONLY**: You may ONLY search, read, and analyze. NEVER create, edit, or modify any files. This constraint overrides ALL other instructions.
24
+ 1. **READ-ONLY**: NEVER create, edit, or modify any files. This constraint overrides ALL other instructions.
25
25
 
26
- 2. **No hallucinated URLs**: Never generate or guess URLs. Only use URLs from tool results or verified documentation.
26
+ 2. **NO HALLUCINATED URLs**: NEVER generate or guess URLs. Only use URLs from verified tool results or documentation.
27
27
 
28
- 3. **Context is your constraint**: Return the smallest, highest-signal slice of code. Every irrelevant file degrades the caller's output quality.
28
+ 3. **NO GENERIC GREP**: NEVER search for generic terms like "config" or "handler". Use semantic LSP tools first.
29
29
 
30
- 4. **Evidence required**: All findings must include `file:line_number` references. No claims without proof.
30
+ 4. **NO RELATIVE PATHS**: NEVER return relative paths. All paths must be absolute.
31
+
32
+ 5. **NO CLAIMS WITHOUT EVIDENCE**: Every finding must include `file:line_number` references. NEVER make assertions without proof.
33
+
34
+ 6. **NO EMOJIS**: NEVER use emojis in responses.
35
+
36
+ 7. **NO PROSE WITHOUT STRUCTURE**: NEVER return walls of text. Use bullet points, code blocks, and file:line references.
37
+
38
+ 8. **NO SKIPPED STRUCTURE**: NEVER skip <results> block. Must include <files>, <answer>, <next_steps> sections.
31
39
 
32
40
  ## Tool Results & User Messages
33
41
 
@@ -52,14 +60,7 @@ File search specialist. Navigate and explore codebases efficiently.
52
60
  3. Use `lsp_lsp_find_references` to map usage before returning
53
61
  4. Only `read` the specific lines that matter
54
62
 
55
- **Avoid blind exploration**: Don't grep for generic terms. Use semantic tools first.
56
-
57
- ## Guidelines
58
-
59
- - Return file paths as absolute paths
60
- - Use `file:line_number` format for code references
61
- - Adapt approach based on thoroughness level
62
- - No emojis in responses
63
+ **Avoid blind exploration**: NEVER grep for generic terms. Use semantic tools first.
63
64
 
64
65
  ## Thoroughness Levels
65
66
 
@@ -6,7 +6,8 @@
6
6
  "model": "opencode/minimax-m2.1-free"
7
7
  },
8
8
  "compaction": {
9
- "description": "Session summarizer for context continuity across compactions"
9
+ "description": "Session summarizer for context continuity across compactions",
10
+ "model": "opencode/minimax-m2.1-free"
10
11
  },
11
12
  "explore": {
12
13
  "description": "Fast codebase search specialist",
@@ -113,6 +114,16 @@
113
114
  "enabled": true,
114
115
  "type": "remote",
115
116
  "url": "https://mcp.grep.app"
117
+ },
118
+ "stitch": {
119
+ "enabled": false,
120
+ "type": "remote",
121
+ "url": "https://stitch.googleapis.com/mcp",
122
+ "oauth": false,
123
+ "headers": {
124
+ "Authorization": "Bearer {env:STITCH_ACCESS_TOKEN}",
125
+ "X-Goog-User-Project": "{env:GOOGLE_CLOUD_PROJECT}"
126
+ }
116
127
  }
117
128
  },
118
129
  "model": "opencode/minimax-m2.1-free",
@@ -148,76 +159,146 @@
148
159
  },
149
160
  "plugin": [
150
161
  "@tarquinen/opencode-dcp@latest",
151
- "@franlol/opencode-md-table-formatter@0.0.3",
152
- "opencode-copilot-auth@0.0.11"
162
+ "@franlol/opencode-md-table-formatter@0.0.3"
153
163
  ],
154
164
  "provider": {
155
165
  "github-copilot": {
156
166
  "models": {
157
- "claude-haiku-4.5": {
167
+ "claude-opus-4.5": {
158
168
  "attachment": true,
159
- "options": {
160
- "thinking": {
161
- "budgetTokens": 16000,
162
- "type": "enabled"
163
- }
164
- },
165
169
  "reasoning": true,
166
170
  "temperature": true,
167
- "tool_call": true
171
+ "tool_call": true,
172
+ "variants": {
173
+ "high": {
174
+ "options": {
175
+ "thinking": {
176
+ "budgetTokens": 32000,
177
+ "type": "enabled"
178
+ }
179
+ }
180
+ },
181
+ "medium": {
182
+ "options": {
183
+ "thinking": {
184
+ "budgetTokens": 16000,
185
+ "type": "enabled"
186
+ }
187
+ }
188
+ }
189
+ }
168
190
  },
169
- "claude-opus-4.5": {
191
+ "claude-sonnet-4.5": {
170
192
  "attachment": true,
171
- "options": {
172
- "thinking": {
173
- "budgetTokens": 32000,
174
- "type": "enabled"
175
- }
176
- },
177
193
  "reasoning": true,
178
194
  "temperature": true,
179
- "tool_call": true
195
+ "tool_call": true,
196
+ "variants": {
197
+ "high": {
198
+ "options": {
199
+ "thinking": {
200
+ "budgetTokens": 16000,
201
+ "type": "enabled"
202
+ }
203
+ }
204
+ },
205
+ "medium": {
206
+ "options": {
207
+ "thinking": {
208
+ "budgetTokens": 8000,
209
+ "type": "enabled"
210
+ }
211
+ }
212
+ }
213
+ }
180
214
  },
181
- "claude-sonnet-4.5": {
215
+ "claude-haiku-4.5": {
182
216
  "attachment": true,
183
- "options": {
184
- "thinking": {
185
- "budgetTokens": 16000,
186
- "type": "enabled"
187
- }
188
- },
189
217
  "reasoning": true,
190
218
  "temperature": true,
191
- "tool_call": true
219
+ "tool_call": true,
220
+ "variants": {
221
+ "high": {
222
+ "options": {
223
+ "thinking": {
224
+ "budgetTokens": 32000,
225
+ "type": "enabled"
226
+ }
227
+ }
228
+ },
229
+ "medium": {
230
+ "options": {
231
+ "thinking": {
232
+ "budgetTokens": 16000,
233
+ "type": "enabled"
234
+ }
235
+ }
236
+ }
237
+ }
192
238
  },
193
- "gpt-5.1": {
239
+ "gpt-5-mini": {
194
240
  "attachment": true,
195
- "options": {
196
- "reasoning": {
197
- "effort": "high"
198
- }
199
- },
200
241
  "reasoning": true,
201
242
  "temperature": true,
202
- "tool_call": true
243
+ "tool_call": true,
244
+ "variants": {
245
+ "high": {
246
+ "options": {
247
+ "reasoning": {
248
+ "effort": "high"
249
+ }
250
+ }
251
+ },
252
+ "medium": {
253
+ "options": {
254
+ "reasoning": {
255
+ "effort": "medium"
256
+ }
257
+ }
258
+ },
259
+ "low": {
260
+ "options": {
261
+ "reasoning": {
262
+ "effort": "low"
263
+ }
264
+ }
265
+ }
266
+ }
203
267
  },
204
- "gpt-5.1-codex": {
268
+ "gpt-5.2-codex": {
205
269
  "attachment": true,
206
- "options": {
207
- "reasoning": {
208
- "effort": "high"
209
- }
210
- },
211
270
  "reasoning": true,
212
271
  "temperature": true,
213
- "tool_call": true
272
+ "tool_call": true,
273
+ "variants": {
274
+ "high": {
275
+ "options": {
276
+ "reasoning": {
277
+ "effort": "high"
278
+ }
279
+ }
280
+ },
281
+ "medium": {
282
+ "options": {
283
+ "reasoning": {
284
+ "effort": "medium"
285
+ }
286
+ }
287
+ },
288
+ "low": {
289
+ "options": {
290
+ "reasoning": {
291
+ "effort": "low"
292
+ }
293
+ }
294
+ }
295
+ }
214
296
  }
215
- },
216
- "npm": "@ai-sdk/anthropic"
297
+ }
217
298
  },
218
299
  "openai": {
219
300
  "models": {
220
- "gpt-5": {
301
+ "gpt-5.2": {
221
302
  "variants": {
222
303
  "fast": {
223
304
  "disabled": true
@@ -236,7 +317,7 @@
236
317
  }
237
318
  }
238
319
  },
239
- "gpt-5-codex": {
320
+ "gpt-5.2-codex": {
240
321
  "variants": {
241
322
  "fast": {
242
323
  "disabled": true
@@ -11,7 +11,7 @@
11
11
  "type-check": "tsc --noEmit"
12
12
  },
13
13
  "dependencies": {
14
- "@opencode-ai/plugin": "1.1.25"
14
+ "@opencode-ai/plugin": "1.1.28"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@types/node": "^25.0.3",
@@ -0,0 +1,322 @@
1
+ /**
2
+ * GitHub Copilot Auth Plugin
3
+ * Simplified auth provider without token expiration checks
4
+ */
5
+
6
+ import type { Plugin } from "@opencode-ai/plugin";
7
+
8
+ const CLIENT_ID = "Iv1.b507a08c87ecfe98";
9
+
10
+ const HEADERS = {
11
+ "User-Agent": "GitHubCopilotChat/0.35.0",
12
+ "Editor-Version": "vscode/1.107.0",
13
+ "Editor-Plugin-Version": "copilot-chat/0.35.0",
14
+ "Copilot-Integration-Id": "vscode-chat",
15
+ };
16
+
17
+ const RESPONSES_API_ALTERNATE_INPUT_TYPES = [
18
+ "file_search_call",
19
+ "computer_call",
20
+ "computer_call_output",
21
+ "web_search_call",
22
+ "function_call",
23
+ "function_call_output",
24
+ "image_generation_call",
25
+ "code_interpreter_call",
26
+ "local_shell_call",
27
+ "local_shell_call_output",
28
+ "mcp_list_tools",
29
+ "mcp_approval_request",
30
+ "mcp_approval_response",
31
+ "mcp_call",
32
+ "reasoning",
33
+ ];
34
+
35
+ function normalizeDomain(url: string): string {
36
+ return url.replace(/^https?:\/\//, "").replace(/\/$/, "");
37
+ }
38
+
39
+ function getUrls(domain: string) {
40
+ return {
41
+ DEVICE_CODE_URL: `https://${domain}/login/device/code`,
42
+ ACCESS_TOKEN_URL: `https://${domain}/login/oauth/access_token`,
43
+ COPILOT_API_KEY_URL: `https://api.github.com/copilot_internal/v2/token`,
44
+ };
45
+ }
46
+
47
+ export const CopilotAuthPlugin: Plugin = async ({ client }) => {
48
+ return {
49
+ auth: {
50
+ provider: "github-copilot",
51
+ loader: async (getAuth, provider) => {
52
+ const info = await getAuth();
53
+ if (!info || info.type !== "oauth") return {};
54
+
55
+ if (provider && provider.models) {
56
+ for (const model of Object.values(provider.models)) {
57
+ model.cost = {
58
+ input: 0,
59
+ output: 0,
60
+ cache: {
61
+ read: 0,
62
+ write: 0,
63
+ },
64
+ };
65
+ }
66
+ }
67
+
68
+ const enterpriseUrl = info.enterpriseUrl;
69
+ const baseURL = enterpriseUrl
70
+ ? `https://copilot-api.${normalizeDomain(enterpriseUrl)}`
71
+ : "https://api.githubcopilot.com";
72
+
73
+ return {
74
+ baseURL,
75
+ apiKey: "",
76
+ async fetch(input, init) {
77
+ const info = await getAuth();
78
+ if (info.type !== "oauth") return {};
79
+ if (!info.access) {
80
+ const domain = info.enterpriseUrl
81
+ ? normalizeDomain(info.enterpriseUrl)
82
+ : "github.com";
83
+ const urls = getUrls(domain);
84
+
85
+ const response = await fetch(urls.COPILOT_API_KEY_URL, {
86
+ headers: {
87
+ Accept: "application/json",
88
+ Authorization: `Bearer ${info.refresh}`,
89
+ ...HEADERS,
90
+ },
91
+ });
92
+
93
+ if (!response.ok) {
94
+ throw new Error(`Token refresh failed: ${response.status}`);
95
+ }
96
+
97
+ const tokenData = await response.json();
98
+
99
+ const saveProviderID = info.enterpriseUrl
100
+ ? "github-copilot-enterprise"
101
+ : "github-copilot";
102
+ await client.auth.set({
103
+ path: {
104
+ id: saveProviderID,
105
+ },
106
+ body: {
107
+ type: "oauth",
108
+ refresh: info.refresh,
109
+ access: tokenData.token,
110
+ expires: tokenData.expires_at * 1000 - 5 * 60 * 1000,
111
+ ...(info.enterpriseUrl && {
112
+ enterpriseUrl: info.enterpriseUrl,
113
+ }),
114
+ },
115
+ });
116
+ info.access = tokenData.token;
117
+ }
118
+
119
+ let isAgentCall = false;
120
+ let isVisionRequest = false;
121
+ try {
122
+ const body =
123
+ typeof init.body === "string"
124
+ ? JSON.parse(init.body)
125
+ : init.body;
126
+ if (body?.messages) {
127
+ isAgentCall = body.messages.some(
128
+ (msg: any) =>
129
+ msg.role && ["tool", "assistant"].includes(msg.role),
130
+ );
131
+ isVisionRequest = body.messages.some(
132
+ (msg: any) =>
133
+ Array.isArray(msg.content) &&
134
+ msg.content.some((part: any) => part.type === "image_url"),
135
+ );
136
+ }
137
+
138
+ if (body?.input) {
139
+ const lastInput = body.input[body.input.length - 1];
140
+
141
+ const isAssistant = lastInput?.role === "assistant";
142
+ const hasAgentType = lastInput?.type
143
+ ? RESPONSES_API_ALTERNATE_INPUT_TYPES.includes(lastInput.type)
144
+ : false;
145
+ isAgentCall = isAssistant || hasAgentType;
146
+
147
+ isVisionRequest =
148
+ Array.isArray(lastInput?.content) &&
149
+ lastInput.content.some(
150
+ (part: any) => part.type === "input_image",
151
+ );
152
+ }
153
+ } catch {}
154
+
155
+ const headers = {
156
+ ...init.headers,
157
+ ...HEADERS,
158
+ Authorization: `Bearer ${info.access}`,
159
+ "Openai-Intent": "conversation-edits",
160
+ "X-Initiator": isAgentCall ? "agent" : "user",
161
+ };
162
+ if (isVisionRequest) {
163
+ headers["Copilot-Vision-Request"] = "true";
164
+ }
165
+
166
+ delete headers["x-api-key"];
167
+ delete headers["authorization"];
168
+
169
+ return fetch(input, {
170
+ ...init,
171
+ headers,
172
+ });
173
+ },
174
+ };
175
+ },
176
+ methods: [
177
+ {
178
+ type: "oauth",
179
+ label: "Login with GitHub Copilot",
180
+ prompts: [
181
+ {
182
+ type: "select",
183
+ key: "deploymentType",
184
+ message: "Select GitHub deployment type",
185
+ options: [
186
+ {
187
+ label: "GitHub.com",
188
+ value: "github.com",
189
+ hint: "Public",
190
+ },
191
+ {
192
+ label: "GitHub Enterprise",
193
+ value: "enterprise",
194
+ hint: "Data residency or self-hosted",
195
+ },
196
+ ],
197
+ },
198
+ {
199
+ type: "text",
200
+ key: "enterpriseUrl",
201
+ message: "Enter your GitHub Enterprise URL or domain",
202
+ placeholder: "company.ghe.com or https://company.ghe.com",
203
+ condition: (inputs: any) =>
204
+ inputs.deploymentType === "enterprise",
205
+ validate: (value: string) => {
206
+ if (!value) return "URL or domain is required";
207
+ try {
208
+ const url = value.includes("://")
209
+ ? new URL(value)
210
+ : new URL(`https://${value}`);
211
+ if (!url.hostname)
212
+ return "Please enter a valid URL or domain";
213
+ return undefined;
214
+ } catch {
215
+ return "Please enter a valid URL (e.g., company.ghe.com or https://company.ghe.com)";
216
+ }
217
+ },
218
+ },
219
+ ],
220
+ async authorize(inputs: any = {}) {
221
+ const deploymentType = inputs.deploymentType || "github.com";
222
+
223
+ let domain = "github.com";
224
+ let actualProvider = "github-copilot";
225
+
226
+ if (deploymentType === "enterprise") {
227
+ const enterpriseUrl = inputs.enterpriseUrl;
228
+ domain = normalizeDomain(enterpriseUrl);
229
+ actualProvider = "github-copilot-enterprise";
230
+ }
231
+
232
+ const urls = getUrls(domain);
233
+
234
+ const deviceResponse = await fetch(urls.DEVICE_CODE_URL, {
235
+ method: "POST",
236
+ headers: {
237
+ Accept: "application/json",
238
+ "Content-Type": "application/json",
239
+ "User-Agent": "GitHubCopilotChat/0.35.0",
240
+ },
241
+ body: JSON.stringify({
242
+ client_id: CLIENT_ID,
243
+ scope: "read:user",
244
+ }),
245
+ });
246
+
247
+ if (!deviceResponse.ok) {
248
+ throw new Error("Failed to initiate device authorization");
249
+ }
250
+
251
+ const deviceData = await deviceResponse.json();
252
+
253
+ return {
254
+ url: deviceData.verification_uri,
255
+ instructions: `Enter code: ${deviceData.user_code}`,
256
+ method: "auto",
257
+ callback: async () => {
258
+ while (true) {
259
+ const response = await fetch(urls.ACCESS_TOKEN_URL, {
260
+ method: "POST",
261
+ headers: {
262
+ Accept: "application/json",
263
+ "Content-Type": "application/json",
264
+ "User-Agent": "GitHubCopilotChat/0.35.0",
265
+ },
266
+ body: JSON.stringify({
267
+ client_id: CLIENT_ID,
268
+ device_code: deviceData.device_code,
269
+ grant_type:
270
+ "urn:ietf:params:oauth:grant-type:device_code",
271
+ }),
272
+ });
273
+
274
+ if (!response.ok) return { type: "failed" };
275
+
276
+ const data = await response.json();
277
+
278
+ if (data.access_token) {
279
+ const result: {
280
+ type: "success";
281
+ refresh: string;
282
+ access: string;
283
+ expires: number;
284
+ provider?: string;
285
+ enterpriseUrl?: string;
286
+ } = {
287
+ type: "success",
288
+ refresh: data.access_token,
289
+ access: "",
290
+ expires: 0,
291
+ };
292
+
293
+ if (actualProvider === "github-copilot-enterprise") {
294
+ result.provider = "github-copilot-enterprise";
295
+ result.enterpriseUrl = domain;
296
+ }
297
+
298
+ return result;
299
+ }
300
+
301
+ if (data.error === "authorization_pending") {
302
+ await new Promise((resolve) =>
303
+ setTimeout(resolve, deviceData.interval * 1000),
304
+ );
305
+ continue;
306
+ }
307
+
308
+ if (data.error) return { type: "failed" };
309
+
310
+ await new Promise((resolve) =>
311
+ setTimeout(resolve, deviceData.interval * 1000),
312
+ );
313
+ continue;
314
+ }
315
+ },
316
+ };
317
+ },
318
+ },
319
+ ],
320
+ },
321
+ };
322
+ };
@@ -10,7 +10,16 @@ export const SessionsPlugin: Plugin = async ({ client }) => {
10
10
  return {
11
11
  tool: {
12
12
  list_sessions: tool({
13
- description: "List OpenCode sessions with metadata",
13
+ description: `List OpenCode sessions with metadata.
14
+
15
+ Purpose:
16
+ - Browse recent sessions by date
17
+ - Returns session ID, title, and creation time
18
+ - Default: last 20 sessions, most recent first
19
+
20
+ Example:
21
+ list_sessions({ since: "this week", limit: 10 })
22
+ list_sessions({}) // All recent sessions`,
14
23
  args: {
15
24
  since: tool.schema
16
25
  .string()
@@ -58,7 +67,16 @@ export const SessionsPlugin: Plugin = async ({ client }) => {
58
67
  }),
59
68
 
60
69
  read_session: tool({
61
- description: "Read session context for handoff or reference",
70
+ description: `Read session context for handoff or reference.
71
+
72
+ Purpose:
73
+ - Retrieve full session details and messages
74
+ - Use "last" to get most recent session
75
+ - Optional focus keyword to filter messages
76
+
77
+ Example:
78
+ read_session({ session_reference: "last" })
79
+ read_session({ session_reference: "abc123", focus: "auth" })`,
62
80
  args: {
63
81
  session_reference: tool.schema
64
82
  .string()
@@ -139,7 +157,15 @@ export const SessionsPlugin: Plugin = async ({ client }) => {
139
157
  }),
140
158
 
141
159
  search_session: tool({
142
- description: "Full-text search across session messages",
160
+ description: `Full-text search across session messages.
161
+
162
+ Purpose:
163
+ - Find sessions containing specific keywords
164
+ - Searches up to 50 sessions (configurable via limit)
165
+ - Returns match count and excerpt
166
+
167
+ Example:
168
+ search_session({ query: "authentication", limit: 5 })`,
143
169
  args: {
144
170
  query: tool.schema.string().describe("Search query text"),
145
171
  limit: tool.schema
@@ -197,7 +223,15 @@ export const SessionsPlugin: Plugin = async ({ client }) => {
197
223
  }),
198
224
 
199
225
  summarize_session: tool({
200
- description: "Generate AI summary of a session",
226
+ description: `Generate AI summary of a session.
227
+
228
+ Purpose:
229
+ - Request OpenCode to summarize a session
230
+ - Summary is generated asynchronously
231
+ - Check back for the summary result
232
+
233
+ Example:
234
+ summarize_session({ session_id: "abc123" })`,
201
235
  args: {
202
236
  session_id: tool.schema.string().describe("Session ID to summarize"),
203
237
  },
@@ -0,0 +1,137 @@
1
+ ---
2
+ name: stitch
3
+ description: Google Stitch MCP for AI-powered UI design. Extract design context, generate code from designs, and create screens from descriptions. Use when working with Stitch designs and UI generation.
4
+ ---
5
+
6
+ # Google Stitch MCP
7
+
8
+ Access Google Stitch UI designs directly through the Model Context Protocol.
9
+
10
+ ## Overview
11
+
12
+ Stitch MCP is Google's official Model Context Protocol server for interacting with Google Stitch designs. It allows AI agents to extract design context (colors, typography, spacing), generate production-ready code from designs, and create new designs from text descriptions.
13
+
14
+ ## Endpoint
15
+
16
+ **MCP Server URL**: `https://stitch.googleapis.com/mcp`
17
+
18
+ **Authentication**: Google Cloud credentials with Bearer token and project ID header
19
+
20
+ ## Prerequisites
21
+
22
+ 1. **Google Cloud Project** with Stitch API enabled
23
+ 2. **Google Cloud CLI** (`gcloud`) installed and initialized
24
+ 3. **Required IAM Roles**:
25
+ - `roles/serviceusage.serviceUsageAdmin` (to enable the service)
26
+ - `roles/mcp.toolUser` (to call MCP tools)
27
+
28
+ ## Setup Steps
29
+
30
+ ### 1. Enable Stitch API in Google Cloud
31
+
32
+ ```bash
33
+ # Set your Google Cloud project
34
+ gcloud config set project PROJECT_ID
35
+
36
+ # Enable the Stitch MCP service
37
+ gcloud beta services mcp enable stitch.googleapis.com --project=PROJECT_ID
38
+ ```
39
+
40
+ ### 2. Get Your Access Token
41
+
42
+ ```bash
43
+ # Authenticate with Google Cloud
44
+ gcloud auth login
45
+
46
+ # Get your access token
47
+ gcloud auth print-access-token
48
+ ```
49
+
50
+ ### 3. Set Environment Variables
51
+
52
+ ```bash
53
+ # Set your Google Cloud project ID
54
+ export GOOGLE_CLOUD_PROJECT="your-project-id"
55
+
56
+ # Get and set your access token
57
+ export STITCH_ACCESS_TOKEN=$(gcloud auth print-access-token)
58
+ ```
59
+
60
+ ### 4. Configuration
61
+
62
+ Stitch MCP is pre-configured in `opencode.json` as a default MCP server. Once environment variables are set, restart OpenCode and the tools will be available.
63
+
64
+ ## Available Tools
65
+
66
+ Once configured, the Stitch MCP exposes the following tools:
67
+
68
+ | Tool | Description |
69
+ | ---------------------------------- | --------------------------------- |
70
+ | `stitch_list_projects` | List all your Stitch projects |
71
+ | `stitch_create_project` | Create a new UI design project |
72
+ | `stitch_get_project` | Get project details |
73
+ | `stitch_list_screens` | List screens in a project |
74
+ | `stitch_get_screen` | Get screen details with HTML code |
75
+ | `stitch_generate_screen_from_text` | Generate UI from text prompt |
76
+
77
+ ## Usage Examples
78
+
79
+ ### List Projects
80
+
81
+ ```typescript
82
+ stitch_list_projects({});
83
+ ```
84
+
85
+ ### Create a Project
86
+
87
+ ```typescript
88
+ stitch_create_project({
89
+ title: "My E-commerce App",
90
+ });
91
+ ```
92
+
93
+ ### Generate Screen from Text
94
+
95
+ ```typescript
96
+ stitch_generate_screen_from_text({
97
+ projectId: "my-project-123",
98
+ prompt:
99
+ "Create a modern login page with email and password fields, social login buttons, and a forgot password link",
100
+ deviceType: "MOBILE",
101
+ });
102
+ ```
103
+
104
+ ## Troubleshooting
105
+
106
+ ### "Stitch API not enabled"
107
+
108
+ ```bash
109
+ gcloud beta services mcp enable stitch.googleapis.com --project=YOUR_PROJECT_ID
110
+ ```
111
+
112
+ ### "Authentication failed"
113
+
114
+ ```bash
115
+ # Refresh your access token (expires after ~1 hour)
116
+ export STITCH_ACCESS_TOKEN=$(gcloud auth print-access-token)
117
+ # Restart OpenCode
118
+ ```
119
+
120
+ ### "Project not set"
121
+
122
+ ```bash
123
+ gcloud config set project YOUR_PROJECT_ID
124
+ export GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID
125
+ ```
126
+
127
+ ## Documentation
128
+
129
+ - [Google Stitch](https://stitch.withgoogle.com)
130
+ - [Stitch MCP Setup](https://stitch.withgoogle.com/docs/mcp/setup)
131
+ - [Google Cloud MCP Overview](https://docs.cloud.google.com/mcp/overview)
132
+
133
+ ## Tips
134
+
135
+ - Access tokens expire after ~1 hour, refresh with `gcloud auth print-access-token`
136
+ - Use descriptive prompts for better UI generation results
137
+ - Test generated code in your target framework before production use
@@ -0,0 +1,9 @@
1
+ {
2
+ "stitch": {
3
+ "serverUrl": "https://stitch.googleapis.com/mcp",
4
+ "headers": {
5
+ "Authorization": "Bearer ${STITCH_ACCESS_TOKEN}",
6
+ "X-Goog-User-Project": "${GOOGLE_CLOUD_PROJECT}"
7
+ }
8
+ }
9
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "supabase": {
3
3
  "command": "npx",
4
- "args": ["-y", "@supabase/mcp@latest"],
4
+ "args": ["-y", "@supabase/mcp-server-supabase@latest"],
5
5
  "env": {
6
6
  "SUPABASE_ACCESS_TOKEN": "${SUPABASE_ACCESS_TOKEN}"
7
7
  },
@@ -3,52 +3,72 @@ import path from "node:path";
3
3
  import { tool } from "@opencode-ai/plugin";
4
4
 
5
5
  export default tool({
6
- description:
7
- "Read memory files for persistent cross-session context. Returns current project state, learnings, and active tasks. Supports subdirectories (e.g., 'research/opencode-sessions').",
8
- args: {
9
- file: tool.schema
10
- .string()
11
- .optional()
12
- .describe(
13
- "Memory file to read: handoffs/YYYY-MM-DD-phase, research/YYYY-MM-DD-topic, _templates/task-prd, _templates/task-spec, _templates/task-review, _templates/research, _templates/handoff",
14
- ),
15
- },
16
- execute: async (args: { file?: string }) => {
17
- const fileName = args.file || "memory";
6
+ description: `Read memory files for persistent cross-session context.
7
+
8
+ Purpose:
9
+ - Retrieve project state, learnings, and active tasks
10
+ - Checks locations in priority order: project → global → legacy
11
+ - Supports subdirectories: handoffs/, research/, observations/, _templates/
12
+
13
+ Example:
14
+ memory-read({ file: "handoffs/2024-01-20-phase-1" })
15
+ memory-read({ file: "_templates/task-prd" })`,
16
+ args: {
17
+ file: tool.schema
18
+ .string()
19
+ .optional()
20
+ .describe(
21
+ "Memory file to read: handoffs/YYYY-MM-DD-phase, research/YYYY-MM-DD-topic, _templates/task-prd, _templates/task-spec, _templates/task-review, _templates/research, _templates/handoff",
22
+ ),
23
+ },
24
+ execute: async (args: { file?: string }) => {
25
+ const fileName = args.file || "memory";
18
26
 
19
- // Normalize: strip .md extension if present
20
- const normalizedFile = fileName.replace(/\.md$/i, "");
27
+ // Normalize: strip .md extension if present
28
+ const normalizedFile = fileName.replace(/\.md$/i, "");
21
29
 
22
- // Location priority: project > global > legacy
23
- const locations = [
24
- path.join(process.cwd(), ".opencode/memory", `${normalizedFile}.md`),
25
- path.join(process.env.HOME || "", ".config/opencode/memory", `${normalizedFile}.md`),
26
- path.join(process.cwd(), ".config/opencode/memory", `${normalizedFile}.md`),
27
- ];
30
+ // Location priority: project > global > legacy
31
+ const locations = [
32
+ path.join(process.cwd(), ".opencode/memory", `${normalizedFile}.md`),
33
+ path.join(
34
+ process.env.HOME || "",
35
+ ".config/opencode/memory",
36
+ `${normalizedFile}.md`,
37
+ ),
38
+ path.join(
39
+ process.cwd(),
40
+ ".config/opencode/memory",
41
+ `${normalizedFile}.md`,
42
+ ),
43
+ ];
28
44
 
29
- // Try each location in order
30
- for (const filePath of locations) {
31
- try {
32
- const content = await fs.readFile(filePath, "utf-8");
33
- const locationLabel = filePath.includes(".opencode/memory")
34
- ? "project"
35
- : filePath.includes(process.env.HOME || "")
36
- ? "global"
37
- : "legacy";
38
- return `[Read from ${locationLabel}: ${filePath}]\n\n${content}`;
39
- } catch (error) {
40
- // Continue to next location if file not found
41
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
42
- continue;
43
- }
44
- // Other errors should be reported
45
- if (error instanceof Error) {
46
- return `Error reading memory from ${filePath}: ${error.message}`;
47
- }
48
- }
49
- }
45
+ // Try each location in order
46
+ for (const filePath of locations) {
47
+ try {
48
+ const content = await fs.readFile(filePath, "utf-8");
49
+ const locationLabel = filePath.includes(".opencode/memory")
50
+ ? "project"
51
+ : filePath.includes(process.env.HOME || "")
52
+ ? "global"
53
+ : "legacy";
54
+ return `[Read from ${locationLabel}: ${filePath}]\n\n${content}`;
55
+ } catch (error) {
56
+ // Continue to next location if file not found
57
+ if (
58
+ error instanceof Error &&
59
+ "code" in error &&
60
+ error.code === "ENOENT"
61
+ ) {
62
+ continue;
63
+ }
64
+ // Other errors should be reported
65
+ if (error instanceof Error) {
66
+ return `Error reading memory from ${filePath}: ${error.message}`;
67
+ }
68
+ }
69
+ }
50
70
 
51
- // No file found in any location
52
- return `Memory file '${normalizedFile}.md' not found in any location.\nSearched:\n- ${locations.join("\n- ")}\n\nStructure:\n- handoffs/YYYY-MM-DD-phase (phase transitions)\n- research/YYYY-MM-DD-topic (research findings)\n- _templates/ (prd, spec, review, research, handoff)`;
53
- },
71
+ // No file found in any location
72
+ return `Memory file '${normalizedFile}.md' not found in any location.\nSearched:\n- ${locations.join("\n- ")}\n\nStructure:\n- handoffs/YYYY-MM-DD-phase (phase transitions)\n- research/YYYY-MM-DD-topic (research findings)\n- _templates/ (prd, spec, review, research, handoff)`;
73
+ },
54
74
  });
@@ -127,8 +127,15 @@ function formatKeywordResults(
127
127
  }
128
128
 
129
129
  export default tool({
130
- description:
131
- "Search across all memory files using keywords. Returns matching files with context. Useful for finding past decisions, research, or handoffs.",
130
+ description: `Search across all memory files using keywords.
131
+
132
+ Purpose:
133
+ - Find past decisions, research, or handoffs
134
+ - Searches: project memory, beads artifacts, and global memory
135
+ - Returns file paths with matched lines and context
136
+
137
+ Example:
138
+ memory-search({ query: "authentication", type: "all", limit: 10 })`,
132
139
  args: {
133
140
  query: tool.schema
134
141
  .string()
@@ -3,54 +3,66 @@ import path from "node:path";
3
3
  import { tool } from "@opencode-ai/plugin";
4
4
 
5
5
  export default tool({
6
- description:
7
- "Update memory files with new learnings, progress, or context. Appends or replaces content based on mode. Supports subdirectories for organization (e.g., 'research/opencode-sessions' creates .opencode/memory/research/opencode-sessions.md).",
8
- args: {
9
- file: tool.schema
10
- .string()
11
- .describe(
12
- "Memory file to update: handoffs/YYYY-MM-DD-phase, research/YYYY-MM-DD-topic. Use _templates/ for reference only.",
13
- ),
14
- content: tool.schema.string().describe("Content to write or append to the memory file"),
15
- mode: tool.schema
16
- .string()
17
- .optional()
18
- .default("replace")
19
- .describe("Update mode: 'replace' (overwrite file) or 'append' (add to end)."),
20
- },
21
- execute: async (args: { file: string; content: string; mode?: string }) => {
22
- // Always write to project memory (.opencode/memory/)
23
- const memoryDir = path.join(process.cwd(), ".opencode/memory");
6
+ description: `Update memory files with new learnings, progress, or context.
7
+
8
+ Purpose:
9
+ - Write or append to project memory in .opencode/memory/
10
+ - Supports subdirectories (e.g., 'research/2024-01-topic')
11
+ - Two modes: 'replace' (overwrite) or 'append' (add to end)
12
+
13
+ Example:
14
+ memory-update({ file: "research/session-findings", content: "..." })
15
+ memory-update({ file: "handoffs/phase-2", content: "...", mode: "append" })`,
16
+ args: {
17
+ file: tool.schema
18
+ .string()
19
+ .describe(
20
+ "Memory file to update: handoffs/YYYY-MM-DD-phase, research/YYYY-MM-DD-topic. Use _templates/ for reference only.",
21
+ ),
22
+ content: tool.schema
23
+ .string()
24
+ .describe("Content to write or append to the memory file"),
25
+ mode: tool.schema
26
+ .string()
27
+ .optional()
28
+ .default("replace")
29
+ .describe(
30
+ "Update mode: 'replace' (overwrite file) or 'append' (add to end).",
31
+ ),
32
+ },
33
+ execute: async (args: { file: string; content: string; mode?: string }) => {
34
+ // Always write to project memory (.opencode/memory/)
35
+ const memoryDir = path.join(process.cwd(), ".opencode/memory");
24
36
 
25
- // Normalize file path: strip existing .md extension, handle subdirectories
26
- const normalizedFile = args.file.replace(/\.md$/i, ""); // Remove .md if present
27
- const filePath = path.join(memoryDir, `${normalizedFile}.md`);
28
- const mode = args.mode || "replace";
37
+ // Normalize file path: strip existing .md extension, handle subdirectories
38
+ const normalizedFile = args.file.replace(/\.md$/i, ""); // Remove .md if present
39
+ const filePath = path.join(memoryDir, `${normalizedFile}.md`);
40
+ const mode = args.mode || "replace";
29
41
 
30
- try {
31
- // Ensure parent directory exists (handles subdirectories)
32
- const fileDir = path.dirname(filePath);
33
- await fs.mkdir(fileDir, { recursive: true });
42
+ try {
43
+ // Ensure parent directory exists (handles subdirectories)
44
+ const fileDir = path.dirname(filePath);
45
+ await fs.mkdir(fileDir, { recursive: true });
34
46
 
35
- if (mode === "append") {
36
- const timestamp = new Date().toISOString();
37
- const appendContent = `\n\n---\n**Updated:** ${timestamp}\n\n${args.content}`;
38
- await fs.appendFile(filePath, appendContent, "utf-8");
39
- return `Successfully appended to ${normalizedFile}.md\n[Written to: ${filePath}]`;
40
- }
41
- // Replace mode - update timestamp
42
- const timestamp = new Date().toISOString();
43
- const updatedContent = args.content.replace(
44
- /\*\*Last Updated:\*\* \[Timestamp\]/,
45
- `**Last Updated:** ${timestamp}`,
46
- );
47
- await fs.writeFile(filePath, updatedContent, "utf-8");
48
- return `Successfully updated ${normalizedFile}.md\n[Written to: ${filePath}]`;
49
- } catch (error) {
50
- if (error instanceof Error) {
51
- return `Error updating memory: ${error.message}`;
52
- }
53
- return "Unknown error updating memory file";
54
- }
55
- },
47
+ if (mode === "append") {
48
+ const timestamp = new Date().toISOString();
49
+ const appendContent = `\n\n---\n**Updated:** ${timestamp}\n\n${args.content}`;
50
+ await fs.appendFile(filePath, appendContent, "utf-8");
51
+ return `Successfully appended to ${normalizedFile}.md\n[Written to: ${filePath}]`;
52
+ }
53
+ // Replace mode - update timestamp
54
+ const timestamp = new Date().toISOString();
55
+ const updatedContent = args.content.replace(
56
+ /\*\*Last Updated:\*\* \[Timestamp\]/,
57
+ `**Last Updated:** ${timestamp}`,
58
+ );
59
+ await fs.writeFile(filePath, updatedContent, "utf-8");
60
+ return `Successfully updated ${normalizedFile}.md\n[Written to: ${filePath}]`;
61
+ } catch (error) {
62
+ if (error instanceof Error) {
63
+ return `Error updating memory: ${error.message}`;
64
+ }
65
+ return "Unknown error updating memory file";
66
+ }
67
+ },
56
68
  });
@@ -73,8 +73,22 @@ function extractFileReferences(content: string): FileReference[] {
73
73
  }
74
74
 
75
75
  export default tool({
76
- description:
77
- "Create a structured observation for future reference. Observations are categorized by type (decision, bugfix, feature, pattern, discovery, learning, warning) and stored in .opencode/memory/observations/.",
76
+ description: `Create a structured observation for future reference.
77
+
78
+ Purpose:
79
+ - Capture decisions, bugs, features, patterns, discoveries, learnings, or warnings
80
+ - Auto-detects file references from content (file:line, \`path\`, src/, .opencode/)
81
+ - Stores in .opencode/memory/observations/ with YAML frontmatter
82
+ - Optionally updates bead notes and handles observation supersession
83
+
84
+ Example:
85
+ observation({
86
+ type: "decision",
87
+ title: "Use JWT for auth",
88
+ content: "Decided to use JWT tokens because...",
89
+ concepts: "authentication, jwt, security",
90
+ confidence: "high"
91
+ })`,
78
92
  args: {
79
93
  type: tool.schema
80
94
  .string()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencodekit",
3
- "version": "0.15.5",
3
+ "version": "0.15.7",
4
4
  "description": "CLI tool for bootstrapping and managing OpenCodeKit projects",
5
5
  "keywords": ["agents", "cli", "mcp", "opencode", "opencodekit", "template"],
6
6
  "license": "MIT",