@ncukondo/reference-manager 0.1.0 → 0.3.0

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.
Files changed (116) hide show
  1. package/README.md +40 -0
  2. package/dist/chunks/detector-DHztTaFY.js +619 -0
  3. package/dist/chunks/detector-DHztTaFY.js.map +1 -0
  4. package/dist/chunks/{detector-BF8Mcc72.js → loader-mQ25o6cV.js} +303 -664
  5. package/dist/chunks/loader-mQ25o6cV.js.map +1 -0
  6. package/dist/chunks/search-Be9vzUIH.js +29541 -0
  7. package/dist/chunks/search-Be9vzUIH.js.map +1 -0
  8. package/dist/cli/commands/add.d.ts +44 -16
  9. package/dist/cli/commands/add.d.ts.map +1 -1
  10. package/dist/cli/commands/cite.d.ts +49 -0
  11. package/dist/cli/commands/cite.d.ts.map +1 -0
  12. package/dist/cli/commands/fulltext.d.ts +101 -0
  13. package/dist/cli/commands/fulltext.d.ts.map +1 -0
  14. package/dist/cli/commands/index.d.ts +14 -10
  15. package/dist/cli/commands/index.d.ts.map +1 -1
  16. package/dist/cli/commands/list.d.ts +23 -6
  17. package/dist/cli/commands/list.d.ts.map +1 -1
  18. package/dist/cli/commands/remove.d.ts +47 -12
  19. package/dist/cli/commands/remove.d.ts.map +1 -1
  20. package/dist/cli/commands/search.d.ts +24 -7
  21. package/dist/cli/commands/search.d.ts.map +1 -1
  22. package/dist/cli/commands/update.d.ts +26 -13
  23. package/dist/cli/commands/update.d.ts.map +1 -1
  24. package/dist/cli/execution-context.d.ts +60 -0
  25. package/dist/cli/execution-context.d.ts.map +1 -0
  26. package/dist/cli/helpers.d.ts +18 -0
  27. package/dist/cli/helpers.d.ts.map +1 -1
  28. package/dist/cli/index.d.ts.map +1 -1
  29. package/dist/cli/server-client.d.ts +73 -10
  30. package/dist/cli/server-client.d.ts.map +1 -1
  31. package/dist/cli.js +1200 -528
  32. package/dist/cli.js.map +1 -1
  33. package/dist/config/csl-styles.d.ts +83 -0
  34. package/dist/config/csl-styles.d.ts.map +1 -0
  35. package/dist/config/defaults.d.ts +10 -0
  36. package/dist/config/defaults.d.ts.map +1 -1
  37. package/dist/config/loader.d.ts.map +1 -1
  38. package/dist/config/schema.d.ts +84 -0
  39. package/dist/config/schema.d.ts.map +1 -1
  40. package/dist/core/csl-json/types.d.ts +15 -3
  41. package/dist/core/csl-json/types.d.ts.map +1 -1
  42. package/dist/core/library.d.ts +60 -0
  43. package/dist/core/library.d.ts.map +1 -1
  44. package/dist/features/format/bibtex.d.ts +6 -0
  45. package/dist/features/format/bibtex.d.ts.map +1 -0
  46. package/dist/features/format/citation-csl.d.ts +41 -0
  47. package/dist/features/format/citation-csl.d.ts.map +1 -0
  48. package/dist/features/format/citation-fallback.d.ts +24 -0
  49. package/dist/features/format/citation-fallback.d.ts.map +1 -0
  50. package/dist/features/format/index.d.ts +10 -0
  51. package/dist/features/format/index.d.ts.map +1 -0
  52. package/dist/features/format/json.d.ts +6 -0
  53. package/dist/features/format/json.d.ts.map +1 -0
  54. package/dist/features/format/pretty.d.ts +6 -0
  55. package/dist/features/format/pretty.d.ts.map +1 -0
  56. package/dist/features/fulltext/filename.d.ts +17 -0
  57. package/dist/features/fulltext/filename.d.ts.map +1 -0
  58. package/dist/features/fulltext/index.d.ts +7 -0
  59. package/dist/features/fulltext/index.d.ts.map +1 -0
  60. package/dist/features/fulltext/manager.d.ts +109 -0
  61. package/dist/features/fulltext/manager.d.ts.map +1 -0
  62. package/dist/features/fulltext/types.d.ts +12 -0
  63. package/dist/features/fulltext/types.d.ts.map +1 -0
  64. package/dist/features/import/cache.d.ts +37 -0
  65. package/dist/features/import/cache.d.ts.map +1 -0
  66. package/dist/features/import/detector.d.ts +42 -0
  67. package/dist/features/import/detector.d.ts.map +1 -0
  68. package/dist/features/import/fetcher.d.ts +49 -0
  69. package/dist/features/import/fetcher.d.ts.map +1 -0
  70. package/dist/features/import/importer.d.ts +61 -0
  71. package/dist/features/import/importer.d.ts.map +1 -0
  72. package/dist/features/import/index.d.ts +8 -0
  73. package/dist/features/import/index.d.ts.map +1 -0
  74. package/dist/features/import/normalizer.d.ts +15 -0
  75. package/dist/features/import/normalizer.d.ts.map +1 -0
  76. package/dist/features/import/parser.d.ts +33 -0
  77. package/dist/features/import/parser.d.ts.map +1 -0
  78. package/dist/features/import/rate-limiter.d.ts +45 -0
  79. package/dist/features/import/rate-limiter.d.ts.map +1 -0
  80. package/dist/features/operations/add.d.ts +65 -0
  81. package/dist/features/operations/add.d.ts.map +1 -0
  82. package/dist/features/operations/cite.d.ts +48 -0
  83. package/dist/features/operations/cite.d.ts.map +1 -0
  84. package/dist/features/operations/list.d.ts +28 -0
  85. package/dist/features/operations/list.d.ts.map +1 -0
  86. package/dist/features/operations/remove.d.ts +29 -0
  87. package/dist/features/operations/remove.d.ts.map +1 -0
  88. package/dist/features/operations/search.d.ts +30 -0
  89. package/dist/features/operations/search.d.ts.map +1 -0
  90. package/dist/features/operations/update.d.ts +39 -0
  91. package/dist/features/operations/update.d.ts.map +1 -0
  92. package/dist/index.js +18 -16
  93. package/dist/index.js.map +1 -1
  94. package/dist/server/index.d.ts +3 -1
  95. package/dist/server/index.d.ts.map +1 -1
  96. package/dist/server/routes/add.d.ts +11 -0
  97. package/dist/server/routes/add.d.ts.map +1 -0
  98. package/dist/server/routes/cite.d.ts +9 -0
  99. package/dist/server/routes/cite.d.ts.map +1 -0
  100. package/dist/server/routes/list.d.ts +25 -0
  101. package/dist/server/routes/list.d.ts.map +1 -0
  102. package/dist/server/routes/references.d.ts.map +1 -1
  103. package/dist/server/routes/search.d.ts +26 -0
  104. package/dist/server/routes/search.d.ts.map +1 -0
  105. package/dist/server.js +215 -32
  106. package/dist/server.js.map +1 -1
  107. package/package.json +15 -4
  108. package/dist/chunks/detector-BF8Mcc72.js.map +0 -1
  109. package/dist/cli/output/bibtex.d.ts +0 -6
  110. package/dist/cli/output/bibtex.d.ts.map +0 -1
  111. package/dist/cli/output/index.d.ts +0 -7
  112. package/dist/cli/output/index.d.ts.map +0 -1
  113. package/dist/cli/output/json.d.ts +0 -6
  114. package/dist/cli/output/json.d.ts.map +0 -1
  115. package/dist/cli/output/pretty.d.ts +0 -6
  116. package/dist/cli/output/pretty.d.ts.map +0 -1
@@ -1,10 +1,11 @@
1
1
  import { randomUUID, createHash } from "node:crypto";
2
2
  import { createReadStream, existsSync, readFileSync } from "node:fs";
3
3
  import { readFile, mkdir, writeFile } from "node:fs/promises";
4
- import { z } from "zod";
4
+ import { C as CslLibrarySchema } from "./detector-DHztTaFY.js";
5
5
  import { dirname, join } from "node:path";
6
+ import { homedir, tmpdir } from "node:os";
6
7
  import { parse } from "@iarna/toml";
7
- import { tmpdir, homedir } from "node:os";
8
+ import { z } from "zod";
8
9
  function normalizeText(text) {
9
10
  return text.toLowerCase().replace(/\s+/g, "_").replace(/[^a-z0-9_]/g, "").replace(/_+/g, "_").replace(/^_|_$/g, "");
10
11
  }
@@ -32,7 +33,7 @@ function extractAuthorName(item) {
32
33
  }
33
34
  return "";
34
35
  }
35
- function extractYear$3(item) {
36
+ function extractYear(item) {
36
37
  if (!item.issued || !item.issued["date-parts"] || item.issued["date-parts"].length === 0) {
37
38
  return "";
38
39
  }
@@ -57,7 +58,7 @@ function determineTitlePart(hasAuthor, hasYear, title) {
57
58
  }
58
59
  function generateId(item) {
59
60
  const author = extractAuthorName(item);
60
- const year = extractYear$3(item);
61
+ const year = extractYear(item);
61
62
  const title = item.title ? normalizeTitleSlug(item.title) : "";
62
63
  const authorPart = author || "anon";
63
64
  const yearPart = year || "nd";
@@ -295,54 +296,6 @@ async function computeFileHash(filePath) {
295
296
  stream.on("error", (error) => reject(error));
296
297
  });
297
298
  }
298
- const CslNameSchema = z.object({
299
- family: z.string().optional(),
300
- given: z.string().optional(),
301
- literal: z.string().optional(),
302
- "dropping-particle": z.string().optional(),
303
- "non-dropping-particle": z.string().optional(),
304
- suffix: z.string().optional()
305
- });
306
- const CslDateSchema = z.object({
307
- "date-parts": z.array(z.array(z.number())).optional(),
308
- raw: z.string().optional(),
309
- season: z.string().optional(),
310
- circa: z.boolean().optional(),
311
- literal: z.string().optional()
312
- });
313
- const CslCustomSchema = z.object({
314
- uuid: z.string(),
315
- created_at: z.string(),
316
- timestamp: z.string(),
317
- additional_urls: z.array(z.string()).optional()
318
- });
319
- const CslItemSchema = z.object({
320
- id: z.string(),
321
- type: z.string(),
322
- title: z.string().optional(),
323
- author: z.array(CslNameSchema).optional(),
324
- editor: z.array(CslNameSchema).optional(),
325
- issued: CslDateSchema.optional(),
326
- accessed: CslDateSchema.optional(),
327
- "container-title": z.string().optional(),
328
- volume: z.string().optional(),
329
- issue: z.string().optional(),
330
- page: z.string().optional(),
331
- DOI: z.string().optional(),
332
- PMID: z.string().optional(),
333
- PMCID: z.string().optional(),
334
- ISBN: z.string().optional(),
335
- ISSN: z.string().optional(),
336
- URL: z.string().optional(),
337
- abstract: z.string().optional(),
338
- publisher: z.string().optional(),
339
- "publisher-place": z.string().optional(),
340
- note: z.string().optional(),
341
- keyword: z.array(z.string()).optional(),
342
- custom: CslCustomSchema.optional()
343
- // Allow additional fields
344
- }).passthrough();
345
- const CslLibrarySchema = z.array(CslItemSchema);
346
299
  function parseKeyword(keyword) {
347
300
  if (typeof keyword !== "string") {
348
301
  return void 0;
@@ -478,6 +431,32 @@ class Library {
478
431
  }
479
432
  return this.removeReference(ref);
480
433
  }
434
+ /**
435
+ * Update a reference by UUID.
436
+ * @param uuid - The UUID of the reference to update
437
+ * @param updates - Partial updates to apply to the reference
438
+ * @returns true if the reference was found and updated, false otherwise
439
+ */
440
+ updateByUuid(uuid, updates, options = {}) {
441
+ const ref = this.uuidIndex.get(uuid);
442
+ if (!ref) {
443
+ return { updated: false };
444
+ }
445
+ return this.updateReference(ref, updates, options);
446
+ }
447
+ /**
448
+ * Update a reference by ID.
449
+ * @param id - The ID of the reference to update
450
+ * @param updates - Partial updates to apply to the reference
451
+ * @returns true if the reference was found and updated, false otherwise
452
+ */
453
+ updateById(id, updates, options = {}) {
454
+ const ref = this.idIndex.get(id);
455
+ if (!ref) {
456
+ return { updated: false };
457
+ }
458
+ return this.updateReference(ref, updates, options);
459
+ }
481
460
  /**
482
461
  * Find a reference by UUID
483
462
  */
@@ -545,6 +524,82 @@ class Library {
545
524
  return false;
546
525
  }
547
526
  this.references.splice(index, 1);
527
+ this.removeFromIndices(ref);
528
+ return true;
529
+ }
530
+ /**
531
+ * Update a reference with partial updates.
532
+ * Preserves uuid and created_at, updates timestamp.
533
+ */
534
+ updateReference(ref, updates, options = {}) {
535
+ const index = this.references.indexOf(ref);
536
+ if (index === -1) {
537
+ return { updated: false };
538
+ }
539
+ const existingItem = ref.getItem();
540
+ const currentId = ref.getId();
541
+ const { newId, idChanged, collision } = this.resolveNewId(
542
+ updates.id ?? existingItem.id,
543
+ currentId,
544
+ options
545
+ );
546
+ if (collision) {
547
+ return { updated: false, idCollision: true };
548
+ }
549
+ const updatedItem = this.buildUpdatedItem(existingItem, updates, newId);
550
+ this.removeFromIndices(ref);
551
+ const newRef = new Reference(updatedItem);
552
+ this.references[index] = newRef;
553
+ this.addToIndices(newRef);
554
+ const result = { updated: true };
555
+ if (idChanged) {
556
+ result.idChanged = true;
557
+ result.newId = newId;
558
+ }
559
+ return result;
560
+ }
561
+ /**
562
+ * Resolve the new ID, handling collisions based on options.
563
+ */
564
+ resolveNewId(requestedId, currentId, options) {
565
+ if (requestedId === currentId) {
566
+ return { newId: requestedId, idChanged: false, collision: false };
567
+ }
568
+ const conflictingRef = this.idIndex.get(requestedId);
569
+ if (!conflictingRef) {
570
+ return { newId: requestedId, idChanged: false, collision: false };
571
+ }
572
+ const onIdCollision = options.onIdCollision ?? "fail";
573
+ if (onIdCollision === "fail") {
574
+ return { newId: requestedId, idChanged: false, collision: true };
575
+ }
576
+ const existingIds = new Set(this.references.map((r) => r.getId()));
577
+ existingIds.delete(currentId);
578
+ const resolvedId = this.resolveIdCollision(requestedId, existingIds);
579
+ return { newId: resolvedId, idChanged: true, collision: false };
580
+ }
581
+ /**
582
+ * Build the updated CslItem, preserving uuid and created_at.
583
+ */
584
+ buildUpdatedItem(existingItem, updates, newId) {
585
+ return {
586
+ ...existingItem,
587
+ ...updates,
588
+ id: newId,
589
+ type: updates.type ?? existingItem.type,
590
+ custom: {
591
+ ...existingItem.custom || {},
592
+ ...updates.custom || {},
593
+ uuid: existingItem.custom?.uuid || "",
594
+ created_at: existingItem.custom?.created_at || (/* @__PURE__ */ new Date()).toISOString(),
595
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
596
+ }
597
+ };
598
+ }
599
+ /**
600
+ * Remove a reference from all indices.
601
+ */
602
+ removeFromIndices(ref) {
548
603
  this.uuidIndex.delete(ref.getUuid());
549
604
  this.idIndex.delete(ref.getId());
550
605
  const doi = ref.getDoi();
@@ -555,7 +610,36 @@ class Library {
555
610
  if (pmid) {
556
611
  this.pmidIndex.delete(pmid);
557
612
  }
558
- return true;
613
+ }
614
+ /**
615
+ * Generate an alphabetic suffix for ID collision resolution.
616
+ * 0 -> 'a', 1 -> 'b', ..., 25 -> 'z', 26 -> 'aa', etc.
617
+ */
618
+ generateSuffix(index) {
619
+ const alphabet = "abcdefghijklmnopqrstuvwxyz";
620
+ let suffix = "";
621
+ let n = index;
622
+ do {
623
+ suffix = alphabet[n % 26] + suffix;
624
+ n = Math.floor(n / 26) - 1;
625
+ } while (n >= 0);
626
+ return suffix;
627
+ }
628
+ /**
629
+ * Resolve ID collision by appending alphabetic suffix.
630
+ */
631
+ resolveIdCollision(baseId, existingIds) {
632
+ if (!existingIds.has(baseId)) {
633
+ return baseId;
634
+ }
635
+ let index = 0;
636
+ let newId;
637
+ do {
638
+ const suffix = this.generateSuffix(index);
639
+ newId = `${baseId}${suffix}`;
640
+ index++;
641
+ } while (existingIds.has(newId));
642
+ return newId;
559
643
  }
560
644
  }
561
645
  const logLevelSchema = z.enum(["silent", "info", "debug"]);
@@ -575,12 +659,29 @@ const serverConfigSchema = z.object({
575
659
  autoStart: z.boolean(),
576
660
  autoStopMinutes: z.number().int().nonnegative()
577
661
  });
662
+ const citationFormatSchema = z.enum(["text", "html", "rtf"]);
663
+ const citationConfigSchema = z.object({
664
+ defaultStyle: z.string(),
665
+ cslDirectory: z.array(z.string()),
666
+ defaultLocale: z.string(),
667
+ defaultFormat: citationFormatSchema
668
+ });
669
+ const pubmedConfigSchema = z.object({
670
+ email: z.string().optional(),
671
+ apiKey: z.string().optional()
672
+ });
673
+ const fulltextConfigSchema = z.object({
674
+ directory: z.string().min(1)
675
+ });
578
676
  const configSchema = z.object({
579
677
  library: z.string().min(1),
580
678
  logLevel: logLevelSchema,
581
679
  backup: backupConfigSchema,
582
680
  watch: watchConfigSchema,
583
- server: serverConfigSchema
681
+ server: serverConfigSchema,
682
+ citation: citationConfigSchema,
683
+ pubmed: pubmedConfigSchema,
684
+ fulltext: fulltextConfigSchema
584
685
  });
585
686
  const partialConfigSchema = z.object({
586
687
  library: z.string().min(1).optional(),
@@ -610,6 +711,24 @@ const partialConfigSchema = z.object({
610
711
  auto_start: z.boolean().optional(),
611
712
  autoStopMinutes: z.number().int().nonnegative().optional(),
612
713
  auto_stop_minutes: z.number().int().nonnegative().optional()
714
+ }).optional(),
715
+ citation: z.object({
716
+ defaultStyle: z.string().optional(),
717
+ default_style: z.string().optional(),
718
+ cslDirectory: z.union([z.string(), z.array(z.string())]).optional(),
719
+ csl_directory: z.union([z.string(), z.array(z.string())]).optional(),
720
+ defaultLocale: z.string().optional(),
721
+ default_locale: z.string().optional(),
722
+ defaultFormat: citationFormatSchema.optional(),
723
+ default_format: citationFormatSchema.optional()
724
+ }).optional(),
725
+ pubmed: z.object({
726
+ email: z.string().optional(),
727
+ apiKey: z.string().optional(),
728
+ api_key: z.string().optional()
729
+ }).optional(),
730
+ fulltext: z.object({
731
+ directory: z.string().min(1).optional()
613
732
  }).optional()
614
733
  }).passthrough();
615
734
  function normalizeBackupConfig(backup) {
@@ -662,6 +781,37 @@ function normalizeServerConfig(server) {
662
781
  }
663
782
  return Object.keys(normalized).length > 0 ? normalized : void 0;
664
783
  }
784
+ function normalizeCitationConfig(citation) {
785
+ const normalized = {};
786
+ const defaultStyle = citation.defaultStyle ?? citation.default_style;
787
+ if (defaultStyle !== void 0) {
788
+ normalized.defaultStyle = defaultStyle;
789
+ }
790
+ const cslDirectory = citation.cslDirectory ?? citation.csl_directory;
791
+ if (cslDirectory !== void 0) {
792
+ normalized.cslDirectory = Array.isArray(cslDirectory) ? cslDirectory : [cslDirectory];
793
+ }
794
+ const defaultLocale = citation.defaultLocale ?? citation.default_locale;
795
+ if (defaultLocale !== void 0) {
796
+ normalized.defaultLocale = defaultLocale;
797
+ }
798
+ const defaultFormat = citation.defaultFormat ?? citation.default_format;
799
+ if (defaultFormat !== void 0) {
800
+ normalized.defaultFormat = defaultFormat;
801
+ }
802
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
803
+ }
804
+ function normalizePubmedConfig(pubmed) {
805
+ const normalized = {};
806
+ if (pubmed.email !== void 0) {
807
+ normalized.email = pubmed.email;
808
+ }
809
+ const apiKey = pubmed.apiKey ?? pubmed.api_key;
810
+ if (apiKey !== void 0) {
811
+ normalized.apiKey = apiKey;
812
+ }
813
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
814
+ }
665
815
  function normalizePartialConfig(partial) {
666
816
  const normalized = {};
667
817
  if (partial.library !== void 0) {
@@ -671,30 +821,39 @@ function normalizePartialConfig(partial) {
671
821
  if (logLevel !== void 0) {
672
822
  normalized.logLevel = logLevel;
673
823
  }
674
- if (partial.backup !== void 0) {
675
- const backup = normalizeBackupConfig(
676
- partial.backup
677
- );
678
- if (backup) {
679
- normalized.backup = backup;
680
- }
824
+ const backup = partial.backup !== void 0 ? normalizeBackupConfig(partial.backup) : void 0;
825
+ if (backup) {
826
+ normalized.backup = backup;
681
827
  }
682
- if (partial.watch !== void 0) {
683
- const watch = normalizeWatchConfig(partial.watch);
684
- if (watch) {
685
- normalized.watch = watch;
686
- }
828
+ const watch = partial.watch !== void 0 ? normalizeWatchConfig(partial.watch) : void 0;
829
+ if (watch) {
830
+ normalized.watch = watch;
687
831
  }
688
- if (partial.server !== void 0) {
689
- const server = normalizeServerConfig(
690
- partial.server
691
- );
692
- if (server) {
693
- normalized.server = server;
694
- }
832
+ const server = partial.server !== void 0 ? normalizeServerConfig(partial.server) : void 0;
833
+ if (server) {
834
+ normalized.server = server;
835
+ }
836
+ const citation = partial.citation !== void 0 ? normalizeCitationConfig(partial.citation) : void 0;
837
+ if (citation) {
838
+ normalized.citation = citation;
839
+ }
840
+ const pubmed = partial.pubmed !== void 0 ? normalizePubmedConfig(partial.pubmed) : void 0;
841
+ if (pubmed) {
842
+ normalized.pubmed = pubmed;
843
+ }
844
+ const fulltext = partial.fulltext !== void 0 ? normalizeFulltextConfig(partial.fulltext) : void 0;
845
+ if (fulltext) {
846
+ normalized.fulltext = fulltext;
695
847
  }
696
848
  return normalized;
697
849
  }
850
+ function normalizeFulltextConfig(fulltext) {
851
+ const normalized = {};
852
+ if (fulltext.directory !== void 0) {
853
+ normalized.directory = fulltext.directory;
854
+ }
855
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
856
+ }
698
857
  function getDefaultBackupDirectory() {
699
858
  return join(tmpdir(), "reference-manager", "backups");
700
859
  }
@@ -707,6 +866,12 @@ function getDefaultUserConfigPath() {
707
866
  function getDefaultCurrentDirConfigFilename() {
708
867
  return ".reference-manager.config.toml";
709
868
  }
869
+ function getDefaultCslDirectory() {
870
+ return join(homedir(), ".reference-manager", "csl");
871
+ }
872
+ function getDefaultFulltextDirectory() {
873
+ return join(homedir(), ".reference-manager", "fulltext");
874
+ }
710
875
  const defaultConfig = {
711
876
  library: getDefaultLibraryPath(),
712
877
  logLevel: "info",
@@ -725,6 +890,19 @@ const defaultConfig = {
725
890
  server: {
726
891
  autoStart: false,
727
892
  autoStopMinutes: 0
893
+ },
894
+ citation: {
895
+ defaultStyle: "apa",
896
+ cslDirectory: [getDefaultCslDirectory()],
897
+ defaultLocale: "en-US",
898
+ defaultFormat: "text"
899
+ },
900
+ pubmed: {
901
+ email: void 0,
902
+ apiKey: void 0
903
+ },
904
+ fulltext: {
905
+ directory: getDefaultFulltextDirectory()
728
906
  }
729
907
  };
730
908
  function loadTOMLFile(path) {
@@ -744,6 +922,7 @@ function loadTOMLFile(path) {
744
922
  }
745
923
  function mergeConfigs(base, ...overrides) {
746
924
  const result = { ...base };
925
+ const sectionKeys = ["backup", "watch", "server", "citation", "pubmed", "fulltext"];
747
926
  for (const override of overrides) {
748
927
  if (!override) continue;
749
928
  if (override.library !== void 0) {
@@ -752,23 +931,13 @@ function mergeConfigs(base, ...overrides) {
752
931
  if (override.logLevel !== void 0) {
753
932
  result.logLevel = override.logLevel;
754
933
  }
755
- if (override.backup !== void 0) {
756
- result.backup = {
757
- ...result.backup,
758
- ...override.backup
759
- };
760
- }
761
- if (override.watch !== void 0) {
762
- result.watch = {
763
- ...result.watch,
764
- ...override.watch
765
- };
766
- }
767
- if (override.server !== void 0) {
768
- result.server = {
769
- ...result.server,
770
- ...override.server
771
- };
934
+ for (const key of sectionKeys) {
935
+ if (override[key] !== void 0) {
936
+ result[key] = {
937
+ ...result[key],
938
+ ...override[key]
939
+ };
940
+ }
772
941
  }
773
942
  }
774
943
  return result;
@@ -792,7 +961,39 @@ function fillDefaults(partial) {
792
961
  server: {
793
962
  autoStart: partial.server?.autoStart ?? defaultConfig.server.autoStart,
794
963
  autoStopMinutes: partial.server?.autoStopMinutes ?? defaultConfig.server.autoStopMinutes
795
- }
964
+ },
965
+ citation: fillCitationDefaults(partial.citation),
966
+ pubmed: fillPubmedDefaults(partial.pubmed),
967
+ fulltext: fillFulltextDefaults(partial.fulltext)
968
+ };
969
+ }
970
+ function fillCitationDefaults(partial) {
971
+ return {
972
+ defaultStyle: partial?.defaultStyle ?? defaultConfig.citation.defaultStyle,
973
+ cslDirectory: partial?.cslDirectory ?? defaultConfig.citation.cslDirectory,
974
+ defaultLocale: partial?.defaultLocale ?? defaultConfig.citation.defaultLocale,
975
+ defaultFormat: partial?.defaultFormat ?? defaultConfig.citation.defaultFormat
976
+ };
977
+ }
978
+ function fillPubmedDefaults(partial) {
979
+ const email = process.env.PUBMED_EMAIL ?? partial?.email ?? defaultConfig.pubmed.email;
980
+ const apiKey = process.env.PUBMED_API_KEY ?? partial?.apiKey ?? defaultConfig.pubmed.apiKey;
981
+ return {
982
+ email,
983
+ apiKey
984
+ };
985
+ }
986
+ function expandTilde(path) {
987
+ if (path.startsWith("~/")) {
988
+ return join(homedir(), path.slice(2));
989
+ }
990
+ return path;
991
+ }
992
+ function fillFulltextDefaults(partial) {
993
+ const envDir = process.env.REFERENCE_MANAGER_FULLTEXT_DIR;
994
+ const directory = envDir ?? partial?.directory ?? defaultConfig.fulltext.directory;
995
+ return {
996
+ directory: expandTilde(directory)
796
997
  };
797
998
  }
798
999
  function loadConfig(options = {}) {
@@ -822,567 +1023,7 @@ function loadConfig(options = {}) {
822
1023
  );
823
1024
  }
824
1025
  }
825
- const VALID_FIELDS = /* @__PURE__ */ new Set([
826
- "author",
827
- "title",
828
- "year",
829
- "doi",
830
- "pmid",
831
- "pmcid",
832
- "url",
833
- "keyword"
834
- ]);
835
- function isWhitespace(query, index) {
836
- return /\s/.test(query.charAt(index));
837
- }
838
- function isQuote(query, index) {
839
- return query.charAt(index) === '"';
840
- }
841
- function tokenize(query) {
842
- const tokens = [];
843
- let i = 0;
844
- while (i < query.length) {
845
- if (isWhitespace(query, i)) {
846
- i++;
847
- continue;
848
- }
849
- const result = parseNextToken(query, i);
850
- if (result.token) {
851
- tokens.push(result.token);
852
- }
853
- i = result.nextIndex;
854
- }
855
- return {
856
- original: query,
857
- tokens
858
- };
859
- }
860
- function hasWhitespaceBetween(query, start, end) {
861
- for (let j = start; j < end; j++) {
862
- if (isWhitespace(query, j)) {
863
- return true;
864
- }
865
- }
866
- return false;
867
- }
868
- function tryParseFieldValue(query, startIndex) {
869
- const colonIndex = query.indexOf(":", startIndex);
870
- if (colonIndex === -1) {
871
- return null;
872
- }
873
- if (hasWhitespaceBetween(query, startIndex, colonIndex)) {
874
- return null;
875
- }
876
- const fieldName = query.substring(startIndex, colonIndex);
877
- if (!VALID_FIELDS.has(fieldName)) {
878
- return null;
879
- }
880
- const afterColon = colonIndex + 1;
881
- if (afterColon >= query.length || isWhitespace(query, afterColon)) {
882
- return { token: null, nextIndex: afterColon };
883
- }
884
- if (isQuote(query, afterColon)) {
885
- const quoteResult = parseQuotedValue(query, afterColon);
886
- if (quoteResult.value !== null) {
887
- return {
888
- token: {
889
- raw: query.substring(startIndex, quoteResult.nextIndex),
890
- value: quoteResult.value,
891
- field: fieldName,
892
- isPhrase: true
893
- },
894
- nextIndex: quoteResult.nextIndex
895
- };
896
- }
897
- return null;
898
- }
899
- const valueResult = parseUnquotedValue(query, afterColon);
900
- return {
901
- token: {
902
- raw: query.substring(startIndex, valueResult.nextIndex),
903
- value: valueResult.value,
904
- field: fieldName,
905
- isPhrase: false
906
- },
907
- nextIndex: valueResult.nextIndex
908
- };
909
- }
910
- function parseQuotedToken(query, startIndex) {
911
- const quoteResult = parseQuotedValue(query, startIndex);
912
- if (quoteResult.value !== null) {
913
- return {
914
- token: {
915
- raw: query.substring(startIndex, quoteResult.nextIndex),
916
- value: quoteResult.value,
917
- isPhrase: true
918
- },
919
- nextIndex: quoteResult.nextIndex
920
- };
921
- }
922
- if (quoteResult.nextIndex > startIndex) {
923
- return { token: null, nextIndex: quoteResult.nextIndex };
924
- }
925
- const valueResult = parseUnquotedValue(query, startIndex, true);
926
- return {
927
- token: {
928
- raw: valueResult.value,
929
- value: valueResult.value,
930
- isPhrase: false
931
- },
932
- nextIndex: valueResult.nextIndex
933
- };
934
- }
935
- function parseRegularToken(query, startIndex) {
936
- const valueResult = parseUnquotedValue(query, startIndex);
937
- return {
938
- token: {
939
- raw: valueResult.value,
940
- value: valueResult.value,
941
- isPhrase: false
942
- },
943
- nextIndex: valueResult.nextIndex
944
- };
945
- }
946
- function parseNextToken(query, startIndex) {
947
- const fieldResult = tryParseFieldValue(query, startIndex);
948
- if (fieldResult !== null) {
949
- return fieldResult;
950
- }
951
- if (isQuote(query, startIndex)) {
952
- return parseQuotedToken(query, startIndex);
953
- }
954
- return parseRegularToken(query, startIndex);
955
- }
956
- function parseQuotedValue(query, startIndex) {
957
- if (!isQuote(query, startIndex)) {
958
- return { value: null, nextIndex: startIndex };
959
- }
960
- let i = startIndex + 1;
961
- const valueStart = i;
962
- while (i < query.length && !isQuote(query, i)) {
963
- i++;
964
- }
965
- if (i >= query.length) {
966
- return { value: null, nextIndex: startIndex };
967
- }
968
- const value = query.substring(valueStart, i);
969
- i++;
970
- if (value.trim() === "") {
971
- return { value: null, nextIndex: i };
972
- }
973
- return { value, nextIndex: i };
974
- }
975
- function parseUnquotedValue(query, startIndex, includeQuotes = false) {
976
- let i = startIndex;
977
- while (i < query.length && !isWhitespace(query, i)) {
978
- if (!includeQuotes && isQuote(query, i)) {
979
- break;
980
- }
981
- i++;
982
- }
983
- return {
984
- value: query.substring(startIndex, i),
985
- nextIndex: i
986
- };
987
- }
988
- function normalize(text) {
989
- let normalized = text.normalize("NFKC");
990
- normalized = normalized.toLowerCase();
991
- normalized = normalized.normalize("NFD").replace(new RegExp("\\p{M}", "gu"), "");
992
- normalized = normalized.replace(/[^\p{L}\p{N}/\s]/gu, " ");
993
- normalized = normalized.replace(/\s+/g, " ").trim();
994
- return normalized;
995
- }
996
- const ID_FIELDS = /* @__PURE__ */ new Set(["DOI", "PMID", "PMCID", "URL"]);
997
- function extractYear$2(reference) {
998
- if (reference.issued?.["date-parts"]?.[0]?.[0]) {
999
- return String(reference.issued["date-parts"][0][0]);
1000
- }
1001
- return "0000";
1002
- }
1003
- function extractAuthors(reference) {
1004
- if (!reference.author || reference.author.length === 0) {
1005
- return "";
1006
- }
1007
- return reference.author.map((author) => {
1008
- const family = author.family || "";
1009
- const givenInitial = author.given ? author.given[0] : "";
1010
- return givenInitial ? `${family} ${givenInitial}` : family;
1011
- }).join(" ");
1012
- }
1013
- function getFieldValue(reference, field) {
1014
- if (field === "year") {
1015
- return extractYear$2(reference);
1016
- }
1017
- if (field === "author") {
1018
- return extractAuthors(reference);
1019
- }
1020
- const value = reference[field];
1021
- if (typeof value === "string") {
1022
- return value;
1023
- }
1024
- if (field.startsWith("custom.")) {
1025
- const customField = field.substring(7);
1026
- const customValue = reference.custom?.[customField];
1027
- if (typeof customValue === "string") {
1028
- return customValue;
1029
- }
1030
- }
1031
- return null;
1032
- }
1033
- function matchUrl(queryValue, reference) {
1034
- if (reference.URL === queryValue) {
1035
- return {
1036
- field: "URL",
1037
- strength: "exact",
1038
- value: reference.URL
1039
- };
1040
- }
1041
- const additionalUrls = reference.custom?.additional_urls;
1042
- if (Array.isArray(additionalUrls)) {
1043
- for (const url of additionalUrls) {
1044
- if (typeof url === "string" && url === queryValue) {
1045
- return {
1046
- field: "custom.additional_urls",
1047
- strength: "exact",
1048
- value: url
1049
- };
1050
- }
1051
- }
1052
- }
1053
- return null;
1054
- }
1055
- function matchKeyword(queryValue, reference) {
1056
- if (!reference.keyword || !Array.isArray(reference.keyword)) {
1057
- return null;
1058
- }
1059
- const normalizedQuery = normalize(queryValue);
1060
- for (const keyword of reference.keyword) {
1061
- if (typeof keyword === "string") {
1062
- const normalizedKeyword = normalize(keyword);
1063
- if (normalizedKeyword.includes(normalizedQuery)) {
1064
- return {
1065
- field: "keyword",
1066
- strength: "partial",
1067
- value: keyword
1068
- };
1069
- }
1070
- }
1071
- }
1072
- return null;
1073
- }
1074
- const FIELD_MAP = {
1075
- author: "author",
1076
- title: "title",
1077
- doi: "DOI",
1078
- pmid: "PMID",
1079
- pmcid: "PMCID"
1080
- };
1081
- function matchYearField(tokenValue, reference) {
1082
- const year = extractYear$2(reference);
1083
- if (year === tokenValue) {
1084
- return {
1085
- field: "year",
1086
- strength: "exact",
1087
- value: year
1088
- };
1089
- }
1090
- return null;
1091
- }
1092
- function matchFieldValue(field, tokenValue, reference) {
1093
- const fieldValue = getFieldValue(reference, field);
1094
- if (fieldValue === null) {
1095
- return null;
1096
- }
1097
- if (ID_FIELDS.has(field)) {
1098
- if (fieldValue === tokenValue) {
1099
- return {
1100
- field,
1101
- strength: "exact",
1102
- value: fieldValue
1103
- };
1104
- }
1105
- return null;
1106
- }
1107
- const normalizedFieldValue = normalize(fieldValue);
1108
- const normalizedQuery = normalize(tokenValue);
1109
- if (normalizedFieldValue.includes(normalizedQuery)) {
1110
- return {
1111
- field,
1112
- strength: "partial",
1113
- value: fieldValue
1114
- };
1115
- }
1116
- return null;
1117
- }
1118
- function matchSpecificField(token, reference) {
1119
- const matches = [];
1120
- const fieldToSearch = token.field;
1121
- if (fieldToSearch === "url") {
1122
- const urlMatch = matchUrl(token.value, reference);
1123
- if (urlMatch) matches.push(urlMatch);
1124
- return matches;
1125
- }
1126
- if (fieldToSearch === "year") {
1127
- const yearMatch = matchYearField(token.value, reference);
1128
- if (yearMatch) matches.push(yearMatch);
1129
- return matches;
1130
- }
1131
- if (fieldToSearch === "keyword") {
1132
- const keywordMatch = matchKeyword(token.value, reference);
1133
- if (keywordMatch) matches.push(keywordMatch);
1134
- return matches;
1135
- }
1136
- const actualField = FIELD_MAP[fieldToSearch] || fieldToSearch;
1137
- const match = matchFieldValue(actualField, token.value, reference);
1138
- if (match) matches.push(match);
1139
- return matches;
1140
- }
1141
- const STANDARD_SEARCH_FIELDS = [
1142
- "title",
1143
- "author",
1144
- "container-title",
1145
- "publisher",
1146
- "DOI",
1147
- "PMID",
1148
- "PMCID",
1149
- "abstract"
1150
- ];
1151
- function matchSingleField(field, tokenValue, reference) {
1152
- if (field === "year") {
1153
- return matchYearField(tokenValue, reference);
1154
- }
1155
- if (field === "URL") {
1156
- return matchUrl(tokenValue, reference);
1157
- }
1158
- if (field === "keyword") {
1159
- return matchKeyword(tokenValue, reference);
1160
- }
1161
- return matchFieldValue(field, tokenValue, reference);
1162
- }
1163
- function matchAllFields(token, reference) {
1164
- const matches = [];
1165
- const specialFields = ["year", "URL", "keyword"];
1166
- for (const field of specialFields) {
1167
- const match = matchSingleField(field, token.value, reference);
1168
- if (match) matches.push(match);
1169
- }
1170
- for (const field of STANDARD_SEARCH_FIELDS) {
1171
- const match = matchFieldValue(field, token.value, reference);
1172
- if (match) matches.push(match);
1173
- }
1174
- return matches;
1175
- }
1176
- function matchToken(token, reference) {
1177
- if (token.field) {
1178
- return matchSpecificField(token, reference);
1179
- }
1180
- return matchAllFields(token, reference);
1181
- }
1182
- function matchReference(reference, tokens) {
1183
- if (tokens.length === 0) {
1184
- return null;
1185
- }
1186
- const tokenMatches = [];
1187
- let overallStrength = "none";
1188
- for (const token of tokens) {
1189
- const matches = matchToken(token, reference);
1190
- if (matches.length === 0) {
1191
- return null;
1192
- }
1193
- const tokenStrength = matches.some((m) => m.strength === "exact") ? "exact" : "partial";
1194
- if (tokenStrength === "exact") {
1195
- overallStrength = "exact";
1196
- } else if (tokenStrength === "partial" && overallStrength === "none") {
1197
- overallStrength = "partial";
1198
- }
1199
- tokenMatches.push({
1200
- token,
1201
- matches
1202
- });
1203
- }
1204
- const score = overallStrength === "exact" ? 100 + tokenMatches.length : 50 + tokenMatches.length;
1205
- return {
1206
- reference,
1207
- tokenMatches,
1208
- overallStrength,
1209
- score
1210
- };
1211
- }
1212
- function search(references, tokens) {
1213
- const results = [];
1214
- for (const reference of references) {
1215
- const match = matchReference(reference, tokens);
1216
- if (match) {
1217
- results.push(match);
1218
- }
1219
- }
1220
- return results;
1221
- }
1222
- function extractYear$1(reference) {
1223
- if (reference.issued?.["date-parts"]?.[0]?.[0]) {
1224
- return String(reference.issued["date-parts"][0][0]);
1225
- }
1226
- return "0000";
1227
- }
1228
- function extractFirstAuthorFamily(reference) {
1229
- if (!reference.author || reference.author.length === 0) {
1230
- return "";
1231
- }
1232
- return reference.author[0]?.family || "";
1233
- }
1234
- function extractTitle(reference) {
1235
- return reference.title || "";
1236
- }
1237
- function compareStrength(a, b) {
1238
- const strengthOrder = { exact: 2, partial: 1, none: 0 };
1239
- return strengthOrder[b] - strengthOrder[a];
1240
- }
1241
- function compareYear(a, b) {
1242
- const yearA = extractYear$1(a);
1243
- const yearB = extractYear$1(b);
1244
- return Number(yearB) - Number(yearA);
1245
- }
1246
- function compareAuthor(a, b) {
1247
- const authorA = extractFirstAuthorFamily(a).toLowerCase();
1248
- const authorB = extractFirstAuthorFamily(b).toLowerCase();
1249
- if (authorA === "" && authorB !== "") return 1;
1250
- if (authorA !== "" && authorB === "") return -1;
1251
- return authorA.localeCompare(authorB);
1252
- }
1253
- function compareTitle(a, b) {
1254
- const titleA = extractTitle(a).toLowerCase();
1255
- const titleB = extractTitle(b).toLowerCase();
1256
- if (titleA === "" && titleB !== "") return 1;
1257
- if (titleA !== "" && titleB === "") return -1;
1258
- return titleA.localeCompare(titleB);
1259
- }
1260
- function sortResults(results) {
1261
- const indexed = results.map((result, index) => ({ result, index }));
1262
- const sorted = indexed.sort((a, b) => {
1263
- const strengthDiff = compareStrength(a.result.overallStrength, b.result.overallStrength);
1264
- if (strengthDiff !== 0) return strengthDiff;
1265
- const yearDiff = compareYear(a.result.reference, b.result.reference);
1266
- if (yearDiff !== 0) return yearDiff;
1267
- const authorDiff = compareAuthor(a.result.reference, b.result.reference);
1268
- if (authorDiff !== 0) return authorDiff;
1269
- const titleDiff = compareTitle(a.result.reference, b.result.reference);
1270
- if (titleDiff !== 0) return titleDiff;
1271
- return a.index - b.index;
1272
- });
1273
- return sorted.map((item) => item.result);
1274
- }
1275
- function normalizeDoi(doi) {
1276
- const normalized = doi.replace(/^https?:\/\/doi\.org\//i, "").replace(/^https?:\/\/dx\.doi\.org\//i, "").replace(/^doi:/i, "");
1277
- return normalized;
1278
- }
1279
- function extractYear(item) {
1280
- const dateParts = item.issued?.["date-parts"]?.[0];
1281
- if (!dateParts || dateParts.length === 0) {
1282
- return null;
1283
- }
1284
- return String(dateParts[0]);
1285
- }
1286
- function normalizeAuthors(item) {
1287
- if (!item.author || item.author.length === 0) {
1288
- return null;
1289
- }
1290
- const authorStrings = item.author.map((author) => {
1291
- const family = author.family || "";
1292
- const givenInitial = author.given ? author.given.charAt(0) : "";
1293
- return `${family} ${givenInitial}`.trim();
1294
- });
1295
- return normalize(authorStrings.join(" "));
1296
- }
1297
- function checkDoiMatch(item, existing) {
1298
- if (!item.DOI || !existing.DOI) {
1299
- return null;
1300
- }
1301
- const normalizedItemDoi = normalizeDoi(item.DOI);
1302
- const normalizedExistingDoi = normalizeDoi(existing.DOI);
1303
- if (normalizedItemDoi === normalizedExistingDoi) {
1304
- return {
1305
- type: "doi",
1306
- existing,
1307
- details: {
1308
- doi: normalizedExistingDoi
1309
- }
1310
- };
1311
- }
1312
- return null;
1313
- }
1314
- function checkPmidMatch(item, existing) {
1315
- if (!item.PMID || !existing.PMID) {
1316
- return null;
1317
- }
1318
- if (item.PMID === existing.PMID) {
1319
- return {
1320
- type: "pmid",
1321
- existing,
1322
- details: {
1323
- pmid: existing.PMID
1324
- }
1325
- };
1326
- }
1327
- return null;
1328
- }
1329
- function checkTitleAuthorYearMatch(item, existing) {
1330
- const itemTitle = item.title ? normalize(item.title) : null;
1331
- const existingTitle = existing.title ? normalize(existing.title) : null;
1332
- const itemAuthors = normalizeAuthors(item);
1333
- const existingAuthors = normalizeAuthors(existing);
1334
- const itemYear = extractYear(item);
1335
- const existingYear = extractYear(existing);
1336
- if (!itemTitle || !existingTitle || !itemAuthors || !existingAuthors || !itemYear || !existingYear) {
1337
- return null;
1338
- }
1339
- if (itemTitle === existingTitle && itemAuthors === existingAuthors && itemYear === existingYear) {
1340
- return {
1341
- type: "title-author-year",
1342
- existing,
1343
- details: {
1344
- normalizedTitle: existingTitle,
1345
- normalizedAuthors: existingAuthors,
1346
- year: existingYear
1347
- }
1348
- };
1349
- }
1350
- return null;
1351
- }
1352
- function checkSingleDuplicate(item, existing) {
1353
- const doiMatch = checkDoiMatch(item, existing);
1354
- if (doiMatch) {
1355
- return doiMatch;
1356
- }
1357
- const pmidMatch = checkPmidMatch(item, existing);
1358
- if (pmidMatch) {
1359
- return pmidMatch;
1360
- }
1361
- return checkTitleAuthorYearMatch(item, existing);
1362
- }
1363
- function detectDuplicate(item, existingReferences) {
1364
- const matches = [];
1365
- const itemUuid = item.custom?.uuid;
1366
- for (const existing of existingReferences) {
1367
- if (itemUuid && existing.custom?.uuid === itemUuid) {
1368
- continue;
1369
- }
1370
- const match = checkSingleDuplicate(item, existing);
1371
- if (match) {
1372
- matches.push(match);
1373
- }
1374
- }
1375
- return {
1376
- isDuplicate: matches.length > 0,
1377
- matches
1378
- };
1379
- }
1380
1026
  export {
1381
- generateUuid as A,
1382
- isValidUuid as B,
1383
- CslLibrarySchema as C,
1384
- ensureCustomMetadata as D,
1385
- extractUuidFromCustom as E,
1386
1027
  Library as L,
1387
1028
  Reference as R,
1388
1029
  computeHash as a,
@@ -1395,21 +1036,19 @@ export {
1395
1036
  getDefaultLibraryPath as h,
1396
1037
  getDefaultUserConfigPath as i,
1397
1038
  logLevelSchema as j,
1398
- normalize as k,
1039
+ parseCslJson as k,
1399
1040
  loadConfig as l,
1400
- sortResults as m,
1041
+ writeCslJson as m,
1401
1042
  normalizePartialConfig as n,
1402
- detectDuplicate as o,
1043
+ generateId as o,
1403
1044
  partialConfigSchema as p,
1404
- CslItemSchema as q,
1405
- parseCslJson as r,
1406
- search as s,
1407
- tokenize as t,
1408
- serializeCslJson as u,
1409
- writeCslJson as v,
1045
+ generateIdWithCollisionCheck as q,
1046
+ normalizeText as r,
1047
+ serializeCslJson as s,
1048
+ generateUuid as t,
1049
+ isValidUuid as u,
1050
+ ensureCustomMetadata as v,
1410
1051
  watchConfigSchema as w,
1411
- generateId as x,
1412
- generateIdWithCollisionCheck as y,
1413
- normalizeText as z
1052
+ extractUuidFromCustom as x
1414
1053
  };
1415
- //# sourceMappingURL=detector-BF8Mcc72.js.map
1054
+ //# sourceMappingURL=loader-mQ25o6cV.js.map