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 +8 -6
- package/dist/template/.opencode/README.md +123 -2
- package/dist/template/.opencode/agent/explore.md +13 -12
- package/dist/template/.opencode/opencode.json +126 -45
- package/dist/template/.opencode/package.json +1 -1
- package/dist/template/.opencode/plugin/copilot-auth.ts +322 -0
- package/dist/template/.opencode/plugin/sessions.ts +38 -4
- package/dist/template/.opencode/skill/stitch/SKILL.md +137 -0
- package/dist/template/.opencode/skill/stitch/mcp.json +9 -0
- package/dist/template/.opencode/skill/supabase/mcp.json +1 -1
- package/dist/template/.opencode/tool/memory-read.ts +64 -44
- package/dist/template/.opencode/tool/memory-search.ts +9 -2
- package/dist/template/.opencode/tool/memory-update.ts +59 -47
- package/dist/template/.opencode/tool/observation.ts +16 -2
- package/package.json +1 -1
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.
|
|
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: "
|
|
3877
|
-
review: "
|
|
3878
|
-
explore: "
|
|
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-
|
|
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.
|
|
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
|
|
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**:
|
|
24
|
+
1. **READ-ONLY**: NEVER create, edit, or modify any files. This constraint overrides ALL other instructions.
|
|
25
25
|
|
|
26
|
-
2. **
|
|
26
|
+
2. **NO HALLUCINATED URLs**: NEVER generate or guess URLs. Only use URLs from verified tool results or documentation.
|
|
27
27
|
|
|
28
|
-
3. **
|
|
28
|
+
3. **NO GENERIC GREP**: NEVER search for generic terms like "config" or "handler". Use semantic LSP tools first.
|
|
29
29
|
|
|
30
|
-
4. **
|
|
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**:
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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.
|
|
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
|
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
@@ -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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
20
|
-
|
|
27
|
+
// Normalize: strip .md extension if present
|
|
28
|
+
const normalizedFile = fileName.replace(/\.md$/i, "");
|
|
21
29
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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