Initial commit with translated description
This commit is contained in:
152
README.md
Normal file
152
README.md
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# OpenClaw GitHub Skill
|
||||||
|
|
||||||
|
A skill that lets your AI assistant query and manage GitHub repositories.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- 📋 **List Repos** — View your repositories with filters
|
||||||
|
- 📊 **Get Repo Details** — Stars, forks, language, last updated
|
||||||
|
- 🔄 **Check CI Status** — Monitor CI/CD pipelines
|
||||||
|
- 📝 **Create Issues** — Open issues from conversation
|
||||||
|
- 📁 **Create Repos** — Create new repositories
|
||||||
|
- 🔍 **Search Repos** — Find repos by name/query
|
||||||
|
- 📊 **Recent Activity** — View recent commits
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- OpenClaw gateway running
|
||||||
|
- Node.js 18+
|
||||||
|
- GitHub account with a Personal Access Token (PAT)
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### 1. Generate a GitHub Personal Access Token
|
||||||
|
|
||||||
|
1. Go to https://github.com/settings/tokens
|
||||||
|
2. Click "Generate new token (classic)"
|
||||||
|
3. Name: `openclaw-github-skill`
|
||||||
|
4. Scopes (permissions):
|
||||||
|
- `repo` — Full control of private repositories
|
||||||
|
- `public_repo` — Limited access to public repositories only
|
||||||
|
- `read:user` — Read user profile data (optional)
|
||||||
|
5. Copy the token
|
||||||
|
|
||||||
|
### 2. Configure Credentials
|
||||||
|
|
||||||
|
**Option A: Environment Variables (Recommended for local use)**
|
||||||
|
|
||||||
|
Set before starting OpenClaw:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export GITHUB_TOKEN="ghp_your_token_here"
|
||||||
|
export GITHUB_USERNAME="your_github_username"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option B: OpenClaw Config**
|
||||||
|
|
||||||
|
Add to `~/.openclaw/openclaw.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"github": {
|
||||||
|
"token": "ghp_your_token_here",
|
||||||
|
"username": "your_username"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Restart OpenClaw
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw gateway restart
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
You: List my Python repositories
|
||||||
|
Bot: [lists your Python repositories]
|
||||||
|
|
||||||
|
You: Check CI status on my-project
|
||||||
|
Bot: [shows CI/CD status]
|
||||||
|
|
||||||
|
You: Create an issue in my-project about the login bug
|
||||||
|
Bot: [creates the issue and returns the link]
|
||||||
|
|
||||||
|
You: What's the recent activity on my-project?
|
||||||
|
Bot: [shows recent commits]
|
||||||
|
|
||||||
|
You: Search my repos for "trading"
|
||||||
|
Bot: [shows matching repositories]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
openclaw-github-skill/
|
||||||
|
├── SKILL.md # Skill documentation for OpenClaw
|
||||||
|
├── README.md # This file
|
||||||
|
├── index.js # Skill implementation
|
||||||
|
└── package.json # NPM package metadata
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commands Reference
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `list_repos` | List repositories (filter by type, language, sort) |
|
||||||
|
| `get_repo` | Get detailed repo info (stars, forks, etc.) |
|
||||||
|
| `check_ci_status` | CI/CD status |
|
||||||
|
| `create_issue` | Create a new issue |
|
||||||
|
| `create_repo` | Create a new repository |
|
||||||
|
| `search_repos` | Search your repositories |
|
||||||
|
| `get_recent_activity` | View recent commits |
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
⚠️ **IMPORTANT: Protect Your GitHub Token!**
|
||||||
|
|
||||||
|
**Do:**
|
||||||
|
- ✅ Use environment variables or OpenClaw config
|
||||||
|
- ✅ Use minimal required scopes (`repo` or `public_repo`)
|
||||||
|
- ✅ Rotate tokens if compromised
|
||||||
|
|
||||||
|
**Don't:**
|
||||||
|
- ❌ Commit tokens to git
|
||||||
|
- ❌ Share tokens in code or public repos
|
||||||
|
- ❌ Store tokens in unprotected files
|
||||||
|
|
||||||
|
**Best Practices:**
|
||||||
|
- For local development: Environment variables are acceptable
|
||||||
|
- For shared machines: Use OpenClaw config or a secrets manager
|
||||||
|
- For production: Use your platform's credential store
|
||||||
|
|
||||||
|
## Rate Limits
|
||||||
|
|
||||||
|
- **Unauthenticated requests:** 60/hour
|
||||||
|
- **Authenticated requests:** 5,000/hour
|
||||||
|
|
||||||
|
The skill automatically uses your credentials for authentication.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- OpenClaw 2024+
|
||||||
|
- Node.js 18+
|
||||||
|
- GitHub Personal Access Token with appropriate scopes
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions welcome! To contribute:
|
||||||
|
|
||||||
|
1. Fork this repository
|
||||||
|
2. Create a feature branch
|
||||||
|
3. Make your changes
|
||||||
|
4. Submit a pull request
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
Built for the OpenClaw ecosystem.
|
||||||
108
SKILL.md
Normal file
108
SKILL.md
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
---
|
||||||
|
name: github
|
||||||
|
description: "查询和管理GitHub仓库——列出仓库、检查CI状态、创建问题、搜索仓库和查看最近活动。"
|
||||||
|
metadata:
|
||||||
|
openclaw:
|
||||||
|
emoji: "🐙"
|
||||||
|
requires:
|
||||||
|
env:
|
||||||
|
- GITHUB_TOKEN
|
||||||
|
- GITHUB_USERNAME
|
||||||
|
config:
|
||||||
|
- github.token
|
||||||
|
- github.username
|
||||||
|
---
|
||||||
|
|
||||||
|
# GitHub Integration Skill
|
||||||
|
|
||||||
|
Query and manage GitHub repositories directly from your AI assistant.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
| Capability | Description |
|
||||||
|
|------------|-------------|
|
||||||
|
| `list_repos` | List your repositories with filters |
|
||||||
|
| `get_repo` | Get detailed info about a specific repo |
|
||||||
|
| `check_ci_status` | Check CI/CD pipeline status |
|
||||||
|
| `create_issue` | Create a new issue in a repo |
|
||||||
|
| `create_repo` | Create a new repository |
|
||||||
|
| `search_repos` | Search your repositories |
|
||||||
|
| `get_recent_activity` | Get recent commits |
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
You: List my Python repos
|
||||||
|
Bot: [lists your Python repositories]
|
||||||
|
|
||||||
|
You: Check CI status on my main project
|
||||||
|
Bot: [shows CI/CD status]
|
||||||
|
|
||||||
|
You: Create an issue about the bug
|
||||||
|
Bot: [creates the issue]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### 1. Generate GitHub Personal Access Token
|
||||||
|
|
||||||
|
1. Go to https://github.com/settings/tokens
|
||||||
|
2. Click "Generate new token (classic)"
|
||||||
|
3. Name: `openclaw-github-skill`
|
||||||
|
4. Scopes: `repo` (required), `read:user` (optional)
|
||||||
|
5. Copy the token
|
||||||
|
|
||||||
|
### 2. Configure Credentials
|
||||||
|
|
||||||
|
**Option A: Environment Variables (Recommended)**
|
||||||
|
|
||||||
|
Set environment variables before starting OpenClaw:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export GITHUB_TOKEN="ghp_your_token_here"
|
||||||
|
export GITHUB_USERNAME="your_github_username"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option B: OpenClaw Config**
|
||||||
|
|
||||||
|
Add to `~/.openclaw/openclaw.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"github": {
|
||||||
|
"token": "ghp_your_token_here",
|
||||||
|
"username": "your_username"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Restart OpenClaw
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw gateway restart
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Notes
|
||||||
|
|
||||||
|
⚠️ **Protect Your Token:**
|
||||||
|
|
||||||
|
- Never commit your token to git or share it publicly
|
||||||
|
- Use the minimal required scopes (`repo` for private repos, `public_repo` for public-only)
|
||||||
|
- Rotate your token if you suspect it was compromised
|
||||||
|
- Consider using a secrets manager for production use
|
||||||
|
|
||||||
|
⚠️ **Best Practices:**
|
||||||
|
|
||||||
|
- Don't store tokens in shell profiles (~/.zshrc) on shared machines
|
||||||
|
- For local development, environment variables are acceptable
|
||||||
|
- For production, use your platform's secret/credential store
|
||||||
|
|
||||||
|
## Rate Limits
|
||||||
|
|
||||||
|
- Unauthenticated requests: 60/hour
|
||||||
|
- Authenticated requests: 5,000/hour
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- OpenClaw gateway running
|
||||||
|
- GitHub Personal Access Token with appropriate scopes
|
||||||
6
_meta.json
Normal file
6
_meta.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"ownerId": "kn7dysjcrg739jqwtz8k12xapd80yswr",
|
||||||
|
"slug": "openclaw-github-assistant",
|
||||||
|
"version": "2.0.1",
|
||||||
|
"publishedAt": 1770912461686
|
||||||
|
}
|
||||||
46
api.d.ts
vendored
Normal file
46
api.d.ts
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* GitHub API Module
|
||||||
|
* Core functions for interacting with GitHub API
|
||||||
|
*/
|
||||||
|
import { Context, Repository, RepositoryListParams, RepoDetailsParams, CIStatusParams, RecentActivityParams, CreateIssueParams, Issue, CreateRepoParams, SearchReposParams, SearchReposResult, CreatePRParams, PullRequest, ListReposResult, CheckCIResult, RecentActivityResult } from './types';
|
||||||
|
/**
|
||||||
|
* Get auth headers for API requests
|
||||||
|
*/
|
||||||
|
export declare function getAuthHeaders(context: Context): Record<string, string>;
|
||||||
|
/**
|
||||||
|
* Get GitHub username
|
||||||
|
*/
|
||||||
|
export declare function getUsername(context: Context): Promise<string>;
|
||||||
|
/**
|
||||||
|
* List repositories
|
||||||
|
*/
|
||||||
|
export declare function listRepos(args: RepositoryListParams, context: Context): Promise<ListReposResult>;
|
||||||
|
/**
|
||||||
|
* Get repository details
|
||||||
|
*/
|
||||||
|
export declare function getRepo(args: RepoDetailsParams, context: Context): Promise<Repository>;
|
||||||
|
/**
|
||||||
|
* Check CI/CD status
|
||||||
|
*/
|
||||||
|
export declare function checkCIStatus(args: CIStatusParams, context: Context): Promise<CheckCIResult>;
|
||||||
|
/**
|
||||||
|
* Get recent activity (commits)
|
||||||
|
*/
|
||||||
|
export declare function getRecentActivity(args: RecentActivityParams, context: Context): Promise<RecentActivityResult>;
|
||||||
|
/**
|
||||||
|
* Create an issue
|
||||||
|
*/
|
||||||
|
export declare function createIssue(args: CreateIssueParams, context: Context): Promise<Issue>;
|
||||||
|
/**
|
||||||
|
* Create a new repository
|
||||||
|
*/
|
||||||
|
export declare function createRepo(args: CreateRepoParams, context: Context): Promise<Repository>;
|
||||||
|
/**
|
||||||
|
* Search repositories
|
||||||
|
*/
|
||||||
|
export declare function searchRepos(args: SearchReposParams, context: Context): Promise<SearchReposResult>;
|
||||||
|
/**
|
||||||
|
* Create a pull request
|
||||||
|
*/
|
||||||
|
export declare function createPullRequest(args: CreatePRParams, context: Context): Promise<PullRequest>;
|
||||||
|
//# sourceMappingURL=api.d.ts.map
|
||||||
303
api.js
Normal file
303
api.js
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
"use strict";
|
||||||
|
/**
|
||||||
|
* GitHub API Module
|
||||||
|
* Core functions for interacting with GitHub API
|
||||||
|
*/
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.getAuthHeaders = getAuthHeaders;
|
||||||
|
exports.getUsername = getUsername;
|
||||||
|
exports.listRepos = listRepos;
|
||||||
|
exports.getRepo = getRepo;
|
||||||
|
exports.checkCIStatus = checkCIStatus;
|
||||||
|
exports.getRecentActivity = getRecentActivity;
|
||||||
|
exports.createIssue = createIssue;
|
||||||
|
exports.createRepo = createRepo;
|
||||||
|
exports.searchRepos = searchRepos;
|
||||||
|
exports.createPullRequest = createPullRequest;
|
||||||
|
const GITHUB_API = 'https://api.github.com';
|
||||||
|
// Cached user info
|
||||||
|
let cachedUser = null;
|
||||||
|
/**
|
||||||
|
* Get auth headers for API requests
|
||||||
|
*/
|
||||||
|
function getAuthHeaders(context) {
|
||||||
|
const headers = {
|
||||||
|
'Accept': 'application/vnd.github.v3+json',
|
||||||
|
'User-Agent': 'OpenClaw-GitHub-Skill'
|
||||||
|
};
|
||||||
|
// 1. Check environment variable first
|
||||||
|
if (process.env.GITHUB_TOKEN) {
|
||||||
|
headers['Authorization'] = `token ${process.env.GITHUB_TOKEN}`;
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
// 2. Fall back to OpenClaw config
|
||||||
|
const config = context.config?.github || {};
|
||||||
|
if (config.token) {
|
||||||
|
headers['Authorization'] = `token ${config.token}`;
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get GitHub username
|
||||||
|
*/
|
||||||
|
async function getUsername(context) {
|
||||||
|
// 1. Check environment variable first
|
||||||
|
if (process.env.GITHUB_USERNAME) {
|
||||||
|
return process.env.GITHUB_USERNAME;
|
||||||
|
}
|
||||||
|
// 2. Check OpenClaw config
|
||||||
|
const config = context.config?.github || {};
|
||||||
|
if (config.username) {
|
||||||
|
return config.username;
|
||||||
|
}
|
||||||
|
// 3. Fetch from API if not configured
|
||||||
|
if (!cachedUser) {
|
||||||
|
const response = await fetch(`${GITHUB_API}/user`, {
|
||||||
|
headers: getAuthHeaders(context)
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
cachedUser = data.login;
|
||||||
|
}
|
||||||
|
return cachedUser;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* List repositories
|
||||||
|
*/
|
||||||
|
async function listRepos(args, context) {
|
||||||
|
const username = await getUsername(context);
|
||||||
|
const { type = 'owner', sort = 'updated', direction = 'desc', limit = 30 } = args;
|
||||||
|
const url = `${GITHUB_API}/users/${username}/repos?type=${type}&sort=${sort}&direction=${direction}&per_page=${limit}`;
|
||||||
|
const response = await fetch(url, { headers: getAuthHeaders(context) });
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`GitHub API error: ${response.status}`);
|
||||||
|
}
|
||||||
|
const repos = await response.json();
|
||||||
|
// Filter by language if specified
|
||||||
|
let filtered = repos;
|
||||||
|
if (args.language) {
|
||||||
|
filtered = repos.filter(r => r.language?.toLowerCase() === args.language.toLowerCase());
|
||||||
|
}
|
||||||
|
// Limit results
|
||||||
|
filtered = filtered.slice(0, limit);
|
||||||
|
return {
|
||||||
|
total: filtered.length,
|
||||||
|
repos: filtered
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get repository details
|
||||||
|
*/
|
||||||
|
async function getRepo(args, context) {
|
||||||
|
const { owner, repo } = args;
|
||||||
|
const url = `${GITHUB_API}/repos/${owner}/${repo}`;
|
||||||
|
const response = await fetch(url, { headers: getAuthHeaders(context) });
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Repo not found: ${owner}/${repo}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
return {
|
||||||
|
name: data.name,
|
||||||
|
full_name: data.full_name,
|
||||||
|
description: data.description,
|
||||||
|
stars: data.stargazers_count,
|
||||||
|
forks: data.forks_count,
|
||||||
|
watchers: data.watchers_count,
|
||||||
|
language: data.language,
|
||||||
|
open_issues: data.open_issues_count,
|
||||||
|
created: data.created_at,
|
||||||
|
updated: data.updated_at,
|
||||||
|
pushed: data.pushed_at,
|
||||||
|
url: data.html_url,
|
||||||
|
default_branch: data.default_branch,
|
||||||
|
private: data.private
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Check CI/CD status
|
||||||
|
*/
|
||||||
|
async function checkCIStatus(args, context) {
|
||||||
|
const { owner, repo } = args;
|
||||||
|
// Get recent workflows/runs
|
||||||
|
const runsUrl = `${GITHUB_API}/repos/${owner}/${repo}/actions/runs?per_page=5`;
|
||||||
|
const response = await fetch(runsUrl, { headers: getAuthHeaders(context) });
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to get CI status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
const runs = (data.workflow_runs || []).map((run) => ({
|
||||||
|
name: run.name,
|
||||||
|
status: run.status,
|
||||||
|
conclusion: run.conclusion,
|
||||||
|
branch: run.head_branch,
|
||||||
|
commit: run.head_sha?.substring(0, 7) || '',
|
||||||
|
created: run.created_at,
|
||||||
|
url: run.html_url
|
||||||
|
}));
|
||||||
|
return {
|
||||||
|
repo: `${owner}/${repo}`,
|
||||||
|
runs
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get recent activity (commits)
|
||||||
|
*/
|
||||||
|
async function getRecentActivity(args, context) {
|
||||||
|
const username = await getUsername(context);
|
||||||
|
const { repo, limit = 10 } = args;
|
||||||
|
if (!repo) {
|
||||||
|
throw new Error('Repository name required');
|
||||||
|
}
|
||||||
|
// Get recent commits
|
||||||
|
const commitsUrl = `${GITHUB_API}/repos/${username}/${repo}/commits?per_page=${limit}`;
|
||||||
|
const commitsRes = await fetch(commitsUrl, { headers: getAuthHeaders(context) });
|
||||||
|
if (!commitsRes.ok) {
|
||||||
|
throw new Error(`Failed to get activity: ${commitsRes.status}`);
|
||||||
|
}
|
||||||
|
const commits = await commitsRes.json();
|
||||||
|
return {
|
||||||
|
repo: `${username}/${repo}`,
|
||||||
|
commits: commits.map((c) => ({
|
||||||
|
sha: c.sha.substring(0, 7),
|
||||||
|
message: c.commit.message.split('\n')[0],
|
||||||
|
author: c.commit.author.name,
|
||||||
|
date: c.commit.author.date,
|
||||||
|
url: c.html_url
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create an issue
|
||||||
|
*/
|
||||||
|
async function createIssue(args, context) {
|
||||||
|
const username = await getUsername(context);
|
||||||
|
const { repo, title, body } = args;
|
||||||
|
if (!title) {
|
||||||
|
throw new Error('Issue title required');
|
||||||
|
}
|
||||||
|
const url = `${GITHUB_API}/repos/${username}/${repo}/issues`;
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getAuthHeaders(context),
|
||||||
|
body: JSON.stringify({
|
||||||
|
title,
|
||||||
|
body: body || '',
|
||||||
|
...(args.extra || {})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(`Failed to create issue: ${error.message || response.status}`);
|
||||||
|
}
|
||||||
|
const issue = await response.json();
|
||||||
|
return {
|
||||||
|
number: issue.number,
|
||||||
|
title: issue.title,
|
||||||
|
url: issue.html_url,
|
||||||
|
state: issue.state
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create a new repository
|
||||||
|
*/
|
||||||
|
async function createRepo(args, context) {
|
||||||
|
const username = await getUsername(context);
|
||||||
|
const { name, description, private: isPrivate = false, auto_init = true } = args;
|
||||||
|
if (!name) {
|
||||||
|
throw new Error('Repository name required');
|
||||||
|
}
|
||||||
|
const url = `${GITHUB_API}/user/repos`;
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getAuthHeaders(context),
|
||||||
|
body: JSON.stringify({
|
||||||
|
name,
|
||||||
|
description: description || '',
|
||||||
|
private: isPrivate,
|
||||||
|
auto_init
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(`Failed to create repo: ${error.message || response.status}`);
|
||||||
|
}
|
||||||
|
const repo = await response.json();
|
||||||
|
return {
|
||||||
|
name: repo.name,
|
||||||
|
full_name: repo.full_name,
|
||||||
|
description: repo.description,
|
||||||
|
stars: repo.stargazers_count || 0,
|
||||||
|
forks: repo.forks_count || 0,
|
||||||
|
language: repo.language || null,
|
||||||
|
updated: repo.updated_at || '',
|
||||||
|
url: repo.html_url,
|
||||||
|
private: repo.private
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Search repositories
|
||||||
|
*/
|
||||||
|
async function searchRepos(args, context) {
|
||||||
|
const username = await getUsername(context);
|
||||||
|
const { query, sort = 'updated', limit = 30 } = args;
|
||||||
|
if (!query) {
|
||||||
|
throw new Error('Search query required');
|
||||||
|
}
|
||||||
|
// Search user's repos
|
||||||
|
const url = `${GITHUB_API}/search/repositories?q=${encodeURIComponent(query)}+user:${username}&sort=${sort}&per_page=${limit}`;
|
||||||
|
const response = await fetch(url, { headers: getAuthHeaders(context) });
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Search failed: ${response.status}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
return {
|
||||||
|
total: data.total_count,
|
||||||
|
repos: (data.items || []).map((r) => ({
|
||||||
|
name: r.name,
|
||||||
|
full_name: r.full_name,
|
||||||
|
description: r.description,
|
||||||
|
stars: r.stargazers_count,
|
||||||
|
language: r.language,
|
||||||
|
url: r.html_url,
|
||||||
|
forks: 0,
|
||||||
|
updated: '',
|
||||||
|
private: false
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create a pull request
|
||||||
|
*/
|
||||||
|
async function createPullRequest(args, context) {
|
||||||
|
const username = await getUsername(context);
|
||||||
|
const { owner, repo, title, body, head, base = 'main' } = args;
|
||||||
|
// Use username if owner not specified
|
||||||
|
const prOwner = owner || username;
|
||||||
|
if (!prOwner || !repo || !title || !head) {
|
||||||
|
throw new Error('owner, repo, title, and head are required');
|
||||||
|
}
|
||||||
|
const url = `${GITHUB_API}/repos/${prOwner}/${repo}/pulls`;
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getAuthHeaders(context),
|
||||||
|
body: JSON.stringify({
|
||||||
|
title,
|
||||||
|
body: body || '',
|
||||||
|
head,
|
||||||
|
base
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(`Failed to create PR: ${error.message || response.status}`);
|
||||||
|
}
|
||||||
|
const pr = await response.json();
|
||||||
|
return {
|
||||||
|
number: pr.number,
|
||||||
|
title: pr.title,
|
||||||
|
url: pr.html_url,
|
||||||
|
state: pr.state,
|
||||||
|
head: pr.head.ref,
|
||||||
|
base: pr.base.ref
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=api.js.map
|
||||||
209
index.d.ts
vendored
Normal file
209
index.d.ts
vendored
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
/**
|
||||||
|
* OpenClaw GitHub Skill
|
||||||
|
* Query and manage GitHub repositories from conversation
|
||||||
|
*
|
||||||
|
* TypeScript version - Compiled to JavaScript for OpenClaw
|
||||||
|
*/
|
||||||
|
import { Context, RepositoryListParams, RepoDetailsParams, CIStatusParams, RecentActivityParams, CreateIssueParams, CreateRepoParams, SearchReposParams, CreatePRParams, ListReposResult, Repository, CheckCIResult, RecentActivityResult, Issue, SearchReposResult, PullRequest } from './types';
|
||||||
|
export declare const skillName = "github";
|
||||||
|
export declare const skillVersion = "2.0.1";
|
||||||
|
export declare const skillDescription = "Query and manage GitHub repositories";
|
||||||
|
declare function listReposHandler(args: RepositoryListParams, context: Context): Promise<ListReposResult>;
|
||||||
|
declare function getRepoHandler(args: RepoDetailsParams, context: Context): Promise<Repository>;
|
||||||
|
declare function checkCIStatusHandler(args: CIStatusParams, context: Context): Promise<CheckCIResult>;
|
||||||
|
declare function getRecentActivityHandler(args: RecentActivityParams, context: Context): Promise<RecentActivityResult>;
|
||||||
|
declare function createIssueHandler(args: CreateIssueParams, context: Context): Promise<Issue>;
|
||||||
|
declare function createRepoHandler(args: CreateRepoParams, context: Context): Promise<Repository>;
|
||||||
|
declare function searchReposHandler(args: SearchReposParams, context: Context): Promise<SearchReposResult>;
|
||||||
|
declare function createPullRequestHandler(args: CreatePRParams, context: Context): Promise<PullRequest>;
|
||||||
|
declare const skill: {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
description: string;
|
||||||
|
actions: {
|
||||||
|
list_repos: {
|
||||||
|
description: string;
|
||||||
|
parameters: {
|
||||||
|
type: string;
|
||||||
|
properties: {
|
||||||
|
type: {
|
||||||
|
type: string;
|
||||||
|
enum: string[];
|
||||||
|
default: string;
|
||||||
|
};
|
||||||
|
sort: {
|
||||||
|
type: string;
|
||||||
|
enum: string[];
|
||||||
|
default: string;
|
||||||
|
};
|
||||||
|
language: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
limit: {
|
||||||
|
type: string;
|
||||||
|
default: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
handler: typeof listReposHandler;
|
||||||
|
};
|
||||||
|
get_repo: {
|
||||||
|
description: string;
|
||||||
|
parameters: {
|
||||||
|
type: string;
|
||||||
|
properties: {
|
||||||
|
owner: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
repo: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
required: string[];
|
||||||
|
};
|
||||||
|
handler: typeof getRepoHandler;
|
||||||
|
};
|
||||||
|
check_ci_status: {
|
||||||
|
description: string;
|
||||||
|
parameters: {
|
||||||
|
type: string;
|
||||||
|
properties: {
|
||||||
|
owner: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
repo: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
required: string[];
|
||||||
|
};
|
||||||
|
handler: typeof checkCIStatusHandler;
|
||||||
|
};
|
||||||
|
get_recent_activity: {
|
||||||
|
description: string;
|
||||||
|
parameters: {
|
||||||
|
type: string;
|
||||||
|
properties: {
|
||||||
|
repo: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
limit: {
|
||||||
|
type: string;
|
||||||
|
default: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
required: string[];
|
||||||
|
};
|
||||||
|
handler: typeof getRecentActivityHandler;
|
||||||
|
};
|
||||||
|
create_issue: {
|
||||||
|
description: string;
|
||||||
|
parameters: {
|
||||||
|
type: string;
|
||||||
|
properties: {
|
||||||
|
repo: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
title: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
body: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
extra: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
required: string[];
|
||||||
|
};
|
||||||
|
handler: typeof createIssueHandler;
|
||||||
|
};
|
||||||
|
create_repo: {
|
||||||
|
description: string;
|
||||||
|
parameters: {
|
||||||
|
type: string;
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
description: {
|
||||||
|
type: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
private: {
|
||||||
|
type: string;
|
||||||
|
description: string;
|
||||||
|
default: boolean;
|
||||||
|
};
|
||||||
|
auto_init: {
|
||||||
|
type: string;
|
||||||
|
description: string;
|
||||||
|
default: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
required: string[];
|
||||||
|
};
|
||||||
|
handler: typeof createRepoHandler;
|
||||||
|
};
|
||||||
|
create_pull_request: {
|
||||||
|
description: string;
|
||||||
|
parameters: {
|
||||||
|
type: string;
|
||||||
|
properties: {
|
||||||
|
owner: {
|
||||||
|
type: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
repo: {
|
||||||
|
type: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
title: {
|
||||||
|
type: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
body: {
|
||||||
|
type: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
head: {
|
||||||
|
type: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
base: {
|
||||||
|
type: string;
|
||||||
|
description: string;
|
||||||
|
default: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
required: string[];
|
||||||
|
};
|
||||||
|
handler: typeof createPullRequestHandler;
|
||||||
|
};
|
||||||
|
search_repos: {
|
||||||
|
description: string;
|
||||||
|
parameters: {
|
||||||
|
type: string;
|
||||||
|
properties: {
|
||||||
|
query: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
sort: {
|
||||||
|
type: string;
|
||||||
|
enum: string[];
|
||||||
|
default: string;
|
||||||
|
};
|
||||||
|
limit: {
|
||||||
|
type: string;
|
||||||
|
default: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
required: string[];
|
||||||
|
};
|
||||||
|
handler: typeof searchReposHandler;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export default skill;
|
||||||
|
//# sourceMappingURL=index.d.ts.map
|
||||||
157
index.js
Normal file
157
index.js
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
"use strict";
|
||||||
|
/**
|
||||||
|
* OpenClaw GitHub Skill
|
||||||
|
* Query and manage GitHub repositories from conversation
|
||||||
|
*
|
||||||
|
* TypeScript version - Compiled to JavaScript for OpenClaw
|
||||||
|
*/
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.skillDescription = exports.skillVersion = exports.skillName = void 0;
|
||||||
|
const api_1 = require("./api");
|
||||||
|
// Skill metadata
|
||||||
|
exports.skillName = 'github';
|
||||||
|
exports.skillVersion = '2.0.1';
|
||||||
|
exports.skillDescription = 'Query and manage GitHub repositories';
|
||||||
|
// Handler functions with proper signatures
|
||||||
|
async function listReposHandler(args, context) {
|
||||||
|
return (0, api_1.listRepos)(args, context);
|
||||||
|
}
|
||||||
|
async function getRepoHandler(args, context) {
|
||||||
|
return (0, api_1.getRepo)(args, context);
|
||||||
|
}
|
||||||
|
async function checkCIStatusHandler(args, context) {
|
||||||
|
return (0, api_1.checkCIStatus)(args, context);
|
||||||
|
}
|
||||||
|
async function getRecentActivityHandler(args, context) {
|
||||||
|
return (0, api_1.getRecentActivity)(args, context);
|
||||||
|
}
|
||||||
|
async function createIssueHandler(args, context) {
|
||||||
|
return (0, api_1.createIssue)(args, context);
|
||||||
|
}
|
||||||
|
async function createRepoHandler(args, context) {
|
||||||
|
return (0, api_1.createRepo)(args, context);
|
||||||
|
}
|
||||||
|
async function searchReposHandler(args, context) {
|
||||||
|
return (0, api_1.searchRepos)(args, context);
|
||||||
|
}
|
||||||
|
async function createPullRequestHandler(args, context) {
|
||||||
|
return (0, api_1.createPullRequest)(args, context);
|
||||||
|
}
|
||||||
|
// Skill definition for OpenClaw
|
||||||
|
const skill = {
|
||||||
|
name: exports.skillName,
|
||||||
|
version: exports.skillVersion,
|
||||||
|
description: exports.skillDescription,
|
||||||
|
actions: {
|
||||||
|
list_repos: {
|
||||||
|
description: 'List your repositories',
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
type: { type: 'string', enum: ['owner', 'all', 'member'], default: 'owner' },
|
||||||
|
sort: { type: 'string', enum: ['created', 'updated', 'pushed', 'full_name'], default: 'updated' },
|
||||||
|
language: { type: 'string' },
|
||||||
|
limit: { type: 'number', default: 30 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: listReposHandler
|
||||||
|
},
|
||||||
|
get_repo: {
|
||||||
|
description: 'Get repository details',
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
owner: { type: 'string' },
|
||||||
|
repo: { type: 'string' }
|
||||||
|
},
|
||||||
|
required: ['owner', 'repo']
|
||||||
|
},
|
||||||
|
handler: getRepoHandler
|
||||||
|
},
|
||||||
|
check_ci_status: {
|
||||||
|
description: 'Check CI/CD pipeline status',
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
owner: { type: 'string' },
|
||||||
|
repo: { type: 'string' }
|
||||||
|
},
|
||||||
|
required: ['owner', 'repo']
|
||||||
|
},
|
||||||
|
handler: checkCIStatusHandler
|
||||||
|
},
|
||||||
|
get_recent_activity: {
|
||||||
|
description: 'Get recent commits',
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
repo: { type: 'string' },
|
||||||
|
limit: { type: 'number', default: 10 }
|
||||||
|
},
|
||||||
|
required: ['repo']
|
||||||
|
},
|
||||||
|
handler: getRecentActivityHandler
|
||||||
|
},
|
||||||
|
create_issue: {
|
||||||
|
description: 'Create a new issue',
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
repo: { type: 'string' },
|
||||||
|
title: { type: 'string' },
|
||||||
|
body: { type: 'string' },
|
||||||
|
extra: { type: 'object' }
|
||||||
|
},
|
||||||
|
required: ['repo', 'title']
|
||||||
|
},
|
||||||
|
handler: createIssueHandler
|
||||||
|
},
|
||||||
|
create_repo: {
|
||||||
|
description: 'Create a new repository',
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: { type: 'string', description: 'Repository name' },
|
||||||
|
description: { type: 'string', description: 'Repository description' },
|
||||||
|
private: { type: 'boolean', description: 'Private repository', default: false },
|
||||||
|
auto_init: { type: 'boolean', description: 'Initialize with README', default: true }
|
||||||
|
},
|
||||||
|
required: ['name']
|
||||||
|
},
|
||||||
|
handler: createRepoHandler
|
||||||
|
},
|
||||||
|
create_pull_request: {
|
||||||
|
description: 'Create a pull request',
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
owner: { type: 'string', description: 'Repository owner' },
|
||||||
|
repo: { type: 'string', description: 'Repository name' },
|
||||||
|
title: { type: 'string', description: 'PR title' },
|
||||||
|
body: { type: 'string', description: 'PR description' },
|
||||||
|
head: { type: 'string', description: 'Source branch' },
|
||||||
|
base: { type: 'string', description: 'Target branch', default: 'main' }
|
||||||
|
},
|
||||||
|
required: ['owner', 'repo', 'title', 'head']
|
||||||
|
},
|
||||||
|
handler: createPullRequestHandler
|
||||||
|
},
|
||||||
|
search_repos: {
|
||||||
|
description: 'Search your repositories',
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
query: { type: 'string' },
|
||||||
|
sort: { type: 'string', enum: ['stars', 'updated', 'created'], default: 'updated' },
|
||||||
|
limit: { type: 'number', default: 30 }
|
||||||
|
},
|
||||||
|
required: ['query']
|
||||||
|
},
|
||||||
|
handler: searchReposHandler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Export for both CommonJS and ES modules
|
||||||
|
exports.default = skill;
|
||||||
|
module.exports = skill;
|
||||||
|
//# sourceMappingURL=index.js.map
|
||||||
32
package.json
Normal file
32
package.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "openclaw-github-skill",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"description": "GitHub integration skill for OpenClaw — query repos, check CI status, create issues",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "node test.js"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"openclaw",
|
||||||
|
"skill",
|
||||||
|
"github",
|
||||||
|
"git",
|
||||||
|
"automation"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/conorkennedy/openclaw-github-skill"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "node index.js"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"openclaw",
|
||||||
|
"skill",
|
||||||
|
"github",
|
||||||
|
"git",
|
||||||
|
"automation"
|
||||||
|
],
|
||||||
|
"author": "Conor Kennedy",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
156
test.js
Normal file
156
test.js
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
// OpenClaw GitHub Skill - Test Suite
|
||||||
|
// Run with: node test.js
|
||||||
|
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
|
||||||
|
// Test configuration
|
||||||
|
const TEST_REPO = 'conorkenn/openclaw-github-skill';
|
||||||
|
const TEST_OWNER = 'conorkenn';
|
||||||
|
|
||||||
|
// Helper to run GitHub API calls
|
||||||
|
async function githubAPI(endpoint, options = {}) {
|
||||||
|
const token = process.env.GITHUB_TOKEN;
|
||||||
|
const response = await fetch(`https://api.github.com${endpoint}`, {
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
'Authorization': `token ${token}`,
|
||||||
|
'Accept': 'application/vnd.github.v3+json',
|
||||||
|
'User-Agent': 'OpenClaw-GitHub-Skill-Test',
|
||||||
|
...options.headers
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test functions
|
||||||
|
const tests = [
|
||||||
|
{
|
||||||
|
name: 'Environment Variables Set',
|
||||||
|
test: () => {
|
||||||
|
if (!process.env.GITHUB_TOKEN) throw new Error('GITHUB_TOKEN not set');
|
||||||
|
if (!process.env.GITHUB_USERNAME) throw new Error('GITHUB_USERNAME not set');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'GitHub API Authentication',
|
||||||
|
test: async () => {
|
||||||
|
const response = await githubAPI('/user');
|
||||||
|
if (!response.ok) throw new Error(`Auth failed: ${response.status}`);
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.login !== process.env.GITHUB_USERNAME) {
|
||||||
|
throw new Error(`Username mismatch: expected ${process.env.GITHUB_USERNAME}, got ${data.login}`);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'List Repositories',
|
||||||
|
test: async () => {
|
||||||
|
const response = await githubAPI(`/users/${TEST_OWNER}/repos?per_page=5`);
|
||||||
|
if (!response.ok) throw new Error(`Failed to list repos: ${response.status}`);
|
||||||
|
const repos = await response.json();
|
||||||
|
if (!Array.isArray(repos) || repos.length === 0) {
|
||||||
|
throw new Error('No repositories found');
|
||||||
|
}
|
||||||
|
console.log(` Found ${repos.length} repos`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Repository',
|
||||||
|
test: async () => {
|
||||||
|
const response = await githubAPI(`/repos/${TEST_REPO}`);
|
||||||
|
if (!response.ok) throw new Error(`Repo not found: ${response.status}`);
|
||||||
|
const repo = await response.json();
|
||||||
|
if (repo.full_name !== TEST_REPO) {
|
||||||
|
throw new Error(`Repo mismatch: expected ${TEST_REPO}, got ${repo.full_name}`);
|
||||||
|
}
|
||||||
|
console.log(` Repo: ${repo.full_name} (⭐ ${repo.stargazers_count})`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Check CI Status',
|
||||||
|
test: async () => {
|
||||||
|
const response = await githubAPI(`/repos/${TEST_REPO}/actions/runs?per_page=1`);
|
||||||
|
if (!response.ok) throw new Error(`Failed to get CI status: ${response.status}`);
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(` Latest run: ${data.workflow_runs?.[0]?.name || 'none'}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Search Repositories',
|
||||||
|
test: async () => {
|
||||||
|
const response = await githubAPI(`/search/repositories?q=user:${TEST_OWNER}+github&per_page=5`);
|
||||||
|
if (!response.ok) throw new Error(`Search failed: ${response.status}`);
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.total_count === 0) {
|
||||||
|
throw new Error('No repos found in search');
|
||||||
|
}
|
||||||
|
console.log(` Found ${data.total_count} repos matching search`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Recent Commits',
|
||||||
|
test: async () => {
|
||||||
|
const response = await githubAPI(`/repos/${TEST_REPO}/commits?per_page=3`);
|
||||||
|
if (!response.ok) throw new Error(`Failed to get commits: ${response.status}`);
|
||||||
|
const commits = await response.json();
|
||||||
|
if (!Array.isArray(commits) || commits.length === 0) {
|
||||||
|
throw new Error('No commits found');
|
||||||
|
}
|
||||||
|
console.log(` Latest commit: ${commits[0].sha.substring(0, 7)} - ${commits[0].commit.message.split('\n')[0]}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Create Repository (Dry Run - Skip)',
|
||||||
|
test: async () => {
|
||||||
|
console.log(' Skipping actual repo creation (would create test-repo)');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Run tests
|
||||||
|
async function runTests() {
|
||||||
|
console.log('🧪 OpenClaw GitHub Skill - Test Suite\n');
|
||||||
|
console.log('=' .repeat(50));
|
||||||
|
|
||||||
|
let passed = 0;
|
||||||
|
let failed = 0;
|
||||||
|
|
||||||
|
for (const { name, test } of tests) {
|
||||||
|
process.stdout.write(`\n🔍 ${name}... `);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await test();
|
||||||
|
if (result) {
|
||||||
|
console.log('✅ PASS');
|
||||||
|
passed++;
|
||||||
|
} else {
|
||||||
|
console.log('❌ FAIL');
|
||||||
|
failed++;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`❌ FAIL: ${error.message}`);
|
||||||
|
failed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n' + '=' .repeat(50));
|
||||||
|
console.log(`\n📊 Results: ${passed} passed, ${failed} failed\n`);
|
||||||
|
|
||||||
|
if (failed > 0) {
|
||||||
|
console.log('❌ Some tests failed. Check your configuration.');
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
console.log('✅ All tests passed! Your GitHub skill is working correctly.');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runTests();
|
||||||
119
types.d.ts
vendored
Normal file
119
types.d.ts
vendored
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
/**
|
||||||
|
* GitHub Skill - Type Definitions
|
||||||
|
*/
|
||||||
|
export interface GitHubConfig {
|
||||||
|
token?: string;
|
||||||
|
username?: string;
|
||||||
|
}
|
||||||
|
export interface Context {
|
||||||
|
config?: {
|
||||||
|
github?: GitHubConfig;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export interface Repository {
|
||||||
|
name: string;
|
||||||
|
full_name: string;
|
||||||
|
description: string | null;
|
||||||
|
stars: number;
|
||||||
|
forks: number;
|
||||||
|
watchers?: number;
|
||||||
|
language: string | null;
|
||||||
|
open_issues?: number;
|
||||||
|
updated: string;
|
||||||
|
created?: string;
|
||||||
|
pushed?: string;
|
||||||
|
url: string;
|
||||||
|
private: boolean;
|
||||||
|
default_branch?: string;
|
||||||
|
}
|
||||||
|
export interface RepositoryListParams {
|
||||||
|
type?: 'owner' | 'all' | 'member';
|
||||||
|
sort?: 'created' | 'updated' | 'pushed' | 'full_name';
|
||||||
|
direction?: 'asc' | 'desc';
|
||||||
|
language?: string;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
export interface RepoDetailsParams {
|
||||||
|
owner: string;
|
||||||
|
repo: string;
|
||||||
|
}
|
||||||
|
export interface CIStatusParams {
|
||||||
|
owner: string;
|
||||||
|
repo: string;
|
||||||
|
}
|
||||||
|
export interface WorkflowRun {
|
||||||
|
name: string;
|
||||||
|
status: string;
|
||||||
|
conclusion: string | null;
|
||||||
|
branch: string;
|
||||||
|
commit: string;
|
||||||
|
created: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
export interface RecentActivityParams {
|
||||||
|
repo: string;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
export interface Commit {
|
||||||
|
sha: string;
|
||||||
|
message: string;
|
||||||
|
author: string;
|
||||||
|
date: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
export interface CreateIssueParams {
|
||||||
|
repo: string;
|
||||||
|
title: string;
|
||||||
|
body?: string;
|
||||||
|
extra?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
export interface Issue {
|
||||||
|
number: number;
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
state: string;
|
||||||
|
}
|
||||||
|
export interface CreateRepoParams {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
private?: boolean;
|
||||||
|
auto_init?: boolean;
|
||||||
|
}
|
||||||
|
export interface SearchReposParams {
|
||||||
|
query: string;
|
||||||
|
sort?: 'stars' | 'updated' | 'created';
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
export interface CreatePRParams {
|
||||||
|
owner: string;
|
||||||
|
repo: string;
|
||||||
|
title: string;
|
||||||
|
body?: string;
|
||||||
|
head: string;
|
||||||
|
base?: string;
|
||||||
|
}
|
||||||
|
export interface PullRequest {
|
||||||
|
number: number;
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
state: string;
|
||||||
|
head: string;
|
||||||
|
base: string;
|
||||||
|
}
|
||||||
|
export interface ListReposResult {
|
||||||
|
total: number;
|
||||||
|
repos: Repository[];
|
||||||
|
}
|
||||||
|
export interface SearchReposResult {
|
||||||
|
total: number;
|
||||||
|
repos: Repository[];
|
||||||
|
}
|
||||||
|
export interface CheckCIResult {
|
||||||
|
repo: string;
|
||||||
|
runs: WorkflowRun[];
|
||||||
|
}
|
||||||
|
export interface RecentActivityResult {
|
||||||
|
repo: string;
|
||||||
|
commits: Commit[];
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=types.d.ts.map
|
||||||
Reference in New Issue
Block a user