@ncukondo/reference-manager 0.3.0 → 0.5.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 (113) hide show
  1. package/README.md +282 -107
  2. package/dist/chunks/file-watcher-B-SiUw5f.js +1557 -0
  3. package/dist/chunks/file-watcher-B-SiUw5f.js.map +1 -0
  4. package/dist/chunks/{search-Be9vzUIH.js → index-DLIGxQaB.js} +399 -89
  5. package/dist/chunks/index-DLIGxQaB.js.map +1 -0
  6. package/dist/chunks/loader-DuzyKV70.js +394 -0
  7. package/dist/chunks/loader-DuzyKV70.js.map +1 -0
  8. package/dist/cli/commands/add.d.ts +3 -3
  9. package/dist/cli/commands/add.d.ts.map +1 -1
  10. package/dist/cli/commands/cite.d.ts +3 -3
  11. package/dist/cli/commands/cite.d.ts.map +1 -1
  12. package/dist/cli/commands/fulltext.d.ts +5 -34
  13. package/dist/cli/commands/fulltext.d.ts.map +1 -1
  14. package/dist/cli/commands/list.d.ts +3 -3
  15. package/dist/cli/commands/list.d.ts.map +1 -1
  16. package/dist/cli/commands/mcp.d.ts +16 -0
  17. package/dist/cli/commands/mcp.d.ts.map +1 -0
  18. package/dist/cli/commands/remove.d.ts +3 -3
  19. package/dist/cli/commands/remove.d.ts.map +1 -1
  20. package/dist/cli/commands/search.d.ts +3 -3
  21. package/dist/cli/commands/search.d.ts.map +1 -1
  22. package/dist/cli/commands/server.d.ts +2 -0
  23. package/dist/cli/commands/server.d.ts.map +1 -1
  24. package/dist/cli/commands/update.d.ts +3 -3
  25. package/dist/cli/commands/update.d.ts.map +1 -1
  26. package/dist/cli/execution-context.d.ts +23 -36
  27. package/dist/cli/execution-context.d.ts.map +1 -1
  28. package/dist/cli/index.d.ts.map +1 -1
  29. package/dist/cli/server-client.d.ts +24 -40
  30. package/dist/cli/server-client.d.ts.map +1 -1
  31. package/dist/cli/server-detection.d.ts +1 -0
  32. package/dist/cli/server-detection.d.ts.map +1 -1
  33. package/dist/cli.js +21061 -317
  34. package/dist/cli.js.map +1 -1
  35. package/dist/config/defaults.d.ts.map +1 -1
  36. package/dist/config/loader.d.ts.map +1 -1
  37. package/dist/config/schema.d.ts +2 -3
  38. package/dist/config/schema.d.ts.map +1 -1
  39. package/dist/core/csl-json/types.d.ts +3 -0
  40. package/dist/core/csl-json/types.d.ts.map +1 -1
  41. package/dist/core/library-interface.d.ts +100 -0
  42. package/dist/core/library-interface.d.ts.map +1 -0
  43. package/dist/core/library.d.ts +29 -46
  44. package/dist/core/library.d.ts.map +1 -1
  45. package/dist/features/operations/add.d.ts +2 -2
  46. package/dist/features/operations/add.d.ts.map +1 -1
  47. package/dist/features/operations/cite.d.ts +2 -2
  48. package/dist/features/operations/cite.d.ts.map +1 -1
  49. package/dist/features/operations/fulltext/attach.d.ts +47 -0
  50. package/dist/features/operations/fulltext/attach.d.ts.map +1 -0
  51. package/dist/features/operations/fulltext/detach.d.ts +38 -0
  52. package/dist/features/operations/fulltext/detach.d.ts.map +1 -0
  53. package/dist/features/operations/fulltext/get.d.ts +41 -0
  54. package/dist/features/operations/fulltext/get.d.ts.map +1 -0
  55. package/dist/features/operations/fulltext/index.d.ts +9 -0
  56. package/dist/features/operations/fulltext/index.d.ts.map +1 -0
  57. package/dist/features/operations/index.d.ts +15 -0
  58. package/dist/features/operations/index.d.ts.map +1 -0
  59. package/dist/features/operations/library-operations.d.ts +64 -0
  60. package/dist/features/operations/library-operations.d.ts.map +1 -0
  61. package/dist/features/operations/list.d.ts +2 -2
  62. package/dist/features/operations/list.d.ts.map +1 -1
  63. package/dist/features/operations/operations-library.d.ts +36 -0
  64. package/dist/features/operations/operations-library.d.ts.map +1 -0
  65. package/dist/features/operations/remove.d.ts +4 -4
  66. package/dist/features/operations/remove.d.ts.map +1 -1
  67. package/dist/features/operations/search.d.ts +2 -2
  68. package/dist/features/operations/search.d.ts.map +1 -1
  69. package/dist/features/operations/update.d.ts +2 -2
  70. package/dist/features/operations/update.d.ts.map +1 -1
  71. package/dist/features/search/matcher.d.ts.map +1 -1
  72. package/dist/features/search/normalizer.d.ts +12 -0
  73. package/dist/features/search/normalizer.d.ts.map +1 -1
  74. package/dist/features/search/tokenizer.d.ts.map +1 -1
  75. package/dist/features/search/types.d.ts +1 -1
  76. package/dist/features/search/types.d.ts.map +1 -1
  77. package/dist/features/search/uppercase.d.ts +41 -0
  78. package/dist/features/search/uppercase.d.ts.map +1 -0
  79. package/dist/index.js +24 -192
  80. package/dist/index.js.map +1 -1
  81. package/dist/mcp/context.d.ts +19 -0
  82. package/dist/mcp/context.d.ts.map +1 -0
  83. package/dist/mcp/index.d.ts +20 -0
  84. package/dist/mcp/index.d.ts.map +1 -0
  85. package/dist/mcp/resources/index.d.ts +10 -0
  86. package/dist/mcp/resources/index.d.ts.map +1 -0
  87. package/dist/mcp/resources/library.d.ts +26 -0
  88. package/dist/mcp/resources/library.d.ts.map +1 -0
  89. package/dist/mcp/tools/add.d.ts +17 -0
  90. package/dist/mcp/tools/add.d.ts.map +1 -0
  91. package/dist/mcp/tools/cite.d.ts +15 -0
  92. package/dist/mcp/tools/cite.d.ts.map +1 -0
  93. package/dist/mcp/tools/fulltext.d.ts +51 -0
  94. package/dist/mcp/tools/fulltext.d.ts.map +1 -0
  95. package/dist/mcp/tools/index.d.ts +12 -0
  96. package/dist/mcp/tools/index.d.ts.map +1 -0
  97. package/dist/mcp/tools/list.d.ts +13 -0
  98. package/dist/mcp/tools/list.d.ts.map +1 -0
  99. package/dist/mcp/tools/remove.d.ts +19 -0
  100. package/dist/mcp/tools/remove.d.ts.map +1 -0
  101. package/dist/mcp/tools/search.d.ts +13 -0
  102. package/dist/mcp/tools/search.d.ts.map +1 -0
  103. package/dist/server/index.d.ts +23 -1
  104. package/dist/server/index.d.ts.map +1 -1
  105. package/dist/server/routes/references.d.ts.map +1 -1
  106. package/dist/server.js +5 -271
  107. package/dist/server.js.map +1 -1
  108. package/package.json +2 -1
  109. package/dist/chunks/detector-DHztTaFY.js +0 -619
  110. package/dist/chunks/detector-DHztTaFY.js.map +0 -1
  111. package/dist/chunks/loader-mQ25o6cV.js +0 -1054
  112. package/dist/chunks/loader-mQ25o6cV.js.map +0 -1
  113. package/dist/chunks/search-Be9vzUIH.js.map +0 -1
@@ -1,1054 +0,0 @@
1
- import { randomUUID, createHash } from "node:crypto";
2
- import { createReadStream, existsSync, readFileSync } from "node:fs";
3
- import { readFile, mkdir, writeFile } from "node:fs/promises";
4
- import { C as CslLibrarySchema } from "./detector-DHztTaFY.js";
5
- import { dirname, join } from "node:path";
6
- import { homedir, tmpdir } from "node:os";
7
- import { parse } from "@iarna/toml";
8
- import { z } from "zod";
9
- function normalizeText(text) {
10
- return text.toLowerCase().replace(/\s+/g, "_").replace(/[^a-z0-9_]/g, "").replace(/_+/g, "_").replace(/^_|_$/g, "");
11
- }
12
- function normalizeAuthorName(name) {
13
- const normalized = normalizeText(name);
14
- return normalized.slice(0, 32);
15
- }
16
- function normalizeTitleSlug(title) {
17
- const normalized = normalizeText(title);
18
- return normalized.slice(0, 32);
19
- }
20
- function extractAuthorName(item) {
21
- if (!item.author || item.author.length === 0) {
22
- return "";
23
- }
24
- const firstAuthor = item.author[0];
25
- if (!firstAuthor) {
26
- return "";
27
- }
28
- if (firstAuthor.family) {
29
- return normalizeAuthorName(firstAuthor.family);
30
- }
31
- if (firstAuthor.literal) {
32
- return normalizeAuthorName(firstAuthor.literal);
33
- }
34
- return "";
35
- }
36
- function extractYear(item) {
37
- if (!item.issued || !item.issued["date-parts"] || item.issued["date-parts"].length === 0) {
38
- return "";
39
- }
40
- const dateParts = item.issued["date-parts"][0];
41
- if (!dateParts || dateParts.length === 0) {
42
- return "";
43
- }
44
- const year = dateParts[0];
45
- return year ? year.toString() : "";
46
- }
47
- function determineTitlePart(hasAuthor, hasYear, title) {
48
- if (hasAuthor && hasYear) {
49
- return "";
50
- }
51
- if (title) {
52
- return `-${title}`;
53
- }
54
- if (!hasAuthor && !hasYear) {
55
- return "-untitled";
56
- }
57
- return "";
58
- }
59
- function generateId(item) {
60
- const author = extractAuthorName(item);
61
- const year = extractYear(item);
62
- const title = item.title ? normalizeTitleSlug(item.title) : "";
63
- const authorPart = author || "anon";
64
- const yearPart = year || "nd";
65
- const titlePart = determineTitlePart(Boolean(author), Boolean(year), title);
66
- return `${authorPart}-${yearPart}${titlePart}`;
67
- }
68
- function generateSuffix(index) {
69
- if (index === 0) {
70
- return "";
71
- }
72
- let suffix = "";
73
- let num = index;
74
- while (num > 0) {
75
- num--;
76
- suffix = String.fromCharCode(97 + num % 26) + suffix;
77
- num = Math.floor(num / 26);
78
- }
79
- return suffix;
80
- }
81
- function generateIdWithCollisionCheck(item, existingIds) {
82
- const baseId = generateId(item);
83
- const normalizedExistingIds = existingIds.map((id) => id.toLowerCase());
84
- let candidate = baseId;
85
- let suffixIndex = 0;
86
- while (normalizedExistingIds.includes(candidate.toLowerCase())) {
87
- suffixIndex++;
88
- const suffix = generateSuffix(suffixIndex);
89
- candidate = `${baseId}${suffix}`;
90
- }
91
- return candidate;
92
- }
93
- const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
94
- function isValidUuid(uuid) {
95
- return UUID_REGEX.test(uuid);
96
- }
97
- function generateUuid() {
98
- return randomUUID();
99
- }
100
- function generateTimestamp() {
101
- return (/* @__PURE__ */ new Date()).toISOString();
102
- }
103
- function extractUuidFromCustom(custom) {
104
- if (!custom || !custom.uuid) {
105
- return null;
106
- }
107
- return isValidUuid(custom.uuid) ? custom.uuid : null;
108
- }
109
- function ensureUuid(custom) {
110
- const existingUuid = extractUuidFromCustom(custom);
111
- if (existingUuid && custom) {
112
- return custom;
113
- }
114
- const newUuid = generateUuid();
115
- return {
116
- ...custom,
117
- uuid: newUuid
118
- };
119
- }
120
- function ensureCreatedAt(custom) {
121
- if (custom.created_at) {
122
- return custom;
123
- }
124
- if (custom.timestamp) {
125
- return {
126
- ...custom,
127
- created_at: custom.timestamp
128
- };
129
- }
130
- const newTimestamp = generateTimestamp();
131
- return {
132
- ...custom,
133
- created_at: newTimestamp
134
- };
135
- }
136
- function ensureTimestamp(custom) {
137
- if (custom.timestamp) {
138
- return custom;
139
- }
140
- return {
141
- ...custom,
142
- timestamp: custom.created_at
143
- };
144
- }
145
- function ensureCustomMetadata(custom) {
146
- const withUuid = ensureUuid(custom);
147
- const withCreatedAt = ensureCreatedAt(withUuid);
148
- const withTimestamp = ensureTimestamp(withCreatedAt);
149
- return withTimestamp;
150
- }
151
- class Reference {
152
- item;
153
- uuid;
154
- constructor(item) {
155
- const customMetadata = ensureCustomMetadata(item.custom);
156
- this.item = { ...item, custom: customMetadata };
157
- const extractedUuid = extractUuidFromCustom(customMetadata);
158
- if (!extractedUuid) {
159
- throw new Error("Failed to extract UUID after ensureCustomMetadata");
160
- }
161
- this.uuid = extractedUuid;
162
- }
163
- /**
164
- * Factory method to create a Reference with UUID and ID generation
165
- */
166
- static create(item, options) {
167
- const existingIds = options?.existingIds || /* @__PURE__ */ new Set();
168
- let updatedItem = item;
169
- if (!item.id || item.id.trim() === "") {
170
- const generatedId = generateIdWithCollisionCheck(item, Array.from(existingIds));
171
- updatedItem = { ...item, id: generatedId };
172
- }
173
- return new Reference(updatedItem);
174
- }
175
- /**
176
- * Get the underlying CSL-JSON item
177
- */
178
- getItem() {
179
- return this.item;
180
- }
181
- /**
182
- * Get the UUID (internal stable identifier)
183
- */
184
- getUuid() {
185
- return this.uuid;
186
- }
187
- /**
188
- * Get the ID (Pandoc citation key / BibTeX-key)
189
- */
190
- getId() {
191
- return this.item.id;
192
- }
193
- /**
194
- * Get the title
195
- */
196
- getTitle() {
197
- return this.item.title;
198
- }
199
- /**
200
- * Get the authors
201
- */
202
- getAuthors() {
203
- return this.item.author;
204
- }
205
- /**
206
- * Get the year from issued date
207
- */
208
- getYear() {
209
- const issued = this.item.issued;
210
- if (!issued || !issued["date-parts"] || issued["date-parts"].length === 0) {
211
- return void 0;
212
- }
213
- const firstDate = issued["date-parts"][0];
214
- return firstDate && firstDate.length > 0 ? firstDate[0] : void 0;
215
- }
216
- /**
217
- * Get the DOI
218
- */
219
- getDoi() {
220
- return this.item.DOI;
221
- }
222
- /**
223
- * Get the PMID
224
- */
225
- getPmid() {
226
- return this.item.PMID;
227
- }
228
- /**
229
- * Get the PMCID
230
- */
231
- getPmcid() {
232
- return this.item.PMCID;
233
- }
234
- /**
235
- * Get the URL
236
- */
237
- getUrl() {
238
- return this.item.URL;
239
- }
240
- /**
241
- * Get the keyword
242
- */
243
- getKeyword() {
244
- return this.item.keyword;
245
- }
246
- /**
247
- * Get additional URLs from custom metadata
248
- */
249
- getAdditionalUrls() {
250
- return this.item.custom?.additional_urls;
251
- }
252
- /**
253
- * Get the creation timestamp from custom metadata (immutable)
254
- */
255
- getCreatedAt() {
256
- if (!this.item.custom?.created_at) {
257
- throw new Error("created_at is missing from custom metadata");
258
- }
259
- return this.item.custom.created_at;
260
- }
261
- /**
262
- * Get the last modification timestamp from custom metadata
263
- */
264
- getTimestamp() {
265
- if (!this.item.custom?.timestamp) {
266
- throw new Error("timestamp is missing from custom metadata");
267
- }
268
- return this.item.custom.timestamp;
269
- }
270
- /**
271
- * Update the timestamp to current time
272
- * Call this whenever the reference is modified
273
- */
274
- touch() {
275
- if (!this.item.custom) {
276
- throw new Error("custom metadata is missing");
277
- }
278
- this.item.custom.timestamp = (/* @__PURE__ */ new Date()).toISOString();
279
- }
280
- /**
281
- * Get the type
282
- */
283
- getType() {
284
- return this.item.type;
285
- }
286
- }
287
- function computeHash(input) {
288
- return createHash("sha256").update(input, "utf-8").digest("hex");
289
- }
290
- async function computeFileHash(filePath) {
291
- return new Promise((resolve, reject) => {
292
- const hash = createHash("sha256");
293
- const stream = createReadStream(filePath);
294
- stream.on("data", (chunk) => hash.update(chunk));
295
- stream.on("end", () => resolve(hash.digest("hex")));
296
- stream.on("error", (error) => reject(error));
297
- });
298
- }
299
- function parseKeyword(keyword) {
300
- if (typeof keyword !== "string") {
301
- return void 0;
302
- }
303
- if (keyword.trim() === "") {
304
- return void 0;
305
- }
306
- const keywords = keyword.split(";").map((k) => k.trim()).filter((k) => k !== "");
307
- return keywords.length > 0 ? keywords : void 0;
308
- }
309
- async function parseCslJson(filePath) {
310
- const content = await readFile(filePath, "utf-8");
311
- let rawData;
312
- try {
313
- rawData = JSON.parse(content);
314
- } catch (error) {
315
- throw new Error(
316
- `Failed to parse JSON: ${error instanceof Error ? error.message : String(error)}`
317
- );
318
- }
319
- if (Array.isArray(rawData)) {
320
- rawData = rawData.map((item) => {
321
- if (item && typeof item === "object" && "keyword" in item) {
322
- const itemWithKeyword = item;
323
- return {
324
- ...itemWithKeyword,
325
- keyword: parseKeyword(itemWithKeyword.keyword)
326
- };
327
- }
328
- return item;
329
- });
330
- }
331
- const parseResult = CslLibrarySchema.safeParse(rawData);
332
- if (!parseResult.success) {
333
- throw new Error(`Invalid CSL-JSON structure: ${parseResult.error.message}`);
334
- }
335
- const library = parseResult.data;
336
- const processedLibrary = library.map((item) => {
337
- const updatedCustom = ensureCustomMetadata(item.custom);
338
- return {
339
- ...item,
340
- custom: updatedCustom
341
- };
342
- });
343
- return processedLibrary;
344
- }
345
- function serializeKeyword(keywords) {
346
- if (!keywords || keywords.length === 0) {
347
- return void 0;
348
- }
349
- return keywords.join("; ");
350
- }
351
- function serializeCslJson(library) {
352
- const libraryForJson = library.map((item) => {
353
- const { keyword, ...rest } = item;
354
- const serializedKeyword = serializeKeyword(keyword);
355
- if (serializedKeyword === void 0) {
356
- return rest;
357
- }
358
- return {
359
- ...rest,
360
- keyword: serializedKeyword
361
- };
362
- });
363
- return JSON.stringify(libraryForJson, null, 2);
364
- }
365
- async function writeCslJson(filePath, library) {
366
- const dir = dirname(filePath);
367
- await mkdir(dir, { recursive: true });
368
- const content = serializeCslJson(library);
369
- await writeFile(filePath, content, "utf-8");
370
- }
371
- class Library {
372
- filePath;
373
- references = [];
374
- currentHash = null;
375
- // Indices for fast lookup
376
- uuidIndex = /* @__PURE__ */ new Map();
377
- idIndex = /* @__PURE__ */ new Map();
378
- doiIndex = /* @__PURE__ */ new Map();
379
- pmidIndex = /* @__PURE__ */ new Map();
380
- constructor(filePath, items) {
381
- this.filePath = filePath;
382
- for (const item of items) {
383
- const ref = new Reference(item);
384
- this.references.push(ref);
385
- this.addToIndices(ref);
386
- }
387
- }
388
- /**
389
- * Load library from file
390
- */
391
- static async load(filePath) {
392
- const items = await parseCslJson(filePath);
393
- const library = new Library(filePath, items);
394
- library.currentHash = await computeFileHash(filePath);
395
- return library;
396
- }
397
- /**
398
- * Save library to file
399
- */
400
- async save() {
401
- const items = this.references.map((ref) => ref.getItem());
402
- await writeCslJson(this.filePath, items);
403
- this.currentHash = await computeFileHash(this.filePath);
404
- }
405
- /**
406
- * Add a reference to the library
407
- */
408
- add(item) {
409
- const existingIds = new Set(this.references.map((ref2) => ref2.getId()));
410
- const ref = Reference.create(item, { existingIds });
411
- this.references.push(ref);
412
- this.addToIndices(ref);
413
- }
414
- /**
415
- * Remove a reference by UUID
416
- */
417
- removeByUuid(uuid) {
418
- const ref = this.uuidIndex.get(uuid);
419
- if (!ref) {
420
- return false;
421
- }
422
- return this.removeReference(ref);
423
- }
424
- /**
425
- * Remove a reference by ID
426
- */
427
- removeById(id) {
428
- const ref = this.idIndex.get(id);
429
- if (!ref) {
430
- return false;
431
- }
432
- return this.removeReference(ref);
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
- }
460
- /**
461
- * Find a reference by UUID
462
- */
463
- findByUuid(uuid) {
464
- return this.uuidIndex.get(uuid);
465
- }
466
- /**
467
- * Find a reference by ID
468
- */
469
- findById(id) {
470
- return this.idIndex.get(id);
471
- }
472
- /**
473
- * Find a reference by DOI
474
- */
475
- findByDoi(doi) {
476
- return this.doiIndex.get(doi);
477
- }
478
- /**
479
- * Find a reference by PMID
480
- */
481
- findByPmid(pmid) {
482
- return this.pmidIndex.get(pmid);
483
- }
484
- /**
485
- * Get all references
486
- */
487
- getAll() {
488
- return [...this.references];
489
- }
490
- /**
491
- * Get the file path
492
- */
493
- getFilePath() {
494
- return this.filePath;
495
- }
496
- /**
497
- * Get the current file hash
498
- * Returns null if the library has not been loaded or saved yet
499
- */
500
- getCurrentHash() {
501
- return this.currentHash;
502
- }
503
- /**
504
- * Add reference to all indices
505
- */
506
- addToIndices(ref) {
507
- this.uuidIndex.set(ref.getUuid(), ref);
508
- this.idIndex.set(ref.getId(), ref);
509
- const doi = ref.getDoi();
510
- if (doi) {
511
- this.doiIndex.set(doi, ref);
512
- }
513
- const pmid = ref.getPmid();
514
- if (pmid) {
515
- this.pmidIndex.set(pmid, ref);
516
- }
517
- }
518
- /**
519
- * Remove reference from all indices and array
520
- */
521
- removeReference(ref) {
522
- const index = this.references.indexOf(ref);
523
- if (index === -1) {
524
- return false;
525
- }
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) {
603
- this.uuidIndex.delete(ref.getUuid());
604
- this.idIndex.delete(ref.getId());
605
- const doi = ref.getDoi();
606
- if (doi) {
607
- this.doiIndex.delete(doi);
608
- }
609
- const pmid = ref.getPmid();
610
- if (pmid) {
611
- this.pmidIndex.delete(pmid);
612
- }
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;
643
- }
644
- }
645
- const logLevelSchema = z.enum(["silent", "info", "debug"]);
646
- const backupConfigSchema = z.object({
647
- maxGenerations: z.number().int().positive(),
648
- maxAgeDays: z.number().int().positive(),
649
- directory: z.string().min(1)
650
- });
651
- const watchConfigSchema = z.object({
652
- enabled: z.boolean(),
653
- debounceMs: z.number().int().nonnegative(),
654
- pollIntervalMs: z.number().int().positive(),
655
- retryIntervalMs: z.number().int().positive(),
656
- maxRetries: z.number().int().nonnegative()
657
- });
658
- const serverConfigSchema = z.object({
659
- autoStart: z.boolean(),
660
- autoStopMinutes: z.number().int().nonnegative()
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
- });
676
- const configSchema = z.object({
677
- library: z.string().min(1),
678
- logLevel: logLevelSchema,
679
- backup: backupConfigSchema,
680
- watch: watchConfigSchema,
681
- server: serverConfigSchema,
682
- citation: citationConfigSchema,
683
- pubmed: pubmedConfigSchema,
684
- fulltext: fulltextConfigSchema
685
- });
686
- const partialConfigSchema = z.object({
687
- library: z.string().min(1).optional(),
688
- logLevel: logLevelSchema.optional(),
689
- log_level: logLevelSchema.optional(),
690
- // snake_case support
691
- backup: z.object({
692
- maxGenerations: z.number().int().positive().optional(),
693
- max_generations: z.number().int().positive().optional(),
694
- maxAgeDays: z.number().int().positive().optional(),
695
- max_age_days: z.number().int().positive().optional(),
696
- directory: z.string().min(1).optional()
697
- }).optional(),
698
- watch: z.object({
699
- enabled: z.boolean().optional(),
700
- debounceMs: z.number().int().nonnegative().optional(),
701
- debounce_ms: z.number().int().nonnegative().optional(),
702
- pollIntervalMs: z.number().int().positive().optional(),
703
- poll_interval_ms: z.number().int().positive().optional(),
704
- retryIntervalMs: z.number().int().positive().optional(),
705
- retry_interval_ms: z.number().int().positive().optional(),
706
- maxRetries: z.number().int().nonnegative().optional(),
707
- max_retries: z.number().int().nonnegative().optional()
708
- }).optional(),
709
- server: z.object({
710
- autoStart: z.boolean().optional(),
711
- auto_start: z.boolean().optional(),
712
- autoStopMinutes: z.number().int().nonnegative().optional(),
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()
732
- }).optional()
733
- }).passthrough();
734
- function normalizeBackupConfig(backup) {
735
- const normalized = {};
736
- const maxGenerations = backup.maxGenerations ?? backup.max_generations;
737
- if (maxGenerations !== void 0) {
738
- normalized.maxGenerations = maxGenerations;
739
- }
740
- const maxAgeDays = backup.maxAgeDays ?? backup.max_age_days;
741
- if (maxAgeDays !== void 0) {
742
- normalized.maxAgeDays = maxAgeDays;
743
- }
744
- if (backup.directory !== void 0) {
745
- normalized.directory = backup.directory;
746
- }
747
- return Object.keys(normalized).length > 0 ? normalized : void 0;
748
- }
749
- function normalizeWatchConfig(watch) {
750
- const normalized = {};
751
- if (watch.enabled !== void 0) {
752
- normalized.enabled = watch.enabled;
753
- }
754
- const debounceMs = watch.debounceMs ?? watch.debounce_ms;
755
- if (debounceMs !== void 0) {
756
- normalized.debounceMs = debounceMs;
757
- }
758
- const pollIntervalMs = watch.pollIntervalMs ?? watch.poll_interval_ms;
759
- if (pollIntervalMs !== void 0) {
760
- normalized.pollIntervalMs = pollIntervalMs;
761
- }
762
- const retryIntervalMs = watch.retryIntervalMs ?? watch.retry_interval_ms;
763
- if (retryIntervalMs !== void 0) {
764
- normalized.retryIntervalMs = retryIntervalMs;
765
- }
766
- const maxRetries = watch.maxRetries ?? watch.max_retries;
767
- if (maxRetries !== void 0) {
768
- normalized.maxRetries = maxRetries;
769
- }
770
- return Object.keys(normalized).length > 0 ? normalized : void 0;
771
- }
772
- function normalizeServerConfig(server) {
773
- const normalized = {};
774
- const autoStart = server.autoStart ?? server.auto_start;
775
- if (autoStart !== void 0) {
776
- normalized.autoStart = autoStart;
777
- }
778
- const autoStopMinutes = server.autoStopMinutes ?? server.auto_stop_minutes;
779
- if (autoStopMinutes !== void 0) {
780
- normalized.autoStopMinutes = autoStopMinutes;
781
- }
782
- return Object.keys(normalized).length > 0 ? normalized : void 0;
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
- }
815
- function normalizePartialConfig(partial) {
816
- const normalized = {};
817
- if (partial.library !== void 0) {
818
- normalized.library = partial.library;
819
- }
820
- const logLevel = partial.logLevel ?? partial.log_level;
821
- if (logLevel !== void 0) {
822
- normalized.logLevel = logLevel;
823
- }
824
- const backup = partial.backup !== void 0 ? normalizeBackupConfig(partial.backup) : void 0;
825
- if (backup) {
826
- normalized.backup = backup;
827
- }
828
- const watch = partial.watch !== void 0 ? normalizeWatchConfig(partial.watch) : void 0;
829
- if (watch) {
830
- normalized.watch = watch;
831
- }
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;
847
- }
848
- return normalized;
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
- }
857
- function getDefaultBackupDirectory() {
858
- return join(tmpdir(), "reference-manager", "backups");
859
- }
860
- function getDefaultLibraryPath() {
861
- return join(homedir(), ".reference-manager", "csl.library.json");
862
- }
863
- function getDefaultUserConfigPath() {
864
- return join(homedir(), ".reference-manager", "config.toml");
865
- }
866
- function getDefaultCurrentDirConfigFilename() {
867
- return ".reference-manager.config.toml";
868
- }
869
- function getDefaultCslDirectory() {
870
- return join(homedir(), ".reference-manager", "csl");
871
- }
872
- function getDefaultFulltextDirectory() {
873
- return join(homedir(), ".reference-manager", "fulltext");
874
- }
875
- const defaultConfig = {
876
- library: getDefaultLibraryPath(),
877
- logLevel: "info",
878
- backup: {
879
- maxGenerations: 50,
880
- maxAgeDays: 365,
881
- directory: getDefaultBackupDirectory()
882
- },
883
- watch: {
884
- enabled: true,
885
- debounceMs: 500,
886
- pollIntervalMs: 5e3,
887
- retryIntervalMs: 200,
888
- maxRetries: 10
889
- },
890
- server: {
891
- autoStart: false,
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()
906
- }
907
- };
908
- function loadTOMLFile(path) {
909
- if (!existsSync(path)) {
910
- return null;
911
- }
912
- try {
913
- const content = readFileSync(path, "utf-8");
914
- const parsed = parse(content);
915
- const validated = partialConfigSchema.parse(parsed);
916
- return validated;
917
- } catch (error) {
918
- throw new Error(
919
- `Failed to load config from ${path}: ${error instanceof Error ? error.message : String(error)}`
920
- );
921
- }
922
- }
923
- function mergeConfigs(base, ...overrides) {
924
- const result = { ...base };
925
- const sectionKeys = ["backup", "watch", "server", "citation", "pubmed", "fulltext"];
926
- for (const override of overrides) {
927
- if (!override) continue;
928
- if (override.library !== void 0) {
929
- result.library = override.library;
930
- }
931
- if (override.logLevel !== void 0) {
932
- result.logLevel = override.logLevel;
933
- }
934
- for (const key of sectionKeys) {
935
- if (override[key] !== void 0) {
936
- result[key] = {
937
- ...result[key],
938
- ...override[key]
939
- };
940
- }
941
- }
942
- }
943
- return result;
944
- }
945
- function fillDefaults(partial) {
946
- return {
947
- library: partial.library ?? defaultConfig.library,
948
- logLevel: partial.logLevel ?? defaultConfig.logLevel,
949
- backup: {
950
- maxGenerations: partial.backup?.maxGenerations ?? defaultConfig.backup.maxGenerations,
951
- maxAgeDays: partial.backup?.maxAgeDays ?? defaultConfig.backup.maxAgeDays,
952
- directory: partial.backup?.directory ?? defaultConfig.backup.directory
953
- },
954
- watch: {
955
- enabled: partial.watch?.enabled ?? defaultConfig.watch.enabled,
956
- debounceMs: partial.watch?.debounceMs ?? defaultConfig.watch.debounceMs,
957
- pollIntervalMs: partial.watch?.pollIntervalMs ?? defaultConfig.watch.pollIntervalMs,
958
- retryIntervalMs: partial.watch?.retryIntervalMs ?? defaultConfig.watch.retryIntervalMs,
959
- maxRetries: partial.watch?.maxRetries ?? defaultConfig.watch.maxRetries
960
- },
961
- server: {
962
- autoStart: partial.server?.autoStart ?? defaultConfig.server.autoStart,
963
- autoStopMinutes: partial.server?.autoStopMinutes ?? defaultConfig.server.autoStopMinutes
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)
997
- };
998
- }
999
- function loadConfig(options = {}) {
1000
- const cwd = options.cwd ?? process.cwd();
1001
- const userConfigPath = options.userConfigPath ?? getDefaultUserConfigPath();
1002
- const userConfig = loadTOMLFile(userConfigPath);
1003
- const envConfigPath = process.env.REFERENCE_MANAGER_CONFIG;
1004
- const envConfig = envConfigPath ? loadTOMLFile(envConfigPath) : null;
1005
- const currentConfigPath = join(cwd, getDefaultCurrentDirConfigFilename());
1006
- const currentConfig = loadTOMLFile(currentConfigPath);
1007
- const normalizedUser = userConfig ? normalizePartialConfig(userConfig) : null;
1008
- const normalizedEnv = envConfig ? normalizePartialConfig(envConfig) : null;
1009
- const normalizedCurrent = currentConfig ? normalizePartialConfig(currentConfig) : null;
1010
- const merged = mergeConfigs(
1011
- {},
1012
- normalizedUser,
1013
- normalizedEnv,
1014
- normalizedCurrent,
1015
- options.overrides
1016
- );
1017
- const config = fillDefaults(merged);
1018
- try {
1019
- return configSchema.parse(config);
1020
- } catch (error) {
1021
- throw new Error(
1022
- `Invalid configuration: ${error instanceof Error ? error.message : String(error)}`
1023
- );
1024
- }
1025
- }
1026
- export {
1027
- Library as L,
1028
- Reference as R,
1029
- computeHash as a,
1030
- backupConfigSchema as b,
1031
- computeFileHash as c,
1032
- configSchema as d,
1033
- defaultConfig as e,
1034
- getDefaultCurrentDirConfigFilename as f,
1035
- getDefaultBackupDirectory as g,
1036
- getDefaultLibraryPath as h,
1037
- getDefaultUserConfigPath as i,
1038
- logLevelSchema as j,
1039
- parseCslJson as k,
1040
- loadConfig as l,
1041
- writeCslJson as m,
1042
- normalizePartialConfig as n,
1043
- generateId as o,
1044
- partialConfigSchema as p,
1045
- generateIdWithCollisionCheck as q,
1046
- normalizeText as r,
1047
- serializeCslJson as s,
1048
- generateUuid as t,
1049
- isValidUuid as u,
1050
- ensureCustomMetadata as v,
1051
- watchConfigSchema as w,
1052
- extractUuidFromCustom as x
1053
- };
1054
- //# sourceMappingURL=loader-mQ25o6cV.js.map