opencodekit 0.13.2 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +50 -3
- package/dist/template/.opencode/AGENTS.md +51 -7
- package/dist/template/.opencode/README.md +98 -2
- package/dist/template/.opencode/agent/build.md +44 -1
- package/dist/template/.opencode/agent/explore.md +1 -0
- package/dist/template/.opencode/agent/planner.md +40 -1
- package/dist/template/.opencode/agent/review.md +1 -0
- package/dist/template/.opencode/agent/rush.md +35 -0
- package/dist/template/.opencode/agent/scout.md +1 -0
- package/dist/template/.opencode/command/brainstorm.md +83 -5
- package/dist/template/.opencode/command/finish.md +39 -12
- package/dist/template/.opencode/command/fix.md +24 -15
- package/dist/template/.opencode/command/handoff.md +17 -0
- package/dist/template/.opencode/command/implement.md +81 -18
- package/dist/template/.opencode/command/import-plan.md +30 -8
- package/dist/template/.opencode/command/new-feature.md +37 -4
- package/dist/template/.opencode/command/plan.md +51 -1
- package/dist/template/.opencode/command/pr.md +25 -15
- package/dist/template/.opencode/command/research.md +61 -5
- package/dist/template/.opencode/command/resume.md +31 -0
- package/dist/template/.opencode/command/revert-feature.md +15 -3
- package/dist/template/.opencode/command/skill-optimize.md +71 -7
- package/dist/template/.opencode/command/start.md +81 -5
- package/dist/template/.opencode/command/triage.md +16 -1
- package/dist/template/.opencode/dcp.jsonc +11 -7
- package/dist/template/.opencode/memory/observations/.gitkeep +0 -0
- package/dist/template/.opencode/memory/observations/2026-01-09-pattern-ampcode-mcp-json-includetools-pattern.md +42 -0
- package/dist/template/.opencode/memory/project/conventions.md +31 -0
- package/dist/template/.opencode/memory/project/gotchas.md +52 -5
- package/dist/template/.opencode/memory/vector_db/memories.lance/_transactions/0-0d25ba80-ba3b-4209-9046-b45d6093b4da.txn +0 -0
- package/dist/template/.opencode/memory/vector_db/memories.lance/_versions/1.manifest +0 -0
- package/dist/template/.opencode/memory/vector_db/memories.lance/data/1111100101010101011010004a9ef34df6b29f36a9a53a2892.lance +0 -0
- package/dist/template/.opencode/opencode.json +5 -3
- package/dist/template/.opencode/package.json +3 -1
- package/dist/template/.opencode/plugin/memory.ts +686 -0
- package/dist/template/.opencode/plugin/package.json +1 -1
- package/dist/template/.opencode/plugin/skill-mcp.ts +155 -36
- package/dist/template/.opencode/skill/chrome-devtools/SKILL.md +43 -65
- package/dist/template/.opencode/skill/chrome-devtools/mcp.json +19 -0
- package/dist/template/.opencode/skill/executing-plans/SKILL.md +32 -2
- package/dist/template/.opencode/skill/finishing-a-development-branch/SKILL.md +42 -17
- package/dist/template/.opencode/skill/playwright/SKILL.md +58 -133
- package/dist/template/.opencode/skill/playwright/mcp.json +16 -0
- package/dist/template/.opencode/tool/memory-embed.ts +183 -0
- package/dist/template/.opencode/tool/memory-index.ts +769 -0
- package/dist/template/.opencode/tool/memory-search.ts +358 -66
- package/dist/template/.opencode/tool/observation.ts +301 -12
- package/dist/template/.opencode/tool/repo-map.ts +451 -0
- package/package.json +1 -1
|
@@ -25,6 +25,7 @@ npm test / cargo test / pytest / go test ./...
|
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
**If tests fail:**
|
|
28
|
+
|
|
28
29
|
```
|
|
29
30
|
Tests failing (<N> failures). Must fix before completing:
|
|
30
31
|
|
|
@@ -48,17 +49,29 @@ Or ask: "This branch split from main - is that correct?"
|
|
|
48
49
|
|
|
49
50
|
### Step 3: Present Options
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
52
|
+
Use question tool to present completion options:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
question({
|
|
56
|
+
questions: [
|
|
57
|
+
{
|
|
58
|
+
header: "Complete",
|
|
59
|
+
question: "Implementation complete. What would you like to do?",
|
|
60
|
+
options: [
|
|
61
|
+
{
|
|
62
|
+
label: "Merge locally (Recommended)",
|
|
63
|
+
description: "Merge to base branch",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
label: "Push & create PR",
|
|
67
|
+
description: "Create pull request for review",
|
|
68
|
+
},
|
|
69
|
+
{ label: "Keep branch", description: "I'll handle it later" },
|
|
70
|
+
{ label: "Discard work", description: "Delete this branch" },
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
});
|
|
62
75
|
```
|
|
63
76
|
|
|
64
77
|
**Don't add explanation** - keep options concise.
|
|
@@ -114,6 +127,7 @@ Report: "Keeping branch <name>. Worktree preserved at <path>."
|
|
|
114
127
|
#### Option 4: Discard
|
|
115
128
|
|
|
116
129
|
**Confirm first:**
|
|
130
|
+
|
|
117
131
|
```
|
|
118
132
|
This will permanently delete:
|
|
119
133
|
- Branch <name>
|
|
@@ -126,6 +140,7 @@ Type 'discard' to confirm.
|
|
|
126
140
|
Wait for exact confirmation.
|
|
127
141
|
|
|
128
142
|
If confirmed:
|
|
143
|
+
|
|
129
144
|
```bash
|
|
130
145
|
git checkout <base-branch>
|
|
131
146
|
git branch -D <feature-branch>
|
|
@@ -138,11 +153,13 @@ Then: Cleanup worktree (Step 5)
|
|
|
138
153
|
**For Options 1, 2, 4:**
|
|
139
154
|
|
|
140
155
|
Check if in worktree:
|
|
156
|
+
|
|
141
157
|
```bash
|
|
142
158
|
git worktree list | grep $(git branch --show-current)
|
|
143
159
|
```
|
|
144
160
|
|
|
145
161
|
If yes:
|
|
162
|
+
|
|
146
163
|
```bash
|
|
147
164
|
git worktree remove <worktree-path>
|
|
148
165
|
```
|
|
@@ -151,40 +168,46 @@ git worktree remove <worktree-path>
|
|
|
151
168
|
|
|
152
169
|
## Quick Reference
|
|
153
170
|
|
|
154
|
-
| Option
|
|
155
|
-
|
|
156
|
-
| 1. Merge locally | ✓
|
|
157
|
-
| 2. Create PR
|
|
158
|
-
| 3. Keep as-is
|
|
159
|
-
| 4. Discard
|
|
171
|
+
| Option | Merge | Push | Keep Worktree | Cleanup Branch |
|
|
172
|
+
| ---------------- | ----- | ---- | ------------- | -------------- |
|
|
173
|
+
| 1. Merge locally | ✓ | - | - | ✓ |
|
|
174
|
+
| 2. Create PR | - | ✓ | ✓ | - |
|
|
175
|
+
| 3. Keep as-is | - | - | ✓ | - |
|
|
176
|
+
| 4. Discard | - | - | - | ✓ (force) |
|
|
160
177
|
|
|
161
178
|
## Common Mistakes
|
|
162
179
|
|
|
163
180
|
**Skipping test verification**
|
|
181
|
+
|
|
164
182
|
- **Problem:** Merge broken code, create failing PR
|
|
165
183
|
- **Fix:** Always verify tests before offering options
|
|
166
184
|
|
|
167
185
|
**Open-ended questions**
|
|
186
|
+
|
|
168
187
|
- **Problem:** "What should I do next?" → ambiguous
|
|
169
188
|
- **Fix:** Present exactly 4 structured options
|
|
170
189
|
|
|
171
190
|
**Automatic worktree cleanup**
|
|
191
|
+
|
|
172
192
|
- **Problem:** Remove worktree when might need it (Option 2, 3)
|
|
173
193
|
- **Fix:** Only cleanup for Options 1 and 4
|
|
174
194
|
|
|
175
195
|
**No confirmation for discard**
|
|
196
|
+
|
|
176
197
|
- **Problem:** Accidentally delete work
|
|
177
198
|
- **Fix:** Require typed "discard" confirmation
|
|
178
199
|
|
|
179
200
|
## Red Flags
|
|
180
201
|
|
|
181
202
|
**Never:**
|
|
203
|
+
|
|
182
204
|
- Proceed with failing tests
|
|
183
205
|
- Merge without verifying tests on result
|
|
184
206
|
- Delete work without confirmation
|
|
185
207
|
- Force-push without explicit request
|
|
186
208
|
|
|
187
209
|
**Always:**
|
|
210
|
+
|
|
188
211
|
- Verify tests before offering options
|
|
189
212
|
- Present exactly 4 options
|
|
190
213
|
- Get typed confirmation for Option 4
|
|
@@ -193,8 +216,10 @@ git worktree remove <worktree-path>
|
|
|
193
216
|
## Integration
|
|
194
217
|
|
|
195
218
|
**Called by:**
|
|
219
|
+
|
|
196
220
|
- **subagent-driven-development** (Step 7) - After all tasks complete
|
|
197
221
|
- **executing-plans** (Step 5) - After all batches complete
|
|
198
222
|
|
|
199
223
|
**Pairs with:**
|
|
224
|
+
|
|
200
225
|
- **using-git-worktrees** - Cleans up worktree created by that skill
|
|
@@ -1,173 +1,106 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: playwright
|
|
3
3
|
description: Browser automation with Playwright MCP. Test pages, fill forms, take screenshots, check responsive design, validate UX, test login flows. Use when user wants to test websites or automate browser interactions.
|
|
4
|
-
mcp:
|
|
5
|
-
playwright:
|
|
6
|
-
command: npx
|
|
7
|
-
args: ["@playwright/mcp@latest"]
|
|
8
4
|
---
|
|
9
5
|
|
|
10
6
|
# Playwright Browser Automation (MCP)
|
|
11
7
|
|
|
12
|
-
Browser automation via Playwright MCP server
|
|
13
|
-
|
|
14
|
-
## Quick Start
|
|
15
|
-
|
|
16
|
-
After loading this skill, use `skill_mcp` to invoke browser tools:
|
|
17
|
-
|
|
18
|
-
```
|
|
19
|
-
skill_mcp(mcp_name="playwright", tool_name="browser_navigate", arguments='{"url": "https://example.com"}')
|
|
20
|
-
```
|
|
8
|
+
Browser automation via Playwright MCP server for testing and automation.
|
|
21
9
|
|
|
22
10
|
## Available Tools
|
|
23
11
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
### Interaction
|
|
34
|
-
|
|
35
|
-
| Tool | Description | Arguments |
|
|
36
|
-
| ----------------------- | ------------------------- | ---------------------------------------------------------------------------------- |
|
|
37
|
-
| `browser_click` | Click element | `{"element": "Submit button", "ref": "e123"}` |
|
|
38
|
-
| `browser_type` | Type text | `{"element": "Search input", "ref": "e456", "text": "query"}` |
|
|
39
|
-
| `browser_fill` | Fill input (clears first) | `{"element": "Email field", "ref": "e789", "text": "test@example.com"}` |
|
|
40
|
-
| `browser_select_option` | Select dropdown option | `{"element": "Country", "ref": "e012", "values": ["US"]}` |
|
|
41
|
-
| `browser_hover` | Hover over element | `{"element": "Menu", "ref": "e345"}` |
|
|
42
|
-
| `browser_drag` | Drag and drop | `{"startElement": "...", "startRef": "...", "endElement": "...", "endRef": "..."}` |
|
|
43
|
-
|
|
44
|
-
### Screenshots & Content
|
|
45
|
-
|
|
46
|
-
| Tool | Description | Arguments |
|
|
47
|
-
| ------------------------- | ------------------------------- | -------------------------------------- |
|
|
48
|
-
| `browser_take_screenshot` | Capture screenshot | `{"filename": "screenshot.png"}` |
|
|
49
|
-
| `browser_snapshot` | Get page accessibility snapshot | `{}` |
|
|
50
|
-
| `browser_evaluate` | Run JavaScript | `{"function": "() => document.title"}` |
|
|
51
|
-
| `browser_pdf_save` | Save page as PDF | `{"filename": "page.pdf"}` |
|
|
52
|
-
|
|
53
|
-
### Viewport & Device
|
|
54
|
-
|
|
55
|
-
| Tool | Description | Arguments |
|
|
56
|
-
| ---------------- | --------------- | ------------------------------------------------------------ |
|
|
57
|
-
| `browser_resize` | Resize viewport | `{"width": 375, "height": 667}` or `{"device": "iPhone 13"}` |
|
|
58
|
-
|
|
59
|
-
### Tabs
|
|
60
|
-
|
|
61
|
-
| Tool | Description | Arguments |
|
|
62
|
-
| -------------------- | -------------- | ------------------------ |
|
|
63
|
-
| `browser_tab_list` | List open tabs | `{}` |
|
|
64
|
-
| `browser_tab_new` | Open new tab | `{"url": "https://..."}` |
|
|
65
|
-
| `browser_tab_select` | Switch to tab | `{"index": 0}` |
|
|
66
|
-
| `browser_tab_close` | Close tab | `{"index": 0}` |
|
|
12
|
+
- `browser_navigate` - Navigate to URL
|
|
13
|
+
- `browser_snapshot` - Get page accessibility snapshot with element refs
|
|
14
|
+
- `browser_take_screenshot` - Capture screenshot
|
|
15
|
+
- `browser_click` - Click element by ref
|
|
16
|
+
- `browser_type` - Type text (appends to existing)
|
|
17
|
+
- `browser_fill` - Fill input (clears first, then types)
|
|
18
|
+
- `browser_wait_for` - Wait for text or selector
|
|
19
|
+
- `browser_resize` - Resize viewport or emulate device
|
|
67
20
|
|
|
68
21
|
## Workflow
|
|
69
22
|
|
|
70
|
-
1. **Navigate** to the target URL
|
|
71
|
-
2. **Snapshot** to see page structure and element refs
|
|
72
|
-
3. **Interact** using element refs from snapshot
|
|
73
|
-
4. **Screenshot** to capture results
|
|
74
|
-
|
|
75
|
-
## Examples
|
|
23
|
+
1. **Navigate** to the target URL using `browser_navigate`
|
|
24
|
+
2. **Snapshot** to see page structure and element refs using `browser_snapshot`
|
|
25
|
+
3. **Interact** using element refs from snapshot (`browser_click`, `browser_fill`)
|
|
26
|
+
4. **Screenshot** to capture results using `browser_take_screenshot`
|
|
76
27
|
|
|
77
|
-
|
|
28
|
+
## Quick Start
|
|
78
29
|
|
|
79
30
|
```
|
|
80
|
-
# Navigate
|
|
81
|
-
skill_mcp(
|
|
31
|
+
# Navigate to page
|
|
32
|
+
skill_mcp(skill_name="playwright", tool_name="browser_navigate", arguments='{"url": "https://example.com"}')
|
|
82
33
|
|
|
83
|
-
# Get page
|
|
84
|
-
skill_mcp(
|
|
34
|
+
# Get page structure with element refs
|
|
35
|
+
skill_mcp(skill_name="playwright", tool_name="browser_snapshot")
|
|
85
36
|
|
|
86
|
-
#
|
|
87
|
-
skill_mcp(
|
|
88
|
-
```
|
|
37
|
+
# Click element (use ref from snapshot)
|
|
38
|
+
skill_mcp(skill_name="playwright", tool_name="browser_click", arguments='{"element": "Submit button", "ref": "e123"}')
|
|
89
39
|
|
|
90
|
-
|
|
40
|
+
# Fill input field
|
|
41
|
+
skill_mcp(skill_name="playwright", tool_name="browser_fill", arguments='{"element": "Email", "ref": "e456", "text": "test@example.com"}')
|
|
91
42
|
|
|
43
|
+
# Take screenshot
|
|
44
|
+
skill_mcp(skill_name="playwright", tool_name="browser_take_screenshot", arguments='{"filename": "/tmp/result.png"}')
|
|
92
45
|
```
|
|
93
|
-
# Navigate to form
|
|
94
|
-
skill_mcp(mcp_name="playwright", tool_name="browser_navigate", arguments='{"url": "http://localhost:3000/contact"}')
|
|
95
|
-
|
|
96
|
-
# Get snapshot to find element refs
|
|
97
|
-
skill_mcp(mcp_name="playwright", tool_name="browser_snapshot")
|
|
98
|
-
|
|
99
|
-
# Fill fields (use refs from snapshot)
|
|
100
|
-
skill_mcp(mcp_name="playwright", tool_name="browser_fill", arguments='{"element": "Name input", "ref": "e12", "text": "John Doe"}')
|
|
101
|
-
skill_mcp(mcp_name="playwright", tool_name="browser_fill", arguments='{"element": "Email input", "ref": "e34", "text": "john@example.com"}')
|
|
102
46
|
|
|
103
|
-
|
|
104
|
-
skill_mcp(mcp_name="playwright", tool_name="browser_click", arguments='{"element": "Submit button", "ref": "e56"}')
|
|
105
|
-
```
|
|
47
|
+
## Examples
|
|
106
48
|
|
|
107
49
|
### Test Responsive Design
|
|
108
50
|
|
|
109
51
|
```
|
|
110
52
|
# Navigate
|
|
111
|
-
skill_mcp(
|
|
53
|
+
skill_mcp(skill_name="playwright", tool_name="browser_navigate", arguments='{"url": "http://localhost:3000"}')
|
|
112
54
|
|
|
113
55
|
# Desktop
|
|
114
|
-
skill_mcp(
|
|
115
|
-
skill_mcp(
|
|
116
|
-
|
|
117
|
-
# Tablet
|
|
118
|
-
skill_mcp(mcp_name="playwright", tool_name="browser_resize", arguments='{"device": "iPad Pro 11"}')
|
|
119
|
-
skill_mcp(mcp_name="playwright", tool_name="browser_take_screenshot", arguments='{"filename": "/tmp/tablet.png"}')
|
|
56
|
+
skill_mcp(skill_name="playwright", tool_name="browser_resize", arguments='{"width": 1920, "height": 1080}')
|
|
57
|
+
skill_mcp(skill_name="playwright", tool_name="browser_take_screenshot", arguments='{"filename": "/tmp/desktop.png"}')
|
|
120
58
|
|
|
121
59
|
# Mobile
|
|
122
|
-
skill_mcp(
|
|
123
|
-
skill_mcp(
|
|
60
|
+
skill_mcp(skill_name="playwright", tool_name="browser_resize", arguments='{"device": "iPhone 13"}')
|
|
61
|
+
skill_mcp(skill_name="playwright", tool_name="browser_take_screenshot", arguments='{"filename": "/tmp/mobile.png"}')
|
|
124
62
|
```
|
|
125
63
|
|
|
126
|
-
###
|
|
64
|
+
### Fill a Form
|
|
127
65
|
|
|
128
66
|
```
|
|
129
|
-
# Navigate to
|
|
130
|
-
skill_mcp(
|
|
131
|
-
|
|
132
|
-
# Snapshot to get refs
|
|
133
|
-
skill_mcp(mcp_name="playwright", tool_name="browser_snapshot")
|
|
134
|
-
|
|
135
|
-
# Fill credentials
|
|
136
|
-
skill_mcp(mcp_name="playwright", tool_name="browser_fill", arguments='{"element": "Email", "ref": "e10", "text": "test@example.com"}')
|
|
137
|
-
skill_mcp(mcp_name="playwright", tool_name="browser_fill", arguments='{"element": "Password", "ref": "e20", "text": "password123"}')
|
|
138
|
-
|
|
139
|
-
# Click login
|
|
140
|
-
skill_mcp(mcp_name="playwright", tool_name="browser_click", arguments='{"element": "Login button", "ref": "e30"}')
|
|
67
|
+
# Navigate to form
|
|
68
|
+
skill_mcp(skill_name="playwright", tool_name="browser_navigate", arguments='{"url": "http://localhost:3000/contact"}')
|
|
141
69
|
|
|
142
|
-
#
|
|
143
|
-
skill_mcp(
|
|
70
|
+
# Get element refs
|
|
71
|
+
skill_mcp(skill_name="playwright", tool_name="browser_snapshot")
|
|
144
72
|
|
|
145
|
-
#
|
|
146
|
-
skill_mcp(
|
|
147
|
-
|
|
73
|
+
# Fill fields (use refs from snapshot)
|
|
74
|
+
skill_mcp(skill_name="playwright", tool_name="browser_fill", arguments='{"element": "Name", "ref": "e12", "text": "John Doe"}')
|
|
75
|
+
skill_mcp(skill_name="playwright", tool_name="browser_fill", arguments='{"element": "Email", "ref": "e34", "text": "john@example.com"}')
|
|
148
76
|
|
|
149
|
-
|
|
77
|
+
# Submit
|
|
78
|
+
skill_mcp(skill_name="playwright", tool_name="browser_click", arguments='{"element": "Submit", "ref": "e56"}')
|
|
150
79
|
|
|
80
|
+
# Wait for confirmation
|
|
81
|
+
skill_mcp(skill_name="playwright", tool_name="browser_wait_for", arguments='{"text": "Thank you", "timeout": 5000}')
|
|
151
82
|
```
|
|
152
|
-
# Get page title
|
|
153
|
-
skill_mcp(mcp_name="playwright", tool_name="browser_evaluate", arguments='{"function": "() => document.title"}')
|
|
154
83
|
|
|
155
|
-
|
|
156
|
-
skill_mcp(mcp_name="playwright", tool_name="browser_evaluate", arguments='{"function": "() => Array.from(document.querySelectorAll(\"a\")).map(a => a.href)"}')
|
|
84
|
+
## Tips
|
|
157
85
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
86
|
+
- **Always snapshot first** to get element refs before interacting
|
|
87
|
+
- **Use descriptive element names** in click/fill for clarity
|
|
88
|
+
- **Save screenshots to /tmp** for easy access
|
|
89
|
+
- **Use device presets** for accurate mobile emulation
|
|
90
|
+
- **Chain wait_for** after navigation for dynamic pages
|
|
161
91
|
|
|
162
92
|
## Server Options
|
|
163
93
|
|
|
164
|
-
For advanced usage, modify
|
|
94
|
+
For advanced usage, modify `mcp.json`:
|
|
165
95
|
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
playwright:
|
|
169
|
-
command: npx
|
|
170
|
-
args: ["@playwright/mcp@latest", "--headless", "--browser=firefox"]
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"playwright": {
|
|
99
|
+
"command": "npx",
|
|
100
|
+
"args": ["@playwright/mcp@latest", "--headless", "--browser=firefox"],
|
|
101
|
+
"includeTools": ["browser_navigate", "browser_snapshot", "..."]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
171
104
|
```
|
|
172
105
|
|
|
173
106
|
Common options:
|
|
@@ -175,13 +108,5 @@ Common options:
|
|
|
175
108
|
- `--headless` - Run without visible browser
|
|
176
109
|
- `--browser=chrome|firefox|webkit` - Choose browser
|
|
177
110
|
- `--device="iPhone 13"` - Emulate device
|
|
178
|
-
- `--viewport-size=1280x720` - Set viewport
|
|
179
|
-
- `--user-data-dir=/path` - Persist browser data
|
|
180
111
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
- **Always snapshot first** to get element refs before interacting
|
|
184
|
-
- **Use descriptive element names** in click/fill for clarity
|
|
185
|
-
- **Save screenshots to /tmp** for easy access
|
|
186
|
-
- **Use device presets** for accurate mobile emulation
|
|
187
|
-
- **Chain wait_for** after navigation for dynamic pages
|
|
112
|
+
> **Note**: This skill loads 8 essential tools. For full 17+ tools (tabs, PDF, evaluate, drag), modify `mcp.json` to add more tools to `includeTools`.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"playwright": {
|
|
3
|
+
"command": "npx",
|
|
4
|
+
"args": ["@playwright/mcp@latest"],
|
|
5
|
+
"includeTools": [
|
|
6
|
+
"browser_navigate",
|
|
7
|
+
"browser_snapshot",
|
|
8
|
+
"browser_take_screenshot",
|
|
9
|
+
"browser_click",
|
|
10
|
+
"browser_type",
|
|
11
|
+
"browser_fill",
|
|
12
|
+
"browser_wait_for",
|
|
13
|
+
"browser_resize"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
|
+
|
|
3
|
+
// Configuration - Qwen3-Embedding-0.6B
|
|
4
|
+
// Better for code, multilingual (100+ langs), instruction-aware
|
|
5
|
+
// See: https://ollama.com/library/qwen3-embedding
|
|
6
|
+
const OLLAMA_MODEL = "qwen3-embedding:0.6b";
|
|
7
|
+
const OLLAMA_DIMENSIONS = 1024;
|
|
8
|
+
const OLLAMA_BASE_URL = process.env.OLLAMA_HOST || "http://127.0.0.1:11434";
|
|
9
|
+
|
|
10
|
+
interface EmbeddingResult {
|
|
11
|
+
text: string;
|
|
12
|
+
embedding: number[];
|
|
13
|
+
model: string;
|
|
14
|
+
dimensions: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface BatchEmbeddingResult {
|
|
18
|
+
results: EmbeddingResult[];
|
|
19
|
+
failed: { text: string; error: string }[];
|
|
20
|
+
model: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function checkOllamaRunning(): Promise<boolean> {
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch(`${OLLAMA_BASE_URL}/api/version`, {
|
|
26
|
+
method: "GET",
|
|
27
|
+
signal: AbortSignal.timeout(3000),
|
|
28
|
+
});
|
|
29
|
+
return response.ok;
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function ensureModelAvailable(): Promise<{
|
|
36
|
+
ok: boolean;
|
|
37
|
+
error?: string;
|
|
38
|
+
}> {
|
|
39
|
+
try {
|
|
40
|
+
const listResponse = await fetch(`${OLLAMA_BASE_URL}/api/tags`, {
|
|
41
|
+
method: "GET",
|
|
42
|
+
signal: AbortSignal.timeout(5000),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (!listResponse.ok) {
|
|
46
|
+
return { ok: false, error: "Failed to list models" };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const data = (await listResponse.json()) as {
|
|
50
|
+
models?: { name: string }[];
|
|
51
|
+
};
|
|
52
|
+
const models = data.models || [];
|
|
53
|
+
const modelExists = models.some(
|
|
54
|
+
(m) => m.name === OLLAMA_MODEL || m.name === `${OLLAMA_MODEL}:latest`,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
if (modelExists) {
|
|
58
|
+
return { ok: true };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
error: `Model '${OLLAMA_MODEL}' not found. Run: ollama pull ${OLLAMA_MODEL}`,
|
|
64
|
+
};
|
|
65
|
+
} catch (err) {
|
|
66
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
67
|
+
return { ok: false, error: `Failed to check models: ${message}` };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function embedWithOllama(
|
|
72
|
+
texts: string[],
|
|
73
|
+
): Promise<{ embeddings: number[][]; error?: string }> {
|
|
74
|
+
try {
|
|
75
|
+
const embeddings: number[][] = [];
|
|
76
|
+
|
|
77
|
+
for (const text of texts) {
|
|
78
|
+
const response = await fetch(`${OLLAMA_BASE_URL}/api/embeddings`, {
|
|
79
|
+
method: "POST",
|
|
80
|
+
headers: { "Content-Type": "application/json" },
|
|
81
|
+
body: JSON.stringify({
|
|
82
|
+
model: OLLAMA_MODEL,
|
|
83
|
+
prompt: text,
|
|
84
|
+
}),
|
|
85
|
+
signal: AbortSignal.timeout(30000),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (!response.ok) {
|
|
89
|
+
const errorText = await response.text();
|
|
90
|
+
return {
|
|
91
|
+
embeddings: [],
|
|
92
|
+
error: `Ollama API error (${response.status}): ${errorText}`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const data = (await response.json()) as { embedding?: number[] };
|
|
97
|
+
if (!data.embedding) {
|
|
98
|
+
return { embeddings: [], error: "No embedding in response" };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
embeddings.push(data.embedding);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { embeddings };
|
|
105
|
+
} catch (err) {
|
|
106
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
107
|
+
|
|
108
|
+
if (message.includes("ECONNREFUSED") || message.includes("fetch failed")) {
|
|
109
|
+
return {
|
|
110
|
+
embeddings: [],
|
|
111
|
+
error: "Cannot connect to Ollama. See .opencode/README.md for setup.",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { embeddings: [], error: `Ollama error: ${message}` };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export default tool({
|
|
120
|
+
description: `Generate embeddings using Ollama (${OLLAMA_MODEL}). Requires Ollama running locally.`,
|
|
121
|
+
args: {
|
|
122
|
+
text: tool.schema
|
|
123
|
+
.union([tool.schema.string(), tool.schema.array(tool.schema.string())])
|
|
124
|
+
.describe("Text or array of texts to embed"),
|
|
125
|
+
},
|
|
126
|
+
execute: async (args: { text: string | string[] }) => {
|
|
127
|
+
const texts = Array.isArray(args.text) ? args.text : [args.text];
|
|
128
|
+
|
|
129
|
+
if (texts.length === 0) {
|
|
130
|
+
return "Error: No text provided";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const maxChars = 32000;
|
|
134
|
+
const longTexts = texts.filter((t) => t.length > maxChars);
|
|
135
|
+
if (longTexts.length > 0) {
|
|
136
|
+
return `Error: ${longTexts.length} text(s) exceed ${maxChars} char limit`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const ollamaRunning = await checkOllamaRunning();
|
|
140
|
+
if (!ollamaRunning) {
|
|
141
|
+
return "Error: Ollama not running. See .opencode/README.md for setup.";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const modelCheck = await ensureModelAvailable();
|
|
145
|
+
if (!modelCheck.ok) {
|
|
146
|
+
return `Error: ${modelCheck.error}. See .opencode/README.md for setup.`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const result = await embedWithOllama(texts);
|
|
150
|
+
|
|
151
|
+
if (result.error) {
|
|
152
|
+
return `Error: ${result.error}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const response: BatchEmbeddingResult = {
|
|
156
|
+
results: texts.map((text, i) => ({
|
|
157
|
+
text: text.substring(0, 100) + (text.length > 100 ? "..." : ""),
|
|
158
|
+
embedding: result.embeddings[i],
|
|
159
|
+
model: OLLAMA_MODEL,
|
|
160
|
+
dimensions: OLLAMA_DIMENSIONS,
|
|
161
|
+
})),
|
|
162
|
+
failed: [],
|
|
163
|
+
model: OLLAMA_MODEL,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
return JSON.stringify(response, null, 2);
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Export for other tools (memory-index, observation)
|
|
171
|
+
export async function generateEmbedding(
|
|
172
|
+
text: string,
|
|
173
|
+
): Promise<{ embedding: number[]; model: string } | null> {
|
|
174
|
+
const ollamaRunning = await checkOllamaRunning();
|
|
175
|
+
if (!ollamaRunning) return null;
|
|
176
|
+
|
|
177
|
+
const result = await embedWithOllama([text]);
|
|
178
|
+
if (result.error || result.embeddings.length === 0) return null;
|
|
179
|
+
|
|
180
|
+
return { embedding: result.embeddings[0], model: OLLAMA_MODEL };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export const EMBEDDING_DIMENSIONS = OLLAMA_DIMENSIONS;
|