@ncukondo/reference-manager 0.20.0 → 0.21.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.
- package/dist/chunks/{action-menu-Brg5Lmz7.js → action-menu-CpkiDbRj.js} +3 -3
- package/dist/chunks/{action-menu-Brg5Lmz7.js.map → action-menu-CpkiDbRj.js.map} +1 -1
- package/dist/chunks/{format-CYO99JV-.js → format-n2Es_y_u.js} +2 -2
- package/dist/chunks/{format-CYO99JV-.js.map → format-n2Es_y_u.js.map} +1 -1
- package/dist/chunks/{index-D--7n1SB.js → index-Bh6xryeS.js} +4 -4
- package/dist/chunks/{index-D--7n1SB.js.map → index-Bh6xryeS.js.map} +1 -1
- package/dist/chunks/index-CScRH8g4.js +5928 -0
- package/dist/chunks/index-CScRH8g4.js.map +1 -0
- package/dist/chunks/{index-Cno7_aWr.js → index-Cq83Elk8.js} +422 -426
- package/dist/chunks/index-Cq83Elk8.js.map +1 -0
- package/dist/chunks/{index-BR5tlrNU.js → index-DiF2ezv-.js} +3 -2
- package/dist/chunks/index-DiF2ezv-.js.map +1 -0
- package/dist/chunks/{loader-BtW20O32.js → loader-BHvcats5.js} +113 -27
- package/dist/chunks/loader-BHvcats5.js.map +1 -0
- package/dist/chunks/{reference-select-DrINWBuP.js → reference-select-DlFG6N3o.js} +3 -3
- package/dist/chunks/{reference-select-DrINWBuP.js.map → reference-select-DlFG6N3o.js.map} +1 -1
- package/dist/chunks/{style-select-DxcSWBSF.js → style-select-1f5zHT8h.js} +3 -3
- package/dist/chunks/{style-select-DxcSWBSF.js.map → style-select-1f5zHT8h.js.map} +1 -1
- package/dist/cli/commands/add.d.ts +20 -0
- package/dist/cli/commands/add.d.ts.map +1 -1
- package/dist/cli/commands/fulltext.d.ts +86 -3
- package/dist/cli/commands/fulltext.d.ts.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/env-override.d.ts.map +1 -1
- package/dist/config/key-parser.d.ts.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/schema.d.ts +65 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/features/operations/fulltext/convert.d.ts +22 -0
- package/dist/features/operations/fulltext/convert.d.ts.map +1 -0
- package/dist/features/operations/fulltext/discover.d.ts +35 -0
- package/dist/features/operations/fulltext/discover.d.ts.map +1 -0
- package/dist/features/operations/fulltext/fetch.d.ts +34 -0
- package/dist/features/operations/fulltext/fetch.d.ts.map +1 -0
- package/dist/features/operations/fulltext/index.d.ts +3 -0
- package/dist/features/operations/fulltext/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp/tools/fulltext.d.ts +37 -0
- package/dist/mcp/tools/fulltext.d.ts.map +1 -1
- package/dist/mcp/tools/index.d.ts.map +1 -1
- package/dist/server/routes/references.d.ts +3 -1
- package/dist/server/routes/references.d.ts.map +1 -1
- package/dist/server.js +2 -2
- package/package.json +2 -1
- package/dist/chunks/index-BR5tlrNU.js.map +0 -1
- package/dist/chunks/index-Cno7_aWr.js.map +0 -1
- package/dist/chunks/index-DrZawbND.js +0 -2082
- package/dist/chunks/index-DrZawbND.js.map +0 -1
- 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
|
|
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, {
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
import { o as openWithSystemApp, l as loadConfig, e as getDefaultCurrentDirConfigFilename, h as getDefaultUserConfigPath } from "./loader-
|
|
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, B as BUILTIN_STYLES, q as getFulltextAttachmentTypes, s as startServerWithFileWatcher } from "./index-CScRH8g4.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.
|
|
22
|
+
const version$1 = "0.21.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
|
|
418
|
-
|
|
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
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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
|
-
|
|
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-
|
|
907
|
+
const { searchReferences } = await import("./index-CScRH8g4.js").then((n) => n.y);
|
|
1097
908
|
return searchReferences(this.library, options);
|
|
1098
909
|
}
|
|
1099
910
|
async list(options) {
|
|
1100
|
-
const { listReferences } = await import("./index-
|
|
911
|
+
const { listReferences } = await import("./index-CScRH8g4.js").then((n) => n.x);
|
|
1101
912
|
return listReferences(this.library, options ?? {});
|
|
1102
913
|
}
|
|
1103
914
|
async cite(options) {
|
|
1104
|
-
const { citeReferences } = await import("./index-
|
|
915
|
+
const { citeReferences } = await import("./index-CScRH8g4.js").then((n) => n.w);
|
|
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-
|
|
926
|
+
const { addReferences } = await import("./index-CScRH8g4.js").then((n) => n.v);
|
|
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-
|
|
931
|
+
const { addAttachment: addAttachment2 } = await import("./index-DiF2ezv-.js");
|
|
1121
932
|
return addAttachment2(this.library, options);
|
|
1122
933
|
}
|
|
1123
934
|
async attachList(options) {
|
|
1124
|
-
const { listAttachments: listAttachments2 } = await import("./index-
|
|
935
|
+
const { listAttachments: listAttachments2 } = await import("./index-DiF2ezv-.js");
|
|
1125
936
|
return listAttachments2(this.library, options);
|
|
1126
937
|
}
|
|
1127
938
|
async attachGet(options) {
|
|
1128
|
-
const { getAttachment: getAttachment2 } = await import("./index-
|
|
939
|
+
const { getAttachment: getAttachment2 } = await import("./index-DiF2ezv-.js");
|
|
1129
940
|
return getAttachment2(this.library, options);
|
|
1130
941
|
}
|
|
1131
942
|
async attachDetach(options) {
|
|
1132
|
-
const { detachAttachment: detachAttachment2 } = await import("./index-
|
|
943
|
+
const { detachAttachment: detachAttachment2 } = await import("./index-DiF2ezv-.js");
|
|
1133
944
|
return detachAttachment2(this.library, options);
|
|
1134
945
|
}
|
|
1135
946
|
async attachSync(options) {
|
|
1136
|
-
const { syncAttachments: syncAttachments2 } = await import("./index-
|
|
947
|
+
const { syncAttachments: syncAttachments2 } = await import("./index-DiF2ezv-.js");
|
|
1137
948
|
return syncAttachments2(this.library, options);
|
|
1138
949
|
}
|
|
1139
950
|
async attachOpen(options) {
|
|
1140
|
-
const { openAttachment: openAttachment2 } = await import("./index-
|
|
951
|
+
const { openAttachment: openAttachment2 } = await import("./index-DiF2ezv-.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-
|
|
1795
|
+
const { selectReferencesOrExit } = await import("./reference-select-DlFG6N3o.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-
|
|
2533
|
-
const { buildStyleChoices, listCustomStyles } = await import("./style-select-
|
|
2343
|
+
const { runCiteFlow } = await import("./index-Bh6xryeS.js");
|
|
2344
|
+
const { buildStyleChoices, listCustomStyles } = await import("./style-select-1f5zHT8h.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
|
|
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
|
|
6516
|
-
detectType
|
|
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-
|
|
6926
|
+
const { selectReferencesOrExit } = await import("./reference-select-DlFG6N3o.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-
|
|
10463
|
+
const { selectReferencesOrExit } = await import("./reference-select-DlFG6N3o.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,19 +10651,144 @@ 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" }));
|
|
@@ -31588,6 +31432,115 @@ function registerFulltextDetachTool(server, getLibraryOperations, getConfig) {
|
|
|
31588
31432
|
}
|
|
31589
31433
|
);
|
|
31590
31434
|
}
|
|
31435
|
+
function registerFulltextDiscoverTool(server, getLibraryOperations, getConfig) {
|
|
31436
|
+
server.registerTool(
|
|
31437
|
+
"fulltext_discover",
|
|
31438
|
+
{
|
|
31439
|
+
description: "Check Open Access (OA) availability for a reference. Returns available sources and URLs.",
|
|
31440
|
+
inputSchema: {
|
|
31441
|
+
id: z.string().describe("Reference ID")
|
|
31442
|
+
}
|
|
31443
|
+
},
|
|
31444
|
+
async (args) => {
|
|
31445
|
+
const libraryOps = getLibraryOperations();
|
|
31446
|
+
const config2 = getConfig();
|
|
31447
|
+
const result = await fulltextDiscover(libraryOps, {
|
|
31448
|
+
identifier: args.id,
|
|
31449
|
+
fulltextConfig: config2.fulltext
|
|
31450
|
+
});
|
|
31451
|
+
if (!result.success) {
|
|
31452
|
+
return {
|
|
31453
|
+
content: [{ type: "text", text: result.error ?? "Unknown error" }],
|
|
31454
|
+
isError: true
|
|
31455
|
+
};
|
|
31456
|
+
}
|
|
31457
|
+
if (!result.locations || result.locations.length === 0) {
|
|
31458
|
+
return {
|
|
31459
|
+
content: [{ type: "text", text: `No OA sources found for '${args.id}'` }]
|
|
31460
|
+
};
|
|
31461
|
+
}
|
|
31462
|
+
const lines = [`OA sources for '${args.id}' (status: ${result.oaStatus}):`];
|
|
31463
|
+
for (const loc of result.locations) {
|
|
31464
|
+
lines.push(` ${loc.source}: ${loc.url} (${loc.urlType})`);
|
|
31465
|
+
}
|
|
31466
|
+
return {
|
|
31467
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
31468
|
+
};
|
|
31469
|
+
}
|
|
31470
|
+
);
|
|
31471
|
+
}
|
|
31472
|
+
function registerFulltextFetchTool(server, getLibraryOperations, getConfig) {
|
|
31473
|
+
server.registerTool(
|
|
31474
|
+
"fulltext_fetch",
|
|
31475
|
+
{
|
|
31476
|
+
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.",
|
|
31477
|
+
inputSchema: {
|
|
31478
|
+
id: z.string().describe("Reference ID"),
|
|
31479
|
+
source: z.enum(["pmc", "arxiv", "unpaywall", "core"]).optional().describe("Preferred source (optional)"),
|
|
31480
|
+
force: z.boolean().optional().describe("Force overwrite existing attachment")
|
|
31481
|
+
}
|
|
31482
|
+
},
|
|
31483
|
+
async (args) => {
|
|
31484
|
+
const libraryOps = getLibraryOperations();
|
|
31485
|
+
const config2 = getConfig();
|
|
31486
|
+
const result = await fulltextFetch(libraryOps, {
|
|
31487
|
+
identifier: args.id,
|
|
31488
|
+
fulltextConfig: config2.fulltext,
|
|
31489
|
+
fulltextDirectory: config2.attachments.directory,
|
|
31490
|
+
source: args.source,
|
|
31491
|
+
force: args.force
|
|
31492
|
+
});
|
|
31493
|
+
if (!result.success) {
|
|
31494
|
+
return {
|
|
31495
|
+
content: [{ type: "text", text: result.error ?? "Unknown error" }],
|
|
31496
|
+
isError: true
|
|
31497
|
+
};
|
|
31498
|
+
}
|
|
31499
|
+
const files = result.attachedFiles?.join(", ") ?? "none";
|
|
31500
|
+
return {
|
|
31501
|
+
content: [
|
|
31502
|
+
{
|
|
31503
|
+
type: "text",
|
|
31504
|
+
text: `Fetched fulltext for '${args.id}' from ${result.source}: ${files}`
|
|
31505
|
+
}
|
|
31506
|
+
]
|
|
31507
|
+
};
|
|
31508
|
+
}
|
|
31509
|
+
);
|
|
31510
|
+
}
|
|
31511
|
+
function registerFulltextConvertTool(server, getLibraryOperations, getConfig) {
|
|
31512
|
+
server.registerTool(
|
|
31513
|
+
"fulltext_convert",
|
|
31514
|
+
{
|
|
31515
|
+
description: "Convert an attached PMC JATS XML file to Markdown for a reference.",
|
|
31516
|
+
inputSchema: {
|
|
31517
|
+
id: z.string().describe("Reference ID")
|
|
31518
|
+
}
|
|
31519
|
+
},
|
|
31520
|
+
async (args) => {
|
|
31521
|
+
const libraryOps = getLibraryOperations();
|
|
31522
|
+
const config2 = getConfig();
|
|
31523
|
+
const result = await fulltextConvert(libraryOps, {
|
|
31524
|
+
identifier: args.id,
|
|
31525
|
+
fulltextDirectory: config2.attachments.directory
|
|
31526
|
+
});
|
|
31527
|
+
if (!result.success) {
|
|
31528
|
+
return {
|
|
31529
|
+
content: [{ type: "text", text: result.error ?? "Unknown error" }],
|
|
31530
|
+
isError: true
|
|
31531
|
+
};
|
|
31532
|
+
}
|
|
31533
|
+
return {
|
|
31534
|
+
content: [
|
|
31535
|
+
{
|
|
31536
|
+
type: "text",
|
|
31537
|
+
text: `Converted PMC XML to Markdown for '${args.id}': ${result.filename}`
|
|
31538
|
+
}
|
|
31539
|
+
]
|
|
31540
|
+
};
|
|
31541
|
+
}
|
|
31542
|
+
);
|
|
31543
|
+
}
|
|
31591
31544
|
function registerListTool(server, getLibraryOperations, getConfig) {
|
|
31592
31545
|
server.registerTool(
|
|
31593
31546
|
"list",
|
|
@@ -31721,6 +31674,9 @@ function registerAllTools(server, getLibraryOperations, getConfig) {
|
|
|
31721
31674
|
registerFulltextAttachTool(server, getLibraryOperations, getConfig);
|
|
31722
31675
|
registerFulltextGetTool(server, getLibraryOperations, getConfig);
|
|
31723
31676
|
registerFulltextDetachTool(server, getLibraryOperations, getConfig);
|
|
31677
|
+
registerFulltextDiscoverTool(server, getLibraryOperations, getConfig);
|
|
31678
|
+
registerFulltextFetchTool(server, getLibraryOperations, getConfig);
|
|
31679
|
+
registerFulltextConvertTool(server, getLibraryOperations, getConfig);
|
|
31724
31680
|
}
|
|
31725
31681
|
async function createMcpServer(options) {
|
|
31726
31682
|
const serverInfo = {
|
|
@@ -31774,7 +31730,7 @@ async function mcpStart(options) {
|
|
|
31774
31730
|
async function executeRemove(options, context) {
|
|
31775
31731
|
const { identifier, idType = "id", fulltextDirectory, deleteFulltext = false } = options;
|
|
31776
31732
|
if (context.mode === "local" && deleteFulltext && fulltextDirectory) {
|
|
31777
|
-
const { removeReference } = await import("./index-
|
|
31733
|
+
const { removeReference } = await import("./index-CScRH8g4.js").then((n) => n.u);
|
|
31778
31734
|
return removeReference(context.library, {
|
|
31779
31735
|
identifier,
|
|
31780
31736
|
idType,
|
|
@@ -31829,7 +31785,7 @@ Continue?`;
|
|
|
31829
31785
|
}
|
|
31830
31786
|
async function executeInteractiveRemove(context, config2) {
|
|
31831
31787
|
const { withAlternateScreen: withAlternateScreen2 } = await Promise.resolve().then(() => alternateScreen);
|
|
31832
|
-
const { selectReferenceItemsOrExit } = await import("./reference-select-
|
|
31788
|
+
const { selectReferenceItemsOrExit } = await import("./reference-select-DlFG6N3o.js");
|
|
31833
31789
|
const allReferences = await context.library.getAll();
|
|
31834
31790
|
const selectedItems = await withAlternateScreen2(
|
|
31835
31791
|
() => selectReferenceItemsOrExit(allReferences, { multiSelect: false }, config2.cli.tui)
|
|
@@ -32054,7 +32010,7 @@ async function executeInteractiveSearch(options, context, config2) {
|
|
|
32054
32010
|
validateInteractiveOptions(options);
|
|
32055
32011
|
const { checkTTY } = await import("./tty-BMyaEOhX.js");
|
|
32056
32012
|
const { withAlternateScreen: withAlternateScreen2 } = await Promise.resolve().then(() => alternateScreen);
|
|
32057
|
-
const { runSearchFlow } = await import("./index-
|
|
32013
|
+
const { runSearchFlow } = await import("./index-Bh6xryeS.js");
|
|
32058
32014
|
const { search } = await import("./file-watcher-CrsNHUpz.js").then((n) => n.z);
|
|
32059
32015
|
const { tokenize } = await import("./file-watcher-CrsNHUpz.js").then((n) => n.y);
|
|
32060
32016
|
checkTTY();
|
|
@@ -32073,7 +32029,7 @@ async function executeInteractiveSearch(options, context, config2) {
|
|
|
32073
32029
|
})
|
|
32074
32030
|
);
|
|
32075
32031
|
if (result.selectedItems && !result.cancelled) {
|
|
32076
|
-
const { isSideEffectAction } = await import("./action-menu-
|
|
32032
|
+
const { isSideEffectAction } = await import("./action-menu-CpkiDbRj.js");
|
|
32077
32033
|
if (isSideEffectAction(result.action)) {
|
|
32078
32034
|
await executeSideEffectAction(result.action, result.selectedItems, context, config2);
|
|
32079
32035
|
return { output: "", cancelled: false, action: result.action };
|
|
@@ -32089,7 +32045,7 @@ async function executeSideEffectAction(action, items2, context, config2) {
|
|
|
32089
32045
|
switch (action) {
|
|
32090
32046
|
case "open-url": {
|
|
32091
32047
|
const { resolveDefaultUrl: resolveDefaultUrl2 } = await Promise.resolve().then(() => url);
|
|
32092
|
-
const { openWithSystemApp: openWithSystemApp2 } = await import("./loader-
|
|
32048
|
+
const { openWithSystemApp: openWithSystemApp2 } = await import("./loader-BHvcats5.js").then((n) => n.j);
|
|
32093
32049
|
const item = items2[0];
|
|
32094
32050
|
if (!item) return;
|
|
32095
32051
|
const url$1 = resolveDefaultUrl2(item);
|
|
@@ -32476,7 +32432,7 @@ function formatUpdateOutput(result, identifier) {
|
|
|
32476
32432
|
}
|
|
32477
32433
|
async function executeInteractiveUpdate(context, config2) {
|
|
32478
32434
|
const { withAlternateScreen: withAlternateScreen2 } = await Promise.resolve().then(() => alternateScreen);
|
|
32479
|
-
const { selectReferencesOrExit } = await import("./reference-select-
|
|
32435
|
+
const { selectReferencesOrExit } = await import("./reference-select-DlFG6N3o.js");
|
|
32480
32436
|
const allReferences = await context.library.getAll();
|
|
32481
32437
|
const identifiers = await withAlternateScreen2(
|
|
32482
32438
|
() => selectReferencesOrExit(allReferences, { multiSelect: false }, config2.cli.tui)
|
|
@@ -32771,7 +32727,7 @@ function getUrlExitCode(result) {
|
|
|
32771
32727
|
}
|
|
32772
32728
|
async function executeInteractiveSelect(context, config2) {
|
|
32773
32729
|
const { withAlternateScreen: withAlternateScreen2 } = await Promise.resolve().then(() => alternateScreen);
|
|
32774
|
-
const { selectReferencesOrExit } = await import("./reference-select-
|
|
32730
|
+
const { selectReferencesOrExit } = await import("./reference-select-DlFG6N3o.js");
|
|
32775
32731
|
const allReferences = await context.library.getAll();
|
|
32776
32732
|
const identifiers = await withAlternateScreen2(
|
|
32777
32733
|
() => selectReferencesOrExit(allReferences, { multiSelect: false }, config2.cli.tui)
|
|
@@ -33267,6 +33223,35 @@ async function outputAddResultJson(result, context, full) {
|
|
|
33267
33223
|
process.stdout.write(`${JSON.stringify(jsonOutput2)}
|
|
33268
33224
|
`);
|
|
33269
33225
|
}
|
|
33226
|
+
function shouldAutoFetch(cliFlag, configEnabled) {
|
|
33227
|
+
if (cliFlag !== void 0) {
|
|
33228
|
+
return cliFlag;
|
|
33229
|
+
}
|
|
33230
|
+
return configEnabled;
|
|
33231
|
+
}
|
|
33232
|
+
async function performAutoFetch(addedItems, context, config2) {
|
|
33233
|
+
const { fulltextFetch: fulltextFetch2 } = await import("./index-CScRH8g4.js").then((n) => n.t);
|
|
33234
|
+
const fetchResults = await autoFetchFulltext(addedItems, context, {
|
|
33235
|
+
fulltextConfig: config2.fulltext,
|
|
33236
|
+
fulltextDirectory: config2.attachments.directory,
|
|
33237
|
+
fulltextFetchFn: fulltextFetch2
|
|
33238
|
+
});
|
|
33239
|
+
for (const [i, fetchResult] of fetchResults.entries()) {
|
|
33240
|
+
const addedItem = addedItems[i];
|
|
33241
|
+
if (!addedItem) continue;
|
|
33242
|
+
if (fetchResult.success) {
|
|
33243
|
+
process.stderr.write(
|
|
33244
|
+
`Fulltext for ${addedItem.id}: ${formatFulltextFetchOutput(fetchResult)}
|
|
33245
|
+
`
|
|
33246
|
+
);
|
|
33247
|
+
} else {
|
|
33248
|
+
process.stderr.write(
|
|
33249
|
+
`Fulltext for ${addedItem.id}: ${fetchResult.error ?? "unknown error"}
|
|
33250
|
+
`
|
|
33251
|
+
);
|
|
33252
|
+
}
|
|
33253
|
+
}
|
|
33254
|
+
}
|
|
33270
33255
|
async function handleAddAction(inputs, options, program) {
|
|
33271
33256
|
const outputFormat = options.output ?? "text";
|
|
33272
33257
|
try {
|
|
@@ -33286,6 +33271,9 @@ async function handleAddAction(inputs, options, program) {
|
|
|
33286
33271
|
process.stderr.write(`${output}
|
|
33287
33272
|
`);
|
|
33288
33273
|
}
|
|
33274
|
+
if (result.added.length > 0 && shouldAutoFetch(options.fetchFulltext, config2.fulltext.autoFetchOnAdd)) {
|
|
33275
|
+
await performAutoFetch(result.added, context, config2);
|
|
33276
|
+
}
|
|
33289
33277
|
setExitCode(getExitCode(result));
|
|
33290
33278
|
} catch (error) {
|
|
33291
33279
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -33300,7 +33288,7 @@ async function handleAddAction(inputs, options, program) {
|
|
|
33300
33288
|
}
|
|
33301
33289
|
}
|
|
33302
33290
|
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) => {
|
|
33291
|
+
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
33292
|
await handleAddAction(inputs, options, program);
|
|
33305
33293
|
});
|
|
33306
33294
|
}
|
|
@@ -33472,6 +33460,15 @@ function registerFulltextCommand(program) {
|
|
|
33472
33460
|
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
33461
|
await handleFulltextOpenAction(identifier, options, program.opts());
|
|
33474
33462
|
});
|
|
33463
|
+
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) => {
|
|
33464
|
+
await handleFulltextDiscoverAction(identifier, options, program.opts());
|
|
33465
|
+
});
|
|
33466
|
+
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) => {
|
|
33467
|
+
await handleFulltextFetchAction(identifier, options, program.opts());
|
|
33468
|
+
});
|
|
33469
|
+
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) => {
|
|
33470
|
+
await handleFulltextConvertAction(identifier, options, program.opts());
|
|
33471
|
+
});
|
|
33475
33472
|
}
|
|
33476
33473
|
function registerUrlCommand(program) {
|
|
33477
33474
|
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 +33492,7 @@ async function main(argv) {
|
|
|
33495
33492
|
}
|
|
33496
33493
|
export {
|
|
33497
33494
|
Select as S,
|
|
33498
|
-
|
|
33499
|
-
stringify as b,
|
|
33495
|
+
stringify as a,
|
|
33500
33496
|
createProgram as c,
|
|
33501
33497
|
detachAttachment as d,
|
|
33502
33498
|
formatBibtex as f,
|
|
@@ -33508,4 +33504,4 @@ export {
|
|
|
33508
33504
|
restoreStdinAfterInk as r,
|
|
33509
33505
|
syncAttachments as s
|
|
33510
33506
|
};
|
|
33511
|
-
//# sourceMappingURL=index-
|
|
33507
|
+
//# sourceMappingURL=index-Cq83Elk8.js.map
|