@ashdev/codex-plugin-metadata-openlibrary 1.0.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -247,7 +247,7 @@ var init_api = __esm({
247
247
  }
248
248
  });
249
249
 
250
- // ../sdk-typescript/dist/types/rpc.js
250
+ // node_modules/@ashdev/codex-plugin-sdk/dist/types/rpc.js
251
251
  var JSON_RPC_ERROR_CODES = {
252
252
  /** Invalid JSON was received */
253
253
  PARSE_ERROR: -32700,
@@ -261,7 +261,7 @@ var JSON_RPC_ERROR_CODES = {
261
261
  INTERNAL_ERROR: -32603
262
262
  };
263
263
 
264
- // ../sdk-typescript/dist/errors.js
264
+ // node_modules/@ashdev/codex-plugin-sdk/dist/errors.js
265
265
  var PluginError = class extends Error {
266
266
  data;
267
267
  constructor(message, data) {
@@ -281,7 +281,7 @@ var PluginError = class extends Error {
281
281
  }
282
282
  };
283
283
 
284
- // ../sdk-typescript/dist/logger.js
284
+ // node_modules/@ashdev/codex-plugin-sdk/dist/logger.js
285
285
  var LOG_LEVELS = {
286
286
  debug: 0,
287
287
  info: 1,
@@ -346,7 +346,7 @@ function createLogger(options) {
346
346
  return new Logger(options);
347
347
  }
348
348
 
349
- // ../sdk-typescript/dist/server.js
349
+ // node_modules/@ashdev/codex-plugin-sdk/dist/server.js
350
350
  import { createInterface } from "node:readline";
351
351
  function validateStringFields(params, fields) {
352
352
  if (params === null || params === void 0) {
@@ -379,6 +379,24 @@ function validateGetParams(params) {
379
379
  function validateMatchParams(params) {
380
380
  return validateStringFields(params, ["title"]);
381
381
  }
382
+ function validateBookSearchParams(params) {
383
+ if (params === null || params === void 0) {
384
+ return { field: "params", message: "params is required" };
385
+ }
386
+ if (typeof params !== "object") {
387
+ return { field: "params", message: "params must be an object" };
388
+ }
389
+ const obj = params;
390
+ const hasIsbn = obj.isbn !== void 0 && obj.isbn !== null && obj.isbn !== "";
391
+ const hasQuery = obj.query !== void 0 && obj.query !== null && obj.query !== "";
392
+ if (!hasIsbn && !hasQuery) {
393
+ return { field: "isbn/query", message: "either isbn or query is required" };
394
+ }
395
+ return null;
396
+ }
397
+ function validateBookMatchParams(params) {
398
+ return validateStringFields(params, ["title"]);
399
+ }
382
400
  function invalidParamsError(id, error) {
383
401
  return {
384
402
  jsonrpc: "2.0",
@@ -391,15 +409,22 @@ function invalidParamsError(id, error) {
391
409
  };
392
410
  }
393
411
  function createMetadataPlugin(options) {
394
- const { manifest: manifest2, provider, onInitialize, logLevel = "info" } = options;
412
+ const { manifest: manifest2, provider, bookProvider: bookProvider2, onInitialize, logLevel = "info" } = options;
395
413
  const logger2 = createLogger({ name: manifest2.name, level: logLevel });
414
+ const contentTypes = manifest2.capabilities.metadataProvider;
415
+ if (contentTypes.includes("series") && !provider) {
416
+ throw new Error("Series metadata provider is required when 'series' is in metadataProvider capabilities");
417
+ }
418
+ if (contentTypes.includes("book") && !bookProvider2) {
419
+ throw new Error("Book metadata provider is required when 'book' is in metadataProvider capabilities");
420
+ }
396
421
  logger2.info(`Starting plugin: ${manifest2.displayName} v${manifest2.version}`);
397
422
  const rl = createInterface({
398
423
  input: process.stdin,
399
424
  terminal: false
400
425
  });
401
426
  rl.on("line", (line) => {
402
- void handleLine(line, manifest2, provider, onInitialize, logger2);
427
+ void handleLine(line, manifest2, provider, bookProvider2, onInitialize, logger2);
403
428
  });
404
429
  rl.on("close", () => {
405
430
  logger2.info("stdin closed, shutting down");
@@ -413,7 +438,7 @@ function createMetadataPlugin(options) {
413
438
  logger2.error("Unhandled rejection", reason);
414
439
  });
415
440
  }
416
- async function handleLine(line, manifest2, provider, onInitialize, logger2) {
441
+ async function handleLine(line, manifest2, provider, bookProvider2, onInitialize, logger2) {
417
442
  const trimmed = line.trim();
418
443
  if (!trimmed)
419
444
  return;
@@ -422,7 +447,7 @@ async function handleLine(line, manifest2, provider, onInitialize, logger2) {
422
447
  const request = JSON.parse(trimmed);
423
448
  id = request.id;
424
449
  logger2.debug(`Received request: ${request.method}`, { id: request.id });
425
- const response = await handleRequest(request, manifest2, provider, onInitialize, logger2);
450
+ const response = await handleRequest(request, manifest2, provider, bookProvider2, onInitialize, logger2);
426
451
  if (response !== null) {
427
452
  writeResponse(response);
428
453
  }
@@ -456,7 +481,7 @@ async function handleLine(line, manifest2, provider, onInitialize, logger2) {
456
481
  }
457
482
  }
458
483
  }
459
- async function handleRequest(request, manifest2, provider, onInitialize, logger2) {
484
+ async function handleRequest(request, manifest2, provider, bookProvider2, onInitialize, logger2) {
460
485
  const { method, params, id } = request;
461
486
  switch (method) {
462
487
  case "initialize":
@@ -487,8 +512,20 @@ async function handleRequest(request, manifest2, provider, onInitialize, logger2
487
512
  });
488
513
  return null;
489
514
  }
490
- // Series metadata methods (scoped by content type)
515
+ // =========================================================================
516
+ // Series metadata methods
517
+ // =========================================================================
491
518
  case "metadata/series/search": {
519
+ if (!provider) {
520
+ return {
521
+ jsonrpc: "2.0",
522
+ id,
523
+ error: {
524
+ code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
525
+ message: "This plugin does not support series metadata"
526
+ }
527
+ };
528
+ }
492
529
  const validationError = validateSearchParams(params);
493
530
  if (validationError) {
494
531
  return invalidParamsError(id, validationError);
@@ -500,6 +537,16 @@ async function handleRequest(request, manifest2, provider, onInitialize, logger2
500
537
  };
501
538
  }
502
539
  case "metadata/series/get": {
540
+ if (!provider) {
541
+ return {
542
+ jsonrpc: "2.0",
543
+ id,
544
+ error: {
545
+ code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
546
+ message: "This plugin does not support series metadata"
547
+ }
548
+ };
549
+ }
503
550
  const validationError = validateGetParams(params);
504
551
  if (validationError) {
505
552
  return invalidParamsError(id, validationError);
@@ -511,13 +558,23 @@ async function handleRequest(request, manifest2, provider, onInitialize, logger2
511
558
  };
512
559
  }
513
560
  case "metadata/series/match": {
561
+ if (!provider) {
562
+ return {
563
+ jsonrpc: "2.0",
564
+ id,
565
+ error: {
566
+ code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
567
+ message: "This plugin does not support series metadata"
568
+ }
569
+ };
570
+ }
514
571
  if (!provider.match) {
515
572
  return {
516
573
  jsonrpc: "2.0",
517
574
  id,
518
575
  error: {
519
576
  code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
520
- message: "This plugin does not support match"
577
+ message: "This plugin does not support series match"
521
578
  }
522
579
  };
523
580
  }
@@ -531,10 +588,82 @@ async function handleRequest(request, manifest2, provider, onInitialize, logger2
531
588
  result: await provider.match(params)
532
589
  };
533
590
  }
534
- // Future: book metadata methods
535
- // case "metadata/book/search":
536
- // case "metadata/book/get":
537
- // case "metadata/book/match":
591
+ // =========================================================================
592
+ // Book metadata methods
593
+ // =========================================================================
594
+ case "metadata/book/search": {
595
+ if (!bookProvider2) {
596
+ return {
597
+ jsonrpc: "2.0",
598
+ id,
599
+ error: {
600
+ code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
601
+ message: "This plugin does not support book metadata"
602
+ }
603
+ };
604
+ }
605
+ const validationError = validateBookSearchParams(params);
606
+ if (validationError) {
607
+ return invalidParamsError(id, validationError);
608
+ }
609
+ return {
610
+ jsonrpc: "2.0",
611
+ id,
612
+ result: await bookProvider2.search(params)
613
+ };
614
+ }
615
+ case "metadata/book/get": {
616
+ if (!bookProvider2) {
617
+ return {
618
+ jsonrpc: "2.0",
619
+ id,
620
+ error: {
621
+ code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
622
+ message: "This plugin does not support book metadata"
623
+ }
624
+ };
625
+ }
626
+ const validationError = validateGetParams(params);
627
+ if (validationError) {
628
+ return invalidParamsError(id, validationError);
629
+ }
630
+ return {
631
+ jsonrpc: "2.0",
632
+ id,
633
+ result: await bookProvider2.get(params)
634
+ };
635
+ }
636
+ case "metadata/book/match": {
637
+ if (!bookProvider2) {
638
+ return {
639
+ jsonrpc: "2.0",
640
+ id,
641
+ error: {
642
+ code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
643
+ message: "This plugin does not support book metadata"
644
+ }
645
+ };
646
+ }
647
+ if (!bookProvider2.match) {
648
+ return {
649
+ jsonrpc: "2.0",
650
+ id,
651
+ error: {
652
+ code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
653
+ message: "This plugin does not support book match"
654
+ }
655
+ };
656
+ }
657
+ const validationError = validateBookMatchParams(params);
658
+ if (validationError) {
659
+ return invalidParamsError(id, validationError);
660
+ }
661
+ return {
662
+ jsonrpc: "2.0",
663
+ id,
664
+ result: await bookProvider2.match(params)
665
+ };
666
+ }
538
667
  default:
539
668
  return {
540
669
  jsonrpc: "2.0",
@@ -557,7 +686,7 @@ init_api();
557
686
  // package.json
558
687
  var package_default = {
559
688
  name: "@ashdev/codex-plugin-metadata-openlibrary",
560
- version: "1.0.0",
689
+ version: "1.8.1",
561
690
  description: "Open Library metadata plugin for Codex - fetches book metadata by ISBN or title search",
562
691
  main: "dist/index.js",
563
692
  bin: "dist/index.js",
@@ -597,7 +726,7 @@ var package_default = {
597
726
  node: ">=22.0.0"
598
727
  },
599
728
  dependencies: {
600
- "@ashdev/codex-plugin-sdk": "file:../sdk-typescript"
729
+ "@ashdev/codex-plugin-sdk": "^1.8.1"
601
730
  },
602
731
  devDependencies: {
603
732
  "@biomejs/biome": "^2.3.13",
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/api.ts", "../../sdk-typescript/src/types/rpc.ts", "../../sdk-typescript/src/errors.ts", "../../sdk-typescript/src/logger.ts", "../../sdk-typescript/src/server.ts", "../src/index.ts", "../package.json", "../src/manifest.ts", "../src/mapper.ts"],
4
- "sourcesContent": ["/**\n * Open Library API Client\n *\n * Handles communication with the Open Library API with:\n * - Rate limiting (100 requests per 5 minutes recommended)\n * - Caching to reduce API calls\n * - Error handling with retries\n *\n * @see https://openlibrary.org/developers/api\n */\n\nimport type {\n OLAuthor,\n OLEdition,\n OLSearchResponse,\n OLWork,\n OLWorkEditionsResponse,\n} from \"./types.js\";\n\nconst BASE_URL = \"https://openlibrary.org\";\nconst COVERS_BASE_URL = \"https://covers.openlibrary.org\";\n\n// Simple in-memory cache with TTL\ninterface CacheEntry<T> {\n data: T;\n timestamp: number;\n}\n\nconst CACHE_TTL_MS = 15 * 60 * 1000; // 15 minutes\nconst cache = new Map<string, CacheEntry<unknown>>();\n\n/**\n * Get cached value if not expired\n */\nfunction getCached<T>(key: string): T | null {\n const entry = cache.get(key);\n if (entry && Date.now() - entry.timestamp < CACHE_TTL_MS) {\n return entry.data as T;\n }\n if (entry) {\n cache.delete(key); // Cleanup expired\n }\n return null;\n}\n\n/**\n * Store value in cache\n */\nfunction setCache<T>(key: string, data: T): void {\n cache.set(key, { data, timestamp: Date.now() });\n}\n\n/**\n * Make an HTTP request with error handling\n */\nasync function fetchJson<T>(url: string, description: string): Promise<T | null> {\n // Check cache first\n const cached = getCached<T>(url);\n if (cached !== null) {\n return cached;\n }\n\n try {\n const response = await fetch(url, {\n headers: {\n \"User-Agent\": \"Codex/1.0 (https://github.com/AshDevFr/codex; codex-plugin)\",\n Accept: \"application/json\",\n },\n });\n\n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = (await response.json()) as T;\n setCache(url, data);\n return data;\n } catch (error) {\n console.error(`[openlibrary] Failed to fetch ${description}:`, error);\n return null;\n }\n}\n\n/**\n * Normalize ISBN by removing hyphens and spaces\n */\nexport function normalizeIsbn(isbn: string): string {\n return isbn.replace(/[-\\s]/g, \"\").toUpperCase();\n}\n\n/**\n * Check if a string is a valid ISBN-10 or ISBN-13\n */\nexport function isValidIsbn(isbn: string): boolean {\n const normalized = normalizeIsbn(isbn);\n return normalized.length === 10 || normalized.length === 13;\n}\n\n/**\n * Fetch book edition by ISBN\n *\n * @param isbn ISBN-10 or ISBN-13\n * @returns Edition data or null if not found\n */\nexport async function getEditionByIsbn(isbn: string): Promise<OLEdition | null> {\n const normalized = normalizeIsbn(isbn);\n const url = `${BASE_URL}/isbn/${normalized}.json`;\n return fetchJson<OLEdition>(url, `edition by ISBN ${normalized}`);\n}\n\n/**\n * Fetch work details by key\n *\n * @param workKey Work key (e.g., \"/works/OL45883W\" or just \"OL45883W\")\n * @returns Work data or null if not found\n */\nexport async function getWork(workKey: string): Promise<OLWork | null> {\n // Normalize key to just the ID part\n const key = workKey.startsWith(\"/works/\") ? workKey : `/works/${workKey}`;\n const url = `${BASE_URL}${key}.json`;\n return fetchJson<OLWork>(url, `work ${key}`);\n}\n\n/**\n * Fetch editions for a work\n *\n * Returns editions directly associated with a work, ordered by most recent.\n * This is more reliable than searching by title, which can return unrelated books.\n *\n * @param workKey Work key (e.g., \"/works/OL45883W\")\n * @param limit Maximum number of editions to fetch\n * @returns Array of editions or empty array if none found\n */\nexport async function getWorkEditions(workKey: string, limit = 5): Promise<OLEdition[]> {\n const key = workKey.startsWith(\"/works/\") ? workKey : `/works/${workKey}`;\n const url = `${BASE_URL}${key}/editions.json?limit=${limit}`;\n const response = await fetchJson<OLWorkEditionsResponse>(url, `editions for ${key}`);\n return response?.entries || [];\n}\n\n/**\n * Fetch author details by key\n *\n * @param authorKey Author key (e.g., \"/authors/OL34184A\" or just \"OL34184A\")\n * @returns Author data or null if not found\n */\nexport async function getAuthor(authorKey: string): Promise<OLAuthor | null> {\n // Normalize key to just the ID part\n const key = authorKey.startsWith(\"/authors/\") ? authorKey : `/authors/${authorKey}`;\n const url = `${BASE_URL}${key}.json`;\n return fetchJson<OLAuthor>(url, `author ${key}`);\n}\n\n/** Fields to request from the Open Library search API */\nconst SEARCH_FIELDS = [\n \"key\",\n \"title\",\n \"subtitle\",\n \"author_name\",\n \"author_key\",\n \"first_publish_year\",\n \"publish_year\",\n \"publisher\",\n \"isbn\",\n \"number_of_pages_median\",\n \"cover_i\",\n \"cover_edition_key\",\n \"edition_count\",\n \"language\",\n \"subject\",\n \"ratings_average\",\n \"ratings_count\",\n].join(\",\");\n\n/**\n * Search for books\n *\n * When an author is provided, uses the `title` + `author` parameters for\n * more precise results. If that yields no results, falls back to a general\n * `q` search to ensure we still return something useful.\n *\n * @param query Search query (title, author, or combined)\n * @param options Additional search options\n * @returns Search results\n */\nexport async function searchBooks(\n query: string,\n options: {\n author?: string;\n limit?: number;\n } = {},\n): Promise<OLSearchResponse | null> {\n const { author, limit = 10 } = options;\n\n // When author is provided, try a refined title + author search first\n if (author) {\n const params = new URLSearchParams({\n title: query,\n author,\n fields: SEARCH_FIELDS,\n limit: String(limit),\n });\n\n const url = `${BASE_URL}/search.json?${params}`;\n const response = await fetchJson<OLSearchResponse>(\n url,\n `search title=\"${query}\" author=\"${author}\"`,\n );\n\n if (response?.docs?.length) {\n return response;\n }\n\n // Fall back to general q search if title+author yielded no results\n }\n\n // General search using q parameter\n const params = new URLSearchParams({\n q: query,\n fields: SEARCH_FIELDS,\n limit: String(limit),\n });\n\n if (author) {\n params.set(\"author\", author);\n }\n\n const url = `${BASE_URL}/search.json?${params}`;\n return fetchJson<OLSearchResponse>(url, `search \"${query}\"`);\n}\n\n/**\n * Get cover image URL by ISBN\n *\n * @param isbn ISBN-10 or ISBN-13\n * @param size Cover size: S (small ~50w), M (medium ~180w), L (large ~300w+)\n * @returns Cover URL\n */\nexport function getCoverUrlByIsbn(isbn: string, size: \"S\" | \"M\" | \"L\"): string {\n const normalized = normalizeIsbn(isbn);\n return `${COVERS_BASE_URL}/b/isbn/${normalized}-${size}.jpg`;\n}\n\n/**\n * Get cover image URL by cover ID\n *\n * @param coverId Open Library cover ID\n * @param size Cover size: S (small), M (medium), L (large)\n * @returns Cover URL\n */\nexport function getCoverUrlById(coverId: number, size: \"S\" | \"M\" | \"L\"): string {\n return `${COVERS_BASE_URL}/b/id/${coverId}-${size}.jpg`;\n}\n\n/**\n * Get cover image URL by Open Library ID (OLID)\n *\n * @param olid Open Library ID (e.g., \"OL7353617M\" for edition, \"OL45883W\" for work)\n * @param size Cover size: S (small), M (medium), L (large)\n * @returns Cover URL\n */\nexport function getCoverUrlByOlid(olid: string, size: \"S\" | \"M\" | \"L\"): string {\n // Strip any prefix if present\n const id = olid.replace(/^\\/(?:books|works)\\//, \"\");\n return `${COVERS_BASE_URL}/b/olid/${id}-${size}.jpg`;\n}\n\n/**\n * Parse year from Open Library date string\n *\n * Open Library dates can be in various formats:\n * - \"2020\"\n * - \"January 1, 2020\"\n * - \"2020-01-15\"\n * - \"c1985\"\n * - \"1985?\"\n *\n * @param dateStr Date string from Open Library\n * @returns Parsed year or undefined if unable to parse\n */\nexport function parseYear(dateStr: string | undefined): number | undefined {\n if (!dateStr) return undefined;\n\n // Try to extract a 4-digit year\n // Using (?:^|[^0-9]) to handle \"c1985\" format where there's no word boundary\n const match = dateStr.match(/(?:^|[^0-9])(1[89]\\d{2}|20\\d{2})(?:[^0-9]|$)/);\n if (match) {\n return Number.parseInt(match[1], 10);\n }\n\n return undefined;\n}\n\n/**\n * Parse description from Open Library\n *\n * Description can be either a string or an object with { type, value }.\n * Strips HTML tags and normalizes whitespace, since Open Library descriptions\n * can contain raw HTML (e.g., from Standard Ebooks imports).\n */\nexport function parseDescription(\n desc: string | { type?: string; value: string } | undefined,\n): string | undefined {\n if (!desc) return undefined;\n const raw = typeof desc === \"string\" ? desc : desc.value;\n return stripHtml(raw);\n}\n\n/**\n * Strip HTML tags from a string and normalize whitespace.\n *\n * Converts block-level tags (p, br, div, li) to newlines,\n * strips all remaining tags, decodes common HTML entities,\n * and collapses excessive whitespace.\n */\nfunction stripHtml(html: string): string | undefined {\n let text = html;\n\n // Convert block-level elements to newlines\n text = text.replace(/<\\/(p|div|li|tr|h[1-6])>/gi, \"\\n\");\n text = text.replace(/<br\\s*\\/?>/gi, \"\\n\");\n\n // Remove all remaining HTML tags\n text = text.replace(/<[^>]+>/g, \"\");\n\n // Decode common HTML entities\n text = text\n .replace(/&amp;/g, \"&\")\n .replace(/&lt;/g, \"<\")\n .replace(/&gt;/g, \">\")\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/&apos;/g, \"'\")\n .replace(/&nbsp;/g, \" \");\n\n // Collapse multiple spaces/tabs on the same line into one space\n text = text.replace(/[^\\S\\n]+/g, \" \");\n\n // Collapse 3+ consecutive newlines into 2\n text = text.replace(/\\n{3,}/g, \"\\n\\n\");\n\n // Trim each line and remove leading/trailing whitespace\n text = text\n .split(\"\\n\")\n .map((line) => line.trim())\n .join(\"\\n\")\n .trim();\n\n return text || undefined;\n}\n\n/**\n * Convert Open Library language code to BCP47\n *\n * Open Library uses format like \"/languages/eng\"\n *\n * @param langRef Language reference (e.g., \"/languages/eng\")\n * @returns BCP47 language code (e.g., \"en\")\n */\nexport function parseLanguage(langRef: string | undefined): string | undefined {\n if (!langRef) return undefined;\n\n // Extract language code from \"/languages/xxx\" format\n const match = langRef.match(/\\/languages\\/(\\w+)$/);\n if (!match) return undefined;\n\n const code = match[1].toLowerCase();\n\n // Map Open Library 3-letter codes to BCP47 2-letter codes\n const languageMap: Record<string, string> = {\n eng: \"en\",\n spa: \"es\",\n fre: \"fr\",\n fra: \"fr\",\n ger: \"de\",\n deu: \"de\",\n ita: \"it\",\n por: \"pt\",\n rus: \"ru\",\n jpn: \"ja\",\n chi: \"zh\",\n zho: \"zh\",\n kor: \"ko\",\n ara: \"ar\",\n hin: \"hi\",\n pol: \"pl\",\n tur: \"tr\",\n dut: \"nl\",\n nld: \"nl\",\n swe: \"sv\",\n nor: \"no\",\n dan: \"da\",\n fin: \"fi\",\n cze: \"cs\",\n ces: \"cs\",\n gre: \"el\",\n ell: \"el\",\n heb: \"he\",\n hun: \"hu\",\n rom: \"ro\",\n ron: \"ro\",\n tha: \"th\",\n vie: \"vi\",\n ind: \"id\",\n mal: \"ms\",\n msa: \"ms\",\n ukr: \"uk\",\n cat: \"ca\",\n lat: \"la\",\n };\n\n return languageMap[code] || code;\n}\n\n/**\n * Extract Open Library ID from a key\n *\n * @param key Full key (e.g., \"/works/OL45883W\" or \"/books/OL7353617M\")\n * @returns Just the ID (e.g., \"OL45883W\" or \"OL7353617M\")\n */\nexport function extractOlid(key: string): string {\n return key.replace(/^\\/(?:works|books|authors)\\//, \"\");\n}\n\n/**\n * Build Open Library URL from a key\n *\n * @param key Key (e.g., \"/works/OL45883W\")\n * @returns Full URL (e.g., \"https://openlibrary.org/works/OL45883W\")\n */\nexport function buildOpenLibraryUrl(key: string): string {\n return `${BASE_URL}${key.startsWith(\"/\") ? key : `/${key}`}`;\n}\n\n/**\n * Clear the cache\n */\nexport function clearCache(): void {\n cache.clear();\n}\n", "/**\n * JSON-RPC 2.0 types for plugin communication\n */\n\nexport interface JsonRpcRequest {\n jsonrpc: \"2.0\";\n id: string | number | null;\n method: string;\n params?: unknown;\n}\n\nexport interface JsonRpcSuccessResponse {\n jsonrpc: \"2.0\";\n id: string | number | null;\n result: unknown;\n}\n\nexport interface JsonRpcErrorResponse {\n jsonrpc: \"2.0\";\n id: string | number | null;\n error: JsonRpcError;\n}\n\nexport interface JsonRpcError {\n code: number;\n message: string;\n data?: unknown;\n}\n\nexport type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse;\n\n/**\n * Standard JSON-RPC error codes\n */\nexport const JSON_RPC_ERROR_CODES = {\n /** Invalid JSON was received */\n PARSE_ERROR: -32700,\n /** The JSON sent is not a valid Request object */\n INVALID_REQUEST: -32600,\n /** The method does not exist / is not available */\n METHOD_NOT_FOUND: -32601,\n /** Invalid method parameter(s) */\n INVALID_PARAMS: -32602,\n /** Internal JSON-RPC error */\n INTERNAL_ERROR: -32603,\n} as const;\n\n/**\n * Plugin-specific error codes (in the -32000 to -32099 range)\n */\nexport const PLUGIN_ERROR_CODES = {\n /** Rate limited by external API */\n RATE_LIMITED: -32001,\n /** Resource not found (e.g., series ID doesn't exist) */\n NOT_FOUND: -32002,\n /** Authentication failed (invalid credentials) */\n AUTH_FAILED: -32003,\n /** External API error */\n API_ERROR: -32004,\n /** Plugin configuration error */\n CONFIG_ERROR: -32005,\n} as const;\n", "/**\n * Plugin error classes for structured error handling\n */\n\nimport { type JsonRpcError, PLUGIN_ERROR_CODES } from \"./types/rpc.js\";\n\n/**\n * Base class for plugin errors that map to JSON-RPC errors\n */\nexport abstract class PluginError extends Error {\n abstract readonly code: number;\n readonly data?: unknown;\n\n constructor(message: string, data?: unknown) {\n super(message);\n this.name = this.constructor.name;\n this.data = data;\n }\n\n /**\n * Convert to JSON-RPC error format\n */\n toJsonRpcError(): JsonRpcError {\n return {\n code: this.code,\n message: this.message,\n data: this.data,\n };\n }\n}\n\n/**\n * Thrown when rate limited by an external API\n */\nexport class RateLimitError extends PluginError {\n readonly code = PLUGIN_ERROR_CODES.RATE_LIMITED;\n /** Seconds to wait before retrying */\n readonly retryAfterSeconds: number;\n\n constructor(retryAfterSeconds: number, message?: string) {\n super(message ?? `Rate limited, retry after ${retryAfterSeconds}s`, {\n retryAfterSeconds,\n });\n this.retryAfterSeconds = retryAfterSeconds;\n }\n}\n\n/**\n * Thrown when a requested resource is not found\n */\nexport class NotFoundError extends PluginError {\n readonly code = PLUGIN_ERROR_CODES.NOT_FOUND;\n}\n\n/**\n * Thrown when authentication fails (invalid credentials)\n */\nexport class AuthError extends PluginError {\n readonly code = PLUGIN_ERROR_CODES.AUTH_FAILED;\n\n constructor(message?: string) {\n super(message ?? \"Authentication failed\");\n }\n}\n\n/**\n * Thrown when an external API returns an error\n */\nexport class ApiError extends PluginError {\n readonly code = PLUGIN_ERROR_CODES.API_ERROR;\n readonly statusCode: number | undefined;\n\n constructor(message: string, statusCode?: number) {\n super(message, statusCode !== undefined ? { statusCode } : undefined);\n this.statusCode = statusCode;\n }\n}\n\n/**\n * Thrown when the plugin is misconfigured\n */\nexport class ConfigError extends PluginError {\n readonly code = PLUGIN_ERROR_CODES.CONFIG_ERROR;\n}\n", "/**\n * Logging utilities for plugins\n *\n * IMPORTANT: Plugins must ONLY write to stderr for logging.\n * stdout is reserved for JSON-RPC communication.\n */\n\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nconst LOG_LEVELS: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\nexport interface LoggerOptions {\n /** Plugin name to prefix log messages */\n name: string;\n /** Minimum log level (default: \"info\") */\n level?: LogLevel;\n /** Whether to include timestamps (default: true) */\n timestamps?: boolean;\n}\n\n/**\n * Logger that writes to stderr (safe for plugins)\n */\nexport class Logger {\n private readonly name: string;\n private readonly minLevel: number;\n private readonly timestamps: boolean;\n\n constructor(options: LoggerOptions) {\n this.name = options.name;\n this.minLevel = LOG_LEVELS[options.level ?? \"info\"];\n this.timestamps = options.timestamps ?? true;\n }\n\n private shouldLog(level: LogLevel): boolean {\n return LOG_LEVELS[level] >= this.minLevel;\n }\n\n private format(level: LogLevel, message: string, data?: unknown): string {\n const parts: string[] = [];\n\n if (this.timestamps) {\n parts.push(new Date().toISOString());\n }\n\n parts.push(`[${level.toUpperCase()}]`);\n parts.push(`[${this.name}]`);\n parts.push(message);\n\n if (data !== undefined) {\n if (data instanceof Error) {\n parts.push(`- ${data.message}`);\n if (data.stack) {\n parts.push(`\\n${data.stack}`);\n }\n } else if (typeof data === \"object\") {\n parts.push(`- ${JSON.stringify(data)}`);\n } else {\n parts.push(`- ${String(data)}`);\n }\n }\n\n return parts.join(\" \");\n }\n\n private log(level: LogLevel, message: string, data?: unknown): void {\n if (this.shouldLog(level)) {\n // Write to stderr (not stdout!) - stdout is for JSON-RPC only\n process.stderr.write(`${this.format(level, message, data)}\\n`);\n }\n }\n\n debug(message: string, data?: unknown): void {\n this.log(\"debug\", message, data);\n }\n\n info(message: string, data?: unknown): void {\n this.log(\"info\", message, data);\n }\n\n warn(message: string, data?: unknown): void {\n this.log(\"warn\", message, data);\n }\n\n error(message: string, data?: unknown): void {\n this.log(\"error\", message, data);\n }\n}\n\n/**\n * Create a logger for a plugin\n */\nexport function createLogger(options: LoggerOptions): Logger {\n return new Logger(options);\n}\n", "/**\n * Plugin server - handles JSON-RPC communication over stdio\n */\n\nimport { createInterface } from \"node:readline\";\nimport { PluginError } from \"./errors.js\";\nimport { createLogger, type Logger } from \"./logger.js\";\nimport type {\n BookMetadataProvider,\n MetadataContentType,\n MetadataProvider,\n} from \"./types/capabilities.js\";\nimport type { PluginManifest } from \"./types/manifest.js\";\nimport type {\n BookMatchParams,\n BookSearchParams,\n MetadataGetParams,\n MetadataMatchParams,\n MetadataSearchParams,\n} from \"./types/protocol.js\";\nimport {\n JSON_RPC_ERROR_CODES,\n type JsonRpcError,\n type JsonRpcRequest,\n type JsonRpcResponse,\n} from \"./types/rpc.js\";\n\n// =============================================================================\n// Parameter Validation\n// =============================================================================\n\ninterface ValidationError {\n field: string;\n message: string;\n}\n\n/**\n * Validate that the required string fields are present and non-empty\n */\nfunction validateStringFields(params: unknown, fields: string[]): ValidationError | null {\n if (params === null || params === undefined) {\n return { field: \"params\", message: \"params is required\" };\n }\n if (typeof params !== \"object\") {\n return { field: \"params\", message: \"params must be an object\" };\n }\n\n const obj = params as Record<string, unknown>;\n for (const field of fields) {\n const value = obj[field];\n if (value === undefined || value === null) {\n return { field, message: `${field} is required` };\n }\n if (typeof value !== \"string\") {\n return { field, message: `${field} must be a string` };\n }\n if (value.trim() === \"\") {\n return { field, message: `${field} cannot be empty` };\n }\n }\n\n return null;\n}\n\n/**\n * Validate MetadataSearchParams\n */\nfunction validateSearchParams(params: unknown): ValidationError | null {\n return validateStringFields(params, [\"query\"]);\n}\n\n/**\n * Validate MetadataGetParams\n */\nfunction validateGetParams(params: unknown): ValidationError | null {\n return validateStringFields(params, [\"externalId\"]);\n}\n\n/**\n * Validate MetadataMatchParams\n */\nfunction validateMatchParams(params: unknown): ValidationError | null {\n return validateStringFields(params, [\"title\"]);\n}\n\n/**\n * Validate BookSearchParams - requires either isbn or query\n */\nfunction validateBookSearchParams(params: unknown): ValidationError | null {\n if (params === null || params === undefined) {\n return { field: \"params\", message: \"params is required\" };\n }\n if (typeof params !== \"object\") {\n return { field: \"params\", message: \"params must be an object\" };\n }\n\n const obj = params as Record<string, unknown>;\n const hasIsbn = obj.isbn !== undefined && obj.isbn !== null && obj.isbn !== \"\";\n const hasQuery = obj.query !== undefined && obj.query !== null && obj.query !== \"\";\n\n if (!hasIsbn && !hasQuery) {\n return { field: \"isbn/query\", message: \"either isbn or query is required\" };\n }\n\n return null;\n}\n\n/**\n * Validate BookMatchParams\n */\nfunction validateBookMatchParams(params: unknown): ValidationError | null {\n return validateStringFields(params, [\"title\"]);\n}\n\n/**\n * Create an INVALID_PARAMS error response\n */\nfunction invalidParamsError(id: string | number | null, error: ValidationError): JsonRpcResponse {\n return {\n jsonrpc: \"2.0\",\n id,\n error: {\n code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,\n message: `Invalid params: ${error.message}`,\n data: { field: error.field },\n } as JsonRpcError,\n };\n}\n\n/**\n * Initialize parameters received from Codex\n */\nexport interface InitializeParams {\n /** Plugin configuration */\n config?: Record<string, unknown>;\n /** Plugin credentials (API keys, tokens, etc.) */\n credentials?: Record<string, string>;\n}\n\n/**\n * Options for creating a metadata plugin\n */\nexport interface MetadataPluginOptions {\n /** Plugin manifest - must have capabilities.metadataProvider with content types */\n manifest: PluginManifest & {\n capabilities: { metadataProvider: MetadataContentType[] };\n };\n /** Series MetadataProvider implementation (required if \"series\" in metadataProvider) */\n provider?: MetadataProvider;\n /** Book MetadataProvider implementation (required if \"book\" in metadataProvider) */\n bookProvider?: BookMetadataProvider;\n /** Called when plugin receives initialize with credentials/config */\n onInitialize?: (params: InitializeParams) => void | Promise<void>;\n /** Log level (default: \"info\") */\n logLevel?: \"debug\" | \"info\" | \"warn\" | \"error\";\n}\n\n/**\n * Create and run a metadata provider plugin\n *\n * Creates a plugin server that handles JSON-RPC communication over stdio.\n * The TypeScript compiler will ensure you implement all required methods.\n *\n * @example\n * ```typescript\n * import { createMetadataPlugin, type MetadataProvider } from \"@ashdev/codex-plugin-sdk\";\n *\n * const provider: MetadataProvider = {\n * async search(params) {\n * return {\n * results: [{\n * externalId: \"123\",\n * title: \"Example\",\n * alternateTitles: [],\n * relevanceScore: 0.95,\n * }],\n * };\n * },\n * async get(params) {\n * return {\n * externalId: params.externalId,\n * externalUrl: \"https://example.com/123\",\n * alternateTitles: [],\n * genres: [],\n * tags: [],\n * authors: [],\n * artists: [],\n * externalLinks: [],\n * };\n * },\n * };\n *\n * createMetadataPlugin({\n * manifest: {\n * name: \"my-plugin\",\n * displayName: \"My Plugin\",\n * version: \"1.0.0\",\n * description: \"Example plugin\",\n * author: \"Me\",\n * protocolVersion: \"1.0\",\n * capabilities: { metadataProvider: [\"series\"] },\n * },\n * provider,\n * });\n * ```\n */\nexport function createMetadataPlugin(options: MetadataPluginOptions): void {\n const { manifest, provider, bookProvider, onInitialize, logLevel = \"info\" } = options;\n const logger = createLogger({ name: manifest.name, level: logLevel });\n\n // Validate that required providers are present based on manifest\n const contentTypes = manifest.capabilities.metadataProvider;\n if (contentTypes.includes(\"series\") && !provider) {\n throw new Error(\n \"Series metadata provider is required when 'series' is in metadataProvider capabilities\",\n );\n }\n if (contentTypes.includes(\"book\") && !bookProvider) {\n throw new Error(\n \"Book metadata provider is required when 'book' is in metadataProvider capabilities\",\n );\n }\n\n logger.info(`Starting plugin: ${manifest.displayName} v${manifest.version}`);\n\n const rl = createInterface({\n input: process.stdin,\n terminal: false,\n });\n\n rl.on(\"line\", (line) => {\n void handleLine(line, manifest, provider, bookProvider, onInitialize, logger);\n });\n\n rl.on(\"close\", () => {\n logger.info(\"stdin closed, shutting down\");\n process.exit(0);\n });\n\n // Handle uncaught errors\n process.on(\"uncaughtException\", (error) => {\n logger.error(\"Uncaught exception\", error);\n process.exit(1);\n });\n\n process.on(\"unhandledRejection\", (reason) => {\n logger.error(\"Unhandled rejection\", reason);\n });\n}\n\n// =============================================================================\n// Backwards Compatibility (deprecated)\n// =============================================================================\n\n/**\n * @deprecated Use createMetadataPlugin instead\n */\nexport function createSeriesMetadataPlugin(options: SeriesMetadataPluginOptions): void {\n // Convert legacy options to new format\n const newOptions: MetadataPluginOptions = {\n ...options,\n manifest: {\n ...options.manifest,\n capabilities: {\n ...options.manifest.capabilities,\n metadataProvider: [\"series\"] as MetadataContentType[],\n },\n },\n };\n createMetadataPlugin(newOptions);\n}\n\n/**\n * @deprecated Use MetadataPluginOptions instead\n */\nexport interface SeriesMetadataPluginOptions {\n /** Plugin manifest - must have capabilities.seriesMetadataProvider: true */\n manifest: PluginManifest & {\n capabilities: { seriesMetadataProvider: true };\n };\n /** SeriesMetadataProvider implementation */\n provider: MetadataProvider;\n /** Called when plugin receives initialize with credentials/config */\n onInitialize?: (params: InitializeParams) => void | Promise<void>;\n /** Log level (default: \"info\") */\n logLevel?: \"debug\" | \"info\" | \"warn\" | \"error\";\n}\n\n// =============================================================================\n// Internal Implementation\n// =============================================================================\n\nasync function handleLine(\n line: string,\n manifest: PluginManifest,\n provider: MetadataProvider | undefined,\n bookProvider: BookMetadataProvider | undefined,\n onInitialize: ((params: InitializeParams) => void | Promise<void>) | undefined,\n logger: Logger,\n): Promise<void> {\n const trimmed = line.trim();\n if (!trimmed) return;\n\n let id: string | number | null = null;\n\n try {\n const request = JSON.parse(trimmed) as JsonRpcRequest;\n id = request.id;\n\n logger.debug(`Received request: ${request.method}`, { id: request.id });\n\n const response = await handleRequest(\n request,\n manifest,\n provider,\n bookProvider,\n onInitialize,\n logger,\n );\n // Shutdown handler writes response directly and returns null\n if (response !== null) {\n writeResponse(response);\n }\n } catch (error) {\n if (error instanceof SyntaxError) {\n // JSON parse error\n writeResponse({\n jsonrpc: \"2.0\",\n id: null,\n error: {\n code: JSON_RPC_ERROR_CODES.PARSE_ERROR,\n message: \"Parse error: invalid JSON\",\n },\n });\n } else if (error instanceof PluginError) {\n writeResponse({\n jsonrpc: \"2.0\",\n id,\n error: error.toJsonRpcError(),\n });\n } else {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n logger.error(\"Request failed\", error);\n writeResponse({\n jsonrpc: \"2.0\",\n id,\n error: {\n code: JSON_RPC_ERROR_CODES.INTERNAL_ERROR,\n message,\n },\n });\n }\n }\n}\n\nasync function handleRequest(\n request: JsonRpcRequest,\n manifest: PluginManifest,\n provider: MetadataProvider | undefined,\n bookProvider: BookMetadataProvider | undefined,\n onInitialize: ((params: InitializeParams) => void | Promise<void>) | undefined,\n logger: Logger,\n): Promise<JsonRpcResponse> {\n const { method, params, id } = request;\n\n switch (method) {\n case \"initialize\":\n // Call onInitialize callback if provided (to receive credentials/config)\n if (onInitialize) {\n await onInitialize(params as InitializeParams);\n }\n return {\n jsonrpc: \"2.0\",\n id,\n result: manifest,\n };\n\n case \"ping\":\n return {\n jsonrpc: \"2.0\",\n id,\n result: \"pong\",\n };\n\n case \"shutdown\": {\n logger.info(\"Shutdown requested\");\n // Write response directly with callback to ensure it's flushed before exit\n const response: JsonRpcResponse = {\n jsonrpc: \"2.0\",\n id,\n result: null,\n };\n process.stdout.write(`${JSON.stringify(response)}\\n`, () => {\n // Callback is called after the write is flushed to the OS\n process.exit(0);\n });\n // Return a sentinel that handleLine will recognize and skip normal writeResponse\n return null as unknown as JsonRpcResponse;\n }\n\n // =========================================================================\n // Series metadata methods\n // =========================================================================\n case \"metadata/series/search\": {\n if (!provider) {\n return {\n jsonrpc: \"2.0\",\n id,\n error: {\n code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,\n message: \"This plugin does not support series metadata\",\n },\n };\n }\n const validationError = validateSearchParams(params);\n if (validationError) {\n return invalidParamsError(id, validationError);\n }\n return {\n jsonrpc: \"2.0\",\n id,\n result: await provider.search(params as MetadataSearchParams),\n };\n }\n\n case \"metadata/series/get\": {\n if (!provider) {\n return {\n jsonrpc: \"2.0\",\n id,\n error: {\n code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,\n message: \"This plugin does not support series metadata\",\n },\n };\n }\n const validationError = validateGetParams(params);\n if (validationError) {\n return invalidParamsError(id, validationError);\n }\n return {\n jsonrpc: \"2.0\",\n id,\n result: await provider.get(params as MetadataGetParams),\n };\n }\n\n case \"metadata/series/match\": {\n if (!provider) {\n return {\n jsonrpc: \"2.0\",\n id,\n error: {\n code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,\n message: \"This plugin does not support series metadata\",\n },\n };\n }\n if (!provider.match) {\n return {\n jsonrpc: \"2.0\",\n id,\n error: {\n code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,\n message: \"This plugin does not support series match\",\n },\n };\n }\n const validationError = validateMatchParams(params);\n if (validationError) {\n return invalidParamsError(id, validationError);\n }\n return {\n jsonrpc: \"2.0\",\n id,\n result: await provider.match(params as MetadataMatchParams),\n };\n }\n\n // =========================================================================\n // Book metadata methods\n // =========================================================================\n case \"metadata/book/search\": {\n if (!bookProvider) {\n return {\n jsonrpc: \"2.0\",\n id,\n error: {\n code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,\n message: \"This plugin does not support book metadata\",\n },\n };\n }\n const validationError = validateBookSearchParams(params);\n if (validationError) {\n return invalidParamsError(id, validationError);\n }\n return {\n jsonrpc: \"2.0\",\n id,\n result: await bookProvider.search(params as BookSearchParams),\n };\n }\n\n case \"metadata/book/get\": {\n if (!bookProvider) {\n return {\n jsonrpc: \"2.0\",\n id,\n error: {\n code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,\n message: \"This plugin does not support book metadata\",\n },\n };\n }\n const validationError = validateGetParams(params);\n if (validationError) {\n return invalidParamsError(id, validationError);\n }\n return {\n jsonrpc: \"2.0\",\n id,\n result: await bookProvider.get(params as MetadataGetParams),\n };\n }\n\n case \"metadata/book/match\": {\n if (!bookProvider) {\n return {\n jsonrpc: \"2.0\",\n id,\n error: {\n code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,\n message: \"This plugin does not support book metadata\",\n },\n };\n }\n if (!bookProvider.match) {\n return {\n jsonrpc: \"2.0\",\n id,\n error: {\n code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,\n message: \"This plugin does not support book match\",\n },\n };\n }\n const validationError = validateBookMatchParams(params);\n if (validationError) {\n return invalidParamsError(id, validationError);\n }\n return {\n jsonrpc: \"2.0\",\n id,\n result: await bookProvider.match(params as BookMatchParams),\n };\n }\n\n default:\n return {\n jsonrpc: \"2.0\",\n id,\n error: {\n code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,\n message: `Method not found: ${method}`,\n },\n };\n }\n}\n\nfunction writeResponse(response: JsonRpcResponse): void {\n // Write to stdout - this is the JSON-RPC channel\n process.stdout.write(`${JSON.stringify(response)}\\n`);\n}\n", "/**\n * Open Library Metadata Plugin for Codex\n *\n * Fetches book metadata from Open Library (openlibrary.org), a free and open\n * book database with extensive ISBN coverage.\n *\n * Features:\n * - ISBN lookup for direct, accurate matching\n * - Title/author search for fuzzy matching\n * - Cover image fetching in multiple sizes\n * - Author resolution with proper names\n * - Subject/genre extraction\n *\n * @see https://openlibrary.org/developers/api\n */\n\nimport {\n type BookMatchParams,\n type BookMetadataProvider,\n type BookSearchParams,\n createLogger,\n createMetadataPlugin,\n type InitializeParams,\n type MetadataGetParams,\n type MetadataMatchResponse,\n type MetadataSearchResponse,\n type PluginBookMetadata,\n} from \"@ashdev/codex-plugin-sdk\";\n\nimport { getEditionByIsbn, getWork, isValidIsbn, searchBooks } from \"./api.js\";\nimport { DEFAULT_MAX_RESULTS, manifest } from \"./manifest.js\";\nimport {\n getFullBookMetadata,\n mapEditionToBookMetadata,\n mapSearchDocToSearchResult,\n} from \"./mapper.js\";\n\nconst logger = createLogger({ name: \"openlibrary\", level: \"info\" });\n\n// Plugin configuration (set during initialization)\nconst config = {\n maxResults: DEFAULT_MAX_RESULTS,\n};\n\n/**\n * Book metadata provider implementation\n */\nconst bookProvider: BookMetadataProvider = {\n /**\n * Search for books by ISBN or title/author query\n *\n * If ISBN is provided, it takes priority for direct lookup.\n * Otherwise, falls back to title/author search.\n */\n async search(params: BookSearchParams): Promise<MetadataSearchResponse> {\n const { isbn, query, author, limit } = params;\n const maxResults = Math.min(limit || config.maxResults, 50);\n\n // If ISBN is provided, try direct lookup first\n if (isbn && isValidIsbn(isbn)) {\n const edition = await getEditionByIsbn(isbn);\n\n if (edition) {\n // Found by ISBN - return as single result with high relevance\n const workKey = edition.works?.[0]?.key;\n const workData = workKey ? await getWork(workKey) : null;\n const metadata = await mapEditionToBookMetadata(edition, workData);\n\n return {\n results: [\n {\n externalId: metadata.externalId,\n title: metadata.title || \"Unknown\",\n alternateTitles: metadata.subtitle ? [metadata.subtitle] : [],\n year: metadata.year,\n coverUrl: metadata.coverUrl,\n relevanceScore: 1.0, // Perfect match by ISBN\n preview: {\n genres: metadata.subjects.slice(0, 5),\n authors: metadata.authors.map((a) => a.name),\n },\n },\n ],\n };\n }\n\n // ISBN not found, fall through to search if query is also provided\n if (!query) {\n return { results: [] };\n }\n }\n\n // Title/author search\n if (!query) {\n return { results: [] };\n }\n\n const searchResponse = await searchBooks(query, {\n author,\n limit: maxResults,\n });\n\n if (!searchResponse?.docs?.length) {\n return { results: [] };\n }\n\n return {\n results: searchResponse.docs.map(mapSearchDocToSearchResult),\n };\n },\n\n /**\n * Get full book metadata by external ID\n *\n * The external ID can be:\n * - A work key: \"/works/OL45883W\"\n * - An edition key: \"/books/OL7353617M\"\n */\n async get(params: MetadataGetParams): Promise<PluginBookMetadata> {\n const { externalId } = params;\n\n // Try to get full metadata\n const metadata = await getFullBookMetadata(externalId);\n\n if (metadata) {\n return metadata;\n }\n\n // Fallback: return minimal metadata\n return {\n externalId,\n externalUrl: `https://openlibrary.org${externalId.startsWith(\"/\") ? externalId : `/${externalId}`}`,\n alternateTitles: [],\n isbns: [],\n genres: [],\n tags: [],\n subjects: [],\n authors: [],\n artists: [],\n covers: [],\n externalRatings: [],\n awards: [],\n externalLinks: [\n {\n url: `https://openlibrary.org${externalId.startsWith(\"/\") ? externalId : `/${externalId}`}`,\n label: \"Open Library\",\n linkType: \"provider\",\n },\n ],\n };\n },\n\n /**\n * Auto-match a book using available identifiers\n *\n * Match priority:\n * 1. ISBN (if provided) - highest confidence\n * 2. Title + author search - lower confidence\n */\n async match(params: BookMatchParams): Promise<MetadataMatchResponse> {\n const { title, authors, isbn, year } = params;\n\n // Try ISBN first if available\n if (isbn && isValidIsbn(isbn)) {\n const edition = await getEditionByIsbn(isbn);\n\n if (edition) {\n const workKey = edition.works?.[0]?.key;\n const workData = workKey ? await getWork(workKey) : null;\n const metadata = await mapEditionToBookMetadata(edition, workData);\n\n return {\n match: {\n externalId: metadata.externalId,\n title: metadata.title || \"Unknown\",\n alternateTitles: metadata.subtitle ? [metadata.subtitle] : [],\n year: metadata.year,\n coverUrl: metadata.coverUrl,\n relevanceScore: 1.0,\n preview: {\n genres: metadata.subjects.slice(0, 5),\n authors: metadata.authors.map((a) => a.name),\n },\n },\n confidence: 0.99, // Very high confidence for ISBN match\n alternatives: [],\n };\n }\n }\n\n // Fall back to title search\n const searchQuery = authors?.length ? `${title} ${authors[0]}` : title;\n\n const searchResponse = await searchBooks(searchQuery, {\n limit: 5,\n });\n\n if (!searchResponse?.docs?.length) {\n return {\n match: null,\n confidence: 0,\n alternatives: [],\n };\n }\n\n const results = searchResponse.docs.map(mapSearchDocToSearchResult);\n\n // Calculate confidence based on title similarity and other factors\n const bestMatch = results[0];\n let confidence = bestMatch.relevanceScore || 0.5;\n\n // Boost confidence if title matches closely\n const normalizedTitle = title.toLowerCase().trim();\n const normalizedMatchTitle = bestMatch.title.toLowerCase().trim();\n\n if (normalizedTitle === normalizedMatchTitle) {\n confidence = Math.min(1.0, confidence + 0.3);\n } else if (\n normalizedMatchTitle.includes(normalizedTitle) ||\n normalizedTitle.includes(normalizedMatchTitle)\n ) {\n confidence = Math.min(1.0, confidence + 0.15);\n }\n\n // Boost if year matches\n if (year && bestMatch.year === year) {\n confidence = Math.min(1.0, confidence + 0.1);\n }\n\n // Reduce confidence without ISBN\n confidence = Math.min(confidence, 0.85);\n\n return {\n match: bestMatch,\n confidence,\n alternatives: results.slice(1),\n };\n },\n};\n\n// =============================================================================\n// Plugin Initialization\n// =============================================================================\n\ncreateMetadataPlugin({\n manifest,\n bookProvider,\n logLevel: \"info\",\n onInitialize(params: InitializeParams) {\n // Read config from initialization params\n const maxResults = params.config?.maxResults as number | undefined;\n if (maxResults !== undefined) {\n config.maxResults = Math.min(Math.max(1, maxResults), 50); // Clamp 1-50\n }\n logger.info(`Plugin initialized (maxResults: ${config.maxResults})`);\n },\n});\n\nlogger.info(\"Open Library plugin started\");\n", "{\n \"name\": \"@ashdev/codex-plugin-metadata-openlibrary\",\n \"version\": \"1.0.0\",\n \"description\": \"Open Library metadata plugin for Codex - fetches book metadata by ISBN or title search\",\n \"main\": \"dist/index.js\",\n \"bin\": \"dist/index.js\",\n \"type\": \"module\",\n \"files\": [\n \"dist\",\n \"README.md\"\n ],\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/AshDevFr/codex.git\",\n \"directory\": \"plugins/metadata-openlibrary\"\n },\n \"scripts\": {\n \"build\": \"esbuild src/index.ts --bundle --platform=node --target=node22 --format=esm --outfile=dist/index.js --sourcemap --banner:js='#!/usr/bin/env node'\",\n \"dev\": \"npm run build -- --watch\",\n \"clean\": \"rm -rf dist\",\n \"start\": \"node dist/index.js\",\n \"lint\": \"biome check .\",\n \"lint:fix\": \"biome check --write .\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"prepublishOnly\": \"npm run lint && npm run build\"\n },\n \"keywords\": [\n \"codex\",\n \"plugin\",\n \"openlibrary\",\n \"metadata\",\n \"books\",\n \"isbn\"\n ],\n \"author\": \"Codex\",\n \"license\": \"MIT\",\n \"engines\": {\n \"node\": \">=22.0.0\"\n },\n \"dependencies\": {\n \"@ashdev/codex-plugin-sdk\": \"file:../sdk-typescript\"\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"^2.3.13\",\n \"@types/node\": \"^22.0.0\",\n \"esbuild\": \"^0.24.0\",\n \"typescript\": \"^5.7.0\",\n \"vitest\": \"^3.0.0\"\n }\n}\n", "import type { MetadataContentType, PluginManifest } from \"@ashdev/codex-plugin-sdk\";\nimport packageJson from \"../package.json\" with { type: \"json\" };\n\n// Default config values\nexport const DEFAULT_MAX_RESULTS = 10;\n\nexport const manifest = {\n name: \"metadata-openlibrary\",\n displayName: \"Open Library\",\n version: packageJson.version,\n description:\n \"Fetches book metadata from Open Library (openlibrary.org). Supports ISBN lookup and title search for EPUBs, PDFs, and other book formats.\",\n author: \"Codex\",\n homepage: \"https://openlibrary.org\",\n protocolVersion: \"1.0\",\n capabilities: {\n // Book metadata provider only (not series)\n metadataProvider: [\"book\"] as MetadataContentType[],\n },\n configSchema: {\n description: \"Configuration options for the Open Library plugin\",\n fields: [\n {\n key: \"maxResults\",\n label: \"Maximum Results\",\n description: \"Maximum number of results to return for search queries (1-50)\",\n type: \"number\" as const,\n required: false,\n default: DEFAULT_MAX_RESULTS,\n example: 20,\n },\n ],\n },\n} as const satisfies PluginManifest & {\n capabilities: { metadataProvider: MetadataContentType[] };\n};\n", "/**\n * Mapper functions to convert Open Library data to Codex plugin format\n */\n\nimport type {\n BookAuthor,\n BookCover,\n ExternalLink,\n ExternalRating,\n PluginBookMetadata,\n SearchResult,\n} from \"@ashdev/codex-plugin-sdk\";\n\nimport {\n buildOpenLibraryUrl,\n getAuthor,\n getCoverUrlById,\n getCoverUrlByIsbn,\n getWork,\n getWorkEditions,\n parseDescription,\n parseLanguage,\n parseYear,\n} from \"./api.js\";\nimport type { OLAuthorReference, OLEdition, OLSearchDoc, OLWork, ParsedAuthor } from \"./types.js\";\n\n/**\n * Map Open Library search result to Codex SearchResult\n */\nexport function mapSearchDocToSearchResult(doc: OLSearchDoc): SearchResult {\n const year = doc.first_publish_year;\n const coverUrl = doc.cover_i ? getCoverUrlById(doc.cover_i, \"M\") : undefined;\n\n // Calculate a relevance score based on available data\n // More complete entries get higher scores\n let relevanceScore = 0.5;\n if (doc.author_name?.length) relevanceScore += 0.1;\n if (doc.isbn?.length) relevanceScore += 0.15;\n if (doc.cover_i) relevanceScore += 0.1;\n if (doc.first_publish_year) relevanceScore += 0.05;\n if (doc.subject?.length) relevanceScore += 0.05;\n if (doc.ratings_count && doc.ratings_count > 0) relevanceScore += 0.05;\n\n return {\n externalId: doc.key, // Work key, e.g., \"/works/OL45883W\"\n title: doc.title,\n alternateTitles: doc.subtitle ? [doc.subtitle] : [],\n year,\n coverUrl,\n relevanceScore: Math.min(1.0, relevanceScore),\n preview: {\n genres: doc.subject?.slice(0, 5) || [],\n rating: doc.ratings_average\n ? Math.round(doc.ratings_average * 2) / 2 // Normalize to 0-10 scale (OL uses 1-5)\n : undefined,\n authors: doc.author_name?.slice(0, 3) || [],\n description: doc.publisher?.length ? `Published by ${doc.publisher[0]}` : undefined,\n },\n };\n}\n\n/**\n * Resolve author references to full author data\n */\nasync function resolveAuthors(\n authorRefs: OLAuthorReference[] | undefined,\n): Promise<ParsedAuthor[]> {\n if (!authorRefs?.length) return [];\n\n const authors: ParsedAuthor[] = [];\n\n for (const ref of authorRefs) {\n const key = ref.author?.key || ref.key;\n if (!key) continue;\n\n const authorData = await getAuthor(key);\n if (authorData) {\n authors.push({\n name: authorData.name,\n key,\n sortName: authorData.personal_name || undefined,\n });\n }\n }\n\n return authors;\n}\n\n/**\n * Map parsed authors to BookAuthor format\n */\nfunction mapToBookAuthors(authors: ParsedAuthor[]): BookAuthor[] {\n return authors.map((author) => ({\n name: author.name,\n role: \"author\" as const,\n sortName: author.sortName,\n }));\n}\n\n/**\n * Build cover URLs for a book\n */\nfunction buildCoverUrls(isbn: string | undefined, coverId: number | undefined): BookCover[] {\n const covers: BookCover[] = [];\n\n // Prefer ISBN-based URLs as they're more reliable\n if (isbn) {\n covers.push({\n url: getCoverUrlByIsbn(isbn, \"S\"),\n size: \"small\",\n });\n covers.push({\n url: getCoverUrlByIsbn(isbn, \"M\"),\n size: \"medium\",\n });\n covers.push({\n url: getCoverUrlByIsbn(isbn, \"L\"),\n size: \"large\",\n });\n } else if (coverId) {\n // Fallback to cover ID\n covers.push({\n url: getCoverUrlById(coverId, \"S\"),\n size: \"small\",\n });\n covers.push({\n url: getCoverUrlById(coverId, \"M\"),\n size: \"medium\",\n });\n covers.push({\n url: getCoverUrlById(coverId, \"L\"),\n size: \"large\",\n });\n }\n\n return covers;\n}\n\n/**\n * Build external links for Open Library book\n */\nfunction buildExternalLinks(editionKey: string, workKey: string | undefined): ExternalLink[] {\n const links: ExternalLink[] = [\n {\n url: buildOpenLibraryUrl(editionKey),\n label: \"Open Library (Edition)\",\n linkType: \"provider\",\n },\n ];\n\n if (workKey) {\n links.push({\n url: buildOpenLibraryUrl(workKey),\n label: \"Open Library (Work)\",\n linkType: \"provider\",\n });\n }\n\n return links;\n}\n\n/**\n * Get all ISBNs from edition data\n */\nfunction collectIsbns(edition: OLEdition): string[] {\n const isbns: string[] = [];\n\n // Prefer ISBN-13\n if (edition.isbn_13?.length) {\n isbns.push(...edition.isbn_13);\n }\n\n // Add ISBN-10 as well\n if (edition.isbn_10?.length) {\n isbns.push(...edition.isbn_10);\n }\n\n return [...new Set(isbns)]; // Deduplicate\n}\n\n/**\n * Map Open Library edition and optional work to full book metadata\n */\nexport async function mapEditionToBookMetadata(\n edition: OLEdition,\n workData?: OLWork | null,\n): Promise<PluginBookMetadata> {\n // Resolve authors from edition or work\n const authorRefs = edition.authors || workData?.authors;\n const authors = await resolveAuthors(authorRefs);\n\n // Get ISBNs\n const isbns = collectIsbns(edition);\n const primaryIsbn = isbns[0];\n\n // Get cover ID from edition or work\n const coverId = edition.covers?.[0] || workData?.covers?.[0];\n\n // Get description from edition or work\n const description =\n parseDescription(edition.description) || parseDescription(workData?.description);\n\n // Get subjects from both edition and work\n const subjects = [...(edition.subjects || []), ...(workData?.subjects || [])];\n const uniqueSubjects = [...new Set(subjects)];\n\n // Parse year\n const year = parseYear(edition.publish_date);\n const originalYear = parseYear(workData?.first_publish_date);\n\n // Parse language\n const language = parseLanguage(edition.languages?.[0]?.key);\n\n // Build external rating if ratings exist from search\n const externalRatings: ExternalRating[] = [];\n\n // Build metadata\n const workKey = edition.works?.[0]?.key || workData?.key;\n const externalId = workKey || edition.key;\n\n return {\n externalId,\n externalUrl: buildOpenLibraryUrl(externalId),\n\n // Core fields\n title: edition.title,\n subtitle: edition.subtitle || workData?.subtitle,\n alternateTitles: [],\n summary: description,\n bookType: detectBookType(edition),\n\n // Book-specific fields\n pageCount: edition.number_of_pages,\n year,\n\n // ISBN\n isbn: primaryIsbn,\n isbns,\n\n // Edition info\n edition: edition.edition_name,\n originalTitle: workData?.title !== edition.title ? workData?.title : undefined,\n originalYear,\n language,\n\n // Taxonomy\n genres: [], // Open Library doesn't have genres, just subjects\n tags: [],\n subjects: uniqueSubjects.slice(0, 20), // Limit to 20 subjects\n\n // Credits\n authors: mapToBookAuthors(authors),\n artists: [], // Open Library doesn't track artists separately\n publisher: edition.publishers?.[0],\n\n // Media\n coverUrl: primaryIsbn\n ? getCoverUrlByIsbn(primaryIsbn, \"L\")\n : coverId\n ? getCoverUrlById(coverId, \"L\")\n : undefined,\n covers: buildCoverUrls(primaryIsbn, coverId),\n\n // Rating\n externalRatings,\n awards: [],\n\n // Links\n externalLinks: buildExternalLinks(edition.key, workKey),\n };\n}\n\n/**\n * Detect book type from edition data\n *\n * Open Library doesn't have explicit book type, but we can infer from:\n * - physical_format field\n * - subjects\n * - other metadata\n */\nfunction detectBookType(edition: OLEdition): string | undefined {\n const format = edition.physical_format?.toLowerCase();\n\n if (format) {\n if (format.includes(\"comic\") || format.includes(\"graphic novel\")) {\n return \"graphic_novel\";\n }\n if (format.includes(\"manga\")) {\n return \"manga\";\n }\n if (format.includes(\"magazine\") || format.includes(\"periodical\")) {\n return \"magazine\";\n }\n }\n\n // Check subjects for hints\n const subjects = (edition.subjects || []).join(\" \").toLowerCase();\n\n if (subjects.includes(\"graphic novel\") || subjects.includes(\"comics\")) {\n return \"graphic_novel\";\n }\n if (subjects.includes(\"manga\")) {\n return \"manga\";\n }\n\n // Default to novel for most books\n return \"novel\";\n}\n\n/**\n * Map Open Library search doc to book metadata for quick preview\n *\n * This is a lighter version that doesn't fetch additional data\n */\nexport function mapSearchDocToBookPreview(doc: OLSearchDoc): PluginBookMetadata {\n const isbns = doc.isbn?.slice(0, 5) || [];\n const primaryIsbn = isbns[0];\n const coverId = doc.cover_i;\n\n return {\n externalId: doc.key,\n externalUrl: buildOpenLibraryUrl(doc.key),\n\n // Core fields\n title: doc.title,\n subtitle: doc.subtitle,\n alternateTitles: [],\n summary: undefined, // Not available in search results\n\n // Book-specific fields\n pageCount: doc.number_of_pages_median,\n year: doc.first_publish_year,\n\n // ISBN\n isbn: primaryIsbn,\n isbns,\n\n // Taxonomy\n genres: [],\n tags: [],\n subjects: doc.subject?.slice(0, 10) || [],\n\n // Credits\n authors:\n doc.author_name?.map((name) => ({\n name,\n role: \"author\" as const,\n })) || [],\n artists: [],\n publisher: doc.publisher?.[0],\n\n // Media\n coverUrl: primaryIsbn\n ? getCoverUrlByIsbn(primaryIsbn, \"L\")\n : coverId\n ? getCoverUrlById(coverId, \"L\")\n : undefined,\n covers: buildCoverUrls(primaryIsbn, coverId),\n\n // Rating\n rating: doc.ratings_average\n ? {\n score: Math.round(doc.ratings_average * 20), // Convert 1-5 to 0-100\n voteCount: doc.ratings_count,\n source: \"openlibrary\",\n }\n : undefined,\n externalRatings:\n doc.ratings_average && doc.ratings_count\n ? [\n {\n score: Math.round(doc.ratings_average * 20),\n voteCount: doc.ratings_count,\n source: \"openlibrary\",\n },\n ]\n : [],\n awards: [],\n\n // Links\n externalLinks: [\n {\n url: buildOpenLibraryUrl(doc.key),\n label: \"Open Library\",\n linkType: \"provider\",\n },\n ],\n };\n}\n\n/**\n * Get full book metadata by fetching edition, work, and author data\n *\n * @param editionOrWorkKey Either an edition key or work key\n * @param isbn Optional ISBN for direct lookup\n */\nexport async function getFullBookMetadata(\n editionOrWorkKey: string,\n isbn?: string,\n): Promise<PluginBookMetadata | null> {\n // If we have an ISBN, try to get edition directly\n if (isbn) {\n const { getEditionByIsbn } = await import(\"./api.js\");\n const edition = await getEditionByIsbn(isbn);\n if (edition) {\n const workKey = edition.works?.[0]?.key;\n const workData = workKey ? await getWork(workKey) : null;\n return mapEditionToBookMetadata(edition, workData);\n }\n }\n\n // Check if it's a work key\n if (editionOrWorkKey.includes(\"/works/\")) {\n const workData = await getWork(editionOrWorkKey);\n if (!workData) return null;\n\n // Fetch editions directly from the work using the editions API.\n // This is much more reliable than searching by title, which can\n // return completely unrelated books with similar titles.\n const editions = await getWorkEditions(editionOrWorkKey, 5);\n\n if (editions.length > 0) {\n // Prefer an edition that has ISBNs for richer metadata\n const editionWithIsbn = editions.find((e) => e.isbn_13?.length || e.isbn_10?.length);\n const edition = editionWithIsbn || editions[0];\n return mapEditionToBookMetadata(edition, workData);\n }\n\n // Fallback: create metadata from work data only\n const authors = await resolveAuthors(workData.authors);\n const coverId = workData.covers?.[0];\n\n return {\n externalId: workData.key,\n externalUrl: buildOpenLibraryUrl(workData.key),\n title: workData.title,\n subtitle: workData.subtitle,\n alternateTitles: [],\n summary: parseDescription(workData.description),\n isbns: [],\n genres: [],\n tags: [],\n subjects: workData.subjects?.slice(0, 20) || [],\n authors: mapToBookAuthors(authors),\n artists: [],\n coverUrl: coverId ? getCoverUrlById(coverId, \"L\") : undefined,\n covers: coverId\n ? [\n { url: getCoverUrlById(coverId, \"S\"), size: \"small\" },\n { url: getCoverUrlById(coverId, \"M\"), size: \"medium\" },\n { url: getCoverUrlById(coverId, \"L\"), size: \"large\" },\n ]\n : [],\n externalRatings: [],\n awards: [],\n externalLinks: [\n {\n url: buildOpenLibraryUrl(workData.key),\n label: \"Open Library\",\n linkType: \"provider\",\n },\n ],\n };\n }\n\n // It's an edition key - fetch directly\n // For edition keys, we need to use a different approach\n // since there's no direct edition endpoint by key\n // Try to use the key directly\n const url = `https://openlibrary.org${editionOrWorkKey}.json`;\n try {\n const response = await fetch(url, {\n headers: {\n \"User-Agent\": \"Codex/1.0 (https://github.com/AshDevFr/codex; codex-plugin)\",\n Accept: \"application/json\",\n },\n });\n\n if (response.ok) {\n const edition = (await response.json()) as OLEdition;\n const workKey = edition.works?.[0]?.key;\n const workData = workKey ? await getWork(workKey) : null;\n return mapEditionToBookMetadata(edition, workData);\n }\n } catch {\n // Ignore fetch errors\n }\n\n return null;\n}\n"],
5
- "mappings": "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",
6
- "names": ["params", "url", "manifest", "logger", "manifest", "logger", "getEditionByIsbn"]
3
+ "sources": ["../src/api.ts", "../node_modules/@ashdev/codex-plugin-sdk/src/types/rpc.ts", "../node_modules/@ashdev/codex-plugin-sdk/src/errors.ts", "../node_modules/@ashdev/codex-plugin-sdk/src/logger.ts", "../node_modules/@ashdev/codex-plugin-sdk/src/server.ts", "../src/index.ts", "../package.json", "../src/manifest.ts", "../src/mapper.ts"],
4
+ "sourcesContent": ["/**\n * Open Library API Client\n *\n * Handles communication with the Open Library API with:\n * - Rate limiting (100 requests per 5 minutes recommended)\n * - Caching to reduce API calls\n * - Error handling with retries\n *\n * @see https://openlibrary.org/developers/api\n */\n\nimport type {\n OLAuthor,\n OLEdition,\n OLSearchResponse,\n OLWork,\n OLWorkEditionsResponse,\n} from \"./types.js\";\n\nconst BASE_URL = \"https://openlibrary.org\";\nconst COVERS_BASE_URL = \"https://covers.openlibrary.org\";\n\n// Simple in-memory cache with TTL\ninterface CacheEntry<T> {\n data: T;\n timestamp: number;\n}\n\nconst CACHE_TTL_MS = 15 * 60 * 1000; // 15 minutes\nconst cache = new Map<string, CacheEntry<unknown>>();\n\n/**\n * Get cached value if not expired\n */\nfunction getCached<T>(key: string): T | null {\n const entry = cache.get(key);\n if (entry && Date.now() - entry.timestamp < CACHE_TTL_MS) {\n return entry.data as T;\n }\n if (entry) {\n cache.delete(key); // Cleanup expired\n }\n return null;\n}\n\n/**\n * Store value in cache\n */\nfunction setCache<T>(key: string, data: T): void {\n cache.set(key, { data, timestamp: Date.now() });\n}\n\n/**\n * Make an HTTP request with error handling\n */\nasync function fetchJson<T>(url: string, description: string): Promise<T | null> {\n // Check cache first\n const cached = getCached<T>(url);\n if (cached !== null) {\n return cached;\n }\n\n try {\n const response = await fetch(url, {\n headers: {\n \"User-Agent\": \"Codex/1.0 (https://github.com/AshDevFr/codex; codex-plugin)\",\n Accept: \"application/json\",\n },\n });\n\n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = (await response.json()) as T;\n setCache(url, data);\n return data;\n } catch (error) {\n console.error(`[openlibrary] Failed to fetch ${description}:`, error);\n return null;\n }\n}\n\n/**\n * Normalize ISBN by removing hyphens and spaces\n */\nexport function normalizeIsbn(isbn: string): string {\n return isbn.replace(/[-\\s]/g, \"\").toUpperCase();\n}\n\n/**\n * Check if a string is a valid ISBN-10 or ISBN-13\n */\nexport function isValidIsbn(isbn: string): boolean {\n const normalized = normalizeIsbn(isbn);\n return normalized.length === 10 || normalized.length === 13;\n}\n\n/**\n * Fetch book edition by ISBN\n *\n * @param isbn ISBN-10 or ISBN-13\n * @returns Edition data or null if not found\n */\nexport async function getEditionByIsbn(isbn: string): Promise<OLEdition | null> {\n const normalized = normalizeIsbn(isbn);\n const url = `${BASE_URL}/isbn/${normalized}.json`;\n return fetchJson<OLEdition>(url, `edition by ISBN ${normalized}`);\n}\n\n/**\n * Fetch work details by key\n *\n * @param workKey Work key (e.g., \"/works/OL45883W\" or just \"OL45883W\")\n * @returns Work data or null if not found\n */\nexport async function getWork(workKey: string): Promise<OLWork | null> {\n // Normalize key to just the ID part\n const key = workKey.startsWith(\"/works/\") ? workKey : `/works/${workKey}`;\n const url = `${BASE_URL}${key}.json`;\n return fetchJson<OLWork>(url, `work ${key}`);\n}\n\n/**\n * Fetch editions for a work\n *\n * Returns editions directly associated with a work, ordered by most recent.\n * This is more reliable than searching by title, which can return unrelated books.\n *\n * @param workKey Work key (e.g., \"/works/OL45883W\")\n * @param limit Maximum number of editions to fetch\n * @returns Array of editions or empty array if none found\n */\nexport async function getWorkEditions(workKey: string, limit = 5): Promise<OLEdition[]> {\n const key = workKey.startsWith(\"/works/\") ? workKey : `/works/${workKey}`;\n const url = `${BASE_URL}${key}/editions.json?limit=${limit}`;\n const response = await fetchJson<OLWorkEditionsResponse>(url, `editions for ${key}`);\n return response?.entries || [];\n}\n\n/**\n * Fetch author details by key\n *\n * @param authorKey Author key (e.g., \"/authors/OL34184A\" or just \"OL34184A\")\n * @returns Author data or null if not found\n */\nexport async function getAuthor(authorKey: string): Promise<OLAuthor | null> {\n // Normalize key to just the ID part\n const key = authorKey.startsWith(\"/authors/\") ? authorKey : `/authors/${authorKey}`;\n const url = `${BASE_URL}${key}.json`;\n return fetchJson<OLAuthor>(url, `author ${key}`);\n}\n\n/** Fields to request from the Open Library search API */\nconst SEARCH_FIELDS = [\n \"key\",\n \"title\",\n \"subtitle\",\n \"author_name\",\n \"author_key\",\n \"first_publish_year\",\n \"publish_year\",\n \"publisher\",\n \"isbn\",\n \"number_of_pages_median\",\n \"cover_i\",\n \"cover_edition_key\",\n \"edition_count\",\n \"language\",\n \"subject\",\n \"ratings_average\",\n \"ratings_count\",\n].join(\",\");\n\n/**\n * Search for books\n *\n * When an author is provided, uses the `title` + `author` parameters for\n * more precise results. If that yields no results, falls back to a general\n * `q` search to ensure we still return something useful.\n *\n * @param query Search query (title, author, or combined)\n * @param options Additional search options\n * @returns Search results\n */\nexport async function searchBooks(\n query: string,\n options: {\n author?: string;\n limit?: number;\n } = {},\n): Promise<OLSearchResponse | null> {\n const { author, limit = 10 } = options;\n\n // When author is provided, try a refined title + author search first\n if (author) {\n const params = new URLSearchParams({\n title: query,\n author,\n fields: SEARCH_FIELDS,\n limit: String(limit),\n });\n\n const url = `${BASE_URL}/search.json?${params}`;\n const response = await fetchJson<OLSearchResponse>(\n url,\n `search title=\"${query}\" author=\"${author}\"`,\n );\n\n if (response?.docs?.length) {\n return response;\n }\n\n // Fall back to general q search if title+author yielded no results\n }\n\n // General search using q parameter\n const params = new URLSearchParams({\n q: query,\n fields: SEARCH_FIELDS,\n limit: String(limit),\n });\n\n if (author) {\n params.set(\"author\", author);\n }\n\n const url = `${BASE_URL}/search.json?${params}`;\n return fetchJson<OLSearchResponse>(url, `search \"${query}\"`);\n}\n\n/**\n * Get cover image URL by ISBN\n *\n * @param isbn ISBN-10 or ISBN-13\n * @param size Cover size: S (small ~50w), M (medium ~180w), L (large ~300w+)\n * @returns Cover URL\n */\nexport function getCoverUrlByIsbn(isbn: string, size: \"S\" | \"M\" | \"L\"): string {\n const normalized = normalizeIsbn(isbn);\n return `${COVERS_BASE_URL}/b/isbn/${normalized}-${size}.jpg`;\n}\n\n/**\n * Get cover image URL by cover ID\n *\n * @param coverId Open Library cover ID\n * @param size Cover size: S (small), M (medium), L (large)\n * @returns Cover URL\n */\nexport function getCoverUrlById(coverId: number, size: \"S\" | \"M\" | \"L\"): string {\n return `${COVERS_BASE_URL}/b/id/${coverId}-${size}.jpg`;\n}\n\n/**\n * Get cover image URL by Open Library ID (OLID)\n *\n * @param olid Open Library ID (e.g., \"OL7353617M\" for edition, \"OL45883W\" for work)\n * @param size Cover size: S (small), M (medium), L (large)\n * @returns Cover URL\n */\nexport function getCoverUrlByOlid(olid: string, size: \"S\" | \"M\" | \"L\"): string {\n // Strip any prefix if present\n const id = olid.replace(/^\\/(?:books|works)\\//, \"\");\n return `${COVERS_BASE_URL}/b/olid/${id}-${size}.jpg`;\n}\n\n/**\n * Parse year from Open Library date string\n *\n * Open Library dates can be in various formats:\n * - \"2020\"\n * - \"January 1, 2020\"\n * - \"2020-01-15\"\n * - \"c1985\"\n * - \"1985?\"\n *\n * @param dateStr Date string from Open Library\n * @returns Parsed year or undefined if unable to parse\n */\nexport function parseYear(dateStr: string | undefined): number | undefined {\n if (!dateStr) return undefined;\n\n // Try to extract a 4-digit year\n // Using (?:^|[^0-9]) to handle \"c1985\" format where there's no word boundary\n const match = dateStr.match(/(?:^|[^0-9])(1[89]\\d{2}|20\\d{2})(?:[^0-9]|$)/);\n if (match) {\n return Number.parseInt(match[1], 10);\n }\n\n return undefined;\n}\n\n/**\n * Parse description from Open Library\n *\n * Description can be either a string or an object with { type, value }.\n * Strips HTML tags and normalizes whitespace, since Open Library descriptions\n * can contain raw HTML (e.g., from Standard Ebooks imports).\n */\nexport function parseDescription(\n desc: string | { type?: string; value: string } | undefined,\n): string | undefined {\n if (!desc) return undefined;\n const raw = typeof desc === \"string\" ? desc : desc.value;\n return stripHtml(raw);\n}\n\n/**\n * Strip HTML tags from a string and normalize whitespace.\n *\n * Converts block-level tags (p, br, div, li) to newlines,\n * strips all remaining tags, decodes common HTML entities,\n * and collapses excessive whitespace.\n */\nfunction stripHtml(html: string): string | undefined {\n let text = html;\n\n // Convert block-level elements to newlines\n text = text.replace(/<\\/(p|div|li|tr|h[1-6])>/gi, \"\\n\");\n text = text.replace(/<br\\s*\\/?>/gi, \"\\n\");\n\n // Remove all remaining HTML tags\n text = text.replace(/<[^>]+>/g, \"\");\n\n // Decode common HTML entities\n text = text\n .replace(/&amp;/g, \"&\")\n .replace(/&lt;/g, \"<\")\n .replace(/&gt;/g, \">\")\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/&apos;/g, \"'\")\n .replace(/&nbsp;/g, \" \");\n\n // Collapse multiple spaces/tabs on the same line into one space\n text = text.replace(/[^\\S\\n]+/g, \" \");\n\n // Collapse 3+ consecutive newlines into 2\n text = text.replace(/\\n{3,}/g, \"\\n\\n\");\n\n // Trim each line and remove leading/trailing whitespace\n text = text\n .split(\"\\n\")\n .map((line) => line.trim())\n .join(\"\\n\")\n .trim();\n\n return text || undefined;\n}\n\n/**\n * Convert Open Library language code to BCP47\n *\n * Open Library uses format like \"/languages/eng\"\n *\n * @param langRef Language reference (e.g., \"/languages/eng\")\n * @returns BCP47 language code (e.g., \"en\")\n */\nexport function parseLanguage(langRef: string | undefined): string | undefined {\n if (!langRef) return undefined;\n\n // Extract language code from \"/languages/xxx\" format\n const match = langRef.match(/\\/languages\\/(\\w+)$/);\n if (!match) return undefined;\n\n const code = match[1].toLowerCase();\n\n // Map Open Library 3-letter codes to BCP47 2-letter codes\n const languageMap: Record<string, string> = {\n eng: \"en\",\n spa: \"es\",\n fre: \"fr\",\n fra: \"fr\",\n ger: \"de\",\n deu: \"de\",\n ita: \"it\",\n por: \"pt\",\n rus: \"ru\",\n jpn: \"ja\",\n chi: \"zh\",\n zho: \"zh\",\n kor: \"ko\",\n ara: \"ar\",\n hin: \"hi\",\n pol: \"pl\",\n tur: \"tr\",\n dut: \"nl\",\n nld: \"nl\",\n swe: \"sv\",\n nor: \"no\",\n dan: \"da\",\n fin: \"fi\",\n cze: \"cs\",\n ces: \"cs\",\n gre: \"el\",\n ell: \"el\",\n heb: \"he\",\n hun: \"hu\",\n rom: \"ro\",\n ron: \"ro\",\n tha: \"th\",\n vie: \"vi\",\n ind: \"id\",\n mal: \"ms\",\n msa: \"ms\",\n ukr: \"uk\",\n cat: \"ca\",\n lat: \"la\",\n };\n\n return languageMap[code] || code;\n}\n\n/**\n * Extract Open Library ID from a key\n *\n * @param key Full key (e.g., \"/works/OL45883W\" or \"/books/OL7353617M\")\n * @returns Just the ID (e.g., \"OL45883W\" or \"OL7353617M\")\n */\nexport function extractOlid(key: string): string {\n return key.replace(/^\\/(?:works|books|authors)\\//, \"\");\n}\n\n/**\n * Build Open Library URL from a key\n *\n * @param key Key (e.g., \"/works/OL45883W\")\n * @returns Full URL (e.g., \"https://openlibrary.org/works/OL45883W\")\n */\nexport function buildOpenLibraryUrl(key: string): string {\n return `${BASE_URL}${key.startsWith(\"/\") ? key : `/${key}`}`;\n}\n\n/**\n * Clear the cache\n */\nexport function clearCache(): void {\n cache.clear();\n}\n", null, null, null, null, "/**\n * Open Library Metadata Plugin for Codex\n *\n * Fetches book metadata from Open Library (openlibrary.org), a free and open\n * book database with extensive ISBN coverage.\n *\n * Features:\n * - ISBN lookup for direct, accurate matching\n * - Title/author search for fuzzy matching\n * - Cover image fetching in multiple sizes\n * - Author resolution with proper names\n * - Subject/genre extraction\n *\n * @see https://openlibrary.org/developers/api\n */\n\nimport {\n type BookMatchParams,\n type BookMetadataProvider,\n type BookSearchParams,\n createLogger,\n createMetadataPlugin,\n type InitializeParams,\n type MetadataGetParams,\n type MetadataMatchResponse,\n type MetadataSearchResponse,\n type PluginBookMetadata,\n} from \"@ashdev/codex-plugin-sdk\";\n\nimport { getEditionByIsbn, getWork, isValidIsbn, searchBooks } from \"./api.js\";\nimport { DEFAULT_MAX_RESULTS, manifest } from \"./manifest.js\";\nimport {\n getFullBookMetadata,\n mapEditionToBookMetadata,\n mapSearchDocToSearchResult,\n} from \"./mapper.js\";\n\nconst logger = createLogger({ name: \"openlibrary\", level: \"info\" });\n\n// Plugin configuration (set during initialization)\nconst config = {\n maxResults: DEFAULT_MAX_RESULTS,\n};\n\n/**\n * Book metadata provider implementation\n */\nconst bookProvider: BookMetadataProvider = {\n /**\n * Search for books by ISBN or title/author query\n *\n * If ISBN is provided, it takes priority for direct lookup.\n * Otherwise, falls back to title/author search.\n */\n async search(params: BookSearchParams): Promise<MetadataSearchResponse> {\n const { isbn, query, author, limit } = params;\n const maxResults = Math.min(limit || config.maxResults, 50);\n\n // If ISBN is provided, try direct lookup first\n if (isbn && isValidIsbn(isbn)) {\n const edition = await getEditionByIsbn(isbn);\n\n if (edition) {\n // Found by ISBN - return as single result with high relevance\n const workKey = edition.works?.[0]?.key;\n const workData = workKey ? await getWork(workKey) : null;\n const metadata = await mapEditionToBookMetadata(edition, workData);\n\n return {\n results: [\n {\n externalId: metadata.externalId,\n title: metadata.title || \"Unknown\",\n alternateTitles: metadata.subtitle ? [metadata.subtitle] : [],\n year: metadata.year,\n coverUrl: metadata.coverUrl,\n relevanceScore: 1.0, // Perfect match by ISBN\n preview: {\n genres: metadata.subjects.slice(0, 5),\n authors: metadata.authors.map((a) => a.name),\n },\n },\n ],\n };\n }\n\n // ISBN not found, fall through to search if query is also provided\n if (!query) {\n return { results: [] };\n }\n }\n\n // Title/author search\n if (!query) {\n return { results: [] };\n }\n\n const searchResponse = await searchBooks(query, {\n author,\n limit: maxResults,\n });\n\n if (!searchResponse?.docs?.length) {\n return { results: [] };\n }\n\n return {\n results: searchResponse.docs.map(mapSearchDocToSearchResult),\n };\n },\n\n /**\n * Get full book metadata by external ID\n *\n * The external ID can be:\n * - A work key: \"/works/OL45883W\"\n * - An edition key: \"/books/OL7353617M\"\n */\n async get(params: MetadataGetParams): Promise<PluginBookMetadata> {\n const { externalId } = params;\n\n // Try to get full metadata\n const metadata = await getFullBookMetadata(externalId);\n\n if (metadata) {\n return metadata;\n }\n\n // Fallback: return minimal metadata\n return {\n externalId,\n externalUrl: `https://openlibrary.org${externalId.startsWith(\"/\") ? externalId : `/${externalId}`}`,\n alternateTitles: [],\n isbns: [],\n genres: [],\n tags: [],\n subjects: [],\n authors: [],\n artists: [],\n covers: [],\n externalRatings: [],\n awards: [],\n externalLinks: [\n {\n url: `https://openlibrary.org${externalId.startsWith(\"/\") ? externalId : `/${externalId}`}`,\n label: \"Open Library\",\n linkType: \"provider\",\n },\n ],\n };\n },\n\n /**\n * Auto-match a book using available identifiers\n *\n * Match priority:\n * 1. ISBN (if provided) - highest confidence\n * 2. Title + author search - lower confidence\n */\n async match(params: BookMatchParams): Promise<MetadataMatchResponse> {\n const { title, authors, isbn, year } = params;\n\n // Try ISBN first if available\n if (isbn && isValidIsbn(isbn)) {\n const edition = await getEditionByIsbn(isbn);\n\n if (edition) {\n const workKey = edition.works?.[0]?.key;\n const workData = workKey ? await getWork(workKey) : null;\n const metadata = await mapEditionToBookMetadata(edition, workData);\n\n return {\n match: {\n externalId: metadata.externalId,\n title: metadata.title || \"Unknown\",\n alternateTitles: metadata.subtitle ? [metadata.subtitle] : [],\n year: metadata.year,\n coverUrl: metadata.coverUrl,\n relevanceScore: 1.0,\n preview: {\n genres: metadata.subjects.slice(0, 5),\n authors: metadata.authors.map((a) => a.name),\n },\n },\n confidence: 0.99, // Very high confidence for ISBN match\n alternatives: [],\n };\n }\n }\n\n // Fall back to title search\n const searchQuery = authors?.length ? `${title} ${authors[0]}` : title;\n\n const searchResponse = await searchBooks(searchQuery, {\n limit: 5,\n });\n\n if (!searchResponse?.docs?.length) {\n return {\n match: null,\n confidence: 0,\n alternatives: [],\n };\n }\n\n const results = searchResponse.docs.map(mapSearchDocToSearchResult);\n\n // Calculate confidence based on title similarity and other factors\n const bestMatch = results[0];\n let confidence = bestMatch.relevanceScore || 0.5;\n\n // Boost confidence if title matches closely\n const normalizedTitle = title.toLowerCase().trim();\n const normalizedMatchTitle = bestMatch.title.toLowerCase().trim();\n\n if (normalizedTitle === normalizedMatchTitle) {\n confidence = Math.min(1.0, confidence + 0.3);\n } else if (\n normalizedMatchTitle.includes(normalizedTitle) ||\n normalizedTitle.includes(normalizedMatchTitle)\n ) {\n confidence = Math.min(1.0, confidence + 0.15);\n }\n\n // Boost if year matches\n if (year && bestMatch.year === year) {\n confidence = Math.min(1.0, confidence + 0.1);\n }\n\n // Reduce confidence without ISBN\n confidence = Math.min(confidence, 0.85);\n\n return {\n match: bestMatch,\n confidence,\n alternatives: results.slice(1),\n };\n },\n};\n\n// =============================================================================\n// Plugin Initialization\n// =============================================================================\n\ncreateMetadataPlugin({\n manifest,\n bookProvider,\n logLevel: \"info\",\n onInitialize(params: InitializeParams) {\n // Read config from initialization params\n const maxResults = params.config?.maxResults as number | undefined;\n if (maxResults !== undefined) {\n config.maxResults = Math.min(Math.max(1, maxResults), 50); // Clamp 1-50\n }\n logger.info(`Plugin initialized (maxResults: ${config.maxResults})`);\n },\n});\n\nlogger.info(\"Open Library plugin started\");\n", "{\n \"name\": \"@ashdev/codex-plugin-metadata-openlibrary\",\n \"version\": \"1.8.1\",\n \"description\": \"Open Library metadata plugin for Codex - fetches book metadata by ISBN or title search\",\n \"main\": \"dist/index.js\",\n \"bin\": \"dist/index.js\",\n \"type\": \"module\",\n \"files\": [\n \"dist\",\n \"README.md\"\n ],\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/AshDevFr/codex.git\",\n \"directory\": \"plugins/metadata-openlibrary\"\n },\n \"scripts\": {\n \"build\": \"esbuild src/index.ts --bundle --platform=node --target=node22 --format=esm --outfile=dist/index.js --sourcemap --banner:js='#!/usr/bin/env node'\",\n \"dev\": \"npm run build -- --watch\",\n \"clean\": \"rm -rf dist\",\n \"start\": \"node dist/index.js\",\n \"lint\": \"biome check .\",\n \"lint:fix\": \"biome check --write .\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"prepublishOnly\": \"npm run lint && npm run build\"\n },\n \"keywords\": [\n \"codex\",\n \"plugin\",\n \"openlibrary\",\n \"metadata\",\n \"books\",\n \"isbn\"\n ],\n \"author\": \"Codex\",\n \"license\": \"MIT\",\n \"engines\": {\n \"node\": \">=22.0.0\"\n },\n \"dependencies\": {\n \"@ashdev/codex-plugin-sdk\": \"^1.8.1\"\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"^2.3.13\",\n \"@types/node\": \"^22.0.0\",\n \"esbuild\": \"^0.24.0\",\n \"typescript\": \"^5.7.0\",\n \"vitest\": \"^3.0.0\"\n }\n}\n", "import type { MetadataContentType, PluginManifest } from \"@ashdev/codex-plugin-sdk\";\nimport packageJson from \"../package.json\" with { type: \"json\" };\n\n// Default config values\nexport const DEFAULT_MAX_RESULTS = 10;\n\nexport const manifest = {\n name: \"metadata-openlibrary\",\n displayName: \"Open Library\",\n version: packageJson.version,\n description:\n \"Fetches book metadata from Open Library (openlibrary.org). Supports ISBN lookup and title search for EPUBs, PDFs, and other book formats.\",\n author: \"Codex\",\n homepage: \"https://openlibrary.org\",\n protocolVersion: \"1.0\",\n capabilities: {\n // Book metadata provider only (not series)\n metadataProvider: [\"book\"] as MetadataContentType[],\n },\n configSchema: {\n description: \"Configuration options for the Open Library plugin\",\n fields: [\n {\n key: \"maxResults\",\n label: \"Maximum Results\",\n description: \"Maximum number of results to return for search queries (1-50)\",\n type: \"number\" as const,\n required: false,\n default: DEFAULT_MAX_RESULTS,\n example: 20,\n },\n ],\n },\n} as const satisfies PluginManifest & {\n capabilities: { metadataProvider: MetadataContentType[] };\n};\n", "/**\n * Mapper functions to convert Open Library data to Codex plugin format\n */\n\nimport type {\n BookAuthor,\n BookCover,\n ExternalLink,\n ExternalRating,\n PluginBookMetadata,\n SearchResult,\n} from \"@ashdev/codex-plugin-sdk\";\n\nimport {\n buildOpenLibraryUrl,\n getAuthor,\n getCoverUrlById,\n getCoverUrlByIsbn,\n getWork,\n getWorkEditions,\n parseDescription,\n parseLanguage,\n parseYear,\n} from \"./api.js\";\nimport type { OLAuthorReference, OLEdition, OLSearchDoc, OLWork, ParsedAuthor } from \"./types.js\";\n\n/**\n * Map Open Library search result to Codex SearchResult\n */\nexport function mapSearchDocToSearchResult(doc: OLSearchDoc): SearchResult {\n const year = doc.first_publish_year;\n const coverUrl = doc.cover_i ? getCoverUrlById(doc.cover_i, \"M\") : undefined;\n\n // Calculate a relevance score based on available data\n // More complete entries get higher scores\n let relevanceScore = 0.5;\n if (doc.author_name?.length) relevanceScore += 0.1;\n if (doc.isbn?.length) relevanceScore += 0.15;\n if (doc.cover_i) relevanceScore += 0.1;\n if (doc.first_publish_year) relevanceScore += 0.05;\n if (doc.subject?.length) relevanceScore += 0.05;\n if (doc.ratings_count && doc.ratings_count > 0) relevanceScore += 0.05;\n\n return {\n externalId: doc.key, // Work key, e.g., \"/works/OL45883W\"\n title: doc.title,\n alternateTitles: doc.subtitle ? [doc.subtitle] : [],\n year,\n coverUrl,\n relevanceScore: Math.min(1.0, relevanceScore),\n preview: {\n genres: doc.subject?.slice(0, 5) || [],\n rating: doc.ratings_average\n ? Math.round(doc.ratings_average * 2) / 2 // Normalize to 0-10 scale (OL uses 1-5)\n : undefined,\n authors: doc.author_name?.slice(0, 3) || [],\n description: doc.publisher?.length ? `Published by ${doc.publisher[0]}` : undefined,\n },\n };\n}\n\n/**\n * Resolve author references to full author data\n */\nasync function resolveAuthors(\n authorRefs: OLAuthorReference[] | undefined,\n): Promise<ParsedAuthor[]> {\n if (!authorRefs?.length) return [];\n\n const authors: ParsedAuthor[] = [];\n\n for (const ref of authorRefs) {\n const key = ref.author?.key || ref.key;\n if (!key) continue;\n\n const authorData = await getAuthor(key);\n if (authorData) {\n authors.push({\n name: authorData.name,\n key,\n sortName: authorData.personal_name || undefined,\n });\n }\n }\n\n return authors;\n}\n\n/**\n * Map parsed authors to BookAuthor format\n */\nfunction mapToBookAuthors(authors: ParsedAuthor[]): BookAuthor[] {\n return authors.map((author) => ({\n name: author.name,\n role: \"author\" as const,\n sortName: author.sortName,\n }));\n}\n\n/**\n * Build cover URLs for a book\n */\nfunction buildCoverUrls(isbn: string | undefined, coverId: number | undefined): BookCover[] {\n const covers: BookCover[] = [];\n\n // Prefer ISBN-based URLs as they're more reliable\n if (isbn) {\n covers.push({\n url: getCoverUrlByIsbn(isbn, \"S\"),\n size: \"small\",\n });\n covers.push({\n url: getCoverUrlByIsbn(isbn, \"M\"),\n size: \"medium\",\n });\n covers.push({\n url: getCoverUrlByIsbn(isbn, \"L\"),\n size: \"large\",\n });\n } else if (coverId) {\n // Fallback to cover ID\n covers.push({\n url: getCoverUrlById(coverId, \"S\"),\n size: \"small\",\n });\n covers.push({\n url: getCoverUrlById(coverId, \"M\"),\n size: \"medium\",\n });\n covers.push({\n url: getCoverUrlById(coverId, \"L\"),\n size: \"large\",\n });\n }\n\n return covers;\n}\n\n/**\n * Build external links for Open Library book\n */\nfunction buildExternalLinks(editionKey: string, workKey: string | undefined): ExternalLink[] {\n const links: ExternalLink[] = [\n {\n url: buildOpenLibraryUrl(editionKey),\n label: \"Open Library (Edition)\",\n linkType: \"provider\",\n },\n ];\n\n if (workKey) {\n links.push({\n url: buildOpenLibraryUrl(workKey),\n label: \"Open Library (Work)\",\n linkType: \"provider\",\n });\n }\n\n return links;\n}\n\n/**\n * Get all ISBNs from edition data\n */\nfunction collectIsbns(edition: OLEdition): string[] {\n const isbns: string[] = [];\n\n // Prefer ISBN-13\n if (edition.isbn_13?.length) {\n isbns.push(...edition.isbn_13);\n }\n\n // Add ISBN-10 as well\n if (edition.isbn_10?.length) {\n isbns.push(...edition.isbn_10);\n }\n\n return [...new Set(isbns)]; // Deduplicate\n}\n\n/**\n * Map Open Library edition and optional work to full book metadata\n */\nexport async function mapEditionToBookMetadata(\n edition: OLEdition,\n workData?: OLWork | null,\n): Promise<PluginBookMetadata> {\n // Resolve authors from edition or work\n const authorRefs = edition.authors || workData?.authors;\n const authors = await resolveAuthors(authorRefs);\n\n // Get ISBNs\n const isbns = collectIsbns(edition);\n const primaryIsbn = isbns[0];\n\n // Get cover ID from edition or work\n const coverId = edition.covers?.[0] || workData?.covers?.[0];\n\n // Get description from edition or work\n const description =\n parseDescription(edition.description) || parseDescription(workData?.description);\n\n // Get subjects from both edition and work\n const subjects = [...(edition.subjects || []), ...(workData?.subjects || [])];\n const uniqueSubjects = [...new Set(subjects)];\n\n // Parse year\n const year = parseYear(edition.publish_date);\n const originalYear = parseYear(workData?.first_publish_date);\n\n // Parse language\n const language = parseLanguage(edition.languages?.[0]?.key);\n\n // Build external rating if ratings exist from search\n const externalRatings: ExternalRating[] = [];\n\n // Build metadata\n const workKey = edition.works?.[0]?.key || workData?.key;\n const externalId = workKey || edition.key;\n\n return {\n externalId,\n externalUrl: buildOpenLibraryUrl(externalId),\n\n // Core fields\n title: edition.title,\n subtitle: edition.subtitle || workData?.subtitle,\n alternateTitles: [],\n summary: description,\n bookType: detectBookType(edition),\n\n // Book-specific fields\n pageCount: edition.number_of_pages,\n year,\n\n // ISBN\n isbn: primaryIsbn,\n isbns,\n\n // Edition info\n edition: edition.edition_name,\n originalTitle: workData?.title !== edition.title ? workData?.title : undefined,\n originalYear,\n language,\n\n // Taxonomy\n genres: [], // Open Library doesn't have genres, just subjects\n tags: [],\n subjects: uniqueSubjects.slice(0, 20), // Limit to 20 subjects\n\n // Credits\n authors: mapToBookAuthors(authors),\n artists: [], // Open Library doesn't track artists separately\n publisher: edition.publishers?.[0],\n\n // Media\n coverUrl: primaryIsbn\n ? getCoverUrlByIsbn(primaryIsbn, \"L\")\n : coverId\n ? getCoverUrlById(coverId, \"L\")\n : undefined,\n covers: buildCoverUrls(primaryIsbn, coverId),\n\n // Rating\n externalRatings,\n awards: [],\n\n // Links\n externalLinks: buildExternalLinks(edition.key, workKey),\n };\n}\n\n/**\n * Detect book type from edition data\n *\n * Open Library doesn't have explicit book type, but we can infer from:\n * - physical_format field\n * - subjects\n * - other metadata\n */\nfunction detectBookType(edition: OLEdition): string | undefined {\n const format = edition.physical_format?.toLowerCase();\n\n if (format) {\n if (format.includes(\"comic\") || format.includes(\"graphic novel\")) {\n return \"graphic_novel\";\n }\n if (format.includes(\"manga\")) {\n return \"manga\";\n }\n if (format.includes(\"magazine\") || format.includes(\"periodical\")) {\n return \"magazine\";\n }\n }\n\n // Check subjects for hints\n const subjects = (edition.subjects || []).join(\" \").toLowerCase();\n\n if (subjects.includes(\"graphic novel\") || subjects.includes(\"comics\")) {\n return \"graphic_novel\";\n }\n if (subjects.includes(\"manga\")) {\n return \"manga\";\n }\n\n // Default to novel for most books\n return \"novel\";\n}\n\n/**\n * Map Open Library search doc to book metadata for quick preview\n *\n * This is a lighter version that doesn't fetch additional data\n */\nexport function mapSearchDocToBookPreview(doc: OLSearchDoc): PluginBookMetadata {\n const isbns = doc.isbn?.slice(0, 5) || [];\n const primaryIsbn = isbns[0];\n const coverId = doc.cover_i;\n\n return {\n externalId: doc.key,\n externalUrl: buildOpenLibraryUrl(doc.key),\n\n // Core fields\n title: doc.title,\n subtitle: doc.subtitle,\n alternateTitles: [],\n summary: undefined, // Not available in search results\n\n // Book-specific fields\n pageCount: doc.number_of_pages_median,\n year: doc.first_publish_year,\n\n // ISBN\n isbn: primaryIsbn,\n isbns,\n\n // Taxonomy\n genres: [],\n tags: [],\n subjects: doc.subject?.slice(0, 10) || [],\n\n // Credits\n authors:\n doc.author_name?.map((name) => ({\n name,\n role: \"author\" as const,\n })) || [],\n artists: [],\n publisher: doc.publisher?.[0],\n\n // Media\n coverUrl: primaryIsbn\n ? getCoverUrlByIsbn(primaryIsbn, \"L\")\n : coverId\n ? getCoverUrlById(coverId, \"L\")\n : undefined,\n covers: buildCoverUrls(primaryIsbn, coverId),\n\n // Rating\n rating: doc.ratings_average\n ? {\n score: Math.round(doc.ratings_average * 20), // Convert 1-5 to 0-100\n voteCount: doc.ratings_count,\n source: \"openlibrary\",\n }\n : undefined,\n externalRatings:\n doc.ratings_average && doc.ratings_count\n ? [\n {\n score: Math.round(doc.ratings_average * 20),\n voteCount: doc.ratings_count,\n source: \"openlibrary\",\n },\n ]\n : [],\n awards: [],\n\n // Links\n externalLinks: [\n {\n url: buildOpenLibraryUrl(doc.key),\n label: \"Open Library\",\n linkType: \"provider\",\n },\n ],\n };\n}\n\n/**\n * Get full book metadata by fetching edition, work, and author data\n *\n * @param editionOrWorkKey Either an edition key or work key\n * @param isbn Optional ISBN for direct lookup\n */\nexport async function getFullBookMetadata(\n editionOrWorkKey: string,\n isbn?: string,\n): Promise<PluginBookMetadata | null> {\n // If we have an ISBN, try to get edition directly\n if (isbn) {\n const { getEditionByIsbn } = await import(\"./api.js\");\n const edition = await getEditionByIsbn(isbn);\n if (edition) {\n const workKey = edition.works?.[0]?.key;\n const workData = workKey ? await getWork(workKey) : null;\n return mapEditionToBookMetadata(edition, workData);\n }\n }\n\n // Check if it's a work key\n if (editionOrWorkKey.includes(\"/works/\")) {\n const workData = await getWork(editionOrWorkKey);\n if (!workData) return null;\n\n // Fetch editions directly from the work using the editions API.\n // This is much more reliable than searching by title, which can\n // return completely unrelated books with similar titles.\n const editions = await getWorkEditions(editionOrWorkKey, 5);\n\n if (editions.length > 0) {\n // Prefer an edition that has ISBNs for richer metadata\n const editionWithIsbn = editions.find((e) => e.isbn_13?.length || e.isbn_10?.length);\n const edition = editionWithIsbn || editions[0];\n return mapEditionToBookMetadata(edition, workData);\n }\n\n // Fallback: create metadata from work data only\n const authors = await resolveAuthors(workData.authors);\n const coverId = workData.covers?.[0];\n\n return {\n externalId: workData.key,\n externalUrl: buildOpenLibraryUrl(workData.key),\n title: workData.title,\n subtitle: workData.subtitle,\n alternateTitles: [],\n summary: parseDescription(workData.description),\n isbns: [],\n genres: [],\n tags: [],\n subjects: workData.subjects?.slice(0, 20) || [],\n authors: mapToBookAuthors(authors),\n artists: [],\n coverUrl: coverId ? getCoverUrlById(coverId, \"L\") : undefined,\n covers: coverId\n ? [\n { url: getCoverUrlById(coverId, \"S\"), size: \"small\" },\n { url: getCoverUrlById(coverId, \"M\"), size: \"medium\" },\n { url: getCoverUrlById(coverId, \"L\"), size: \"large\" },\n ]\n : [],\n externalRatings: [],\n awards: [],\n externalLinks: [\n {\n url: buildOpenLibraryUrl(workData.key),\n label: \"Open Library\",\n linkType: \"provider\",\n },\n ],\n };\n }\n\n // It's an edition key - fetch directly\n // For edition keys, we need to use a different approach\n // since there's no direct edition endpoint by key\n // Try to use the key directly\n const url = `https://openlibrary.org${editionOrWorkKey}.json`;\n try {\n const response = await fetch(url, {\n headers: {\n \"User-Agent\": \"Codex/1.0 (https://github.com/AshDevFr/codex; codex-plugin)\",\n Accept: \"application/json\",\n },\n });\n\n if (response.ok) {\n const edition = (await response.json()) as OLEdition;\n const workKey = edition.works?.[0]?.key;\n const workData = workKey ? await getWork(workKey) : null;\n return mapEditionToBookMetadata(edition, workData);\n }\n } catch {\n // Ignore fetch errors\n }\n\n return null;\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["params", "url", "manifest", "bookProvider", "logger", "manifest", "bookProvider", "logger", "getEditionByIsbn"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ashdev/codex-plugin-metadata-openlibrary",
3
- "version": "1.0.0",
3
+ "version": "1.8.1",
4
4
  "description": "Open Library metadata plugin for Codex - fetches book metadata by ISBN or title search",
5
5
  "main": "dist/index.js",
6
6
  "bin": "dist/index.js",
@@ -40,7 +40,7 @@
40
40
  "node": ">=22.0.0"
41
41
  },
42
42
  "dependencies": {
43
- "@ashdev/codex-plugin-sdk": "file:../sdk-typescript"
43
+ "@ashdev/codex-plugin-sdk": "^1.8.1"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@biomejs/biome": "^2.3.13",