@ncukondo/reference-manager 0.20.0 → 0.22.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 (57) hide show
  1. package/dist/chunks/{action-menu-Brg5Lmz7.js → action-menu-B1DCdkkp.js} +4 -3
  2. package/dist/chunks/{action-menu-Brg5Lmz7.js.map → action-menu-B1DCdkkp.js.map} +1 -1
  3. package/dist/chunks/{format-CYO99JV-.js → format-CfTZqhUx.js} +2 -2
  4. package/dist/chunks/{format-CYO99JV-.js.map → format-CfTZqhUx.js.map} +1 -1
  5. package/dist/chunks/index-BK3hTiKR.js +5929 -0
  6. package/dist/chunks/index-BK3hTiKR.js.map +1 -0
  7. package/dist/chunks/{index-D--7n1SB.js → index-BvIfOciH.js} +8 -5
  8. package/dist/chunks/index-BvIfOciH.js.map +1 -0
  9. package/dist/chunks/{index-BR5tlrNU.js → index-D7eiOplw.js} +3 -2
  10. package/dist/chunks/index-D7eiOplw.js.map +1 -0
  11. package/dist/chunks/{index-Cno7_aWr.js → index-DOvEusHb.js} +447 -426
  12. package/dist/chunks/index-DOvEusHb.js.map +1 -0
  13. package/dist/chunks/{loader-BtW20O32.js → loader-BHvcats5.js} +113 -27
  14. package/dist/chunks/loader-BHvcats5.js.map +1 -0
  15. package/dist/chunks/{reference-select-DrINWBuP.js → reference-select-DTqhevya.js} +3 -3
  16. package/dist/chunks/{reference-select-DrINWBuP.js.map → reference-select-DTqhevya.js.map} +1 -1
  17. package/dist/chunks/{style-select-DxcSWBSF.js → style-select-CXclvgJO.js} +3 -3
  18. package/dist/chunks/{style-select-DxcSWBSF.js.map → style-select-CXclvgJO.js.map} +1 -1
  19. package/dist/cli/commands/add.d.ts +20 -0
  20. package/dist/cli/commands/add.d.ts.map +1 -1
  21. package/dist/cli/commands/fulltext.d.ts +86 -3
  22. package/dist/cli/commands/fulltext.d.ts.map +1 -1
  23. package/dist/cli/index.d.ts.map +1 -1
  24. package/dist/cli.js +1 -1
  25. package/dist/config/defaults.d.ts.map +1 -1
  26. package/dist/config/env-override.d.ts.map +1 -1
  27. package/dist/config/key-parser.d.ts.map +1 -1
  28. package/dist/config/loader.d.ts.map +1 -1
  29. package/dist/config/schema.d.ts +65 -0
  30. package/dist/config/schema.d.ts.map +1 -1
  31. package/dist/features/format/pretty.d.ts.map +1 -1
  32. package/dist/features/format/resource-indicators.d.ts +8 -0
  33. package/dist/features/format/resource-indicators.d.ts.map +1 -0
  34. package/dist/features/interactive/apps/runSearchFlow.d.ts +5 -0
  35. package/dist/features/interactive/apps/runSearchFlow.d.ts.map +1 -1
  36. package/dist/features/operations/fulltext/convert.d.ts +22 -0
  37. package/dist/features/operations/fulltext/convert.d.ts.map +1 -0
  38. package/dist/features/operations/fulltext/discover.d.ts +35 -0
  39. package/dist/features/operations/fulltext/discover.d.ts.map +1 -0
  40. package/dist/features/operations/fulltext/fetch.d.ts +34 -0
  41. package/dist/features/operations/fulltext/fetch.d.ts.map +1 -0
  42. package/dist/features/operations/fulltext/index.d.ts +3 -0
  43. package/dist/features/operations/fulltext/index.d.ts.map +1 -1
  44. package/dist/index.js +1 -1
  45. package/dist/mcp/tools/fulltext.d.ts +37 -0
  46. package/dist/mcp/tools/fulltext.d.ts.map +1 -1
  47. package/dist/mcp/tools/index.d.ts.map +1 -1
  48. package/dist/server/routes/references.d.ts +3 -1
  49. package/dist/server/routes/references.d.ts.map +1 -1
  50. package/dist/server.js +2 -2
  51. package/package.json +2 -1
  52. package/dist/chunks/index-BR5tlrNU.js.map +0 -1
  53. package/dist/chunks/index-Cno7_aWr.js.map +0 -1
  54. package/dist/chunks/index-D--7n1SB.js.map +0 -1
  55. package/dist/chunks/index-DrZawbND.js +0 -2082
  56. package/dist/chunks/index-DrZawbND.js.map +0 -1
  57. package/dist/chunks/loader-BtW20O32.js.map +0 -1
@@ -1,14 +1,13 @@
1
1
  import { Command } from "commander";
2
2
  import { i as isEqual, M as MANAGED_CUSTOM_FIELDS, L as Library, g as CslItemSchema, p as pickDefined, a as sortOrderSchema, x as paginationOptionsSchema, F as FileWatcher, b as sortFieldSchema, v as searchSortFieldSchema } from "./file-watcher-CrsNHUpz.js";
3
3
  import * as fs from "node:fs";
4
- import { promises, readFileSync, existsSync, writeFileSync, mkdirSync, mkdtempSync } from "node:fs";
4
+ import { promises, readFileSync, existsSync, writeFileSync, mkdirSync } from "node:fs";
5
5
  import * as os from "node:os";
6
- import { tmpdir } from "node:os";
7
6
  import * as path from "node:path";
8
- import path__default, { extname, join, basename, dirname } from "node:path";
9
- import { g as getExtension, i as isValidFulltextFiles, a as isReservedRole, R as RESERVED_ROLES, F as FULLTEXT_ROLE, b as formatToExtension, c as findFulltextFiles, d as findFulltextFile, e as extensionToFormat, B as BUILTIN_STYLES, h as getFulltextAttachmentTypes, s as startServerWithFileWatcher } from "./index-DrZawbND.js";
10
- import fs__default, { stat, rename, copyFile, readFile, unlink, readdir, mkdir, rm } from "node:fs/promises";
11
- import { o as openWithSystemApp, l as loadConfig, e as getDefaultCurrentDirConfigFilename, h as getDefaultUserConfigPath } from "./loader-BtW20O32.js";
7
+ import path__default, { join, basename, dirname } from "node:path";
8
+ import { n as normalizePathForOutput, d as deleteDirectoryIfEmpty, p as parseFilename, i as isReservedRole, e as ensureDirectory, a as addAttachment, R as RESERVED_ROLES, g as generateFilename, b as findFulltextFiles, c as findFulltextFile, h as extensionToFormat, j as fulltextAttach, k as fulltextGet, l as fulltextDiscover, m as fulltextFetch, o as fulltextConvert, q as getExtension, B as BUILTIN_STYLES, r as getFulltextAttachmentTypes, s as startServerWithFileWatcher } from "./index-BK3hTiKR.js";
9
+ import { readFile, unlink, stat, readdir, rename, mkdir } from "node:fs/promises";
10
+ import { o as openWithSystemApp, l as loadConfig, e as getDefaultCurrentDirConfigFilename, h as getDefaultUserConfigPath } from "./loader-BHvcats5.js";
12
11
  import { spawn, spawnSync } from "node:child_process";
13
12
  import process$1, { stdin, stdout } from "node:process";
14
13
  import * as readline from "node:readline";
@@ -20,7 +19,7 @@ import "@citation-js/plugin-csl";
20
19
  import { ZodOptional as ZodOptional$2, z } from "zod";
21
20
  import { serve } from "@hono/node-server";
22
21
  const name = "@ncukondo/reference-manager";
23
- const version$1 = "0.20.0";
22
+ const version$1 = "0.22.0";
24
23
  const description$1 = "A local reference management tool using CSL-JSON as the single source of truth";
25
24
  const packageJson = {
26
25
  name,
@@ -414,216 +413,28 @@ function getExitCode(result) {
414
413
  }
415
414
  return 0;
416
415
  }
417
- function slugifyLabel(label) {
418
- return label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
419
- }
420
- function generateFilename(role, ext, label) {
421
- if (label) {
422
- const slug = slugifyLabel(label);
423
- return `${role}-${slug}.${ext}`;
424
- }
425
- return `${role}.${ext}`;
426
- }
427
- function parseFilename(filename) {
428
- if (!filename) {
429
- return null;
430
- }
431
- const ext = path__default.extname(filename);
432
- const extWithoutDot = ext.startsWith(".") ? ext.slice(1) : ext;
433
- const basename2 = ext ? filename.slice(0, -ext.length) : filename;
434
- const firstHyphenIndex = basename2.indexOf("-");
435
- if (firstHyphenIndex === -1) {
436
- return {
437
- role: basename2,
438
- ext: extWithoutDot
439
- };
440
- }
441
- const role = basename2.slice(0, firstHyphenIndex);
442
- const label = basename2.slice(firstHyphenIndex + 1);
443
- if (label) {
444
- return {
445
- role,
446
- ext: extWithoutDot,
447
- label
448
- };
449
- }
450
- return {
451
- role,
452
- ext: extWithoutDot
453
- };
454
- }
455
- function normalizePathForOutput(p) {
456
- return p.replace(/\\/g, "/");
457
- }
458
- function extractUuidPrefix(uuid2) {
459
- const normalized = uuid2.replace(/-/g, "");
460
- return normalized.slice(0, 8);
461
- }
462
- function generateDirectoryName(ref2) {
463
- const uuid2 = ref2.custom?.uuid;
464
- if (!uuid2) {
465
- throw new Error("Reference must have custom.uuid");
466
- }
467
- const uuidPrefix = extractUuidPrefix(uuid2);
468
- const pmid = ref2.PMID?.trim();
469
- if (pmid) {
470
- return `${ref2.id}-PMID${pmid}-${uuidPrefix}`;
471
- }
472
- return `${ref2.id}-${uuidPrefix}`;
473
- }
474
- function getDirectoryPath(ref2, baseDir) {
475
- const existingDir = ref2.custom?.attachments?.directory;
476
- if (existingDir) {
477
- return normalizePathForOutput(path__default.join(baseDir, existingDir));
478
- }
479
- const dirName = generateDirectoryName(ref2);
480
- return normalizePathForOutput(path__default.join(baseDir, dirName));
481
- }
482
- async function ensureDirectory(ref2, baseDir) {
483
- const dirPath = getDirectoryPath(ref2, baseDir);
484
- try {
485
- await fs__default.mkdir(dirPath, { recursive: true });
486
- } catch (error) {
487
- if (error.code !== "EEXIST") {
488
- throw error;
489
- }
490
- }
491
- return dirPath;
492
- }
493
- async function deleteDirectoryIfEmpty(dirPath) {
494
- try {
495
- const files = await fs__default.readdir(dirPath);
496
- if (files.length === 0) {
497
- await fs__default.rmdir(dirPath);
498
- }
499
- } catch (error) {
500
- if (error.code !== "ENOENT") {
501
- throw error;
502
- }
503
- }
504
- }
505
- async function checkSourceFile(filePath) {
506
- try {
507
- await stat(filePath);
508
- return null;
509
- } catch {
510
- return `Source file not found: ${filePath}`;
511
- }
512
- }
513
- function validateFulltextConstraint(existingFiles, newFile) {
514
- if (newFile.role !== "fulltext") {
515
- return null;
516
- }
517
- const newExt = getExtension(newFile.filename);
518
- const existingFulltexts = existingFiles.filter((f) => f.role === "fulltext");
519
- for (const existing of existingFulltexts) {
520
- const existingExt = getExtension(existing.filename);
521
- if (existingExt === newExt) {
522
- return `A fulltext ${newExt.toUpperCase()} already exists. Use --force to overwrite.`;
523
- }
524
- }
525
- const testFiles = [...existingFiles, newFile];
526
- if (!isValidFulltextFiles(testFiles)) {
527
- return "fulltext role allows max 2 files (1 PDF + 1 Markdown)";
528
- }
529
- return null;
530
- }
531
- function findExistingFile(files, filename) {
532
- return files.find((f) => f.filename === filename);
533
- }
534
- async function copyOrMoveFile(sourcePath, destPath, move) {
535
- try {
536
- if (move) {
537
- await rename(sourcePath, destPath);
538
- } else {
539
- await copyFile(sourcePath, destPath);
540
- }
541
- return null;
542
- } catch (error) {
543
- return `Failed to ${move ? "move" : "copy"} file: ${error instanceof Error ? error.message : String(error)}`;
544
- }
545
- }
546
- async function updateAttachmentMetadata$1(library, item, updatedAttachments) {
547
- await library.update(item.id, {
548
- custom: {
549
- ...item.custom,
550
- attachments: updatedAttachments
551
- }
552
- });
553
- }
554
- function buildUpdatedFiles$1(existingFiles, newFile, existingFile) {
555
- if (existingFile) {
556
- return existingFiles.map((f) => f.filename === newFile.filename ? newFile : f);
557
- }
558
- return [...existingFiles, newFile];
559
- }
560
- async function addAttachment(library, options) {
561
- const {
562
- identifier,
563
- filePath,
564
- role,
565
- label,
566
- move = false,
567
- force = false,
568
- idType = "id",
569
- attachmentsDirectory
570
- } = options;
571
- const item = await library.find(identifier, { idType });
572
- if (!item) {
573
- return { success: false, error: `Reference '${identifier}' not found` };
574
- }
575
- const uuid2 = item.custom?.uuid;
576
- if (!uuid2) {
577
- return { success: false, error: "Reference has no UUID. Cannot create attachment directory." };
578
- }
579
- const sourceError = await checkSourceFile(filePath);
580
- if (sourceError) {
581
- return { success: false, error: sourceError };
416
+ async function autoFetchFulltext(addedItems, context, options) {
417
+ if (addedItems.length === 0) {
418
+ return [];
582
419
  }
583
- const ext = extname(filePath).slice(1).toLowerCase();
584
- const filename = generateFilename(role, ext, label);
585
- const existingAttachments = item.custom?.attachments;
586
- const existingFiles = existingAttachments?.files ?? [];
587
- const newFile = {
588
- filename,
589
- role,
590
- ...label && { label }
591
- };
592
- const existingFile = findExistingFile(existingFiles, filename);
593
- if (!existingFile || !force) {
594
- const constraintError = validateFulltextConstraint(existingFiles, newFile);
595
- if (constraintError) {
596
- return { success: false, error: constraintError };
420
+ const results = [];
421
+ for (const item of addedItems) {
422
+ try {
423
+ const result = await options.fulltextFetchFn(context.library, {
424
+ identifier: item.id,
425
+ idType: "id",
426
+ fulltextConfig: options.fulltextConfig,
427
+ fulltextDirectory: options.fulltextDirectory
428
+ });
429
+ results.push(result);
430
+ } catch (error) {
431
+ results.push({
432
+ success: false,
433
+ error: error instanceof Error ? error.message : String(error)
434
+ });
597
435
  }
598
436
  }
599
- if (existingFile && !force) {
600
- return {
601
- success: false,
602
- existingFile: filename,
603
- requiresConfirmation: true
604
- };
605
- }
606
- const ref2 = item;
607
- const dirPath = await ensureDirectory(ref2, attachmentsDirectory);
608
- const dirName = existingAttachments?.directory ?? generateDirectoryName(ref2);
609
- const destPath = join(dirPath, filename);
610
- const copyError = await copyOrMoveFile(filePath, destPath, move);
611
- if (copyError) {
612
- return { success: false, error: copyError };
613
- }
614
- const updatedFiles = buildUpdatedFiles$1(existingFiles, newFile, existingFile);
615
- const updatedAttachments = {
616
- directory: dirName,
617
- files: updatedFiles
618
- };
619
- await updateAttachmentMetadata$1(library, item, updatedAttachments);
620
- await library.save();
621
- return {
622
- success: true,
623
- filename,
624
- directory: dirName,
625
- overwritten: !!existingFile
626
- };
437
+ return results;
627
438
  }
628
439
  async function listAttachments(library, options) {
629
440
  const { identifier, idType = "id", role } = options;
@@ -1093,15 +904,15 @@ class OperationsLibrary {
1093
904
  }
1094
905
  // High-level operations
1095
906
  async search(options) {
1096
- const { searchReferences } = await import("./index-DrZawbND.js").then((n) => n.n);
907
+ const { searchReferences } = await import("./index-BK3hTiKR.js").then((n) => n.z);
1097
908
  return searchReferences(this.library, options);
1098
909
  }
1099
910
  async list(options) {
1100
- const { listReferences } = await import("./index-DrZawbND.js").then((n) => n.m);
911
+ const { listReferences } = await import("./index-BK3hTiKR.js").then((n) => n.y);
1101
912
  return listReferences(this.library, options ?? {});
1102
913
  }
1103
914
  async cite(options) {
1104
- const { citeReferences } = await import("./index-DrZawbND.js").then((n) => n.l);
915
+ const { citeReferences } = await import("./index-BK3hTiKR.js").then((n) => n.x);
1105
916
  const defaultStyle = options.defaultStyle ?? this.citationConfig?.defaultStyle;
1106
917
  const cslDirectory = options.cslDirectory ?? this.citationConfig?.cslDirectory;
1107
918
  const mergedOptions = {
@@ -1112,32 +923,32 @@ class OperationsLibrary {
1112
923
  return citeReferences(this.library, mergedOptions);
1113
924
  }
1114
925
  async import(inputs, options) {
1115
- const { addReferences } = await import("./index-DrZawbND.js").then((n) => n.k);
926
+ const { addReferences } = await import("./index-BK3hTiKR.js").then((n) => n.w);
1116
927
  return addReferences(inputs, this.library, options ?? {});
1117
928
  }
1118
929
  // Attachment operations
1119
930
  async attachAdd(options) {
1120
- const { addAttachment: addAttachment2 } = await import("./index-BR5tlrNU.js");
931
+ const { addAttachment: addAttachment2 } = await import("./index-D7eiOplw.js");
1121
932
  return addAttachment2(this.library, options);
1122
933
  }
1123
934
  async attachList(options) {
1124
- const { listAttachments: listAttachments2 } = await import("./index-BR5tlrNU.js");
935
+ const { listAttachments: listAttachments2 } = await import("./index-D7eiOplw.js");
1125
936
  return listAttachments2(this.library, options);
1126
937
  }
1127
938
  async attachGet(options) {
1128
- const { getAttachment: getAttachment2 } = await import("./index-BR5tlrNU.js");
939
+ const { getAttachment: getAttachment2 } = await import("./index-D7eiOplw.js");
1129
940
  return getAttachment2(this.library, options);
1130
941
  }
1131
942
  async attachDetach(options) {
1132
- const { detachAttachment: detachAttachment2 } = await import("./index-BR5tlrNU.js");
943
+ const { detachAttachment: detachAttachment2 } = await import("./index-D7eiOplw.js");
1133
944
  return detachAttachment2(this.library, options);
1134
945
  }
1135
946
  async attachSync(options) {
1136
- const { syncAttachments: syncAttachments2 } = await import("./index-BR5tlrNU.js");
947
+ const { syncAttachments: syncAttachments2 } = await import("./index-D7eiOplw.js");
1137
948
  return syncAttachments2(this.library, options);
1138
949
  }
1139
950
  async attachOpen(options) {
1140
- const { openAttachment: openAttachment2 } = await import("./index-BR5tlrNU.js");
951
+ const { openAttachment: openAttachment2 } = await import("./index-D7eiOplw.js");
1141
952
  return openAttachment2(this.library, options);
1142
953
  }
1143
954
  }
@@ -1981,7 +1792,7 @@ function getAttachExitCode(result) {
1981
1792
  }
1982
1793
  async function executeInteractiveSelect$2(context, config2) {
1983
1794
  const { withAlternateScreen: withAlternateScreen2 } = await Promise.resolve().then(() => alternateScreen);
1984
- const { selectReferencesOrExit } = await import("./reference-select-DrINWBuP.js");
1795
+ const { selectReferencesOrExit } = await import("./reference-select-DTqhevya.js");
1985
1796
  const allReferences = await context.library.getAll();
1986
1797
  const identifiers = await withAlternateScreen2(
1987
1798
  () => selectReferencesOrExit(allReferences, { multiSelect: false }, config2.cli.tui)
@@ -2529,8 +2340,8 @@ function getCiteExitCode(result) {
2529
2340
  }
2530
2341
  async function executeInteractiveCite(options, context, config2) {
2531
2342
  const { withAlternateScreen: withAlternateScreen2 } = await Promise.resolve().then(() => alternateScreen);
2532
- const { runCiteFlow } = await import("./index-D--7n1SB.js");
2533
- const { buildStyleChoices, listCustomStyles } = await import("./style-select-DxcSWBSF.js");
2343
+ const { runCiteFlow } = await import("./index-BvIfOciH.js");
2344
+ const { buildStyleChoices, listCustomStyles } = await import("./style-select-CXclvgJO.js");
2534
2345
  const { search } = await import("./file-watcher-CrsNHUpz.js").then((n) => n.z);
2535
2346
  const { tokenize } = await import("./file-watcher-CrsNHUpz.js").then((n) => n.y);
2536
2347
  const { checkTTY } = await import("./tty-BMyaEOhX.js");
@@ -2615,7 +2426,9 @@ const ENV_OVERRIDE_MAP = {
2615
2426
  REFERENCE_MANAGER_MCP_DEFAULT_LIMIT: "mcp.default_limit",
2616
2427
  REFERENCE_MANAGER_CLIPBOARD_AUTO_COPY: "cli.tui.clipboard_auto_copy",
2617
2428
  PUBMED_EMAIL: "pubmed.email",
2618
- PUBMED_API_KEY: "pubmed.api_key"
2429
+ PUBMED_API_KEY: "pubmed.api_key",
2430
+ UNPAYWALL_EMAIL: "fulltext.sources.unpaywall_email",
2431
+ CORE_API_KEY: "fulltext.sources.core_api_key"
2619
2432
  };
2620
2433
  const KEY_TO_ENV_MAP = Object.fromEntries(
2621
2434
  Object.entries(ENV_OVERRIDE_MAP).map(([envVar, configKey]) => [configKey, envVar])
@@ -2679,6 +2492,29 @@ const CONFIG_KEY_REGISTRY = [
2679
2492
  // pubmed section
2680
2493
  { key: "pubmed.email", type: "string", description: "Email for PubMed API", optional: true },
2681
2494
  { key: "pubmed.api_key", type: "string", description: "API key for PubMed", optional: true },
2495
+ // fulltext section
2496
+ {
2497
+ key: "fulltext.prefer_sources",
2498
+ type: "string[]",
2499
+ description: "Fulltext source priority order"
2500
+ },
2501
+ {
2502
+ key: "fulltext.auto_fetch_on_add",
2503
+ type: "boolean",
2504
+ description: "Auto-fetch fulltext when adding references"
2505
+ },
2506
+ {
2507
+ key: "fulltext.sources.unpaywall_email",
2508
+ type: "string",
2509
+ description: "Email for Unpaywall API",
2510
+ optional: true
2511
+ },
2512
+ {
2513
+ key: "fulltext.sources.core_api_key",
2514
+ type: "string",
2515
+ description: "API key for CORE",
2516
+ optional: true
2517
+ },
2682
2518
  // attachments section
2683
2519
  { key: "attachments.directory", type: "string", description: "Attachments storage directory" },
2684
2520
  // cli section
@@ -6478,7 +6314,7 @@ function writeBlockMapping(state, level, object2, compact) {
6478
6314
  state.tag = _tag;
6479
6315
  state.dump = _result || "{}";
6480
6316
  }
6481
- function detectType$1(state, object2, explicit) {
6317
+ function detectType(state, object2, explicit) {
6482
6318
  var _result, typeList, index, length, type2, style;
6483
6319
  typeList = explicit ? state.explicitTypes : state.implicitTypes;
6484
6320
  for (index = 0, length = typeList.length; index < length; index += 1) {
@@ -6512,8 +6348,8 @@ function detectType$1(state, object2, explicit) {
6512
6348
  function writeNode(state, level, object2, block, compact, iskey, isblockseq) {
6513
6349
  state.tag = null;
6514
6350
  state.dump = object2;
6515
- if (!detectType$1(state, object2, false)) {
6516
- detectType$1(state, object2, true);
6351
+ if (!detectType(state, object2, false)) {
6352
+ detectType(state, object2, true);
6517
6353
  }
6518
6354
  var type2 = _toString.call(state.dump);
6519
6355
  var inblock = block;
@@ -7087,7 +6923,7 @@ function formatEditOutput(result) {
7087
6923
  }
7088
6924
  async function executeInteractiveEdit(options, context, config2) {
7089
6925
  const { withAlternateScreen: withAlternateScreen2 } = await Promise.resolve().then(() => alternateScreen);
7090
- const { selectReferencesOrExit } = await import("./reference-select-DrINWBuP.js");
6926
+ const { selectReferencesOrExit } = await import("./reference-select-DTqhevya.js");
7091
6927
  const allReferences = await context.library.getAll();
7092
6928
  const identifiers = await withAlternateScreen2(
7093
6929
  () => selectReferencesOrExit(allReferences, { multiSelect: true }, config2.cli.tui)
@@ -10346,188 +10182,6 @@ function formatExportOutput(result, options) {
10346
10182
  function getExportExitCode(result) {
10347
10183
  return result.notFound.length > 0 ? 1 : 0;
10348
10184
  }
10349
- function detectType(filePath) {
10350
- const ext = extname(filePath).toLowerCase();
10351
- if (ext === ".pdf") return "pdf";
10352
- if (ext === ".md" || ext === ".markdown") return "markdown";
10353
- return void 0;
10354
- }
10355
- function resolveFileType(explicitType, filePath, stdinContent) {
10356
- let fileType = explicitType;
10357
- if (!fileType && filePath) {
10358
- fileType = detectType(filePath);
10359
- }
10360
- if (stdinContent && !fileType) {
10361
- return {
10362
- error: "File type must be specified with --pdf or --markdown when reading from stdin."
10363
- };
10364
- }
10365
- if (!fileType) {
10366
- return { error: "Cannot detect file type. Use --pdf or --markdown to specify the type." };
10367
- }
10368
- return fileType;
10369
- }
10370
- function prepareStdinSource(stdinContent, fileType) {
10371
- try {
10372
- const tempDir = mkdtempSync(join(tmpdir(), "refmgr-"));
10373
- const ext = formatToExtension(fileType);
10374
- const sourcePath = join(tempDir, `stdin.${ext}`);
10375
- writeFileSync(sourcePath, stdinContent);
10376
- return { sourcePath, tempDir };
10377
- } catch (error) {
10378
- return {
10379
- error: `Failed to write stdin content: ${error instanceof Error ? error.message : String(error)}`
10380
- };
10381
- }
10382
- }
10383
- async function cleanupTempDir(tempDir) {
10384
- if (tempDir) {
10385
- await rm(tempDir, { recursive: true, force: true }).catch(() => {
10386
- });
10387
- }
10388
- }
10389
- function prepareSourcePath(filePath, stdinContent, fileType) {
10390
- if (stdinContent) {
10391
- return prepareStdinSource(stdinContent, fileType);
10392
- }
10393
- if (!filePath) {
10394
- return { error: "No file path or stdin content provided." };
10395
- }
10396
- return { sourcePath: filePath };
10397
- }
10398
- function convertResult(result, fileType) {
10399
- if (result.success) {
10400
- return {
10401
- success: true,
10402
- filename: result.filename,
10403
- type: fileType,
10404
- overwritten: result.overwritten
10405
- };
10406
- }
10407
- if (result.requiresConfirmation) {
10408
- return {
10409
- success: false,
10410
- existingFile: result.existingFile,
10411
- requiresConfirmation: true
10412
- };
10413
- }
10414
- return {
10415
- success: false,
10416
- error: result.error
10417
- };
10418
- }
10419
- async function fulltextAttach(library, options) {
10420
- const {
10421
- identifier,
10422
- filePath,
10423
- type: explicitType,
10424
- move,
10425
- force,
10426
- idType = "id",
10427
- fulltextDirectory,
10428
- stdinContent
10429
- } = options;
10430
- const fileTypeResult = resolveFileType(explicitType, filePath, stdinContent);
10431
- if (typeof fileTypeResult === "object" && "error" in fileTypeResult) {
10432
- const item = await library.find(identifier, { idType });
10433
- if (!item) {
10434
- return { success: false, error: `Reference '${identifier}' not found` };
10435
- }
10436
- return { success: false, error: fileTypeResult.error };
10437
- }
10438
- const fileType = fileTypeResult;
10439
- const sourceResult = prepareSourcePath(filePath, stdinContent, fileType);
10440
- if ("error" in sourceResult) {
10441
- const item = await library.find(identifier, { idType });
10442
- if (!item) {
10443
- return { success: false, error: `Reference '${identifier}' not found` };
10444
- }
10445
- return { success: false, error: sourceResult.error };
10446
- }
10447
- const { sourcePath, tempDir } = sourceResult;
10448
- try {
10449
- const result = await addAttachment(library, {
10450
- identifier,
10451
- filePath: sourcePath,
10452
- role: FULLTEXT_ROLE,
10453
- move: move ?? false,
10454
- force: force ?? false,
10455
- idType,
10456
- attachmentsDirectory: fulltextDirectory
10457
- });
10458
- await cleanupTempDir(tempDir);
10459
- return convertResult(result, fileType);
10460
- } catch (error) {
10461
- await cleanupTempDir(tempDir);
10462
- throw error;
10463
- }
10464
- }
10465
- function buildFilePath$1(attachmentsDirectory, directory, filename) {
10466
- return normalizePathForOutput(join(attachmentsDirectory, directory, filename));
10467
- }
10468
- async function getFileContent(filePath) {
10469
- const content = await readFile(filePath);
10470
- return { success: true, content };
10471
- }
10472
- async function handleStdoutMode(attachments, type2, identifier, fulltextDirectory) {
10473
- const file = findFulltextFile(attachments, type2);
10474
- if (!file || !attachments?.directory) {
10475
- return { success: false, error: `No ${type2} fulltext attached to '${identifier}'` };
10476
- }
10477
- const filePath = buildFilePath$1(fulltextDirectory, attachments.directory, file.filename);
10478
- try {
10479
- return await getFileContent(filePath);
10480
- } catch {
10481
- return { success: false, error: `No ${type2} fulltext attached to '${identifier}'` };
10482
- }
10483
- }
10484
- function getSingleTypePath(attachments, type2, identifier, fulltextDirectory) {
10485
- const file = findFulltextFile(attachments, type2);
10486
- if (!file || !attachments?.directory) {
10487
- return { success: false, error: `No ${type2} fulltext attached to '${identifier}'` };
10488
- }
10489
- const filePath = buildFilePath$1(fulltextDirectory, attachments.directory, file.filename);
10490
- const paths = {};
10491
- paths[type2] = filePath;
10492
- return { success: true, paths };
10493
- }
10494
- function getAllFulltextPaths(attachments, fulltextFiles, fulltextDirectory, identifier) {
10495
- const paths = {};
10496
- for (const file of fulltextFiles) {
10497
- const ext = file.filename.split(".").pop() || "";
10498
- const format2 = extensionToFormat(ext);
10499
- if (format2) {
10500
- const filePath = buildFilePath$1(fulltextDirectory, attachments.directory, file.filename);
10501
- paths[format2] = filePath;
10502
- }
10503
- }
10504
- if (Object.keys(paths).length === 0) {
10505
- return { success: false, error: `No fulltext attached to '${identifier}'` };
10506
- }
10507
- return { success: true, paths };
10508
- }
10509
- async function fulltextGet(library, options) {
10510
- const { identifier, type: type2, stdout: stdout2, idType = "id", fulltextDirectory } = options;
10511
- const item = await library.find(identifier, { idType });
10512
- if (!item) {
10513
- return { success: false, error: `Reference '${identifier}' not found` };
10514
- }
10515
- const attachments = item.custom?.attachments;
10516
- if (stdout2 && type2) {
10517
- return handleStdoutMode(attachments, type2, identifier, fulltextDirectory);
10518
- }
10519
- const fulltextFiles = findFulltextFiles(attachments);
10520
- if (fulltextFiles.length === 0) {
10521
- return { success: false, error: `No fulltext attached to '${identifier}'` };
10522
- }
10523
- if (type2) {
10524
- return getSingleTypePath(attachments, type2, identifier, fulltextDirectory);
10525
- }
10526
- if (!attachments) {
10527
- return { success: false, error: `No fulltext attached to '${identifier}'` };
10528
- }
10529
- return getAllFulltextPaths(attachments, fulltextFiles, fulltextDirectory, identifier);
10530
- }
10531
10185
  function getFilesToDetach(attachments, type2) {
10532
10186
  if (type2) {
10533
10187
  const file = findFulltextFile(attachments, type2);
@@ -10684,6 +10338,33 @@ async function executeFulltextOpen(options, context) {
10684
10338
  };
10685
10339
  return fulltextOpen(context.library, operationOptions);
10686
10340
  }
10341
+ async function executeFulltextDiscover(options, context, config2) {
10342
+ const operationOptions = {
10343
+ identifier: options.identifier,
10344
+ idType: options.idType,
10345
+ fulltextConfig: config2.fulltext
10346
+ };
10347
+ return fulltextDiscover(context.library, operationOptions);
10348
+ }
10349
+ async function executeFulltextFetch(options, context, config2) {
10350
+ const operationOptions = {
10351
+ identifier: options.identifier,
10352
+ idType: options.idType,
10353
+ fulltextConfig: config2.fulltext,
10354
+ fulltextDirectory: options.fulltextDirectory,
10355
+ source: options.source,
10356
+ force: options.force
10357
+ };
10358
+ return fulltextFetch(context.library, operationOptions);
10359
+ }
10360
+ async function executeFulltextConvert(options, context) {
10361
+ const operationOptions = {
10362
+ identifier: options.identifier,
10363
+ idType: options.idType,
10364
+ fulltextDirectory: options.fulltextDirectory
10365
+ };
10366
+ return fulltextConvert(context.library, operationOptions);
10367
+ }
10687
10368
  function formatFulltextAttachOutput(result) {
10688
10369
  if (result.requiresConfirmation) {
10689
10370
  return `File already attached: ${result.existingFile}
@@ -10736,12 +10417,50 @@ function formatFulltextOpenOutput(result) {
10736
10417
  }
10737
10418
  return `Opened ${result.openedType}: ${result.openedPath}`;
10738
10419
  }
10420
+ function formatFulltextDiscoverOutput(result, identifier) {
10421
+ if (!result.success) {
10422
+ return `Error: ${result.error}`;
10423
+ }
10424
+ if (!result.locations || result.locations.length === 0) {
10425
+ return `No OA sources found for ${identifier}`;
10426
+ }
10427
+ const lines = [`OA sources for ${identifier}:`];
10428
+ for (const loc of result.locations) {
10429
+ const license = loc.license ? ` (${loc.license})` : "";
10430
+ lines.push(` ${loc.source}: ${loc.url}${license}`);
10431
+ }
10432
+ if (result.errors && result.errors.length > 0) {
10433
+ for (const err of result.errors) {
10434
+ lines.push(` Warning: ${err.source}: ${err.error}`);
10435
+ }
10436
+ }
10437
+ return lines.join("\n");
10438
+ }
10439
+ function formatFulltextFetchOutput(result) {
10440
+ if (!result.success) {
10441
+ return `Error: ${result.error}`;
10442
+ }
10443
+ const lines = [];
10444
+ if (result.source) {
10445
+ lines.push(`Source: ${result.source}`);
10446
+ }
10447
+ for (const file of result.attachedFiles ?? []) {
10448
+ lines.push(`Attached ${file}: fulltext.${file === "markdown" ? "md" : file}`);
10449
+ }
10450
+ return lines.join("\n");
10451
+ }
10452
+ function formatFulltextConvertOutput(result) {
10453
+ if (!result.success) {
10454
+ return `Error: ${result.error}`;
10455
+ }
10456
+ return `Converted PMC XML to Markdown: ${result.filename}`;
10457
+ }
10739
10458
  function getFulltextExitCode(result) {
10740
10459
  return result.success ? 0 : 1;
10741
10460
  }
10742
10461
  async function executeInteractiveSelect$1(context, config2) {
10743
10462
  const { withAlternateScreen: withAlternateScreen2 } = await Promise.resolve().then(() => alternateScreen);
10744
- const { selectReferencesOrExit } = await import("./reference-select-DrINWBuP.js");
10463
+ const { selectReferencesOrExit } = await import("./reference-select-DTqhevya.js");
10745
10464
  const allReferences = await context.library.getAll();
10746
10465
  const identifiers = await withAlternateScreen2(
10747
10466
  () => selectReferencesOrExit(allReferences, { multiSelect: false }, config2.cli.tui)
@@ -10932,22 +10651,167 @@ async function handleFulltextOpenAction(identifierArg, options, globalOpts) {
10932
10651
  setExitCode(ExitCode.INTERNAL_ERROR);
10933
10652
  }
10934
10653
  }
10654
+ async function handleFulltextDiscoverAction(identifierArg, options, globalOpts) {
10655
+ try {
10656
+ const config2 = await loadConfigWithOverrides({ ...globalOpts, ...options });
10657
+ const context = await createExecutionContext(config2, Library.load);
10658
+ let identifier;
10659
+ if (identifierArg) {
10660
+ identifier = identifierArg;
10661
+ } else if (isTTY()) {
10662
+ identifier = await executeInteractiveSelect$1(context, config2);
10663
+ } else {
10664
+ const stdinId = await readIdentifierFromStdin();
10665
+ if (!stdinId) {
10666
+ process.stderr.write(
10667
+ "Error: No identifier provided. Provide an ID, pipe one via stdin, or run interactively in a TTY.\n"
10668
+ );
10669
+ setExitCode(ExitCode.ERROR);
10670
+ return;
10671
+ }
10672
+ identifier = stdinId;
10673
+ }
10674
+ const discoverOptions = {
10675
+ identifier,
10676
+ ...options.uuid && { idType: "uuid" }
10677
+ };
10678
+ const result = await executeFulltextDiscover(discoverOptions, context, config2);
10679
+ const output = formatFulltextDiscoverOutput(result, identifier);
10680
+ if (result.success) {
10681
+ process.stdout.write(`${output}
10682
+ `);
10683
+ } else {
10684
+ process.stderr.write(`${output}
10685
+ `);
10686
+ }
10687
+ setExitCode(getFulltextExitCode(result));
10688
+ } catch (error) {
10689
+ process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
10690
+ `);
10691
+ setExitCode(ExitCode.INTERNAL_ERROR);
10692
+ }
10693
+ }
10694
+ async function handleFulltextFetchAction(identifierArg, options, globalOpts) {
10695
+ try {
10696
+ const config2 = await loadConfigWithOverrides({ ...globalOpts, ...options });
10697
+ const context = await createExecutionContext(config2, Library.load);
10698
+ let identifier;
10699
+ if (identifierArg) {
10700
+ identifier = identifierArg;
10701
+ } else if (isTTY()) {
10702
+ identifier = await executeInteractiveSelect$1(context, config2);
10703
+ } else {
10704
+ const stdinId = await readIdentifierFromStdin();
10705
+ if (!stdinId) {
10706
+ process.stderr.write(
10707
+ "Error: No identifier provided. Provide an ID, pipe one via stdin, or run interactively in a TTY.\n"
10708
+ );
10709
+ setExitCode(ExitCode.ERROR);
10710
+ return;
10711
+ }
10712
+ identifier = stdinId;
10713
+ }
10714
+ process.stderr.write(`Fetching fulltext for ${identifier}...
10715
+ `);
10716
+ const fetchOptions = {
10717
+ identifier,
10718
+ fulltextDirectory: config2.attachments.directory,
10719
+ ...options.source && { source: options.source },
10720
+ ...options.force && { force: options.force },
10721
+ ...options.uuid && { idType: "uuid" }
10722
+ };
10723
+ const result = await executeFulltextFetch(fetchOptions, context, config2);
10724
+ const output = formatFulltextFetchOutput(result);
10725
+ process.stderr.write(`${output}
10726
+ `);
10727
+ setExitCode(getFulltextExitCode(result));
10728
+ } catch (error) {
10729
+ process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
10730
+ `);
10731
+ setExitCode(ExitCode.INTERNAL_ERROR);
10732
+ }
10733
+ }
10734
+ async function handleFulltextConvertAction(identifierArg, options, globalOpts) {
10735
+ try {
10736
+ const config2 = await loadConfigWithOverrides({ ...globalOpts, ...options });
10737
+ const context = await createExecutionContext(config2, Library.load);
10738
+ let identifier;
10739
+ if (identifierArg) {
10740
+ identifier = identifierArg;
10741
+ } else if (isTTY()) {
10742
+ identifier = await executeInteractiveSelect$1(context, config2);
10743
+ } else {
10744
+ const stdinId = await readIdentifierFromStdin();
10745
+ if (!stdinId) {
10746
+ process.stderr.write(
10747
+ "Error: No identifier provided. Provide an ID, pipe one via stdin, or run interactively in a TTY.\n"
10748
+ );
10749
+ setExitCode(ExitCode.ERROR);
10750
+ return;
10751
+ }
10752
+ identifier = stdinId;
10753
+ }
10754
+ const convertOptions = {
10755
+ identifier,
10756
+ fulltextDirectory: config2.attachments.directory,
10757
+ ...options.uuid && { idType: "uuid" }
10758
+ };
10759
+ const result = await executeFulltextConvert(convertOptions, context);
10760
+ const output = formatFulltextConvertOutput(result);
10761
+ process.stderr.write(`${output}
10762
+ `);
10763
+ setExitCode(getFulltextExitCode(result));
10764
+ } catch (error) {
10765
+ process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
10766
+ `);
10767
+ setExitCode(ExitCode.INTERNAL_ERROR);
10768
+ }
10769
+ }
10935
10770
  const fulltext = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
10936
10771
  __proto__: null,
10937
10772
  executeFulltextAttach,
10773
+ executeFulltextConvert,
10938
10774
  executeFulltextDetach,
10775
+ executeFulltextDiscover,
10776
+ executeFulltextFetch,
10939
10777
  executeFulltextGet,
10940
10778
  executeFulltextOpen,
10941
10779
  formatFulltextAttachOutput,
10780
+ formatFulltextConvertOutput,
10942
10781
  formatFulltextDetachOutput,
10782
+ formatFulltextDiscoverOutput,
10783
+ formatFulltextFetchOutput,
10943
10784
  formatFulltextGetOutput,
10944
10785
  formatFulltextOpenOutput,
10945
10786
  getFulltextExitCode,
10946
10787
  handleFulltextAttachAction,
10788
+ handleFulltextConvertAction,
10947
10789
  handleFulltextDetachAction,
10790
+ handleFulltextDiscoverAction,
10791
+ handleFulltextFetchAction,
10948
10792
  handleFulltextGetAction,
10949
10793
  handleFulltextOpenAction
10950
10794
  }, Symbol.toStringTag, { value: "Module" }));
10795
+ function buildResourceIndicators(item) {
10796
+ const icons = [];
10797
+ const attachments = item.custom?.attachments;
10798
+ const fulltextFiles = findFulltextFiles(attachments);
10799
+ const hasFulltextPdf = fulltextFiles.some(
10800
+ (f) => extensionToFormat(getExtension(f.filename)) === "pdf"
10801
+ );
10802
+ if (hasFulltextPdf) icons.push("📄");
10803
+ const hasFulltextMd = fulltextFiles.some(
10804
+ (f) => extensionToFormat(getExtension(f.filename)) === "markdown"
10805
+ );
10806
+ if (hasFulltextMd) icons.push("📝");
10807
+ const allFiles = attachments?.files ?? [];
10808
+ const hasOtherAttachments = allFiles.length > fulltextFiles.length;
10809
+ if (hasOtherAttachments) icons.push("📎");
10810
+ if (item.URL) icons.push("🔗");
10811
+ const tags = item.custom?.tags;
10812
+ if (Array.isArray(tags) && tags.length > 0) icons.push("🏷");
10813
+ return icons.join("");
10814
+ }
10951
10815
  function formatAuthor(author) {
10952
10816
  const family = author.family || "";
10953
10817
  const givenInitial = author.given ? `${author.given.charAt(0)}.` : "";
@@ -10980,6 +10844,10 @@ function formatSingleReference(item) {
10980
10844
  }
10981
10845
  const uuid2 = item.custom?.uuid || "(no uuid)";
10982
10846
  lines.push(` UUID: ${uuid2}`);
10847
+ const indicators = buildResourceIndicators(item);
10848
+ if (indicators) {
10849
+ lines.push(` ${indicators}`);
10850
+ }
10983
10851
  return lines.join("\n");
10984
10852
  }
10985
10853
  function formatPretty(items2) {
@@ -31588,6 +31456,115 @@ function registerFulltextDetachTool(server, getLibraryOperations, getConfig) {
31588
31456
  }
31589
31457
  );
31590
31458
  }
31459
+ function registerFulltextDiscoverTool(server, getLibraryOperations, getConfig) {
31460
+ server.registerTool(
31461
+ "fulltext_discover",
31462
+ {
31463
+ description: "Check Open Access (OA) availability for a reference. Returns available sources and URLs.",
31464
+ inputSchema: {
31465
+ id: z.string().describe("Reference ID")
31466
+ }
31467
+ },
31468
+ async (args) => {
31469
+ const libraryOps = getLibraryOperations();
31470
+ const config2 = getConfig();
31471
+ const result = await fulltextDiscover(libraryOps, {
31472
+ identifier: args.id,
31473
+ fulltextConfig: config2.fulltext
31474
+ });
31475
+ if (!result.success) {
31476
+ return {
31477
+ content: [{ type: "text", text: result.error ?? "Unknown error" }],
31478
+ isError: true
31479
+ };
31480
+ }
31481
+ if (!result.locations || result.locations.length === 0) {
31482
+ return {
31483
+ content: [{ type: "text", text: `No OA sources found for '${args.id}'` }]
31484
+ };
31485
+ }
31486
+ const lines = [`OA sources for '${args.id}' (status: ${result.oaStatus}):`];
31487
+ for (const loc of result.locations) {
31488
+ lines.push(` ${loc.source}: ${loc.url} (${loc.urlType})`);
31489
+ }
31490
+ return {
31491
+ content: [{ type: "text", text: lines.join("\n") }]
31492
+ };
31493
+ }
31494
+ );
31495
+ }
31496
+ function registerFulltextFetchTool(server, getLibraryOperations, getConfig) {
31497
+ server.registerTool(
31498
+ "fulltext_fetch",
31499
+ {
31500
+ description: "Download and attach Open Access full-text for a reference. Automatically discovers OA sources, downloads PDF/XML, converts PMC XML to Markdown, and attaches files.",
31501
+ inputSchema: {
31502
+ id: z.string().describe("Reference ID"),
31503
+ source: z.enum(["pmc", "arxiv", "unpaywall", "core"]).optional().describe("Preferred source (optional)"),
31504
+ force: z.boolean().optional().describe("Force overwrite existing attachment")
31505
+ }
31506
+ },
31507
+ async (args) => {
31508
+ const libraryOps = getLibraryOperations();
31509
+ const config2 = getConfig();
31510
+ const result = await fulltextFetch(libraryOps, {
31511
+ identifier: args.id,
31512
+ fulltextConfig: config2.fulltext,
31513
+ fulltextDirectory: config2.attachments.directory,
31514
+ source: args.source,
31515
+ force: args.force
31516
+ });
31517
+ if (!result.success) {
31518
+ return {
31519
+ content: [{ type: "text", text: result.error ?? "Unknown error" }],
31520
+ isError: true
31521
+ };
31522
+ }
31523
+ const files = result.attachedFiles?.join(", ") ?? "none";
31524
+ return {
31525
+ content: [
31526
+ {
31527
+ type: "text",
31528
+ text: `Fetched fulltext for '${args.id}' from ${result.source}: ${files}`
31529
+ }
31530
+ ]
31531
+ };
31532
+ }
31533
+ );
31534
+ }
31535
+ function registerFulltextConvertTool(server, getLibraryOperations, getConfig) {
31536
+ server.registerTool(
31537
+ "fulltext_convert",
31538
+ {
31539
+ description: "Convert an attached PMC JATS XML file to Markdown for a reference.",
31540
+ inputSchema: {
31541
+ id: z.string().describe("Reference ID")
31542
+ }
31543
+ },
31544
+ async (args) => {
31545
+ const libraryOps = getLibraryOperations();
31546
+ const config2 = getConfig();
31547
+ const result = await fulltextConvert(libraryOps, {
31548
+ identifier: args.id,
31549
+ fulltextDirectory: config2.attachments.directory
31550
+ });
31551
+ if (!result.success) {
31552
+ return {
31553
+ content: [{ type: "text", text: result.error ?? "Unknown error" }],
31554
+ isError: true
31555
+ };
31556
+ }
31557
+ return {
31558
+ content: [
31559
+ {
31560
+ type: "text",
31561
+ text: `Converted PMC XML to Markdown for '${args.id}': ${result.filename}`
31562
+ }
31563
+ ]
31564
+ };
31565
+ }
31566
+ );
31567
+ }
31591
31568
  function registerListTool(server, getLibraryOperations, getConfig) {
31592
31569
  server.registerTool(
31593
31570
  "list",
@@ -31721,6 +31698,9 @@ function registerAllTools(server, getLibraryOperations, getConfig) {
31721
31698
  registerFulltextAttachTool(server, getLibraryOperations, getConfig);
31722
31699
  registerFulltextGetTool(server, getLibraryOperations, getConfig);
31723
31700
  registerFulltextDetachTool(server, getLibraryOperations, getConfig);
31701
+ registerFulltextDiscoverTool(server, getLibraryOperations, getConfig);
31702
+ registerFulltextFetchTool(server, getLibraryOperations, getConfig);
31703
+ registerFulltextConvertTool(server, getLibraryOperations, getConfig);
31724
31704
  }
31725
31705
  async function createMcpServer(options) {
31726
31706
  const serverInfo = {
@@ -31774,7 +31754,7 @@ async function mcpStart(options) {
31774
31754
  async function executeRemove(options, context) {
31775
31755
  const { identifier, idType = "id", fulltextDirectory, deleteFulltext = false } = options;
31776
31756
  if (context.mode === "local" && deleteFulltext && fulltextDirectory) {
31777
- const { removeReference } = await import("./index-DrZawbND.js").then((n) => n.r);
31757
+ const { removeReference } = await import("./index-BK3hTiKR.js").then((n) => n.v);
31778
31758
  return removeReference(context.library, {
31779
31759
  identifier,
31780
31760
  idType,
@@ -31829,7 +31809,7 @@ Continue?`;
31829
31809
  }
31830
31810
  async function executeInteractiveRemove(context, config2) {
31831
31811
  const { withAlternateScreen: withAlternateScreen2 } = await Promise.resolve().then(() => alternateScreen);
31832
- const { selectReferenceItemsOrExit } = await import("./reference-select-DrINWBuP.js");
31812
+ const { selectReferenceItemsOrExit } = await import("./reference-select-DTqhevya.js");
31833
31813
  const allReferences = await context.library.getAll();
31834
31814
  const selectedItems = await withAlternateScreen2(
31835
31815
  () => selectReferenceItemsOrExit(allReferences, { multiSelect: false }, config2.cli.tui)
@@ -32054,7 +32034,7 @@ async function executeInteractiveSearch(options, context, config2) {
32054
32034
  validateInteractiveOptions(options);
32055
32035
  const { checkTTY } = await import("./tty-BMyaEOhX.js");
32056
32036
  const { withAlternateScreen: withAlternateScreen2 } = await Promise.resolve().then(() => alternateScreen);
32057
- const { runSearchFlow } = await import("./index-D--7n1SB.js");
32037
+ const { runSearchFlow } = await import("./index-BvIfOciH.js");
32058
32038
  const { search } = await import("./file-watcher-CrsNHUpz.js").then((n) => n.z);
32059
32039
  const { tokenize } = await import("./file-watcher-CrsNHUpz.js").then((n) => n.y);
32060
32040
  checkTTY();
@@ -32073,7 +32053,7 @@ async function executeInteractiveSearch(options, context, config2) {
32073
32053
  })
32074
32054
  );
32075
32055
  if (result.selectedItems && !result.cancelled) {
32076
- const { isSideEffectAction } = await import("./action-menu-Brg5Lmz7.js");
32056
+ const { isSideEffectAction } = await import("./action-menu-B1DCdkkp.js");
32077
32057
  if (isSideEffectAction(result.action)) {
32078
32058
  await executeSideEffectAction(result.action, result.selectedItems, context, config2);
32079
32059
  return { output: "", cancelled: false, action: result.action };
@@ -32089,7 +32069,7 @@ async function executeSideEffectAction(action, items2, context, config2) {
32089
32069
  switch (action) {
32090
32070
  case "open-url": {
32091
32071
  const { resolveDefaultUrl: resolveDefaultUrl2 } = await Promise.resolve().then(() => url);
32092
- const { openWithSystemApp: openWithSystemApp2 } = await import("./loader-BtW20O32.js").then((n) => n.j);
32072
+ const { openWithSystemApp: openWithSystemApp2 } = await import("./loader-BHvcats5.js").then((n) => n.j);
32093
32073
  const item = items2[0];
32094
32074
  if (!item) return;
32095
32075
  const url$1 = resolveDefaultUrl2(item);
@@ -32476,7 +32456,7 @@ function formatUpdateOutput(result, identifier) {
32476
32456
  }
32477
32457
  async function executeInteractiveUpdate(context, config2) {
32478
32458
  const { withAlternateScreen: withAlternateScreen2 } = await Promise.resolve().then(() => alternateScreen);
32479
- const { selectReferencesOrExit } = await import("./reference-select-DrINWBuP.js");
32459
+ const { selectReferencesOrExit } = await import("./reference-select-DTqhevya.js");
32480
32460
  const allReferences = await context.library.getAll();
32481
32461
  const identifiers = await withAlternateScreen2(
32482
32462
  () => selectReferencesOrExit(allReferences, { multiSelect: false }, config2.cli.tui)
@@ -32771,7 +32751,7 @@ function getUrlExitCode(result) {
32771
32751
  }
32772
32752
  async function executeInteractiveSelect(context, config2) {
32773
32753
  const { withAlternateScreen: withAlternateScreen2 } = await Promise.resolve().then(() => alternateScreen);
32774
- const { selectReferencesOrExit } = await import("./reference-select-DrINWBuP.js");
32754
+ const { selectReferencesOrExit } = await import("./reference-select-DTqhevya.js");
32775
32755
  const allReferences = await context.library.getAll();
32776
32756
  const identifiers = await withAlternateScreen2(
32777
32757
  () => selectReferencesOrExit(allReferences, { multiSelect: false }, config2.cli.tui)
@@ -33267,6 +33247,35 @@ async function outputAddResultJson(result, context, full) {
33267
33247
  process.stdout.write(`${JSON.stringify(jsonOutput2)}
33268
33248
  `);
33269
33249
  }
33250
+ function shouldAutoFetch(cliFlag, configEnabled) {
33251
+ if (cliFlag !== void 0) {
33252
+ return cliFlag;
33253
+ }
33254
+ return configEnabled;
33255
+ }
33256
+ async function performAutoFetch(addedItems, context, config2) {
33257
+ const { fulltextFetch: fulltextFetch2 } = await import("./index-BK3hTiKR.js").then((n) => n.u);
33258
+ const fetchResults = await autoFetchFulltext(addedItems, context, {
33259
+ fulltextConfig: config2.fulltext,
33260
+ fulltextDirectory: config2.attachments.directory,
33261
+ fulltextFetchFn: fulltextFetch2
33262
+ });
33263
+ for (const [i, fetchResult] of fetchResults.entries()) {
33264
+ const addedItem = addedItems[i];
33265
+ if (!addedItem) continue;
33266
+ if (fetchResult.success) {
33267
+ process.stderr.write(
33268
+ `Fulltext for ${addedItem.id}: ${formatFulltextFetchOutput(fetchResult)}
33269
+ `
33270
+ );
33271
+ } else {
33272
+ process.stderr.write(
33273
+ `Fulltext for ${addedItem.id}: ${fetchResult.error ?? "unknown error"}
33274
+ `
33275
+ );
33276
+ }
33277
+ }
33278
+ }
33270
33279
  async function handleAddAction(inputs, options, program) {
33271
33280
  const outputFormat = options.output ?? "text";
33272
33281
  try {
@@ -33286,6 +33295,9 @@ async function handleAddAction(inputs, options, program) {
33286
33295
  process.stderr.write(`${output}
33287
33296
  `);
33288
33297
  }
33298
+ if (result.added.length > 0 && shouldAutoFetch(options.fetchFulltext, config2.fulltext.autoFetchOnAdd)) {
33299
+ await performAutoFetch(result.added, context, config2);
33300
+ }
33289
33301
  setExitCode(getExitCode(result));
33290
33302
  } catch (error) {
33291
33303
  const message = error instanceof Error ? error.message : String(error);
@@ -33300,7 +33312,7 @@ async function handleAddAction(inputs, options, program) {
33300
33312
  }
33301
33313
  }
33302
33314
  function registerAddCommand(program) {
33303
- program.command("add").description("Add new reference(s) to the library").argument("[input...]", "File paths or identifiers (PMID/DOI/ISBN), or use stdin").option("-f, --force", "Skip duplicate detection").option("-i, --input <format>", "Input format: json|bibtex|ris|pmid|doi|isbn|auto", "auto").option("--verbose", "Show detailed error information").option("-o, --output <format>", "Output format: json|text", "text").option("--full", "Include full CSL-JSON data in JSON output").action(async (inputs, options) => {
33315
+ program.command("add").description("Add new reference(s) to the library").argument("[input...]", "File paths or identifiers (PMID/DOI/ISBN), or use stdin").option("-f, --force", "Skip duplicate detection").option("-i, --input <format>", "Input format: json|bibtex|ris|pmid|doi|isbn|auto", "auto").option("--verbose", "Show detailed error information").option("-o, --output <format>", "Output format: json|text", "text").option("--full", "Include full CSL-JSON data in JSON output").option("--fetch-fulltext", "Auto-fetch OA fulltext after adding").option("--no-fetch-fulltext", "Disable auto-fetch fulltext (overrides config)").action(async (inputs, options) => {
33304
33316
  await handleAddAction(inputs, options, program);
33305
33317
  });
33306
33318
  }
@@ -33472,6 +33484,15 @@ function registerFulltextCommand(program) {
33472
33484
  fulltextCmd.command("open").description("Open full-text file with system default application").argument("[identifier]", "Citation key or UUID (interactive selection if omitted)").option("--pdf", "Open PDF file").option("--markdown", "Open Markdown file").option("--uuid", "Interpret identifier as UUID").action(async (identifier, options) => {
33473
33485
  await handleFulltextOpenAction(identifier, options, program.opts());
33474
33486
  });
33487
+ fulltextCmd.command("discover").description("Check OA (Open Access) availability for a reference").argument("[identifier]", "Citation key or UUID (interactive selection if omitted)").option("--uuid", "Interpret identifier as UUID").action(async (identifier, options) => {
33488
+ await handleFulltextDiscoverAction(identifier, options, program.opts());
33489
+ });
33490
+ fulltextCmd.command("fetch").description("Download OA full text and auto-attach to a reference").argument("[identifier]", "Citation key or UUID (interactive selection if omitted)").option("--source <source>", "Preferred source: pmc, arxiv, unpaywall, core").option("-f, --force", "Overwrite existing fulltext attachment").option("--uuid", "Interpret identifier as UUID").action(async (identifier, options) => {
33491
+ await handleFulltextFetchAction(identifier, options, program.opts());
33492
+ });
33493
+ fulltextCmd.command("convert").description("Convert attached PMC XML to Markdown").argument("[identifier]", "Citation key or UUID (interactive selection if omitted)").option("--uuid", "Interpret identifier as UUID").action(async (identifier, options) => {
33494
+ await handleFulltextConvertAction(identifier, options, program.opts());
33495
+ });
33475
33496
  }
33476
33497
  function registerUrlCommand(program) {
33477
33498
  program.command("url").description("Show URLs for references").argument("[identifiers...]", "Reference identifiers").option("--default", "Show only the best URL by priority").option("--doi", "Show only DOI URL").option("--pubmed", "Show only PubMed URL").option("--pmcid", "Show only PMC URL").option("--open", "Open URL in browser").option("--uuid", "Interpret identifiers as UUIDs").action(async (identifiers, options) => {
@@ -33495,8 +33516,8 @@ async function main(argv) {
33495
33516
  }
33496
33517
  export {
33497
33518
  Select as S,
33498
- addAttachment as a,
33499
- stringify as b,
33519
+ stringify as a,
33520
+ buildResourceIndicators as b,
33500
33521
  createProgram as c,
33501
33522
  detachAttachment as d,
33502
33523
  formatBibtex as f,
@@ -33508,4 +33529,4 @@ export {
33508
33529
  restoreStdinAfterInk as r,
33509
33530
  syncAttachments as s
33510
33531
  };
33511
- //# sourceMappingURL=index-Cno7_aWr.js.map
33532
+ //# sourceMappingURL=index-DOvEusHb.js.map