Initial commit
This commit is contained in:
292
skills/calibre/SKILL.md
Normal file
292
skills/calibre/SKILL.md
Normal file
@@ -0,0 +1,292 @@
|
||||
---
|
||||
name: calibre
|
||||
description: Search and query Calibre library databases. Use when the user asks about books, TBR (to-be-read), reading lists, Calibre library queries, book searches, or mentions Calibre. Also use for queries about book ratings, authors, reading status, or library statistics.
|
||||
---
|
||||
|
||||
# Calibre Library Search Skill
|
||||
|
||||
You are helping the user search and query their Calibre library using `calibredb`.
|
||||
|
||||
## Library Setup
|
||||
|
||||
**Library URL**: `http://killington.home.bitbin.de:8454/#`
|
||||
|
||||
**calibredb Location**: `/Applications/calibre.app/Contents/MacOS/calibredb`
|
||||
|
||||
**Authentication**:
|
||||
- Username: `calibre`
|
||||
- Password: `calibre`
|
||||
|
||||
**Note**: Using Calibre Content Server means no database locking issues - queries work even while Calibre GUI is running.
|
||||
|
||||
## Custom Fields
|
||||
|
||||
The Calibre library has these custom fields:
|
||||
|
||||
| Field | Search Name | Display Name | Type | Values |
|
||||
|------------|-------------|--------------|----------|--------------|
|
||||
| read | #read | *read | Boolean | Yes/No |
|
||||
| dateread | #dateread | *dateread | Datetime | ISO date |
|
||||
| archived | #archived | *archived | Boolean | Yes/No |
|
||||
| goodreads | #goodreads | *goodreads | Float | 0.0-5.0 |
|
||||
| pages | #pages | *pages | Integer | page count |
|
||||
| priority | #priority | *priority | Text | varies |
|
||||
| words | #words | *words | Integer | word count |
|
||||
|
||||
**IMPORTANT - Boolean Field Syntax**:
|
||||
- Use `#field` syntax in searches with `Yes` or `No` (capitalized): e.g., `#read:Yes`, `#read:No`
|
||||
- Use `*field` syntax when displaying fields (e.g., `*read`)
|
||||
- Boolean values are **NOT** `true`/`false` - they are `Yes`/`No`
|
||||
|
||||
## calibredb Command Pattern
|
||||
|
||||
Always use this pattern:
|
||||
|
||||
```bash
|
||||
/Applications/calibre.app/Contents/MacOS/calibredb list \
|
||||
--with-library='http://killington.home.bitbin.de:8454/#' \
|
||||
--username='calibre' \
|
||||
--password='calibre' \
|
||||
--fields='field1,field2,*customfield' \
|
||||
--search='search query' \
|
||||
--for-machine | python3 -m json.tool
|
||||
```
|
||||
|
||||
### Common Options
|
||||
|
||||
- `--fields='field1,field2'` - Comma-separated list of fields to display
|
||||
- `--search='query'` - Search query using Calibre's search syntax
|
||||
- `--sort-by='field'` - Sort results by field
|
||||
- `--limit=N` - Limit results to N books
|
||||
- `--for-machine` - Output as JSON (always use this for parsing)
|
||||
|
||||
### Available Built-in Fields
|
||||
|
||||
- `title`, `authors`, `series`, `series_index`
|
||||
- `timestamp` (when added to library)
|
||||
- `last_modified` (when book record was last modified)
|
||||
- `pubdate`, `publisher`, `isbn`
|
||||
- `rating`, `tags`, `comments`
|
||||
- `formats`, `size`, `uuid`
|
||||
|
||||
## Search Query Syntax
|
||||
|
||||
### Basic Searches
|
||||
|
||||
```bash
|
||||
# Search by title
|
||||
--search='title:"Book Title"'
|
||||
|
||||
# Search by author
|
||||
--search='authors:"Author Name"'
|
||||
|
||||
# Search in series
|
||||
--search='series:"Series Name"'
|
||||
|
||||
# Combine searches with AND
|
||||
--search='authors:"Sanderson" and series:"Mistborn"'
|
||||
|
||||
# Combine searches with OR
|
||||
--search='authors:"Sanderson" or authors:"Wells"'
|
||||
|
||||
# NOT operator
|
||||
--search='not #archived:Yes'
|
||||
```
|
||||
|
||||
### Custom Field Searches
|
||||
|
||||
```bash
|
||||
# Books marked as read
|
||||
--search='#read:Yes'
|
||||
|
||||
# Books NOT read
|
||||
--search='#read:No'
|
||||
|
||||
# Books not archived and not read (TBR)
|
||||
--search='#read:No and #archived:No'
|
||||
|
||||
# Highly rated books (>= 4.0)
|
||||
--search='#goodreads:">4"'
|
||||
|
||||
# Books with specific page count
|
||||
--search='#pages:"<300"'
|
||||
|
||||
# Empty/not set custom fields
|
||||
--search='#goodreads:""'
|
||||
|
||||
# Books read in a specific date range
|
||||
--search='#dateread:">=2024-01-01" and #dateread:"<2025-01-01"'
|
||||
|
||||
# Books read in the last 30 days
|
||||
--search='#dateread:">=30daysago"'
|
||||
```
|
||||
|
||||
## Common Query Examples
|
||||
|
||||
### To-Be-Read List
|
||||
|
||||
Get unread, non-archived books:
|
||||
|
||||
```bash
|
||||
/Applications/calibre.app/Contents/MacOS/calibredb list \
|
||||
--with-library='http://killington.home.bitbin.de:8454/#' \
|
||||
--username='calibre' \
|
||||
--password='calibre' \
|
||||
--fields='title,authors,series,series_index,*goodreads,*pages' \
|
||||
--search='#read:No and #archived:No' \
|
||||
--for-machine | python3 -m json.tool
|
||||
```
|
||||
|
||||
### Recently Added Books
|
||||
|
||||
```bash
|
||||
/Applications/calibre.app/Contents/MacOS/calibredb list \
|
||||
--with-library='http://killington.home.bitbin.de:8454/#' \
|
||||
--username='calibre' \
|
||||
--password='calibre' \
|
||||
--fields='title,authors,*goodreads,*pages,timestamp' \
|
||||
--search='#read:No and #archived:No' \
|
||||
--sort-by='timestamp' \
|
||||
--limit=10 \
|
||||
--for-machine | python3 -m json.tool
|
||||
```
|
||||
|
||||
### Recently Read Books
|
||||
|
||||
Use the `*dateread` field to see when books were actually finished:
|
||||
|
||||
```bash
|
||||
/Applications/calibre.app/Contents/MacOS/calibredb list \
|
||||
--with-library='http://killington.home.bitbin.de:8454/#' \
|
||||
--username='calibre' \
|
||||
--password='calibre' \
|
||||
--fields='title,authors,series,series_index,*goodreads,*pages,*dateread' \
|
||||
--search='#read:Yes' \
|
||||
--sort-by='*dateread' \
|
||||
--limit=15 \
|
||||
--for-machine | python3 -m json.tool
|
||||
```
|
||||
|
||||
### Highly Rated Unread Books
|
||||
|
||||
```bash
|
||||
/Applications/calibre.app/Contents/MacOS/calibredb list \
|
||||
--with-library='http://killington.home.bitbin.de:8454/#' \
|
||||
--username='calibre' \
|
||||
--password='calibre' \
|
||||
--fields='title,authors,series,*goodreads,*pages' \
|
||||
--search='#read:No and #archived:No and #goodreads:">4"' \
|
||||
--sort-by='*goodreads' \
|
||||
--for-machine | python3 -m json.tool
|
||||
```
|
||||
|
||||
### Books by Specific Author
|
||||
|
||||
```bash
|
||||
/Applications/calibre.app/Contents/MacOS/calibredb list \
|
||||
--with-library='http://killington.home.bitbin.de:8454/#' \
|
||||
--username='calibre' \
|
||||
--password='calibre' \
|
||||
--fields='title,authors,series,*read,*goodreads,*pages' \
|
||||
--search='authors:"Sanderson" and #archived:No' \
|
||||
--for-machine | python3 -m json.tool
|
||||
```
|
||||
|
||||
### Quick Reads (< 300 pages)
|
||||
|
||||
```bash
|
||||
/Applications/calibre.app/Contents/MacOS/calibredb list \
|
||||
--with-library='http://killington.home.bitbin.de:8454/#' \
|
||||
--username='calibre' \
|
||||
--password='calibre' \
|
||||
--fields='title,authors,*pages,*goodreads' \
|
||||
--search='#read:No and #archived:No and #pages:"<300"' \
|
||||
--sort-by='*goodreads' \
|
||||
--for-machine | python3 -m json.tool
|
||||
```
|
||||
|
||||
### Unread Books in a Series
|
||||
|
||||
```bash
|
||||
/Applications/calibre.app/Contents/MacOS/calibredb list \
|
||||
--with-library='http://killington.home.bitbin.de:8454/#' \
|
||||
--username='calibre' \
|
||||
--password='calibre' \
|
||||
--fields='title,authors,series,series_index,*goodreads,*pages,timestamp' \
|
||||
--search='series:"Between Earth and Sky" and #read:No and #archived:No' \
|
||||
--sort-by='series_index' \
|
||||
--for-machine | python3 -m json.tool
|
||||
```
|
||||
|
||||
### Books Read in a Time Period
|
||||
|
||||
```bash
|
||||
# Books read in October 2024
|
||||
/Applications/calibre.app/Contents/MacOS/calibredb list \
|
||||
--with-library='http://killington.home.bitbin.de:8454/#' \
|
||||
--username='calibre' \
|
||||
--password='calibre' \
|
||||
--fields='title,authors,*dateread,*goodreads,*pages' \
|
||||
--search='#dateread:">=2024-10-01" and #dateread:"<2024-11-01"' \
|
||||
--sort-by='*dateread' \
|
||||
--for-machine | python3 -m json.tool
|
||||
|
||||
# Books read this year
|
||||
/Applications/calibre.app/Contents/MacOS/calibredb list \
|
||||
--with-library='http://killington.home.bitbin.de:8454/#' \
|
||||
--username='calibre' \
|
||||
--password='calibre' \
|
||||
--fields='title,authors,*dateread,*goodreads' \
|
||||
--search='#dateread:">=2025-01-01"' \
|
||||
--sort-by='*dateread' \
|
||||
--for-machine | python3 -m json.tool
|
||||
```
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
When the user asks to search their Calibre library:
|
||||
|
||||
1. **Determine** what they're looking for (to-be-read, specific book, by rating, etc.)
|
||||
2. **Construct** the appropriate calibredb command based on examples above
|
||||
3. **Execute** the command using the Bash tool
|
||||
4. **Parse** the JSON output and present results in a readable format
|
||||
|
||||
## Query Tips
|
||||
|
||||
- **Always exclude archived books** unless specifically requested: add `and #archived:No` to searches
|
||||
- Use `--for-machine` to get JSON output that's easier to parse
|
||||
- The `timestamp` field shows when a book was added to the library
|
||||
- The `*dateread` field shows when a book was actually finished reading (synced from Goodreads)
|
||||
- The `last_modified` field shows when a book record was last changed (not reliable for "recently read")
|
||||
- When a custom field is not set, it won't appear in the JSON output
|
||||
- Use `python3 -m json.tool` to pretty-print JSON for readability
|
||||
- Search queries are case-insensitive
|
||||
- Use quotes around field values that contain spaces
|
||||
- **Boolean fields**: In search queries use `Yes`/`No` (e.g., `#read:Yes`), but in JSON output they appear as `true`/`false` (lowercase)
|
||||
- **Date fields**: Use `*dateread` to sort by when books were actually read (more accurate than `last_modified`)
|
||||
|
||||
## Examples
|
||||
|
||||
**User**: "Show me my to-be-read list"
|
||||
→ Search with `#read:No and #archived:No`
|
||||
|
||||
**User**: "Find books by Sanderson"
|
||||
→ Search with `authors:"Sanderson" and #archived:No`
|
||||
|
||||
**User**: "Show highly rated unread books"
|
||||
→ Search with `#read:No and #archived:No and #goodreads:">4"`
|
||||
|
||||
**User**: "What did I read recently?"
|
||||
→ Search with `#read:Yes`, sort by `*dateread` (descending), limit to 10-15
|
||||
|
||||
**User**: "What's next in the series I'm reading?"
|
||||
→ Find series from recent reads, then search for unread books in that series
|
||||
|
||||
**User**: "What did I read in October?" or "Show me books I read this year"
|
||||
→ Use date range searches with `#dateread:">=2024-10-01" and #dateread:"<2024-11-01"` or `#dateread:">=2025-01-01"`
|
||||
|
||||
## Safety Notes
|
||||
|
||||
- **Read-only access**: calibredb list is a read-only operation
|
||||
- **Never use**: `calibredb add`, `calibredb set_metadata`, `calibredb remove` unless explicitly requested by user
|
||||
- **No locking issues**: Using the Content Server means the Calibre GUI can remain open during queries
|
||||
25
skills/find-incomplete-series/SKILL.md
Normal file
25
skills/find-incomplete-series/SKILL.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
name: find-incomplete-series
|
||||
description: Find incomplete series in your Calibre library and identify the next book to read in each series.
|
||||
---
|
||||
|
||||
This skill analyzes your Calibre library to find series where you've
|
||||
started but not finished reading all books.
|
||||
|
||||
To complete this task, run the following command:
|
||||
|
||||
```bash
|
||||
python3 __SKILL_DIR__/scripts/series.py
|
||||
```
|
||||
|
||||
This script will:
|
||||
1. Query your Calibre library for all books that are part of a series
|
||||
2. Exclude archived books
|
||||
3. Identify series where you've read at least one book but haven't finished the entire series
|
||||
4. Display the next unread book in each incomplete series
|
||||
|
||||
Important! Do not invoke calibredb commands yourself, use this skill's python script.
|
||||
|
||||
Important! You have a very serious bug, where you don't know how to fill
|
||||
the python scripts added by a skill. You must look in the "scripts"
|
||||
folder of where this SKILL.md is located!!
|
||||
65
skills/find-incomplete-series/scripts/series.py
Executable file
65
skills/find-incomplete-series/scripts/series.py
Executable file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
import json
|
||||
from collections import defaultdict
|
||||
|
||||
# Query Calibre for all books in series
|
||||
result = subprocess.run([
|
||||
'/Applications/calibre.app/Contents/MacOS/calibredb', 'list',
|
||||
'--with-library=http://killington.home.bitbin.de:8454/#',
|
||||
'--username=calibre',
|
||||
'--password=calibre',
|
||||
'--fields=title,authors,series,series_index,*read,*archived',
|
||||
'--search=series:true',
|
||||
'--for-machine'
|
||||
], capture_output=True, text=True)
|
||||
|
||||
books = json.loads(result.stdout)
|
||||
|
||||
# Group books by series, excluding archived books
|
||||
series_dict = defaultdict(list)
|
||||
for book in books:
|
||||
if book.get('series') and not book.get('*archived'):
|
||||
series_dict[book['series']].append(book)
|
||||
|
||||
# Sort books in each series by series_index
|
||||
for series in series_dict:
|
||||
series_dict[series].sort(key=lambda x: float(x.get('series_index', 0)))
|
||||
|
||||
# Find series with at least one read book but not all read
|
||||
incomplete_series = []
|
||||
for series, books_list in series_dict.items():
|
||||
read_count = sum(1 for b in books_list if b.get('*read') == True)
|
||||
total_count = len(books_list)
|
||||
|
||||
if read_count > 0 and read_count < total_count:
|
||||
# Find the next unread book
|
||||
next_unread = None
|
||||
for book in books_list:
|
||||
if not book.get('*read'):
|
||||
next_unread = book
|
||||
break
|
||||
|
||||
incomplete_series.append({
|
||||
'series': series,
|
||||
'read': read_count,
|
||||
'total': total_count,
|
||||
'next_book': next_unread
|
||||
})
|
||||
|
||||
# Sort by series name
|
||||
incomplete_series.sort(key=lambda x: x['series'])
|
||||
|
||||
# Output results
|
||||
print(f"\n# UNFINISHED SERIES\n")
|
||||
print(f"You have {len(incomplete_series)} incomplete series:\n")
|
||||
|
||||
for i, s in enumerate(incomplete_series, 1):
|
||||
print(f"{i}. **{s['series']}** ({s['read']}/{s['total']} books read)")
|
||||
if s['next_book']:
|
||||
authors = s['next_book'].get('authors', 'Unknown')
|
||||
title = s['next_book'].get('title', 'Unknown')
|
||||
index = s['next_book'].get('series_index', 0)
|
||||
print(f" Next: **{title}** by {authors} (Book #{index})\n")
|
||||
else:
|
||||
print(f" Next: Unable to determine\n")
|
||||
Reference in New Issue
Block a user