Skip to content

Add calibre-search extension#27295

Open
BrunoMiguelMonteiro wants to merge 3 commits intoraycast:mainfrom
BrunoMiguelMonteiro:ext/calibre-search
Open

Add calibre-search extension#27295
BrunoMiguelMonteiro wants to merge 3 commits intoraycast:mainfrom
BrunoMiguelMonteiro:ext/calibre-search

Conversation

@BrunoMiguelMonteiro
Copy link
Copy Markdown

@BrunoMiguelMonteiro BrunoMiguelMonteiro commented Apr 20, 2026

Description

Screencast

Checklist

- Apply Prettier formatting to all source files
- Add ESLint flat config and remove unused import for store validation
- Add store screenshots and fix author handle for Raycast Store submission
- Add README with commands, configuration, and installation instructions
- Exclude CLAUDE.md from version control
- Add "Add to Calibre Library" command and fix open-in-Calibre navigation
- Initial release: Calibre Library Raycast extension
@raycastbot raycastbot added new extension Label for PRs with new extensions platform: macOS labels Apr 20, 2026
@raycastbot
Copy link
Copy Markdown
Collaborator

Congratulations on your new Raycast extension! 🚀

We're currently experiencing a high volume of incoming requests. As a result, the initial review may take up to 10-15 business days.

Once the PR is approved and merged, the extension will be available on our Store.

@BrunoMiguelMonteiro BrunoMiguelMonteiro marked this pull request as ready for review April 20, 2026 08:52
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 20, 2026

Greptile Summary

This PR adds a new calibre-search Raycast extension that lets users search their Calibre ebook library via SQLite and add ebooks from disk to Calibre directly from Raycast.

  • P1 — UI freeze in add-to-library.tsx: handleAdd calls spawnSync(CALIBREDB, ...) on the main thread; this blocks the entire event loop (and the Raycast UI) for the duration of the calibredb command. Replace with an async alternative (execFile + promisify, or spawn with a Promise wrapper).
  • P1 — Uncaught qlmanage spawn error: the qlmanage child process in loadThumbnail has no 'error' event listener; a missing binary or spawn failure will throw an uncaught exception and crash the extension.

Confidence Score: 3/5

Not safe to merge until the blocking spawnSync and unhandled spawn error are fixed.

Two real defects in the changed code — a synchronous spawnSync that freezes Raycast's UI during book ingestion and a missing error handler that can crash the extension — need to be resolved before store publication.

extensions/calibre-search/src/add-to-library.tsx requires the most attention (blocking call + missing error handler).

Important Files Changed

Filename Overview
extensions/calibre-search/src/add-to-library.tsx Add-to-library command; contains a blocking spawnSync call that freezes the UI and a missing error handler on the qlmanage spawn.
extensions/calibre-search/src/index.tsx Main search command; uses useSQL, memoized filtering, and proper async open() — looks correct.
extensions/calibre-search/src/calibre.ts SQL query and mapRow helper; logic is straightforward and handles nulls correctly.
extensions/calibre-search/src/ebooks.ts Ebook helpers (extension detection, EPUB cover parsing, calibredb arg builder); well-tested pure functions.
extensions/calibre-search/src/types.ts Manually defines Preferences interface — should be removed in favour of the auto-generated raycast-env.d.ts types.
extensions/calibre-search/package.json Valid extension manifest; preference titles "Add books" / "Add books (optional)" violate Title Case convention.
extensions/calibre-search/CHANGELOG.md Changelog uses a hard-coded date (2026-04-20) instead of the required {PR_MERGE_DATE} placeholder.
extensions/calibre-search/eslint.config.js Uses .flat() pattern instead of defineConfig from eslint/config as required by the repo convention.
extensions/calibre-search/src/utils.ts Pure utility functions (HTML stripping, year extraction, format parsing, path helpers); logic is sound.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/calibre-search/src/add-to-library.tsx
Line: 255-262

Comment:
**`spawnSync` blocks the event loop**

`spawnSync` is synchronous and will freeze the entire Node.js event loop — including Raycast's UI — while `calibredb` is running. Adding a book to Calibre can take several seconds, making the interface completely unresponsive despite the animated toast. Replace with the async `execFile` (or `spawnAsync` via `util.promisify`) so the event loop stays free.

```typescript
import { execFile } from "child_process";
import { promisify } from "util";

const execFileAsync = promisify(execFile);

// inside handleAdd:
const { stdout = "", stderr = "" } = await execFileAsync(
  CALIBREDB,
  buildCalibredbArgs(file.path, libraryPath),
).catch((err) => ({ stdout: "", stderr: err.message ?? "" }));
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/calibre-search/src/add-to-library.tsx
Line: 181-188

Comment:
**Missing error handler on `qlmanage` process**

If `qlmanage` is not found or fails to spawn, the child process emits an `'error'` event with no listener, resulting in an uncaught exception that can crash the extension. Add an error listener:

```suggestion
  const proc = spawn("qlmanage", ["-t", "-s", "256", "-o", qlDir, file.path]);
  proc.on("error", () => {});
  proc.on("close", () => {
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/calibre-search/CHANGELOG.md
Line: 3

Comment:
**Changelog date should use `{PR_MERGE_DATE}` placeholder**

The Raycast convention is to use the `{PR_MERGE_DATE}` template variable for initial release entries; the merge date is substituted automatically when the PR lands.

```suggestion
## [Initial Release] - {PR_MERGE_DATE}
```

**Rule Used:** What: In Raycast extension changelogs, `{PR_MERGE_... ([source](https://app.greptile.com/review/custom-context?memory=799af734-ebd9-4b40-9ffd-97a70fc71c8a))

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/calibre-search/src/types.ts
Line: 34-36

Comment:
**Manual `Preferences` interface should not be defined**

Preference types are auto-generated in `raycast-env.d.ts` when the extension runs. Manually defining `Preferences` (here) and `AddPreferences extends Preferences` (in `add-to-library.tsx`) can drift out of sync with `package.json`, causing silent type mismatches. Remove both manual interfaces and use `getPreferenceValues<Preferences>()` directly — the generated types will include all defined preferences.

**Rule Used:** What: Don't manually define `Preferences` for `get... ([source](https://app.greptile.com/review/custom-context?memory=d93fc9fb-a45d-4479-a6a4-b1b4af98ebc8))

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/calibre-search/eslint.config.js
Line: 1

Comment:
**Use `defineConfig` from `eslint/config` subpath**

The current pattern manually calls `.flat()` instead of using the canonical ESLint v9 helper. The preferred pattern is:

```suggestion
const { defineConfig } = require("eslint/config");
const raycastConfig = require("@raycast/eslint-config");

module.exports = defineConfig([...raycastConfig]);
```

**Rule Used:** What: Enforce importing `defineConfig` from `"esli... ([source](https://app.greptile.com/review/custom-context?memory=645a7150-4078-490e-a70c-d6aad94e0cf5))

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/calibre-search/package.json
Line: 44-63

Comment:
**Preference titles should use Title Case**

`"Add books"` and `"Add books (optional)"` are lowercase. Raycast guidelines require Title Case for all user-facing preference titles.

```suggestion
      "title": "Add Books",
```

Apply the same fix to the two `"Add books (optional)"` entries on lines 52 and 58.

**Rule Used:** What: Use title case for titles in `package.json`.... ([source](https://app.greptile.com/review/custom-context?memory=a44fb089-4d03-4b60-a4ff-03431cdf0eb4))

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "Add CHANGELOG for store submission" | Re-trigger Greptile

Comment on lines +255 to +262
const result = spawnSync(
CALIBREDB,
buildCalibredbArgs(file.path, libraryPath),
{ encoding: "utf8" },
);
const output = result.stdout ?? "";
const stderr = result.stderr?.trim() ?? "";
const { addedIds } = parseCalibredbOutput(output);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 spawnSync blocks the event loop

spawnSync is synchronous and will freeze the entire Node.js event loop — including Raycast's UI — while calibredb is running. Adding a book to Calibre can take several seconds, making the interface completely unresponsive despite the animated toast. Replace with the async execFile (or spawnAsync via util.promisify) so the event loop stays free.

import { execFile } from "child_process";
import { promisify } from "util";

const execFileAsync = promisify(execFile);

// inside handleAdd:
const { stdout = "", stderr = "" } = await execFileAsync(
  CALIBREDB,
  buildCalibredbArgs(file.path, libraryPath),
).catch((err) => ({ stdout: "", stderr: err.message ?? "" }));
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/calibre-search/src/add-to-library.tsx
Line: 255-262

Comment:
**`spawnSync` blocks the event loop**

`spawnSync` is synchronous and will freeze the entire Node.js event loop — including Raycast's UI — while `calibredb` is running. Adding a book to Calibre can take several seconds, making the interface completely unresponsive despite the animated toast. Replace with the async `execFile` (or `spawnAsync` via `util.promisify`) so the event loop stays free.

```typescript
import { execFile } from "child_process";
import { promisify } from "util";

const execFileAsync = promisify(execFile);

// inside handleAdd:
const { stdout = "", stderr = "" } = await execFileAsync(
  CALIBREDB,
  buildCalibredbArgs(file.path, libraryPath),
).catch((err) => ({ stdout: "", stderr: err.message ?? "" }));
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +181 to +188
const proc = spawn("qlmanage", ["-t", "-s", "256", "-o", qlDir, file.path]);
proc.on("close", () => {
if (!cancelled && existsSync(expectedThumb)) onReady(expectedThumb);
});
return () => {
cancelled = true;
proc.kill();
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing error handler on qlmanage process

If qlmanage is not found or fails to spawn, the child process emits an 'error' event with no listener, resulting in an uncaught exception that can crash the extension. Add an error listener:

Suggested change
const proc = spawn("qlmanage", ["-t", "-s", "256", "-o", qlDir, file.path]);
proc.on("close", () => {
if (!cancelled && existsSync(expectedThumb)) onReady(expectedThumb);
});
return () => {
cancelled = true;
proc.kill();
};
const proc = spawn("qlmanage", ["-t", "-s", "256", "-o", qlDir, file.path]);
proc.on("error", () => {});
proc.on("close", () => {
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/calibre-search/src/add-to-library.tsx
Line: 181-188

Comment:
**Missing error handler on `qlmanage` process**

If `qlmanage` is not found or fails to spawn, the child process emits an `'error'` event with no listener, resulting in an uncaught exception that can crash the extension. Add an error listener:

```suggestion
  const proc = spawn("qlmanage", ["-t", "-s", "256", "-o", qlDir, file.path]);
  proc.on("error", () => {});
  proc.on("close", () => {
```

How can I resolve this? If you propose a fix, please make it concise.

Comment thread extensions/calibre-search/CHANGELOG.md Outdated
@@ -0,0 +1,13 @@
# Calibre Library Changelog

## [1.0.0] - 2026-04-20
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Changelog date should use {PR_MERGE_DATE} placeholder

The Raycast convention is to use the {PR_MERGE_DATE} template variable for initial release entries; the merge date is substituted automatically when the PR lands.

Suggested change
## [1.0.0] - 2026-04-20
## [Initial Release] - {PR_MERGE_DATE}

Rule Used: What: In Raycast extension changelogs, `{PR_MERGE_... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/calibre-search/CHANGELOG.md
Line: 3

Comment:
**Changelog date should use `{PR_MERGE_DATE}` placeholder**

The Raycast convention is to use the `{PR_MERGE_DATE}` template variable for initial release entries; the merge date is substituted automatically when the PR lands.

```suggestion
## [Initial Release] - {PR_MERGE_DATE}
```

**Rule Used:** What: In Raycast extension changelogs, `{PR_MERGE_... ([source](https://app.greptile.com/review/custom-context?memory=799af734-ebd9-4b40-9ffd-97a70fc71c8a))

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread extensions/calibre-search/src/types.ts Outdated
Comment on lines +34 to +36
export interface Preferences {
libraryPath: string;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Manual Preferences interface should not be defined

Preference types are auto-generated in raycast-env.d.ts when the extension runs. Manually defining Preferences (here) and AddPreferences extends Preferences (in add-to-library.tsx) can drift out of sync with package.json, causing silent type mismatches. Remove both manual interfaces and use getPreferenceValues<Preferences>() directly — the generated types will include all defined preferences.

Rule Used: What: Don't manually define Preferences for `get... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/calibre-search/src/types.ts
Line: 34-36

Comment:
**Manual `Preferences` interface should not be defined**

Preference types are auto-generated in `raycast-env.d.ts` when the extension runs. Manually defining `Preferences` (here) and `AddPreferences extends Preferences` (in `add-to-library.tsx`) can drift out of sync with `package.json`, causing silent type mismatches. Remove both manual interfaces and use `getPreferenceValues<Preferences>()` directly — the generated types will include all defined preferences.

**Rule Used:** What: Don't manually define `Preferences` for `get... ([source](https://app.greptile.com/review/custom-context?memory=d93fc9fb-a45d-4479-a6a4-b1b4af98ebc8))

How can I resolve this? If you propose a fix, please make it concise.

@@ -0,0 +1 @@
module.exports = require("@raycast/eslint-config").flat();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Use defineConfig from eslint/config subpath

The current pattern manually calls .flat() instead of using the canonical ESLint v9 helper. The preferred pattern is:

Suggested change
module.exports = require("@raycast/eslint-config").flat();
const { defineConfig } = require("eslint/config");
const raycastConfig = require("@raycast/eslint-config");
module.exports = defineConfig([...raycastConfig]);

Rule Used: What: Enforce importing defineConfig from `"esli... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/calibre-search/eslint.config.js
Line: 1

Comment:
**Use `defineConfig` from `eslint/config` subpath**

The current pattern manually calls `.flat()` instead of using the canonical ESLint v9 helper. The preferred pattern is:

```suggestion
const { defineConfig } = require("eslint/config");
const raycastConfig = require("@raycast/eslint-config");

module.exports = defineConfig([...raycastConfig]);
```

**Rule Used:** What: Enforce importing `defineConfig` from `"esli... ([source](https://app.greptile.com/review/custom-context?memory=645a7150-4078-490e-a70c-d6aad94e0cf5))

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread extensions/calibre-search/package.json Outdated
Comment on lines +44 to +63
"title": "Add books",
"description": "Primary folder to scan for ebook files",
"type": "directory",
"required": false,
"default": "~/Downloads"
},
{
"name": "searchPath2",
"title": "Add books (optional)",
"description": "Additional folder to scan",
"type": "directory",
"required": false
},
{
"name": "searchPath3",
"title": "Add books (optional)",
"description": "Additional folder to scan",
"type": "directory",
"required": false
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Preference titles should use Title Case

"Add books" and "Add books (optional)" are lowercase. Raycast guidelines require Title Case for all user-facing preference titles.

Suggested change
"title": "Add books",
"description": "Primary folder to scan for ebook files",
"type": "directory",
"required": false,
"default": "~/Downloads"
},
{
"name": "searchPath2",
"title": "Add books (optional)",
"description": "Additional folder to scan",
"type": "directory",
"required": false
},
{
"name": "searchPath3",
"title": "Add books (optional)",
"description": "Additional folder to scan",
"type": "directory",
"required": false
}
"title": "Add Books",

Apply the same fix to the two "Add books (optional)" entries on lines 52 and 58.

Rule Used: What: Use title case for titles in package.json.... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/calibre-search/package.json
Line: 44-63

Comment:
**Preference titles should use Title Case**

`"Add books"` and `"Add books (optional)"` are lowercase. Raycast guidelines require Title Case for all user-facing preference titles.

```suggestion
      "title": "Add Books",
```

Apply the same fix to the two `"Add books (optional)"` entries on lines 52 and 58.

**Rule Used:** What: Use title case for titles in `package.json`.... ([source](https://app.greptile.com/review/custom-context?memory=a44fb089-4d03-4b60-a4ff-03431cdf0eb4))

How can I resolve this? If you propose a fix, please make it concise.

- Update build artifacts
- Address Raycast store review feedback
@BrunoMiguelMonteiro
Copy link
Copy Markdown
Author

Addressed all Greptile feedback: replaced spawnSync with async execFileAsync, added error handler on qlmanage, removed manual Preferences interface, fixed eslint.config.js, Title Case on preference titles, and {PR_MERGE_DATE} in CHANGELOG.

@raycastbot
Copy link
Copy Markdown
Collaborator

This pull request has been automatically marked as stale because it did not have any recent activity.

It will be closed if no further activity occurs in the next 7 days to keep our backlog clean 😊

@raycastbot raycastbot added the status: stalled Stalled due inactivity label May 4, 2026
@BrunoMiguelMonteiro
Copy link
Copy Markdown
Author

All feedback from the Greptile review has been addressed in commit 4580d07:

  • spawnSync → async: replaced with execFileAsync (promisified execFile) so the UI stays responsive during calibredb calls
  • qlmanage error handler: added proc.on('error', () => {}) to prevent uncaught exceptions on spawn failure
  • Manual Preferences interface: removed from both types.ts and add-to-library.tsx; using auto-generated types via getPreferenceValues<Preferences>()
  • eslint.config.js: updated to use defineConfig from eslint/config
  • Preference titles: updated to Title Case (Add Books, Add Books (Optional))
  • CHANGELOG.md: updated to use {PR_MERGE_DATE} placeholder

Would appreciate a re-review when time allows. Thanks!

@raycastbot raycastbot removed the status: stalled Stalled due inactivity label May 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new extension Label for PRs with new extensions platform: macOS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants