Strategy: TekDek foundational planning (personas, narrative, tools, Brick profile)
This commit is contained in:
7
skills/bookstack/.clawhub/origin.json
Normal file
7
skills/bookstack/.clawhub/origin.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"version": 1,
|
||||
"registry": "https://clawhub.ai",
|
||||
"slug": "bookstack",
|
||||
"installedVersion": "1.0.3",
|
||||
"installedAt": 1775917021865
|
||||
}
|
||||
120
skills/bookstack/SKILL.md
Normal file
120
skills/bookstack/SKILL.md
Normal file
@@ -0,0 +1,120 @@
|
||||
---
|
||||
name: bookstack
|
||||
description: "BookStack Wiki & Documentation API integration. Manage your knowledge base programmatically: create, read, update, and delete books, chapters, pages, and shelves. Full-text search across all content. Use when you need to: (1) Create or edit wiki pages and documentation, (2) Organize content in books and chapters, (3) Search your knowledge base, (4) Automate documentation workflows, (5) Sync content between systems. Supports both HTML and Markdown content."
|
||||
metadata:
|
||||
openclaw:
|
||||
requires:
|
||||
env:
|
||||
- BOOKSTACK_URL
|
||||
- BOOKSTACK_TOKEN_ID
|
||||
- BOOKSTACK_TOKEN_SECRET
|
||||
---
|
||||
|
||||
# BookStack Skill
|
||||
|
||||
**BookStack** is an open-source wiki and documentation platform. This skill lets you manage your entire knowledge base via API – perfect for automation and integration.
|
||||
|
||||
## Features
|
||||
|
||||
- 📚 **Books** – create, edit, delete
|
||||
- 📑 **Chapters** – organize content within books
|
||||
- 📄 **Pages** – create/edit with HTML or Markdown
|
||||
- 🔍 **Full-text search** – search across all content
|
||||
- 📁 **Shelves** – organize books into collections
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# List all books
|
||||
python3 scripts/bookstack.py list_books
|
||||
|
||||
# Search the knowledge base
|
||||
python3 scripts/bookstack.py search "Home Assistant"
|
||||
|
||||
# Get a page
|
||||
python3 scripts/bookstack.py get_page 123
|
||||
|
||||
# Create a new page (Markdown)
|
||||
python3 scripts/bookstack.py create_page --book-id 1 --name "My Page" --markdown "# Title\n\nContent here..."
|
||||
```
|
||||
|
||||
## All Commands
|
||||
|
||||
### Books
|
||||
```bash
|
||||
python3 scripts/bookstack.py list_books # List all books
|
||||
python3 scripts/bookstack.py get_book <id> # Book details
|
||||
python3 scripts/bookstack.py create_book "Name" ["Desc"] # New book
|
||||
python3 scripts/bookstack.py update_book <id> [--name] [--description]
|
||||
python3 scripts/bookstack.py delete_book <id>
|
||||
```
|
||||
|
||||
### Chapters
|
||||
```bash
|
||||
python3 scripts/bookstack.py list_chapters # List all chapters
|
||||
python3 scripts/bookstack.py get_chapter <id> # Chapter details
|
||||
python3 scripts/bookstack.py create_chapter --book-id <id> --name "Name"
|
||||
python3 scripts/bookstack.py update_chapter <id> [--name] [--description]
|
||||
python3 scripts/bookstack.py delete_chapter <id>
|
||||
```
|
||||
|
||||
### Pages
|
||||
```bash
|
||||
python3 scripts/bookstack.py list_pages # List all pages
|
||||
python3 scripts/bookstack.py get_page <id> # Page preview
|
||||
python3 scripts/bookstack.py get_page <id> --content # With HTML content
|
||||
python3 scripts/bookstack.py get_page <id> --markdown # As Markdown
|
||||
|
||||
# Create page (in book or chapter)
|
||||
python3 scripts/bookstack.py create_page --book-id <id> --name "Name" --markdown "# Content"
|
||||
python3 scripts/bookstack.py create_page --chapter-id <id> --name "Name" --html "<p>HTML</p>"
|
||||
|
||||
# Edit page
|
||||
python3 scripts/bookstack.py update_page <id> [--name] [--content] [--markdown]
|
||||
python3 scripts/bookstack.py delete_page <id>
|
||||
```
|
||||
|
||||
### Search
|
||||
```bash
|
||||
python3 scripts/bookstack.py search "query" # Search everything
|
||||
python3 scripts/bookstack.py search "query" --type page # Pages only
|
||||
python3 scripts/bookstack.py search "query" --type book # Books only
|
||||
```
|
||||
|
||||
### Shelves
|
||||
```bash
|
||||
python3 scripts/bookstack.py list_shelves # List all shelves
|
||||
python3 scripts/bookstack.py get_shelf <id> # Shelf details
|
||||
python3 scripts/bookstack.py create_shelf "Name" ["Desc"] # New shelf
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Set the following environment variables:
|
||||
|
||||
```bash
|
||||
export BOOKSTACK_URL="https://your-bookstack.example.com"
|
||||
export BOOKSTACK_TOKEN_ID="your-token-id"
|
||||
export BOOKSTACK_TOKEN_SECRET="your-token-secret"
|
||||
```
|
||||
|
||||
Or configure via your gateway config file under `skills.entries.bookstack.env`.
|
||||
|
||||
### Create an API Token
|
||||
|
||||
1. Log in to your BookStack instance
|
||||
2. Go to **Edit Profile** → **API Tokens**
|
||||
3. Click **Create Token**
|
||||
4. Copy the Token ID and Secret
|
||||
|
||||
⚠️ The user needs a role with **"Access System API"** permission!
|
||||
|
||||
## API Reference
|
||||
|
||||
- **Base URL**: `{BOOKSTACK_URL}/api`
|
||||
- **Auth Header**: `Authorization: Token {ID}:{SECRET}`
|
||||
- **Official Docs**: https://demo.bookstackapp.com/api/docs
|
||||
|
||||
---
|
||||
|
||||
**Author**: xenofex7 | **Version**: 1.0.2
|
||||
6
skills/bookstack/_meta.json
Normal file
6
skills/bookstack/_meta.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"ownerId": "kn74cct4grf5r7mhc6rw5056e17z60s0",
|
||||
"slug": "bookstack",
|
||||
"version": "1.0.3",
|
||||
"publishedAt": 1770825967434
|
||||
}
|
||||
477
skills/bookstack/scripts/bookstack.py
Normal file
477
skills/bookstack/scripts/bookstack.py
Normal file
@@ -0,0 +1,477 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
BookStack API Integration
|
||||
Full CRUD for books, chapters, pages, shelves + search
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
|
||||
# Configuration from environment
|
||||
BASE_URL = os.getenv('BOOKSTACK_URL', '').rstrip('/')
|
||||
TOKEN_ID = os.getenv('BOOKSTACK_TOKEN_ID', '')
|
||||
TOKEN_SECRET = os.getenv('BOOKSTACK_TOKEN_SECRET', '')
|
||||
|
||||
def api_call(method, endpoint, data=None, params=None):
|
||||
"""Make API call to BookStack"""
|
||||
if not BASE_URL or not TOKEN_ID or not TOKEN_SECRET:
|
||||
print("❌ Error: BOOKSTACK_URL, BOOKSTACK_TOKEN_ID, and BOOKSTACK_TOKEN_SECRET required!")
|
||||
print(" Set them as environment variables or in your gateway config.")
|
||||
sys.exit(1)
|
||||
|
||||
url = f"{BASE_URL}/api/{endpoint}"
|
||||
|
||||
if params:
|
||||
url += '?' + urllib.parse.urlencode(params)
|
||||
|
||||
try:
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
headers={
|
||||
"Authorization": f"Token {TOKEN_ID}:{TOKEN_SECRET}",
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json"
|
||||
},
|
||||
method=method
|
||||
)
|
||||
|
||||
if data:
|
||||
data = {k: v for k, v in data.items() if v is not None}
|
||||
req.data = json.dumps(data).encode()
|
||||
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
if response.status == 204:
|
||||
return None
|
||||
return json.loads(response.read().decode())
|
||||
|
||||
except urllib.error.HTTPError as e:
|
||||
try:
|
||||
error_data = json.loads(e.read().decode())
|
||||
print(f"❌ HTTP {e.code}: {error_data.get('error', {}).get('message', 'Unknown error')}")
|
||||
except:
|
||||
print(f"❌ HTTP {e.code}: {e.reason}")
|
||||
sys.exit(1)
|
||||
except urllib.error.URLError as e:
|
||||
print(f"❌ Connection error: {e.reason}")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# ============ BOOKS ============
|
||||
|
||||
def list_books(args):
|
||||
"""List all books"""
|
||||
params = {'count': args.count} if args.count else {}
|
||||
result = api_call("GET", "books", params=params)
|
||||
|
||||
if not result.get('data'):
|
||||
print("📚 No books found")
|
||||
return
|
||||
|
||||
print(f"📚 {result.get('total', len(result['data']))} Books:\n")
|
||||
for book in result['data']:
|
||||
desc = book.get('description', '')[:50] + '...' if book.get('description') else ''
|
||||
print(f" [{book['id']}] {book['name']}")
|
||||
if desc:
|
||||
print(f" {desc}")
|
||||
|
||||
def get_book(args):
|
||||
"""Get book details"""
|
||||
result = api_call("GET", f"books/{args.id}")
|
||||
print(f"📚 Book: {result['name']}")
|
||||
print(f" ID: {result['id']}")
|
||||
print(f" Slug: {result['slug']}")
|
||||
if result.get('description'):
|
||||
print(f" Description: {result['description']}")
|
||||
print(f" Created: {result['created_at']}")
|
||||
print(f" Updated: {result['updated_at']}")
|
||||
if result.get('contents'):
|
||||
print(f"\n Contents ({len(result['contents'])} items):")
|
||||
for item in result['contents'][:10]:
|
||||
icon = '📑' if item['type'] == 'chapter' else '📄'
|
||||
print(f" {icon} [{item['id']}] {item['name']}")
|
||||
|
||||
def create_book(args):
|
||||
"""Create a new book"""
|
||||
data = {
|
||||
"name": args.name,
|
||||
"description": args.description
|
||||
}
|
||||
result = api_call("POST", "books", data)
|
||||
print(f"✅ Book created: {result['name']} (ID: {result['id']})")
|
||||
|
||||
def update_book(args):
|
||||
"""Update a book"""
|
||||
data = {}
|
||||
if args.name:
|
||||
data['name'] = args.name
|
||||
if args.description:
|
||||
data['description'] = args.description
|
||||
|
||||
if not data:
|
||||
print("❌ Nothing to update. Use --name or --description")
|
||||
sys.exit(1)
|
||||
|
||||
result = api_call("PUT", f"books/{args.id}", data)
|
||||
print(f"✅ Book updated: {result['name']}")
|
||||
|
||||
def delete_book(args):
|
||||
"""Delete a book"""
|
||||
api_call("DELETE", f"books/{args.id}")
|
||||
print(f"✅ Book {args.id} deleted")
|
||||
|
||||
# ============ CHAPTERS ============
|
||||
|
||||
def list_chapters(args):
|
||||
"""List all chapters"""
|
||||
params = {'count': args.count} if args.count else {}
|
||||
result = api_call("GET", "chapters", params=params)
|
||||
|
||||
if not result.get('data'):
|
||||
print("📑 No chapters found")
|
||||
return
|
||||
|
||||
print(f"📑 {result.get('total', len(result['data']))} Chapters:\n")
|
||||
for ch in result['data']:
|
||||
print(f" [{ch['id']}] {ch['name']} (Book: {ch.get('book_id', '?')})")
|
||||
|
||||
def get_chapter(args):
|
||||
"""Get chapter details"""
|
||||
result = api_call("GET", f"chapters/{args.id}")
|
||||
print(f"📑 Chapter: {result['name']}")
|
||||
print(f" ID: {result['id']}")
|
||||
print(f" Book ID: {result['book_id']}")
|
||||
if result.get('description'):
|
||||
print(f" Description: {result['description']}")
|
||||
if result.get('pages'):
|
||||
print(f"\n Pages ({len(result['pages'])}):")
|
||||
for page in result['pages'][:10]:
|
||||
print(f" 📄 [{page['id']}] {page['name']}")
|
||||
|
||||
def create_chapter(args):
|
||||
"""Create a new chapter"""
|
||||
data = {
|
||||
"book_id": args.book_id,
|
||||
"name": args.name,
|
||||
"description": args.description
|
||||
}
|
||||
result = api_call("POST", "chapters", data)
|
||||
print(f"✅ Chapter created: {result['name']} (ID: {result['id']})")
|
||||
|
||||
def update_chapter(args):
|
||||
"""Update a chapter"""
|
||||
data = {}
|
||||
if args.name:
|
||||
data['name'] = args.name
|
||||
if args.description:
|
||||
data['description'] = args.description
|
||||
if args.book_id:
|
||||
data['book_id'] = args.book_id
|
||||
|
||||
if not data:
|
||||
print("❌ Nothing to update")
|
||||
sys.exit(1)
|
||||
|
||||
result = api_call("PUT", f"chapters/{args.id}", data)
|
||||
print(f"✅ Chapter updated: {result['name']}")
|
||||
|
||||
def delete_chapter(args):
|
||||
"""Delete a chapter"""
|
||||
api_call("DELETE", f"chapters/{args.id}")
|
||||
print(f"✅ Chapter {args.id} deleted")
|
||||
|
||||
# ============ PAGES ============
|
||||
|
||||
def list_pages(args):
|
||||
"""List all pages"""
|
||||
params = {'count': args.count} if args.count else {}
|
||||
result = api_call("GET", "pages", params=params)
|
||||
|
||||
if not result.get('data'):
|
||||
print("📄 No pages found")
|
||||
return
|
||||
|
||||
print(f"📄 {result.get('total', len(result['data']))} Pages:\n")
|
||||
for page in result['data']:
|
||||
location = f"Chapter {page['chapter_id']}" if page.get('chapter_id') else f"Book {page['book_id']}"
|
||||
print(f" [{page['id']}] {page['name']} ({location})")
|
||||
|
||||
def get_page(args):
|
||||
"""Get page with full content"""
|
||||
result = api_call("GET", f"pages/{args.id}")
|
||||
print(f"📄 Page: {result['name']}")
|
||||
print(f" ID: {result['id']}")
|
||||
print(f" Book ID: {result['book_id']}")
|
||||
if result.get('chapter_id'):
|
||||
print(f" Chapter ID: {result['chapter_id']}")
|
||||
print(f" Editor: {result.get('editor', 'unknown')}")
|
||||
print(f" Created: {result['created_at']}")
|
||||
print(f" Updated: {result['updated_at']}")
|
||||
|
||||
if args.content:
|
||||
print(f"\n--- Content (HTML) ---")
|
||||
print(result.get('html', ''))
|
||||
elif args.markdown:
|
||||
print(f"\n--- Content (Markdown) ---")
|
||||
print(result.get('markdown', result.get('html', '')))
|
||||
else:
|
||||
# Show preview
|
||||
html = result.get('html', '')
|
||||
if html:
|
||||
# Strip HTML tags for preview
|
||||
import re
|
||||
text = re.sub('<[^<]+?>', '', html)
|
||||
text = ' '.join(text.split())[:200]
|
||||
print(f"\n Preview: {text}...")
|
||||
|
||||
def create_page(args):
|
||||
"""Create a new page"""
|
||||
data = {
|
||||
"name": args.name,
|
||||
}
|
||||
|
||||
if args.book_id:
|
||||
data['book_id'] = args.book_id
|
||||
if args.chapter_id:
|
||||
data['chapter_id'] = args.chapter_id
|
||||
|
||||
if not args.book_id and not args.chapter_id:
|
||||
print("❌ Either --book-id or --chapter-id required")
|
||||
sys.exit(1)
|
||||
|
||||
if args.html:
|
||||
data['html'] = args.html
|
||||
elif args.markdown:
|
||||
data['markdown'] = args.markdown
|
||||
elif args.content:
|
||||
# Auto-detect: if starts with # or no HTML tags, treat as markdown
|
||||
if args.content.startswith('#') or '<' not in args.content:
|
||||
data['markdown'] = args.content
|
||||
else:
|
||||
data['html'] = args.content
|
||||
|
||||
result = api_call("POST", "pages", data)
|
||||
print(f"✅ Page created: {result['name']} (ID: {result['id']})")
|
||||
|
||||
def update_page(args):
|
||||
"""Update a page"""
|
||||
data = {}
|
||||
if args.name:
|
||||
data['name'] = args.name
|
||||
if args.html:
|
||||
data['html'] = args.html
|
||||
if args.markdown:
|
||||
data['markdown'] = args.markdown
|
||||
if args.content:
|
||||
if args.content.startswith('#') or '<' not in args.content:
|
||||
data['markdown'] = args.content
|
||||
else:
|
||||
data['html'] = args.content
|
||||
if args.book_id:
|
||||
data['book_id'] = args.book_id
|
||||
if args.chapter_id:
|
||||
data['chapter_id'] = args.chapter_id
|
||||
|
||||
if not data:
|
||||
print("❌ Nothing to update")
|
||||
sys.exit(1)
|
||||
|
||||
result = api_call("PUT", f"pages/{args.id}", data)
|
||||
print(f"✅ Page updated: {result['name']}")
|
||||
|
||||
def delete_page(args):
|
||||
"""Delete a page"""
|
||||
api_call("DELETE", f"pages/{args.id}")
|
||||
print(f"✅ Page {args.id} deleted")
|
||||
|
||||
# ============ SHELVES ============
|
||||
|
||||
def list_shelves(args):
|
||||
"""List all shelves"""
|
||||
params = {'count': args.count} if args.count else {}
|
||||
result = api_call("GET", "shelves", params=params)
|
||||
|
||||
if not result.get('data'):
|
||||
print("📁 No shelves found")
|
||||
return
|
||||
|
||||
print(f"📁 {result.get('total', len(result['data']))} Shelves:\n")
|
||||
for shelf in result['data']:
|
||||
print(f" [{shelf['id']}] {shelf['name']}")
|
||||
|
||||
def get_shelf(args):
|
||||
"""Get shelf details"""
|
||||
result = api_call("GET", f"shelves/{args.id}")
|
||||
print(f"📁 Shelf: {result['name']}")
|
||||
print(f" ID: {result['id']}")
|
||||
if result.get('description'):
|
||||
print(f" Description: {result['description']}")
|
||||
if result.get('books'):
|
||||
print(f"\n Books ({len(result['books'])}):")
|
||||
for book in result['books'][:10]:
|
||||
print(f" 📚 [{book['id']}] {book['name']}")
|
||||
|
||||
def create_shelf(args):
|
||||
"""Create a new shelf"""
|
||||
data = {
|
||||
"name": args.name,
|
||||
"description": args.description
|
||||
}
|
||||
result = api_call("POST", "shelves", data)
|
||||
print(f"✅ Shelf created: {result['name']} (ID: {result['id']})")
|
||||
|
||||
# ============ SEARCH ============
|
||||
|
||||
def search(args):
|
||||
"""Search content"""
|
||||
params = {
|
||||
'query': args.query,
|
||||
'count': args.count or 20
|
||||
}
|
||||
if args.type:
|
||||
params['query'] = f"{{{args.type}}} {args.query}"
|
||||
|
||||
result = api_call("GET", "search", params=params)
|
||||
|
||||
if not result.get('data'):
|
||||
print(f"🔍 No results for: {args.query}")
|
||||
return
|
||||
|
||||
print(f"🔍 {result.get('total', len(result['data']))} Results for '{args.query}':\n")
|
||||
for item in result['data']:
|
||||
icon = {'page': '📄', 'chapter': '📑', 'book': '📚', 'bookshelf': '📁'}.get(item['type'], '📎')
|
||||
print(f" {icon} [{item['type']}:{item['id']}] {item['name']}")
|
||||
if item.get('preview_html'):
|
||||
import re
|
||||
preview = re.sub('<[^<]+?>', '', item['preview_html'].get('content', ''))
|
||||
preview = ' '.join(preview.split())[:80]
|
||||
if preview:
|
||||
print(f" {preview}...")
|
||||
|
||||
# ============ MAIN ============
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='BookStack API CLI')
|
||||
subparsers = parser.add_subparsers(dest='command', help='Commands')
|
||||
|
||||
# Books
|
||||
p = subparsers.add_parser('list_books', help='List all books')
|
||||
p.add_argument('--count', type=int, help='Max results')
|
||||
p.set_defaults(func=list_books)
|
||||
|
||||
p = subparsers.add_parser('get_book', help='Get book details')
|
||||
p.add_argument('id', type=int)
|
||||
p.set_defaults(func=get_book)
|
||||
|
||||
p = subparsers.add_parser('create_book', help='Create a book')
|
||||
p.add_argument('name')
|
||||
p.add_argument('description', nargs='?')
|
||||
p.set_defaults(func=create_book)
|
||||
|
||||
p = subparsers.add_parser('update_book', help='Update a book')
|
||||
p.add_argument('id', type=int)
|
||||
p.add_argument('--name')
|
||||
p.add_argument('--description')
|
||||
p.set_defaults(func=update_book)
|
||||
|
||||
p = subparsers.add_parser('delete_book', help='Delete a book')
|
||||
p.add_argument('id', type=int)
|
||||
p.set_defaults(func=delete_book)
|
||||
|
||||
# Chapters
|
||||
p = subparsers.add_parser('list_chapters', help='List all chapters')
|
||||
p.add_argument('--count', type=int)
|
||||
p.set_defaults(func=list_chapters)
|
||||
|
||||
p = subparsers.add_parser('get_chapter', help='Get chapter details')
|
||||
p.add_argument('id', type=int)
|
||||
p.set_defaults(func=get_chapter)
|
||||
|
||||
p = subparsers.add_parser('create_chapter', help='Create a chapter')
|
||||
p.add_argument('--book-id', type=int, required=True)
|
||||
p.add_argument('--name', required=True)
|
||||
p.add_argument('--description')
|
||||
p.set_defaults(func=create_chapter)
|
||||
|
||||
p = subparsers.add_parser('update_chapter', help='Update a chapter')
|
||||
p.add_argument('id', type=int)
|
||||
p.add_argument('--name')
|
||||
p.add_argument('--description')
|
||||
p.add_argument('--book-id', type=int)
|
||||
p.set_defaults(func=update_chapter)
|
||||
|
||||
p = subparsers.add_parser('delete_chapter', help='Delete a chapter')
|
||||
p.add_argument('id', type=int)
|
||||
p.set_defaults(func=delete_chapter)
|
||||
|
||||
# Pages
|
||||
p = subparsers.add_parser('list_pages', help='List all pages')
|
||||
p.add_argument('--count', type=int)
|
||||
p.set_defaults(func=list_pages)
|
||||
|
||||
p = subparsers.add_parser('get_page', help='Get page with content')
|
||||
p.add_argument('id', type=int)
|
||||
p.add_argument('--content', action='store_true', help='Show full HTML')
|
||||
p.add_argument('--markdown', action='store_true', help='Show as markdown')
|
||||
p.set_defaults(func=get_page)
|
||||
|
||||
p = subparsers.add_parser('create_page', help='Create a page')
|
||||
p.add_argument('--name', required=True)
|
||||
p.add_argument('--book-id', type=int)
|
||||
p.add_argument('--chapter-id', type=int)
|
||||
p.add_argument('--content', help='Content (auto-detect HTML/MD)')
|
||||
p.add_argument('--html', help='HTML content')
|
||||
p.add_argument('--markdown', help='Markdown content')
|
||||
p.set_defaults(func=create_page)
|
||||
|
||||
p = subparsers.add_parser('update_page', help='Update a page')
|
||||
p.add_argument('id', type=int)
|
||||
p.add_argument('--name')
|
||||
p.add_argument('--content')
|
||||
p.add_argument('--html')
|
||||
p.add_argument('--markdown')
|
||||
p.add_argument('--book-id', type=int)
|
||||
p.add_argument('--chapter-id', type=int)
|
||||
p.set_defaults(func=update_page)
|
||||
|
||||
p = subparsers.add_parser('delete_page', help='Delete a page')
|
||||
p.add_argument('id', type=int)
|
||||
p.set_defaults(func=delete_page)
|
||||
|
||||
# Shelves
|
||||
p = subparsers.add_parser('list_shelves', help='List all shelves')
|
||||
p.add_argument('--count', type=int)
|
||||
p.set_defaults(func=list_shelves)
|
||||
|
||||
p = subparsers.add_parser('get_shelf', help='Get shelf details')
|
||||
p.add_argument('id', type=int)
|
||||
p.set_defaults(func=get_shelf)
|
||||
|
||||
p = subparsers.add_parser('create_shelf', help='Create a shelf')
|
||||
p.add_argument('name')
|
||||
p.add_argument('description', nargs='?')
|
||||
p.set_defaults(func=create_shelf)
|
||||
|
||||
# Search
|
||||
p = subparsers.add_parser('search', help='Search content')
|
||||
p.add_argument('query')
|
||||
p.add_argument('--type', choices=['page', 'chapter', 'book', 'shelf'])
|
||||
p.add_argument('--count', type=int)
|
||||
p.set_defaults(func=search)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.command:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
args.func(args)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
7
skills/gitea/.clawhub/origin.json
Normal file
7
skills/gitea/.clawhub/origin.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"version": 1,
|
||||
"registry": "https://clawhub.ai",
|
||||
"slug": "gitea",
|
||||
"installedVersion": "1.0.0",
|
||||
"installedAt": 1775917150508
|
||||
}
|
||||
203
skills/gitea/SKILL.md
Normal file
203
skills/gitea/SKILL.md
Normal file
@@ -0,0 +1,203 @@
|
||||
---
|
||||
name: gitea
|
||||
description: "Interact with Gitea using the `tea` CLI. Use `tea issues`, `tea pulls`, `tea releases`, and other commands for issues, PRs, releases, and repository management."
|
||||
---
|
||||
|
||||
# Gitea Skill
|
||||
|
||||
Use the `tea` CLI to interact with Gitea servers. Use `--repo owner/repo` when not in a git directory, or `--login instance.com` to specify a Gitea instance.
|
||||
|
||||
## Setup
|
||||
|
||||
Add a login once to get started:
|
||||
```bash
|
||||
tea login add
|
||||
```
|
||||
|
||||
Check current logged in user:
|
||||
```bash
|
||||
tea whoami
|
||||
```
|
||||
|
||||
## Repositories
|
||||
|
||||
List repositories you have access to:
|
||||
```bash
|
||||
tea repos list
|
||||
```
|
||||
|
||||
Create a new repository:
|
||||
```bash
|
||||
tea repos create --name my-repo --description "My project" --init
|
||||
```
|
||||
|
||||
Create a private repository:
|
||||
```bash
|
||||
tea repos create --name my-repo --private --init
|
||||
```
|
||||
|
||||
Fork a repository:
|
||||
```bash
|
||||
tea repos fork owner/repo
|
||||
```
|
||||
|
||||
Delete a repository:
|
||||
```bash
|
||||
tea repos delete --name my-repo --owner myuser --force
|
||||
```
|
||||
|
||||
## Pull Requests
|
||||
|
||||
List open pull requests:
|
||||
```bash
|
||||
tea pulls --repo owner/repo
|
||||
```
|
||||
|
||||
View a specific PR:
|
||||
```bash
|
||||
tea pr 55 --repo owner/repo
|
||||
```
|
||||
|
||||
Checkout a PR locally:
|
||||
```bash
|
||||
tea pr checkout 55
|
||||
```
|
||||
|
||||
Create a new PR:
|
||||
```bash
|
||||
tea pr create --title "Feature title" --description "Description"
|
||||
```
|
||||
|
||||
## Issues
|
||||
|
||||
List open issues:
|
||||
```bash
|
||||
tea issues --repo owner/repo
|
||||
```
|
||||
|
||||
View a specific issue:
|
||||
```bash
|
||||
tea issue 189 --repo owner/repo
|
||||
```
|
||||
|
||||
Create a new issue:
|
||||
```bash
|
||||
tea issue create --title "Bug title" --body "Description"
|
||||
```
|
||||
|
||||
View issues for a milestone:
|
||||
```bash
|
||||
tea milestone issues 0.7.0
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
Add a comment to an issue or PR:
|
||||
```bash
|
||||
tea comment 189 --body "Your comment here"
|
||||
```
|
||||
|
||||
## Releases
|
||||
|
||||
List releases:
|
||||
```bash
|
||||
tea releases --repo owner/repo
|
||||
```
|
||||
|
||||
Create a new release:
|
||||
```bash
|
||||
tea release create --tag v1.0.0 --title "Release 1.0.0"
|
||||
```
|
||||
|
||||
## Actions (CI/CD)
|
||||
|
||||
List repository action secrets:
|
||||
```bash
|
||||
tea actions secrets list
|
||||
```
|
||||
|
||||
Create a new secret:
|
||||
```bash
|
||||
tea actions secrets create API_KEY
|
||||
```
|
||||
|
||||
List action variables:
|
||||
```bash
|
||||
tea actions variables list
|
||||
```
|
||||
|
||||
Set an action variable:
|
||||
```bash
|
||||
tea actions variables set API_URL https://api.example.com
|
||||
```
|
||||
|
||||
## Webhooks
|
||||
|
||||
List repository webhooks:
|
||||
```bash
|
||||
tea webhooks list
|
||||
```
|
||||
|
||||
List organization webhooks:
|
||||
```bash
|
||||
tea webhooks list --org myorg
|
||||
```
|
||||
|
||||
Create a webhook:
|
||||
```bash
|
||||
tea webhooks create https://example.com/hook --events push,pull_request
|
||||
```
|
||||
|
||||
## Other Entities
|
||||
|
||||
List branches:
|
||||
```bash
|
||||
tea branches --repo owner/repo
|
||||
```
|
||||
|
||||
List labels:
|
||||
```bash
|
||||
tea labels --repo owner/repo
|
||||
```
|
||||
|
||||
List milestones:
|
||||
```bash
|
||||
tea milestones --repo owner/repo
|
||||
```
|
||||
|
||||
List organizations:
|
||||
```bash
|
||||
tea organizations
|
||||
```
|
||||
|
||||
Show repository details:
|
||||
```bash
|
||||
tea repo --repo owner/repo
|
||||
```
|
||||
|
||||
## Helpers
|
||||
|
||||
Open something in browser:
|
||||
```bash
|
||||
tea open 189 # open issue/PR 189
|
||||
tea open milestones # open milestones page
|
||||
```
|
||||
|
||||
Clone a repository:
|
||||
```bash
|
||||
tea clone owner/repo
|
||||
```
|
||||
|
||||
Show notifications:
|
||||
```bash
|
||||
tea notifications --mine
|
||||
```
|
||||
|
||||
## Output Formats
|
||||
|
||||
Use `--output` or `-o` to control output format:
|
||||
```bash
|
||||
tea issues --output simple # simple text output
|
||||
tea issues --output csv # CSV format
|
||||
tea issues --output yaml # YAML format
|
||||
```
|
||||
6
skills/gitea/_meta.json
Normal file
6
skills/gitea/_meta.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"ownerId": "kn7dnbj0wvhgz2c6bg8cvbsmb9808s4w",
|
||||
"slug": "gitea",
|
||||
"version": "1.0.0",
|
||||
"publishedAt": 1769899848068
|
||||
}
|
||||
Reference in New Issue
Block a user