commit b8dc705a954898bc37ecca31b55cc193af5fc7f6 Author: Zhongwei Li Date: Sun Nov 30 08:58:23 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..fcd5f60 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,14 @@ +{ + "name": "books", + "description": "TODO: Add description", + "version": "0.0.1", + "author": { + "name": "TODO: Add author" + }, + "skills": [ + "./skills" + ], + "commands": [ + "./commands" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c014eb5 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# books + +TODO: Add description diff --git a/commands/next.md b/commands/next.md new file mode 100644 index 0000000..f6f04b6 --- /dev/null +++ b/commands/next.md @@ -0,0 +1,100 @@ +--- +description: Analyze my reading patterns and suggest what to read next from my TBR +--- + +You are helping the user decide what to read next from their Calibre library TBR list. + +## Analysis Steps + +Perform the following analysis using the Calibre skill: + +### 1. Analyze Recent Reading Patterns + +Query the last 15 books marked as read (sorted by *dateread DESC): +- Calculate average page count of recent reads +- Identify if the user has been reading mostly long books (>600 pages) +- Look for series patterns in recent reads +- Use the `*dateread` field to determine actual reading order +- Look at `rating` field to see what books the user liked + +### 2. Check for Series Continuity + +For each series found in recent reads: +- Check if there are unread books in that series on the TBR +- Prioritize the next book in sequence (series_index), especially if the previous book had a high rating +- This is important for maintaining reading momentum! + +### 3. Consider Reading Fatigue + +Based on recent page counts: +- If average recent reads > 600 pages: Suggest shorter books (< 300 pages) +- If average recent reads < 400 pages: User might be ready for something longer +- Look for highly-rated short books as "palate cleansers" + +### 4. Check Book Age in Library + +Query books by timestamp (when added to library): +- Find recently added books (last 30 days) that are unread +- Find old books (added >1 year ago) that may have been forgotten +- Use `b.timestamp` field to determine when book was added + +### 5. Filter by Quality + +Prioritize books with: +- Goodreads rating >= 3.75 (if available) +- Consider page count relative to recent reading patterns +- Balance between series continuity and variety + +## Output Format + +Structure your response as a structured report with these categories: + +``` +# READING PATTERN SUMMARY +- Books read in last 30 days: X (use #dateread:">=30daysago") +- Average page count: Y pages +- Notable patterns: [e.g., "Completed Mistborn Era 2 series"] + +# RECOMMENDATIONS BY CATEGORY + +## 📚 SERIES CONTINUITY +Books that continue series you're currently reading: + +- **Book Title** by Author + Series: Series Name #X | Pages: XXX | Rating: X/5 | Added: [date/age] + +## 🆕 RECENTLY ADDED +Books added to your library in the last 30 days: + +- **Book Title** by Author + Pages: XXX | Rating: X/5 | Added: [date] + +## 💎 FORGOTTEN GEMS +Books added over a year ago that you may have forgotten: + +- **Book Title** by Author + Pages: XXX | Rating: X/5 | Added: [date/years ago] + +## ⚡ QUICK READS +Shorter books (< 300 pages) for reading fatigue: + +- **Book Title** by Author + Pages: XXX | Rating: X/5 | Added: [age] + +## 🌟 HIGHLY RATED +Top-rated unread books from your TBR: + +- **Book Title** by Author + Pages: XXX | Rating: X/5 | Added: [age] +``` + +## Important Notes + +- Use `b.timestamp` to determine when books were added to the library +- Calculate age from timestamp (e.g., "2 days ago", "3 months ago", "2 years ago") +- Include 1-3 books per category (skip categories if no matches) +- ALWAYS check for incomplete series from recent reads first +- Balance series continuity with reading fatigue and variety +- Present data in a clean, scannable format +- Each category should help answer a different need: momentum, novelty, rediscovery, fatigue, or quality +- **IMPORTANT**: All queries should exclude archived books from recommendations diff --git a/commands/random.md b/commands/random.md new file mode 100644 index 0000000..46b58d4 --- /dev/null +++ b/commands/random.md @@ -0,0 +1,110 @@ +--- +description: Pick a random book from TBR or library +--- + +You are helping the user pick a random book from their Calibre library. + +## Selection Process + +Use the Calibre skill to select a random book: + +### 1. Determine Selection Pool + +By default, select from TBR list, but check if user specified: +- **TBR** (default): `#read:No and #archived:No` +- **All unread**: `#read:No` +- **Entire library**: no filter +- **Specific criteria**: by author, series, genre, rating, page count, etc. + +### 2. Query the Pool + +Query all books matching the criteria: +```bash +--fields='title,authors,series,series_index,*goodreads,*pages,timestamp' +--search='[appropriate search based on user criteria]' +--for-machine +``` + +### 3. Random Selection + +From the results: +- Count total books in pool +- Select one at random +- Present it with full details + +### 4. Context About the Selection + +Provide helpful context: +- When it was added to library (from `timestamp`) +- Why it might be interesting to read now +- How it fits current reading patterns +- Series context if applicable + +## Output Format + +Present the random selection: + +``` +# 🎲 RANDOM BOOK SELECTION + +**[Book Title]** by [Author] + +## Details +- **Series**: [Series Name #X] or "Standalone" +- **Pages**: XXX pages +- **Rating**: X.X / 5 (Goodreads) +- **Added to Library**: [date or "X months/years ago"] + +## Why Read This Now? + +[Provide 2-3 reasons why this might be a good choice, such as:] +- Fits your recent reading pattern of [genre/length/style] +- Next book in [Series Name] series +- Highly rated on Goodreads +- Been in your TBR for [time], might be a good time to revisit +- Quick read at XXX pages if you're between longer books +- This author is similar to [recent author] you enjoyed + +## Pool Information +Selected randomly from **X books** in your [TBR/unread books/library/specified criteria] + +--- +Not feeling it? Run /books:random again for another suggestion! +Or try /books:vibes to find books with similar themes to one you enjoyed. +``` + +## Advanced Options + +If the user specifies additional criteria, combine them: + +**Examples:** +- "Random book under 300 pages": Add `#pages:"<300"` +- "Random unread Sanderson": Add `authors:"Sanderson"` +- "Random highly rated book": Add `#goodreads:">4"` +- "Random from series": Add `not series:""` +- "Random standalone": Add `series:""` +- "Random book added this year": Add `timestamp:">YYYY-01-01"` + +## Implementation Notes + +- Use Python or similar to select random index from JSON array results +- Handle edge cases (empty pool, pool of 1) +- If pool is small (< 10 books), mention all options +- Exclude archived books by default unless user specifically asks +- Make selection truly random - don't bias toward highest rated or recently added +- If user runs command multiple times, try to avoid repeating recent suggestions + +## Example Python Snippet for Randomization + +```python +import json +import random +import sys + +data = json.load(sys.stdin) +if data: + book = random.choice(data) + print(json.dumps(book, indent=2)) +``` + +You can pipe calibredb output through this to get random selection. diff --git a/commands/series.md b/commands/series.md new file mode 100644 index 0000000..aab3f61 --- /dev/null +++ b/commands/series.md @@ -0,0 +1,7 @@ +--- +description: List unfinished series and the next book to read in each +--- + +Use the `books:find-incomplete-series` skill to find incomplete series. DO NOT call +calibredb yourself, use the skill! It provides a python script to help +you find unfinished series. diff --git a/commands/stats.md b/commands/stats.md new file mode 100644 index 0000000..c476f1e --- /dev/null +++ b/commands/stats.md @@ -0,0 +1,141 @@ +--- +description: Show reading statistics (books per year/month, pages read, average rating, genre breakdown) +--- + +You are helping the user analyze their reading statistics from their Calibre library. + +## Analysis to Perform + +Use the Calibre skill to gather and analyze the following statistics: + +### 1. Reading Velocity + +Query books read in different time periods: +- Books read this year (use `#dateread:">=YYYY-01-01"` where YYYY is current year) +- Books read last 30 days (use `#dateread:">=30daysago"`) +- Books read last 90 days (use `#dateread:">=90daysago"`) +- Break down by month for current year + +Calculate: +- Books per month average (current year) +- Pages per month average +- Current reading pace vs yearly average + +### 2. Page Statistics + +Query all read books with page counts: +- Total pages read this year +- Total pages read all time +- Average pages per book +- Longest book read +- Shortest book read + +### 3. Rating Analysis + +Query all read books with ratings: +- Average rating given (your `rating` field) +- Average Goodreads rating of books read (`*goodreads` field) +- Most common rating you give +- Distribution of ratings (how many 5-star, 4-star, etc.) + +### 4. Author Statistics + +Query all read books: +- Most read authors (count by author name) +- Total unique authors read + +### 5. Series Statistics + +Query all read books with series information: +- Number of complete series finished +- Books read that are part of series vs standalone +- Most read series + +### 6. To-Be-Read Statistics + +Query TBR list (`#read:No and #archived:No`): +- Total books in TBR +- Total pages in TBR +- Average Goodreads rating of TBR +- Oldest book in TBR (by timestamp) +- Books added to TBR in last 30 days + +## Output Format + +Present statistics in a clean, organized report: + +``` +# READING STATISTICS + +## 📊 Reading Velocity +- **This Year**: X books (Y pages) +- **Last 30 Days**: X books (Y pages) +- **Average Pace**: X books/month, Y pages/month + +### Monthly Breakdown (YYYY) +Jan: X books | Feb: X books | Mar: X books | etc. + +## 📖 Page Statistics +- **Total Pages Read (All Time)**: X,XXX pages +- **Total Pages Read (This Year)**: X,XXX pages +- **Average Book Length**: XXX pages +- **Longest Book**: [Title] by [Author] (XXX pages) +- **Shortest Book**: [Title] by [Author] (XXX pages) + +## ⭐ Rating Analysis +- **Your Average Rating**: X.X / 5 +- **Goodreads Average of Books Read**: X.X / 5 +- **Most Common Rating**: X stars + +### Rating Distribution +★★★★★: XX books (XX%) +★★★★☆: XX books (XX%) +★★★☆☆: XX books (XX%) +★★☆☆☆: XX books (XX%) +★☆☆☆☆: XX books (XX%) + +## ✍️ Author Statistics +- **Total Authors Read**: XX unique authors +- **Most Read Authors**: + 1. [Author Name]: X books + 2. [Author Name]: X books + 3. [Author Name]: X books + +## 📚 Series Statistics +- **Books in Series**: XX books (XX% of total) +- **Standalone Books**: XX books (XX% of total) +- **Most Read Series**: + 1. [Series Name]: X books + 2. [Series Name]: X books + +## 📋 To-Be-Read Statistics +- **Total TBR Books**: XXX books (X,XXX pages) +- **Average TBR Rating**: X.X / 5 +- **Added Recently**: XX books in last 30 days +- **Oldest Unread**: [Title] (added X years/months ago) + +## 🎯 Reading Insights +[Provide 2-3 interesting insights, such as:] +- You're on track to read XX books this year +- Your reading pace has [increased/decreased] by XX% compared to last year +- You tend to rate books higher/lower than Goodreads average +- You're reading more/fewer series books than standalone +``` + +## Query Tips + +- Use `#dateread` field with date ranges for time-based queries +- Calculate percentages and averages from the data +- Present large numbers with thousand separators for readability +- Compare current year to all-time averages where interesting +- Exclude archived books from all queries +- Handle missing data gracefully (some books may not have all custom fields set) + +## Implementation Notes + +**Bash/Python Pitfalls:** +- Multi-line bash for loops are tricky - use Python with heredoc instead for complex iteration +- When looping through months to count books, use Python's subprocess module rather than bash for loops +- When processing JSON data from calibredb, be careful with missing fields - always use `.get()` with defaults +- Keep Python data processing scripts simple - avoid complex inline data structures that can have KeyError issues +- Better to do multiple simple queries than one complex Python script with hard-coded data diff --git a/commands/vibes.md b/commands/vibes.md new file mode 100644 index 0000000..0044b85 --- /dev/null +++ b/commands/vibes.md @@ -0,0 +1,231 @@ +--- +description: Find similar books in your library based on genre, author, or themes +--- + +You are helping the user find books in their TBR that have similar vibes/themes to a book they specify. + +## Analysis Process + +Use the Calibre skill to find books with similar vibes: + +### 1. Get the Reference Book + +First, query for the book the user mentions (or ask them to specify one): +```bash +--fields='title,authors,series,tags,comments,*goodreads,*pages' +--search='title:"[book title]"' +``` + +Get all available information: +- Author(s) +- Series (if part of one) +- Tags (if any) +- Comments/description +- Goodreads rating +- Page count + +### 2. Identify Similar Books + +Query TBR for books with similar characteristics: + +**Same Author** +```bash +--search='authors:"[author]" and #read:No and #archived:No' +``` + +**Same Series** +```bash +--search='series:"[series]" and #read:No and #archived:No' +``` + +**Similar Tags/Genres** +If the reference book has tags, search for books with those tags: +```bash +--search='tags:"[tag]" and #read:No and #archived:No' +``` + +**Similar Page Count** (±100 pages) +```bash +--search='#pages:">[low]" and #pages:"<[high]" and #read:No and #archived:No' +``` + +**Similar Rating Range** +```bash +--search='#goodreads:">[rating-0.5]" and #goodreads:"<[rating+0.5]" and #read:No and #archived:No' +``` + +### 3. Use Available Information + +Even without formal tags, extract information from: +- Book titles (often contain genre hints: "The [Fantasy] of...") +- Series names (often thematic) +- Author names (can search for co-authors, related authors) +- Comments field (may contain descriptions with genre/theme keywords) + +### 4. Search for Community Recommendations + +Use WebSearch to find what other readers recommend as similar books: + +**Search for Reddit recommendations:** +``` +"books like [book title]" site:reddit.com +``` + +**Search for Goodreads lists:** +``` +"similar to [book title]" site:goodreads.com +``` + +**General recommendation searches:** +``` +"if you liked [book title]" recommendations +"books similar to [book title]" +"read alikes [book title]" +``` + +Extract book titles and authors from search results, then: +- Check if any are in the user's TBR (prioritize these) +- Note popular recommendations even if not in TBR (user might want to add them) + +### 5. Similarity Scoring + +For each book found, explain WHY it's similar: +- Same author +- Same/related series +- Shared tags/genres +- Similar length (good for pacing match) +- Similar rating (quality match) +- Similar themes (if detectable from metadata) +- **Recommended by readers** (found in search results) + +## Output Format + +``` +# 📖 BOOKS WITH SIMILAR VIBES + +Finding books similar to: **[Reference Book]** by [Author] + +## About the Reference Book +- **Series**: [Series Name] or "Standalone" +- **Length**: XXX pages +- **Rating**: X.X / 5 +- **Key Themes**: [Based on tags, series, title, or "Limited metadata available"] + +## Similar Books in Your TBR + +### By Same Author +[If any found:] +- **[Title]** ([XXX pages], rated X.X/5, added [time ago]) + Why similar: Same author, similar [length/rating/style] + +### In Related Series +[If any found:] +- **[Title]** by [Author] + Series: [Series Name] + Why similar: [Connection to original series] + +### Similar Themes/Genres +[If any found based on tags or metadata:] +- **[Title]** by [Author] ([XXX pages], rated X.X/5) + Why similar: [Shared tags/themes/genre indicators] + +### Similar Length & Quality +[Books with similar page count and rating:] +- **[Title]** by [Author] ([XXX pages], rated X.X/5) + Why similar: Similar reading commitment and quality + +### Recommended by the Community +[Books found via web search that are in your TBR:] +- **[Title]** by [Author] ([XXX pages], rated X.X/5) + ✓ In your TBR + Why similar: Frequently mentioned on [Reddit/Goodreads/etc.] as similar to [reference book] + +## 🎯 Top Recommendations from Your TBR + +Based on the similarity analysis, here are the strongest matches: + +1. **[Title]** by [Author] + Best match because: [Specific reasons] + +2. **[Title]** by [Author] + Good match because: [Specific reasons] + +3. **[Title]** by [Author] + Worth considering: [Specific reasons] + +## 📚 Popular Recommendations Not in Your Library + +Based on web search, these books are frequently recommended as similar but aren't in your TBR yet: + +1. **[Title]** by [Author] + Why recommended: [Based on search results - Reddit threads, Goodreads lists, etc.] + Rating: X.X / 5 on Goodreads (if found) + +2. **[Title]** by [Author] + Why recommended: [Reason from search results] + +3. **[Title]** by [Author] + Why recommended: [Reason from search results] + +--- +💡 Tips: +- Want to add any of these to your library? Use Calibre to import them +- If no similar books were found in your TBR, try /books:random for a surprise pick +- Use /books:next for personalized recommendations based on reading patterns +``` + +## Handling Limited Metadata + +If the reference book or TBR books have limited metadata: +- Focus on author and series connections (most reliable) +- Use page count for "similar reading experience" +- Use ratings for quality matching +- Look for patterns in titles +- Be transparent about limitations: "Based on available metadata..." + +## User Interaction + +If the user just says "/books:vibes" without specifying a book: +1. Ask them which book they want to find similar books to +2. Suggest they could use a recent read they enjoyed +3. Or ask them to name a book they loved + +If they specify a book not in the library: +1. Ask if they want to find books similar to that book's: + - Author + - Genre/themes (ask them to describe) + - Length/style + +## Query Strategy + +1. **Start broad**: Query all potential matches from library +2. **Search the web**: Use WebSearch to find community recommendations (Reddit, Goodreads, book blogs) +3. **Cross-reference**: Match web recommendations against your TBR +4. **Present both**: Show matches in TBR first, then popular recommendations not in library +5. **Score by similarity**: Books matching multiple criteria rank higher +6. **Prioritize quality**: Weight highly-rated books (>4.0) higher +7. **Prioritize community picks**: Books mentioned by other readers are strong matches +8. **Limit results**: Show top 5-10 from each category (in TBR and not in TBR) +9. **Explain reasoning**: Always say WHY books are similar +10. **Exclude archived**: Always add `#archived:No` unless user specifies otherwise +11. **Include context**: Note which platform recommended the book (Reddit, Goodreads, etc.) + +## Example Scenarios + +**User**: "Books like Mistborn" +- Library query: Author (Sanderson), Series (Mistborn), Tags (fantasy, magic) +- Web search: "books like Mistborn" Reddit/Goodreads +- Find in TBR: Other Sanderson books, high fantasy, magic systems +- Suggest from web: Stormlight Archive, Wheel of Time, etc. + +**User**: "Something like this cozy mystery I just finished" +- Library query: By title/author, look for tags like "mystery", "cozy" +- Web search: "[title] read alikes" "similar cozy mysteries" +- Find in TBR: Similar page count, similar authors, mystery tags +- Suggest from web: Popular cozy mystery series recommendations + +**User**: "Books with similar vibes to Name of the Wind" +- Library query: Author (Rothfuss), Tags (fantasy, coming-of-age) +- Web search: "books similar to Name of the Wind" site:reddit.com +- Find in TBR: Epic fantasy, similar length, highly-rated +- Suggest from web: Popular epic fantasy series often recommended together diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..7030ee5 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,73 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:stbenjam/claude-nine:plugins/books", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "967e4707fcffcbd1198f315288258bc4a03dd7a9", + "treeHash": "1cfbd76d0c993e2606d914b412e3436ab9cdd4c7503040188b6e1d30a6b48809", + "generatedAt": "2025-11-28T10:28:27.319664Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "books", + "description": "TODO: Add description", + "version": "0.0.1" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "26b81e542f16db968832cf056145cd445b5e52b69146494b344627ed8d17d320" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "d8fe64888b7afe9a9e19a06dcbb25875629777409e58ce72cc386b225a805f70" + }, + { + "path": "commands/vibes.md", + "sha256": "81a63df2715a0e719e40665766cc29817f94ff556ec2c99d0779b9f499ca7ba9" + }, + { + "path": "commands/stats.md", + "sha256": "aefa603ab5abec105a6a67407f991f939bb9a7df33d6ec5dbc4d7db8c91f4738" + }, + { + "path": "commands/next.md", + "sha256": "797f5c8e0cadb6c4b11627982010381632beb6422706bb00f02617b4d2624a00" + }, + { + "path": "commands/series.md", + "sha256": "b13f2a4472a0acaf4f18820c431e7a29f1b918b8315c03e4d9cc0a017ad409fc" + }, + { + "path": "commands/random.md", + "sha256": "88d21663e703888bf9b70b66b437ec08142b477e3a097374b671d309cd3119c7" + }, + { + "path": "skills/calibre/SKILL.md", + "sha256": "3bd13d886f4ee135e7e958ff1e57955c75195a24910c3d6bf7ceb898086ae2cb" + }, + { + "path": "skills/find-incomplete-series/SKILL.md", + "sha256": "98e418dc6ddf68d4046c51cd14360d535aeb641b8e1a39cc423c07b054980e4b" + }, + { + "path": "skills/find-incomplete-series/scripts/series.py", + "sha256": "e9e5c646af239e6cb88409b41ee4cda1326637c2e327eab393f3db834605101f" + } + ], + "dirSha256": "1cfbd76d0c993e2606d914b412e3436ab9cdd4c7503040188b6e1d30a6b48809" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/calibre/SKILL.md b/skills/calibre/SKILL.md new file mode 100644 index 0000000..99a339e --- /dev/null +++ b/skills/calibre/SKILL.md @@ -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 diff --git a/skills/find-incomplete-series/SKILL.md b/skills/find-incomplete-series/SKILL.md new file mode 100644 index 0000000..35def00 --- /dev/null +++ b/skills/find-incomplete-series/SKILL.md @@ -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!! diff --git a/skills/find-incomplete-series/scripts/series.py b/skills/find-incomplete-series/scripts/series.py new file mode 100755 index 0000000..b54dcfd --- /dev/null +++ b/skills/find-incomplete-series/scripts/series.py @@ -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")