Initial library template with built-in library management tools
This commit is contained in:
98
README.md
Normal file
98
README.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# OpenAgent Library Template
|
||||
|
||||
This is the default template for OpenAgent configuration libraries. Fork this repository to create your own library with custom skills, commands, tools, rules, and agents.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
library/
|
||||
├── skill/ # Reusable skills (SKILL.md + reference files)
|
||||
├── command/ # Slash commands (markdown with YAML frontmatter)
|
||||
├── tool/ # Custom TypeScript tools (@opencode-ai/plugin)
|
||||
├── rule/ # Reusable instruction sets
|
||||
├── agent/ # Custom agent configurations
|
||||
└── mcp.json # MCP server configurations
|
||||
```
|
||||
|
||||
## Built-in Library Tools
|
||||
|
||||
This template includes tools for managing library content programmatically:
|
||||
|
||||
### `library-skills.ts`
|
||||
- `list_skills` - List all skills in the library
|
||||
- `get_skill` - Get full skill content by name
|
||||
- `save_skill` - Create or update a skill
|
||||
- `delete_skill` - Delete a skill
|
||||
|
||||
### `library-commands.ts`
|
||||
- `list_commands` - List all slash commands
|
||||
- `get_command` - Get command content
|
||||
- `save_command` - Create or update a command
|
||||
- `delete_command` - Delete a command
|
||||
- `list_tools` - List custom tools
|
||||
- `get_tool` - Get tool source code
|
||||
- `save_tool` - Create or update a tool
|
||||
- `delete_tool` - Delete a tool
|
||||
- `list_rules` - List rules
|
||||
- `get_rule` - Get rule content
|
||||
- `save_rule` - Create or update a rule
|
||||
- `delete_rule` - Delete a rule
|
||||
|
||||
### `library-git.ts`
|
||||
- `status` - Get library git status
|
||||
- `sync` - Pull latest changes from remote
|
||||
- `commit` - Commit changes with a message
|
||||
- `push` - Push to remote
|
||||
- `get_mcps` - Get MCP server configurations
|
||||
- `save_mcps` - Save MCP configurations
|
||||
|
||||
## Usage
|
||||
|
||||
1. Fork this repository
|
||||
2. Configure your OpenAgent instance with `LIBRARY_REMOTE=git@github.com:your-username/your-library.git`
|
||||
3. Add skills, commands, and tools via the dashboard or using the library tools
|
||||
|
||||
## Creating Skills
|
||||
|
||||
Skills are directories containing a `SKILL.md` file with YAML frontmatter:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: my-skill
|
||||
description: Description of what this skill does
|
||||
---
|
||||
|
||||
Instructions for the agent on how to use this skill...
|
||||
```
|
||||
|
||||
## Creating Commands
|
||||
|
||||
Commands are markdown files with YAML frontmatter:
|
||||
|
||||
```markdown
|
||||
---
|
||||
description: What this command does
|
||||
model: claude-sonnet-4-20250514
|
||||
---
|
||||
|
||||
Prompt template for the command...
|
||||
```
|
||||
|
||||
## Creating Tools
|
||||
|
||||
Tools are TypeScript files using `@opencode-ai/plugin`:
|
||||
|
||||
```typescript
|
||||
import { tool } from "@opencode-ai/plugin"
|
||||
|
||||
export const my_tool = tool({
|
||||
description: "What this tool does",
|
||||
args: {
|
||||
param: tool.schema.string().describe("Parameter description"),
|
||||
},
|
||||
async execute(args) {
|
||||
// Tool implementation
|
||||
return "Result"
|
||||
},
|
||||
})
|
||||
```
|
||||
0
agent/.gitkeep
Normal file
0
agent/.gitkeep
Normal file
0
command/.gitkeep
Normal file
0
command/.gitkeep
Normal file
0
rule/.gitkeep
Normal file
0
rule/.gitkeep
Normal file
0
skill/.gitkeep
Normal file
0
skill/.gitkeep
Normal file
209
tool/library-commands.ts
Normal file
209
tool/library-commands.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
import { tool } from "@opencode-ai/plugin"
|
||||
|
||||
// The Open Agent API URL - the backend handles library configuration internally
|
||||
const API_BASE = "http://127.0.0.1:3000"
|
||||
|
||||
async function apiRequest(endpoint: string, options: RequestInit = {}) {
|
||||
const url = `${API_BASE}/api/library${endpoint}`
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text()
|
||||
throw new Error(`API error ${response.status}: ${text}`)
|
||||
}
|
||||
|
||||
const contentType = response.headers.get("content-type")
|
||||
if (contentType?.includes("application/json")) {
|
||||
return response.json()
|
||||
}
|
||||
return response.text()
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Commands
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
export const list_commands = tool({
|
||||
description: "List all commands in the library (slash commands like /commit, /test)",
|
||||
args: {},
|
||||
async execute() {
|
||||
const commands = await apiRequest("/command")
|
||||
if (!commands || commands.length === 0) {
|
||||
return "No commands found in the library."
|
||||
}
|
||||
return commands.map((c: { name: string; description?: string }) =>
|
||||
`- /${c.name}: ${c.description || "(no description)"}`
|
||||
).join("\n")
|
||||
},
|
||||
})
|
||||
|
||||
export const get_command = tool({
|
||||
description: "Get the full content of a command by name",
|
||||
args: {
|
||||
name: tool.schema.string().describe("The command name (without the leading /)"),
|
||||
},
|
||||
async execute(args) {
|
||||
const command = await apiRequest(`/command/${encodeURIComponent(args.name)}`)
|
||||
let result = `# Command: /${command.name}\n\n`
|
||||
result += `**Path:** ${command.path}\n`
|
||||
if (command.description) result += `**Description:** ${command.description}\n`
|
||||
result += `\n## Content\n\n${command.content}`
|
||||
return result
|
||||
},
|
||||
})
|
||||
|
||||
export const save_command = tool({
|
||||
description: "Create or update a command. Provide the full markdown content including YAML frontmatter.",
|
||||
args: {
|
||||
name: tool.schema.string().describe("The command name (without the leading /)"),
|
||||
content: tool.schema.string().describe("Full markdown content with YAML frontmatter (description, model, subtask, agent)"),
|
||||
},
|
||||
async execute(args) {
|
||||
await apiRequest(`/command/${encodeURIComponent(args.name)}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({ content: args.content }),
|
||||
})
|
||||
return `Command '/${args.name}' saved successfully. Remember to commit and push your changes.`
|
||||
},
|
||||
})
|
||||
|
||||
export const delete_command = tool({
|
||||
description: "Delete a command from the library",
|
||||
args: {
|
||||
name: tool.schema.string().describe("The command name to delete (without the leading /)"),
|
||||
},
|
||||
async execute(args) {
|
||||
await apiRequest(`/command/${encodeURIComponent(args.name)}`, {
|
||||
method: "DELETE",
|
||||
})
|
||||
return `Command '/${args.name}' deleted. Remember to commit and push your changes.`
|
||||
},
|
||||
})
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Library Tools
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
export const list_tools = tool({
|
||||
description: "List all custom tools in the library (TypeScript tool definitions)",
|
||||
args: {},
|
||||
async execute() {
|
||||
const tools = await apiRequest("/tool")
|
||||
if (!tools || tools.length === 0) {
|
||||
return "No custom tools found in the library."
|
||||
}
|
||||
return tools.map((t: { name: string; description?: string }) =>
|
||||
`- ${t.name}: ${t.description || "(no description)"}`
|
||||
).join("\n")
|
||||
},
|
||||
})
|
||||
|
||||
export const get_tool = tool({
|
||||
description: "Get the full TypeScript code of a custom tool by name",
|
||||
args: {
|
||||
name: tool.schema.string().describe("The tool name"),
|
||||
},
|
||||
async execute(args) {
|
||||
const t = await apiRequest(`/tool/${encodeURIComponent(args.name)}`)
|
||||
let result = `# Tool: ${t.name}\n\n`
|
||||
result += `**Path:** ${t.path}\n`
|
||||
if (t.description) result += `**Description:** ${t.description}\n`
|
||||
result += `\n## Code\n\n\`\`\`typescript\n${t.content}\n\`\`\``
|
||||
return result
|
||||
},
|
||||
})
|
||||
|
||||
export const save_tool = tool({
|
||||
description: "Create or update a custom tool in the library. Provide TypeScript code using the @opencode-ai/plugin tool() helper.",
|
||||
args: {
|
||||
name: tool.schema.string().describe("The tool name"),
|
||||
content: tool.schema.string().describe("Full TypeScript code for the tool"),
|
||||
},
|
||||
async execute(args) {
|
||||
await apiRequest(`/tool/${encodeURIComponent(args.name)}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({ content: args.content }),
|
||||
})
|
||||
return `Tool '${args.name}' saved successfully. Remember to commit and push your changes.`
|
||||
},
|
||||
})
|
||||
|
||||
export const delete_tool = tool({
|
||||
description: "Delete a custom tool from the library",
|
||||
args: {
|
||||
name: tool.schema.string().describe("The tool name to delete"),
|
||||
},
|
||||
async execute(args) {
|
||||
await apiRequest(`/tool/${encodeURIComponent(args.name)}`, {
|
||||
method: "DELETE",
|
||||
})
|
||||
return `Tool '${args.name}' deleted. Remember to commit and push your changes.`
|
||||
},
|
||||
})
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Rules
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
export const list_rules = tool({
|
||||
description: "List all rules in the library (reusable instruction sets for agents)",
|
||||
args: {},
|
||||
async execute() {
|
||||
const rules = await apiRequest("/rule")
|
||||
if (!rules || rules.length === 0) {
|
||||
return "No rules found in the library."
|
||||
}
|
||||
return rules.map((r: { name: string; description?: string }) =>
|
||||
`- ${r.name}: ${r.description || "(no description)"}`
|
||||
).join("\n")
|
||||
},
|
||||
})
|
||||
|
||||
export const get_rule = tool({
|
||||
description: "Get the full content of a rule by name",
|
||||
args: {
|
||||
name: tool.schema.string().describe("The rule name"),
|
||||
},
|
||||
async execute(args) {
|
||||
const rule = await apiRequest(`/rule/${encodeURIComponent(args.name)}`)
|
||||
let result = `# Rule: ${rule.name}\n\n`
|
||||
result += `**Path:** ${rule.path}\n`
|
||||
if (rule.description) result += `**Description:** ${rule.description}\n`
|
||||
result += `\n## Content\n\n${rule.content}`
|
||||
return result
|
||||
},
|
||||
})
|
||||
|
||||
export const save_rule = tool({
|
||||
description: "Create or update a rule in the library. Provide markdown content with optional YAML frontmatter.",
|
||||
args: {
|
||||
name: tool.schema.string().describe("The rule name"),
|
||||
content: tool.schema.string().describe("Full markdown content, optionally with YAML frontmatter (description)"),
|
||||
},
|
||||
async execute(args) {
|
||||
await apiRequest(`/rule/${encodeURIComponent(args.name)}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({ content: args.content }),
|
||||
})
|
||||
return `Rule '${args.name}' saved successfully. Remember to commit and push your changes.`
|
||||
},
|
||||
})
|
||||
|
||||
export const delete_rule = tool({
|
||||
description: "Delete a rule from the library",
|
||||
args: {
|
||||
name: tool.schema.string().describe("The rule name to delete"),
|
||||
},
|
||||
async execute(args) {
|
||||
await apiRequest(`/rule/${encodeURIComponent(args.name)}`, {
|
||||
method: "DELETE",
|
||||
})
|
||||
return `Rule '${args.name}' deleted. Remember to commit and push your changes.`
|
||||
},
|
||||
})
|
||||
140
tool/library-git.ts
Normal file
140
tool/library-git.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { tool } from "@opencode-ai/plugin"
|
||||
|
||||
// The Open Agent API URL - the backend handles library configuration internally
|
||||
const API_BASE = "http://127.0.0.1:3000"
|
||||
|
||||
async function apiRequest(endpoint: string, options: RequestInit = {}) {
|
||||
const url = `${API_BASE}/api/library${endpoint}`
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text()
|
||||
throw new Error(`API error ${response.status}: ${text}`)
|
||||
}
|
||||
|
||||
const contentType = response.headers.get("content-type")
|
||||
if (contentType?.includes("application/json")) {
|
||||
return response.json()
|
||||
}
|
||||
return response.text()
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Git Operations
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
export const status = tool({
|
||||
description: "Get the git status of the library: current branch, commits ahead/behind, and modified files",
|
||||
args: {},
|
||||
async execute() {
|
||||
const status = await apiRequest("/status")
|
||||
let result = `# Library Git Status\n\n`
|
||||
result += `**Branch:** ${status.branch || "unknown"}\n`
|
||||
result += `**Remote:** ${status.remote || "not configured"}\n`
|
||||
|
||||
if (status.commits_ahead !== undefined) {
|
||||
result += `**Commits ahead:** ${status.commits_ahead}\n`
|
||||
}
|
||||
if (status.commits_behind !== undefined) {
|
||||
result += `**Commits behind:** ${status.commits_behind}\n`
|
||||
}
|
||||
|
||||
if (status.modified_files && status.modified_files.length > 0) {
|
||||
result += `\n## Modified Files\n`
|
||||
result += status.modified_files.map((f: string) => `- ${f}`).join("\n")
|
||||
} else {
|
||||
result += `\nNo uncommitted changes.`
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
})
|
||||
|
||||
export const sync = tool({
|
||||
description: "Pull latest changes from the library remote (git pull)",
|
||||
args: {},
|
||||
async execute() {
|
||||
await apiRequest("/sync", { method: "POST" })
|
||||
return "Library synced successfully. Latest changes pulled from remote."
|
||||
},
|
||||
})
|
||||
|
||||
export const commit = tool({
|
||||
description: "Commit all changes in the library with a message",
|
||||
args: {
|
||||
message: tool.schema.string().describe("Commit message describing what changed"),
|
||||
},
|
||||
async execute(args) {
|
||||
await apiRequest("/commit", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ message: args.message }),
|
||||
})
|
||||
return `Changes committed with message: "${args.message}"\n\nUse library-git_push to push to remote.`
|
||||
},
|
||||
})
|
||||
|
||||
export const push = tool({
|
||||
description: "Push committed changes to the library remote (git push)",
|
||||
args: {},
|
||||
async execute() {
|
||||
await apiRequest("/push", { method: "POST" })
|
||||
return "Changes pushed to remote successfully."
|
||||
},
|
||||
})
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// MCP Servers
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
export const get_mcps = tool({
|
||||
description: "Get all MCP server configurations from the library",
|
||||
args: {},
|
||||
async execute() {
|
||||
const mcps = await apiRequest("/mcps")
|
||||
if (!mcps || Object.keys(mcps).length === 0) {
|
||||
return "No MCP servers configured in the library."
|
||||
}
|
||||
|
||||
let result = "# MCP Servers\n\n"
|
||||
for (const [name, config] of Object.entries(mcps)) {
|
||||
const c = config as { type: string; command?: string[]; url?: string; enabled?: boolean }
|
||||
result += `## ${name}\n`
|
||||
result += `- Type: ${c.type}\n`
|
||||
if (c.type === "local" && c.command) {
|
||||
result += `- Command: \`${c.command.join(" ")}\`\n`
|
||||
}
|
||||
if (c.type === "remote" && c.url) {
|
||||
result += `- URL: ${c.url}\n`
|
||||
}
|
||||
result += `- Enabled: ${c.enabled !== false}\n\n`
|
||||
}
|
||||
return result
|
||||
},
|
||||
})
|
||||
|
||||
export const save_mcps = tool({
|
||||
description: "Save MCP server configurations to the library. Provide the full JSON object with all servers.",
|
||||
args: {
|
||||
servers: tool.schema.string().describe("JSON object with MCP server configurations. Each server has type (local/remote), command/url, env/headers, and enabled fields."),
|
||||
},
|
||||
async execute(args) {
|
||||
let parsed: Record<string, unknown>
|
||||
try {
|
||||
parsed = JSON.parse(args.servers)
|
||||
} catch (e) {
|
||||
throw new Error(`Invalid JSON: ${e}`)
|
||||
}
|
||||
|
||||
await apiRequest("/mcps", {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(parsed),
|
||||
})
|
||||
return "MCP server configurations saved successfully. Remember to commit and push your changes."
|
||||
},
|
||||
})
|
||||
102
tool/library-skills.ts
Normal file
102
tool/library-skills.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { tool } from "@opencode-ai/plugin"
|
||||
|
||||
// The Open Agent API URL - the backend handles library configuration internally
|
||||
const API_BASE = "http://127.0.0.1:3000"
|
||||
|
||||
async function apiRequest(endpoint: string, options: RequestInit = {}) {
|
||||
const url = `${API_BASE}/api/library${endpoint}`
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text()
|
||||
throw new Error(`API error ${response.status}: ${text}`)
|
||||
}
|
||||
|
||||
const contentType = response.headers.get("content-type")
|
||||
if (contentType?.includes("application/json")) {
|
||||
return response.json()
|
||||
}
|
||||
return response.text()
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Skills
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
export const list_skills = tool({
|
||||
description: "List all skills in the library with their names and descriptions",
|
||||
args: {},
|
||||
async execute() {
|
||||
const skills = await apiRequest("/skill")
|
||||
if (!skills || skills.length === 0) {
|
||||
return "No skills found in the library."
|
||||
}
|
||||
return skills.map((s: { name: string; description?: string }) =>
|
||||
`- ${s.name}: ${s.description || "(no description)"}`
|
||||
).join("\n")
|
||||
},
|
||||
})
|
||||
|
||||
export const get_skill = tool({
|
||||
description: "Get the full content of a skill by name, including SKILL.md and any additional files",
|
||||
args: {
|
||||
name: tool.schema.string().describe("The skill name (e.g., 'git-release')"),
|
||||
},
|
||||
async execute(args) {
|
||||
const skill = await apiRequest(`/skill/${encodeURIComponent(args.name)}`)
|
||||
let result = `# Skill: ${skill.name}\n\n`
|
||||
result += `**Path:** ${skill.path}\n`
|
||||
if (skill.description) {
|
||||
result += `**Description:** ${skill.description}\n`
|
||||
}
|
||||
result += `\n## SKILL.md Content\n\n${skill.content}`
|
||||
|
||||
if (skill.files && skill.files.length > 0) {
|
||||
result += "\n\n## Additional Files\n"
|
||||
for (const file of skill.files) {
|
||||
result += `\n### ${file.path}\n\n${file.content}`
|
||||
}
|
||||
}
|
||||
|
||||
if (skill.references && skill.references.length > 0) {
|
||||
result += "\n\n## Reference Files\n"
|
||||
result += skill.references.map((r: string) => `- ${r}`).join("\n")
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
})
|
||||
|
||||
export const save_skill = tool({
|
||||
description: "Create or update a skill in the library. Provide the full SKILL.md content including YAML frontmatter.",
|
||||
args: {
|
||||
name: tool.schema.string().describe("The skill name (lowercase, hyphens allowed, 1-64 chars)"),
|
||||
content: tool.schema.string().describe("Full SKILL.md content including YAML frontmatter with name and description"),
|
||||
},
|
||||
async execute(args) {
|
||||
await apiRequest(`/skill/${encodeURIComponent(args.name)}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({ content: args.content }),
|
||||
})
|
||||
return `Skill '${args.name}' saved successfully. Remember to commit and push your changes.`
|
||||
},
|
||||
})
|
||||
|
||||
export const delete_skill = tool({
|
||||
description: "Delete a skill from the library",
|
||||
args: {
|
||||
name: tool.schema.string().describe("The skill name to delete"),
|
||||
},
|
||||
async execute(args) {
|
||||
await apiRequest(`/skill/${encodeURIComponent(args.name)}`, {
|
||||
method: "DELETE",
|
||||
})
|
||||
return `Skill '${args.name}' deleted. Remember to commit and push your changes.`
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user