@yeyuan98/opencode-bioresearcher-plugin 1.3.0-alpha.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/dist/shared/tool-restrictions.js +4 -0
- package/dist/skill-tools/index.d.ts +1 -1
- package/dist/skill-tools/index.js +1 -1
- package/dist/skill-tools/registry.d.ts +8 -3
- package/dist/skill-tools/registry.js +85 -17
- package/dist/skill-tools/tool.js +2 -2
- package/dist/skill-tools/types.d.ts +2 -1
- package/dist/skills/demo-skill/SKILL.md +1 -1
- package/dist/skills/demo-skill/demo_script.py +1 -1
- package/dist/skills/python-setup-uv/SKILL.md +141 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -75,6 +75,32 @@ Download pubmed article data from https://ftp.ncbi.nlm.nih.gov/pubmed/updatefile
|
|
|
75
75
|
|
|
76
76
|
Reference: [PubMed Download Data](https://pubmed.ncbi.nlm.nih.gov/download/).
|
|
77
77
|
|
|
78
|
+
## Skills
|
|
79
|
+
|
|
80
|
+
Skills are reusable prompt templates discovered from multiple paths:
|
|
81
|
+
|
|
82
|
+
| Path | Scope |
|
|
83
|
+
|------|-------|
|
|
84
|
+
| `.opencode/skills/` | Project |
|
|
85
|
+
| `~/.config/opencode/skills/` | Global |
|
|
86
|
+
| `.claude/skills/` | Claude Code compatible |
|
|
87
|
+
| `.agents/skills/` | Agents compatible |
|
|
88
|
+
|
|
89
|
+
This plugin provides a skill tool that overrides Opencode's built-in to support plugin-shipped skills.
|
|
90
|
+
|
|
91
|
+
See [skill-tools/README.md](skill-tools/README.md) for full documentation.
|
|
92
|
+
|
|
93
|
+
### Supplied skills
|
|
94
|
+
|
|
95
|
+
- `demo-skill`: showcase skill tool mechanisms.
|
|
96
|
+
- `python-setup-uv`: setup python runtime in your working directory with uv.
|
|
97
|
+
|
|
98
|
+
Prompt the following and follow along:
|
|
99
|
+
|
|
100
|
+
```txt
|
|
101
|
+
Setup python uv with skill
|
|
102
|
+
```
|
|
103
|
+
|
|
78
104
|
## Installation
|
|
79
105
|
|
|
80
106
|
Add the plugin to your `opencode.json`:
|
|
@@ -42,6 +42,9 @@ export const AGENT_TOOL_RESTRICTIONS = {
|
|
|
42
42
|
"read",
|
|
43
43
|
"write",
|
|
44
44
|
"edit",
|
|
45
|
+
"question",
|
|
46
|
+
"todowrite",
|
|
47
|
+
"task"
|
|
45
48
|
]),
|
|
46
49
|
bioresearcherDR_worker: createAllowlist([
|
|
47
50
|
"biomcp*",
|
|
@@ -53,6 +56,7 @@ export const AGENT_TOOL_RESTRICTIONS = {
|
|
|
53
56
|
"read",
|
|
54
57
|
"write",
|
|
55
58
|
"edit",
|
|
59
|
+
"todowrite"
|
|
56
60
|
]),
|
|
57
61
|
};
|
|
58
62
|
/**
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { SkillTool } from "./tool";
|
|
2
|
-
export { getAllSkills, getSkill,
|
|
2
|
+
export { getAllSkills, getSkill, SkillConflictError } from "./registry";
|
|
3
3
|
export type { ExtendedSkill, ExtendedSkillFrontmatter, ParsedSkill } from "./types";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { SkillTool } from "./tool";
|
|
2
|
-
export { getAllSkills, getSkill,
|
|
2
|
+
export { getAllSkills, getSkill, SkillConflictError } from "./registry";
|
|
@@ -6,6 +6,11 @@ export declare class SkillConflictError extends Error {
|
|
|
6
6
|
constructor(skillName: string, pluginLocation: string, userLocation: string);
|
|
7
7
|
}
|
|
8
8
|
export declare function loadPluginSkills(): Promise<ExtendedSkill[]>;
|
|
9
|
-
export declare function
|
|
10
|
-
export declare function
|
|
11
|
-
export declare function
|
|
9
|
+
export declare function discoverOpencodeProjectSkills(directory?: string): Promise<ExtendedSkill[]>;
|
|
10
|
+
export declare function discoverOpencodeGlobalSkills(): Promise<ExtendedSkill[]>;
|
|
11
|
+
export declare function discoverClaudeProjectSkills(directory?: string): Promise<ExtendedSkill[]>;
|
|
12
|
+
export declare function discoverClaudeGlobalSkills(): Promise<ExtendedSkill[]>;
|
|
13
|
+
export declare function discoverAgentsProjectSkills(directory?: string): Promise<ExtendedSkill[]>;
|
|
14
|
+
export declare function discoverAgentsGlobalSkills(): Promise<ExtendedSkill[]>;
|
|
15
|
+
export declare function getAllSkills(directory?: string): Promise<ExtendedSkill[]>;
|
|
16
|
+
export declare function getSkill(name: string, directory?: string): Promise<ExtendedSkill | undefined>;
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import path from "path";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { existsSync } from "fs";
|
|
2
4
|
import { fileURLToPath } from "url";
|
|
5
|
+
import { xdgConfig } from "xdg-basedir";
|
|
3
6
|
import { parseSkillFrontmatter } from "./frontmatter";
|
|
4
7
|
const SKILL_GLOB = new Bun.Glob("**/SKILL.md");
|
|
8
|
+
function getOpenCodeConfigDir() {
|
|
9
|
+
const envDir = process.env.OPENCODE_CONFIG_DIR;
|
|
10
|
+
if (envDir)
|
|
11
|
+
return envDir;
|
|
12
|
+
return path.join(xdgConfig, "opencode");
|
|
13
|
+
}
|
|
14
|
+
function getClaudeConfigDir() {
|
|
15
|
+
return process.env.CLAUDE_CONFIG_DIR || path.join(homedir(), ".claude");
|
|
16
|
+
}
|
|
5
17
|
export class SkillConflictError extends Error {
|
|
6
18
|
skillName;
|
|
7
19
|
pluginLocation;
|
|
@@ -19,12 +31,13 @@ function getPluginSkillsDir() {
|
|
|
19
31
|
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
20
32
|
return path.join(currentDir, "..", "skills");
|
|
21
33
|
}
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
async function loadSkillsFromDir(dir, source) {
|
|
35
|
+
if (!existsSync(dir))
|
|
36
|
+
return [];
|
|
24
37
|
const skills = [];
|
|
25
38
|
try {
|
|
26
39
|
for await (const match of SKILL_GLOB.scan({
|
|
27
|
-
cwd:
|
|
40
|
+
cwd: dir,
|
|
28
41
|
absolute: true,
|
|
29
42
|
onlyFiles: true,
|
|
30
43
|
})) {
|
|
@@ -38,27 +51,82 @@ export async function loadPluginSkills() {
|
|
|
38
51
|
content: parsed.content,
|
|
39
52
|
agent: parsed.frontmatter.agent,
|
|
40
53
|
allowedTools: parsed.frontmatter.allowedTools,
|
|
41
|
-
source
|
|
54
|
+
source,
|
|
42
55
|
});
|
|
43
56
|
}
|
|
44
57
|
}
|
|
45
58
|
catch {
|
|
46
|
-
//
|
|
59
|
+
// Directory may not be readable
|
|
47
60
|
}
|
|
48
61
|
return skills;
|
|
49
62
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (cachedSkills)
|
|
53
|
-
return cachedSkills;
|
|
54
|
-
const pluginSkills = await loadPluginSkills();
|
|
55
|
-
cachedSkills = pluginSkills;
|
|
56
|
-
return pluginSkills;
|
|
63
|
+
export async function loadPluginSkills() {
|
|
64
|
+
return loadSkillsFromDir(getPluginSkillsDir(), "plugin");
|
|
57
65
|
}
|
|
58
|
-
export async function
|
|
59
|
-
const
|
|
60
|
-
return
|
|
66
|
+
export async function discoverOpencodeProjectSkills(directory) {
|
|
67
|
+
const dir = directory ?? process.cwd();
|
|
68
|
+
return loadSkillsFromDir(path.join(dir, ".opencode", "skills"), "opencode-project");
|
|
69
|
+
}
|
|
70
|
+
export async function discoverOpencodeGlobalSkills() {
|
|
71
|
+
return loadSkillsFromDir(path.join(getOpenCodeConfigDir(), "skills"), "opencode-global");
|
|
72
|
+
}
|
|
73
|
+
export async function discoverClaudeProjectSkills(directory) {
|
|
74
|
+
const dir = directory ?? process.cwd();
|
|
75
|
+
return loadSkillsFromDir(path.join(dir, ".claude", "skills"), "claude-project");
|
|
61
76
|
}
|
|
62
|
-
export function
|
|
63
|
-
|
|
77
|
+
export async function discoverClaudeGlobalSkills() {
|
|
78
|
+
return loadSkillsFromDir(path.join(getClaudeConfigDir(), "skills"), "claude-global");
|
|
79
|
+
}
|
|
80
|
+
export async function discoverAgentsProjectSkills(directory) {
|
|
81
|
+
const dir = directory ?? process.cwd();
|
|
82
|
+
return loadSkillsFromDir(path.join(dir, ".agents", "skills"), "agents-project");
|
|
83
|
+
}
|
|
84
|
+
export async function discoverAgentsGlobalSkills() {
|
|
85
|
+
return loadSkillsFromDir(path.join(homedir(), ".agents", "skills"), "agents-global");
|
|
86
|
+
}
|
|
87
|
+
export async function getAllSkills(directory) {
|
|
88
|
+
const dir = directory ?? process.cwd();
|
|
89
|
+
const [opencodeProject, opencodeGlobal, claudeProject, claudeGlobal, agentsProject, agentsGlobal, plugin] = await Promise.all([
|
|
90
|
+
discoverOpencodeProjectSkills(dir),
|
|
91
|
+
discoverOpencodeGlobalSkills(),
|
|
92
|
+
discoverClaudeProjectSkills(dir),
|
|
93
|
+
discoverClaudeGlobalSkills(),
|
|
94
|
+
discoverAgentsProjectSkills(dir),
|
|
95
|
+
discoverAgentsGlobalSkills(),
|
|
96
|
+
loadPluginSkills(),
|
|
97
|
+
]);
|
|
98
|
+
const userSkills = [
|
|
99
|
+
...opencodeProject,
|
|
100
|
+
...opencodeGlobal,
|
|
101
|
+
...claudeProject,
|
|
102
|
+
...claudeGlobal,
|
|
103
|
+
...agentsProject,
|
|
104
|
+
...agentsGlobal,
|
|
105
|
+
];
|
|
106
|
+
const pluginNames = new Set(plugin.map((s) => s.name));
|
|
107
|
+
const conflicts = userSkills.filter((s) => pluginNames.has(s.name));
|
|
108
|
+
if (conflicts.length > 0) {
|
|
109
|
+
throw new SkillConflictError(conflicts[0].name, "", conflicts[0].location);
|
|
110
|
+
}
|
|
111
|
+
const seen = new Set();
|
|
112
|
+
return [
|
|
113
|
+
...opencodeProject,
|
|
114
|
+
...opencodeGlobal,
|
|
115
|
+
...claudeProject,
|
|
116
|
+
...claudeGlobal,
|
|
117
|
+
...agentsProject,
|
|
118
|
+
...agentsGlobal,
|
|
119
|
+
...plugin,
|
|
120
|
+
].filter((skill) => {
|
|
121
|
+
if (seen.has(skill.name)) {
|
|
122
|
+
console.warn(`[skill] Duplicate skill "${skill.name}" - using higher priority version`);
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
seen.add(skill.name);
|
|
126
|
+
return true;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
export async function getSkill(name, directory) {
|
|
130
|
+
const skills = await getAllSkills(directory);
|
|
131
|
+
return skills.find((s) => s.name === name);
|
|
64
132
|
}
|
package/dist/skill-tools/tool.js
CHANGED
|
@@ -53,8 +53,8 @@ export const SkillTool = tool({
|
|
|
53
53
|
name: tool.schema.string().describe("The name of the skill from available_skills"),
|
|
54
54
|
},
|
|
55
55
|
async execute(params, ctx) {
|
|
56
|
-
const skills = await getAllSkills();
|
|
57
|
-
const skill = await getSkill(params.name);
|
|
56
|
+
const skills = await getAllSkills(ctx.directory);
|
|
57
|
+
const skill = await getSkill(params.name, ctx.directory);
|
|
58
58
|
if (!skill) {
|
|
59
59
|
const available = skills.map((s) => s.name).join(", ");
|
|
60
60
|
throw new Error(`Skill "${params.name}" not found. Available skills: ${available || "none"}`);
|
|
@@ -6,6 +6,7 @@ export declare const ExtendedSkillFrontmatter: z.ZodObject<{
|
|
|
6
6
|
allowedTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
7
7
|
}, z.core.$strip>;
|
|
8
8
|
export type ExtendedSkillFrontmatter = z.infer<typeof ExtendedSkillFrontmatter>;
|
|
9
|
+
export type SkillSource = "plugin" | "opencode-project" | "opencode-global" | "claude-project" | "claude-global" | "agents-project" | "agents-global";
|
|
9
10
|
export interface ExtendedSkill {
|
|
10
11
|
name: string;
|
|
11
12
|
description: string;
|
|
@@ -13,7 +14,7 @@ export interface ExtendedSkill {
|
|
|
13
14
|
content: string;
|
|
14
15
|
agent?: string;
|
|
15
16
|
allowedTools?: string[];
|
|
16
|
-
source:
|
|
17
|
+
source: SkillSource;
|
|
17
18
|
}
|
|
18
19
|
export interface ParsedSkill {
|
|
19
20
|
frontmatter: ExtendedSkillFrontmatter;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: python-setup-uv
|
|
3
|
+
description: Setup Python environment with uv package manager - download binaries, create symlink, and install packages
|
|
4
|
+
allowedTools:
|
|
5
|
+
- Bash
|
|
6
|
+
- Read
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Python Environment Setup with uv
|
|
10
|
+
|
|
11
|
+
This skill sets up a Python environment using the uv package manager.
|
|
12
|
+
|
|
13
|
+
## Prerequisites
|
|
14
|
+
- Internet connection for downloading uv
|
|
15
|
+
- Python 3.8+ should be available on PATH (or uv will prompt to install it)
|
|
16
|
+
|
|
17
|
+
## Steps
|
|
18
|
+
|
|
19
|
+
**ABSOLUTE RULE:** Follow steps below EXACTLY AS IS. Do NOT skip/modify steps (nor detailed subtasks in each step) nor assume anything based on user platform information. Use URLs below EXACTLY AS IS. Follow steps below INCLUDING ALL DETAILS AND SUBSTEPS EXACTLY AS IS.
|
|
20
|
+
|
|
21
|
+
### Step 1: Ask user question about which installer to use
|
|
22
|
+
|
|
23
|
+
Use the question tool to ask which installer should be used:
|
|
24
|
+
|
|
25
|
+
- Official astral-uv installer (https://astral.sh)
|
|
26
|
+
- China mainland uv-custom installer (https://gitee.com/wangnov/uv-custom)
|
|
27
|
+
|
|
28
|
+
### Step 2: Detect Shell and Download uv Binary
|
|
29
|
+
|
|
30
|
+
First, detect your shell environment:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Detect shell type
|
|
34
|
+
# MSYSTEM is set by Git Bash, MINGW_PREFIX by MSYS2
|
|
35
|
+
if [ -n "$MSYSTEM" ] || [ -n "$MINGW_PREFIX" ] || command -v curl >/dev/null 2>&1; then
|
|
36
|
+
echo "Unix-like shell detected (Git Bash, bash, zsh, etc.)"
|
|
37
|
+
IS_UNIX_SHELL=true
|
|
38
|
+
else
|
|
39
|
+
echo "Windows cmd.exe detected"
|
|
40
|
+
IS_UNIX_SHELL=false
|
|
41
|
+
fi
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Then download uv based on your shell (see below).
|
|
45
|
+
|
|
46
|
+
Choose the correct `UV_INSTALLER_URL` depending on answer you received in Step 1 from the user:
|
|
47
|
+
|
|
48
|
+
- If opted "Official astral-uv", UV_INSTALLER_URL should be `https://astral.sh/uv/install.sh` (Unix-like) or `https://astral.sh/uv/install.ps1` (Windows)
|
|
49
|
+
- If opted "China mainland uv-custom", UV_INSTALLER_URL should be `https://gitee.com/wangnov/uv-custom/releases/download/latest/uv-installer-custom.sh` (Unix-like) or `https://gitee.com/wangnov/uv-custom/releases/download/latest/uv-installer-custom.ps1` (Windows)
|
|
50
|
+
|
|
51
|
+
**For Unix-like shells (Git Bash / macOS / Linux; use correct UV_INSTALLER_URL):**
|
|
52
|
+
```bash
|
|
53
|
+
mkdir -p .uv
|
|
54
|
+
curl -LsSf UV_INSTALLER_URL | UV_INSTALL_DIR="$(pwd)/.uv" sh
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**For Windows cmd.exe (if Git Bash unavailable; use correct UV_INSTALLER_URL):**
|
|
58
|
+
```bash
|
|
59
|
+
powershell -NoProfile -Command "New-Item -ItemType Directory -Force -Path .uv | Out-Null; $env:UV_INSTALL_DIR = (Get-Location).Path + '\.uv'; Invoke-RestMethod UV_INSTALLER_URL | Invoke-Expression"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Step 3: Create Symlink or Copy uv to Working Directory
|
|
63
|
+
|
|
64
|
+
**For Unix-like shells (Git Bash / macOS / Linux):**
|
|
65
|
+
```bash
|
|
66
|
+
ln -sf .uv/uv uv
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**For Windows cmd.exe:**
|
|
70
|
+
|
|
71
|
+
Try symlink first, fall back to copy if no Admin rights:
|
|
72
|
+
```bash
|
|
73
|
+
cmd /c "(mklink uv .uv\uv.exe) 2>nul || copy /Y .uv\uv.exe uv.exe"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Step 4: Create Virtual Environment and Install pandas
|
|
77
|
+
|
|
78
|
+
NOTE: this step (package installation) may timeout. If timed out, use the question tool to ask if the user would like to retry package installation. If successful, do NOT ask any question and continue to Step 5.
|
|
79
|
+
|
|
80
|
+
**For Unix-like shells:**
|
|
81
|
+
```bash
|
|
82
|
+
./uv venv
|
|
83
|
+
./uv pip install pandas
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**For Windows cmd.exe:**
|
|
87
|
+
```bash
|
|
88
|
+
uv.exe venv
|
|
89
|
+
uv.exe pip install pandas
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Step 5: Verification
|
|
93
|
+
|
|
94
|
+
**For Unix-like shells:**
|
|
95
|
+
```bash
|
|
96
|
+
./uv --version
|
|
97
|
+
./uv run python -c "import pandas; print(pandas.__version__)"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**For Windows cmd.exe:**
|
|
101
|
+
```bash
|
|
102
|
+
uv.exe --version
|
|
103
|
+
uv.exe run python -c "import pandas; print(pandas.__version__)"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Step 6: Update AGENTS.md
|
|
107
|
+
|
|
108
|
+
Use the question tool to ask if the user want to update AGENTS.md in WORKING DIRECTORY to "direct agents to use the installed UV Python" (options: "Yes" / "No"). If received no answer, continue to Step 7 (do NOT modify AGENTS.md NOR create directories). If received yes answer, follow steps belows.
|
|
109
|
+
|
|
110
|
+
1. If AGENTS.md not found in WORKING DIR, create an empty AGENTS.md.
|
|
111
|
+
2. Inspect AGENTS.md content. If you do not see the content block below, APPEND EXACTLY AS IS to end of AGENTS.md.
|
|
112
|
+
3. Check if `./.code/py` exist. If not, create the directories.
|
|
113
|
+
|
|
114
|
+
Content block:
|
|
115
|
+
|
|
116
|
+
```md
|
|
117
|
+
## Important note about Python
|
|
118
|
+
|
|
119
|
+
ALWAYS use the uv package manager available in WORKING DIRECTORY, including `uv add ...` or `uv pip ...` for package management and `uv run ...` to run python package executables.
|
|
120
|
+
|
|
121
|
+
ALWAYS save python scripts under path `./.code/py/` and run the script with `uv run python ...` whenever your work involves executing python scripts. Your script MUST contain concise docstrings and comments and use good engineering practices including separation of concerns.
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Step 7: Return summary to user (Usage After Setup)
|
|
125
|
+
|
|
126
|
+
**For Unix-like shells:**
|
|
127
|
+
```bash
|
|
128
|
+
./uv run python your_script.py
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**For Windows cmd.exe:**
|
|
132
|
+
```bash
|
|
133
|
+
uv.exe run python your_script.py
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Notes
|
|
137
|
+
- Add `.uv/` and `.venv/` to `.gitignore`
|
|
138
|
+
- `uv run` handles venv activation automatically
|
|
139
|
+
- Use `./uv add <package>` (Unix) or `uv.exe add <package>` (Windows cmd.exe) for project dependencies
|
|
140
|
+
- Windows with Git Bash: Follow Unix-like shell instructions
|
|
141
|
+
- Windows cmd.exe without Admin rights: `uv.exe` is copied instead of symlinked
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yeyuan98/opencode-bioresearcher-plugin",
|
|
3
|
-
"version": "1.3.0
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "OpenCode plugin that adds a bioresearcher agent",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@opencode-ai/plugin": "^1.2.6",
|
|
34
34
|
"fast-xml-parser": "^5.3.5",
|
|
35
|
+
"xdg-basedir": "^5.1.0",
|
|
35
36
|
"xlsx": "^0.18.5",
|
|
36
37
|
"zod": "^4.1.8"
|
|
37
38
|
},
|