@ncukondo/reference-manager 0.27.0 → 0.27.2
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/{SearchableMultiSelect-D6-pj_XI.js → SearchableMultiSelect-0cyizees.js} +2 -2
- package/dist/chunks/{SearchableMultiSelect-D6-pj_XI.js.map → SearchableMultiSelect-0cyizees.js.map} +1 -1
- package/dist/chunks/{action-menu-DP1rmCkH.js → action-menu-Crcd5X1-.js} +3 -3
- package/dist/chunks/{action-menu-DP1rmCkH.js.map → action-menu-Crcd5X1-.js.map} +1 -1
- package/dist/chunks/{checker-B-SL7krG.js → checker-gLAPe44T.js} +4 -4
- package/dist/chunks/{checker-B-SL7krG.js.map → checker-gLAPe44T.js.map} +1 -1
- package/dist/chunks/{crossref-client-D6g3pLUI.js → crossref-client-CXw0mw4n.js} +2 -2
- package/dist/chunks/{crossref-client-D6g3pLUI.js.map → crossref-client-CXw0mw4n.js.map} +1 -1
- package/dist/chunks/{fix-interaction-CTIvq9t4.js → fix-interaction-BCtx4Q9F.js} +5 -5
- package/dist/chunks/{fix-interaction-CTIvq9t4.js.map → fix-interaction-BCtx4Q9F.js.map} +1 -1
- package/dist/chunks/{index-DUpYvm-W.js → index-BdLVfbj0.js} +168 -33
- package/dist/chunks/index-BdLVfbj0.js.map +1 -0
- package/dist/chunks/{index-Bo1JIDmF.js → index-Cf5bYLrr.js} +4 -4
- package/dist/chunks/{index-Bo1JIDmF.js.map → index-Cf5bYLrr.js.map} +1 -1
- package/dist/chunks/{index-B8ST0WLa.js → index-DkJT7s7N.js} +87 -30
- package/dist/chunks/index-DkJT7s7N.js.map +1 -0
- package/dist/chunks/{index-F4gbDFWf.js → index-SwsAKyQr.js} +3 -3
- package/dist/chunks/index-SwsAKyQr.js.map +1 -0
- package/dist/chunks/{pubmed-client-mGn5jDIc.js → pubmed-client-DH9y2tEZ.js} +2 -2
- package/dist/chunks/{pubmed-client-mGn5jDIc.js.map → pubmed-client-DH9y2tEZ.js.map} +1 -1
- package/dist/chunks/{reference-select-DtzpiOvp.js → reference-select-Ce9pG8cC.js} +3 -3
- package/dist/chunks/{reference-select-DtzpiOvp.js.map → reference-select-Ce9pG8cC.js.map} +1 -1
- package/dist/chunks/{style-select-BT-HOyFf.js → style-select-AvDsSnlB.js} +3 -3
- package/dist/chunks/{style-select-BT-HOyFf.js.map → style-select-AvDsSnlB.js.map} +1 -1
- package/dist/cli/commands/fulltext.d.ts.map +1 -1
- package/dist/cli/help/search-help.d.ts +1 -0
- package/dist/cli/help/search-help.d.ts.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/features/operations/fulltext/fetch.d.ts +27 -4
- package/dist/features/operations/fulltext/fetch.d.ts.map +1 -1
- package/dist/mcp/tools/fulltext.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunks/index-B8ST0WLa.js.map +0 -1
- package/dist/chunks/index-DUpYvm-W.js.map +0 -1
- package/dist/chunks/index-F4gbDFWf.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checker-B-SL7krG.js","sources":["../../src/features/check/checker.ts"],"sourcesContent":["import type { CslItem } from \"../../core/csl-json/types.js\";\nimport type { CrossrefUpdateInfo, RemoteMetadata } from \"./crossref-client.js\";\nimport type { CheckFinding, CheckResult } from \"./types.js\";\n\nexport interface CheckConfig {\n email?: string;\n pubmed?: { email?: string; apiKey?: string };\n metadata?: boolean;\n}\n\ninterface CrossrefCheckResult {\n findings: CheckFinding[];\n metadata?: RemoteMetadata;\n}\n\n/**\n * Check a single reference against external sources for status changes.\n *\n * @param item - The CSL-JSON item to check\n * @param config - Optional config for API credentials\n * @returns Check result with findings\n */\nexport async function checkReference(item: CslItem, config?: CheckConfig): Promise<CheckResult> {\n const id = item.id;\n const uuid = (item.custom?.uuid as string) ?? \"\";\n const checkedAt = new Date().toISOString();\n const findings: CheckFinding[] = [];\n const checkedSources: string[] = [];\n\n const hasDoi = !!item.DOI;\n const hasPmid = !!item.PMID;\n\n // Skip references with neither DOI nor PMID\n if (!hasDoi && !hasPmid) {\n return { id, uuid, status: \"skipped\", findings: [], checkedAt, checkedSources: [] };\n }\n\n let crossrefMetadata: RemoteMetadata | undefined;\n\n // Query Crossref if DOI is present\n if (hasDoi) {\n checkedSources.push(\"crossref\");\n const crossrefResult = await checkCrossref(item.DOI as string, config);\n findings.push(...crossrefResult.findings);\n crossrefMetadata = crossrefResult.metadata;\n }\n\n // Query PubMed if PMID is present\n if (hasPmid) {\n checkedSources.push(\"pubmed\");\n const pubmedFindings = await checkPubmed(item.PMID as string, config);\n addUniqueFindings(findings, pubmedFindings);\n }\n\n // Metadata comparison (default: enabled)\n const metadataFinding = await checkMetadata(item, config, crossrefMetadata, hasPmid, hasDoi);\n if (metadataFinding) {\n findings.push(metadataFinding);\n }\n\n const status = findings.length > 0 ? \"warning\" : \"ok\";\n return { id, uuid, status, findings, checkedAt, checkedSources };\n}\n\n/**\n * Add findings that aren't already present (by type) to the target list.\n */\nfunction addUniqueFindings(target: CheckFinding[], source: CheckFinding[]): void {\n for (const finding of source) {\n if (!target.some((f) => f.type === finding.type)) {\n target.push(finding);\n }\n }\n}\n\n/**\n * Perform metadata comparison if enabled.\n */\nasync function checkMetadata(\n item: CslItem,\n config: CheckConfig | undefined,\n crossrefMetadata: RemoteMetadata | undefined,\n hasPmid: boolean,\n hasDoi: boolean\n): Promise<CheckFinding | null> {\n if (config?.metadata === false) return null;\n\n if (crossrefMetadata) {\n // DOI-based: compare against Crossref metadata\n return compareItemMetadata(item, crossrefMetadata);\n }\n\n if (hasPmid && !hasDoi) {\n // PubMed-only: fetch remote CSL-JSON via PubMed and compare\n return comparePubmedMetadata(item, config);\n }\n\n return null;\n}\n\n/**\n * Query Crossref and return findings plus metadata.\n */\nasync function checkCrossref(doi: string, config?: CheckConfig): Promise<CrossrefCheckResult> {\n const { queryCrossref } = await import(\"./crossref-client.js\");\n const crossrefConfig = config?.email ? { email: config.email } : undefined;\n const result = await queryCrossref(doi, crossrefConfig);\n if (!result.success) return { findings: [] };\n\n const findings: CheckFinding[] = [];\n for (const update of result.updates) {\n const finding = mapCrossrefUpdate(update);\n if (finding) {\n findings.push(finding);\n }\n }\n return result.metadata ? { findings, metadata: result.metadata } : { findings };\n}\n\n/**\n * Compare item metadata against Crossref metadata.\n */\nasync function compareItemMetadata(\n item: CslItem,\n remoteMetadata: RemoteMetadata\n): Promise<CheckFinding | null> {\n const { compareMetadata } = await import(\"./metadata-comparator.js\");\n\n const local = extractLocalMetadata(item);\n const comparison = compareMetadata(local, remoteMetadata);\n\n if (comparison.classification === \"no_change\") return null;\n\n const type = comparison.classification;\n const message =\n type === \"metadata_mismatch\"\n ? \"Local metadata significantly differs from the remote record\"\n : \"Remote metadata has been updated since import\";\n\n return {\n type,\n message,\n details: {\n updatedFields: comparison.changedFields,\n fieldDiffs: comparison.fieldDiffs,\n },\n };\n}\n\n/**\n * Fetch PubMed CSL-JSON and compare metadata for PMID-only references.\n */\nasync function comparePubmedMetadata(\n item: CslItem,\n config?: CheckConfig\n): Promise<CheckFinding | null> {\n const { fetchPmids } = await import(\"../import/fetcher.js\");\n const pubmedConfig = config?.pubmed ?? {};\n const results = await fetchPmids([item.PMID as string], pubmedConfig);\n const result = results[0];\n if (!result || !result.success) {\n console.error(\n `PubMed metadata fetch failed for PMID ${item.PMID}: ${result?.error ?? \"unknown error\"}`\n );\n return null;\n }\n\n const remoteMetadata = cslItemToRemoteMetadata(result.item);\n return compareItemMetadata(item, remoteMetadata);\n}\n\n/**\n * Convert a CslItem (from PubMed) to RemoteMetadata format for comparison.\n */\nfunction cslItemToRemoteMetadata(item: CslItem): RemoteMetadata {\n const metadata: RemoteMetadata = {};\n if (item.title !== undefined) metadata.title = item.title;\n if (item.author !== undefined) {\n metadata.author = item.author as Array<{ family?: string; given?: string }>;\n }\n if (item[\"container-title\"] !== undefined) metadata.containerTitle = item[\"container-title\"];\n if (item.type !== undefined) metadata.type = item.type;\n if (item.page !== undefined) metadata.page = item.page;\n if (item.volume !== undefined) metadata.volume = item.volume;\n if (item.issue !== undefined) metadata.issue = item.issue;\n if (item.issued !== undefined) {\n metadata.issued = item.issued as { \"date-parts\"?: number[][] };\n }\n return metadata;\n}\n\n/**\n * Extract local metadata fields from a CslItem for comparison.\n */\nfunction extractLocalMetadata(\n item: CslItem\n): import(\"./metadata-comparator.js\").LocalMetadataFields {\n const local: import(\"./metadata-comparator.js\").LocalMetadataFields = {};\n if (item.title !== undefined) local.title = item.title;\n if (item.author !== undefined)\n local.author = item.author as Array<{ family?: string; given?: string }>;\n if (item[\"container-title\"] !== undefined) local[\"container-title\"] = item[\"container-title\"];\n if (item.type !== undefined) local.type = item.type;\n if (item.page !== undefined) local.page = item.page;\n if (item.volume !== undefined) local.volume = item.volume;\n if (item.issue !== undefined) local.issue = item.issue;\n if (item.issued !== undefined) local.issued = item.issued as { \"date-parts\"?: number[][] };\n return local;\n}\n\n/**\n * Query PubMed and return findings.\n */\nasync function checkPubmed(pmid: string, config?: CheckConfig): Promise<CheckFinding[]> {\n const { queryPubmed } = await import(\"./pubmed-client.js\");\n const result = await queryPubmed(pmid, config?.pubmed);\n if (!result.success) return [];\n\n const findings: CheckFinding[] = [];\n if (result.isRetracted) {\n findings.push({\n type: \"retracted\",\n message: \"This article is marked as retracted in PubMed\",\n });\n }\n if (result.hasConcern) {\n findings.push({\n type: \"concern\",\n message: \"Expression of concern noted in PubMed\",\n });\n }\n return findings;\n}\n\n/**\n * Map a Crossref update-to entry to a CheckFinding.\n */\nfunction mapCrossrefUpdate(update: CrossrefUpdateInfo): CheckFinding | null {\n const doiDetail = update.doi ? { retractionDoi: update.doi } : {};\n const dateDetail = update.date ? { retractionDate: update.date } : {};\n const newDoiDetail = update.doi ? { newDoi: update.doi } : {};\n\n switch (update.type) {\n case \"retraction\":\n return {\n type: \"retracted\",\n message: update.date\n ? `This article was retracted on ${update.date}`\n : \"This article was retracted\",\n details: { ...doiDetail, ...dateDetail },\n };\n case \"expression-of-concern\":\n return {\n type: \"concern\",\n message: update.date\n ? `Expression of concern issued on ${update.date}`\n : \"Expression of concern issued\",\n details: { ...doiDetail, ...dateDetail },\n };\n case \"new_version\":\n return {\n type: \"version_changed\",\n message: update.doi\n ? `Published version available: ${update.doi}`\n : \"Published version available\",\n details: newDoiDetail,\n };\n default:\n return null;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"checker-gLAPe44T.js","sources":["../../src/features/check/checker.ts"],"sourcesContent":["import type { CslItem } from \"../../core/csl-json/types.js\";\nimport type { CrossrefUpdateInfo, RemoteMetadata } from \"./crossref-client.js\";\nimport type { CheckFinding, CheckResult } from \"./types.js\";\n\nexport interface CheckConfig {\n email?: string;\n pubmed?: { email?: string; apiKey?: string };\n metadata?: boolean;\n}\n\ninterface CrossrefCheckResult {\n findings: CheckFinding[];\n metadata?: RemoteMetadata;\n}\n\n/**\n * Check a single reference against external sources for status changes.\n *\n * @param item - The CSL-JSON item to check\n * @param config - Optional config for API credentials\n * @returns Check result with findings\n */\nexport async function checkReference(item: CslItem, config?: CheckConfig): Promise<CheckResult> {\n const id = item.id;\n const uuid = (item.custom?.uuid as string) ?? \"\";\n const checkedAt = new Date().toISOString();\n const findings: CheckFinding[] = [];\n const checkedSources: string[] = [];\n\n const hasDoi = !!item.DOI;\n const hasPmid = !!item.PMID;\n\n // Skip references with neither DOI nor PMID\n if (!hasDoi && !hasPmid) {\n return { id, uuid, status: \"skipped\", findings: [], checkedAt, checkedSources: [] };\n }\n\n let crossrefMetadata: RemoteMetadata | undefined;\n\n // Query Crossref if DOI is present\n if (hasDoi) {\n checkedSources.push(\"crossref\");\n const crossrefResult = await checkCrossref(item.DOI as string, config);\n findings.push(...crossrefResult.findings);\n crossrefMetadata = crossrefResult.metadata;\n }\n\n // Query PubMed if PMID is present\n if (hasPmid) {\n checkedSources.push(\"pubmed\");\n const pubmedFindings = await checkPubmed(item.PMID as string, config);\n addUniqueFindings(findings, pubmedFindings);\n }\n\n // Metadata comparison (default: enabled)\n const metadataFinding = await checkMetadata(item, config, crossrefMetadata, hasPmid, hasDoi);\n if (metadataFinding) {\n findings.push(metadataFinding);\n }\n\n const status = findings.length > 0 ? \"warning\" : \"ok\";\n return { id, uuid, status, findings, checkedAt, checkedSources };\n}\n\n/**\n * Add findings that aren't already present (by type) to the target list.\n */\nfunction addUniqueFindings(target: CheckFinding[], source: CheckFinding[]): void {\n for (const finding of source) {\n if (!target.some((f) => f.type === finding.type)) {\n target.push(finding);\n }\n }\n}\n\n/**\n * Perform metadata comparison if enabled.\n */\nasync function checkMetadata(\n item: CslItem,\n config: CheckConfig | undefined,\n crossrefMetadata: RemoteMetadata | undefined,\n hasPmid: boolean,\n hasDoi: boolean\n): Promise<CheckFinding | null> {\n if (config?.metadata === false) return null;\n\n if (crossrefMetadata) {\n // DOI-based: compare against Crossref metadata\n return compareItemMetadata(item, crossrefMetadata);\n }\n\n if (hasPmid && !hasDoi) {\n // PubMed-only: fetch remote CSL-JSON via PubMed and compare\n return comparePubmedMetadata(item, config);\n }\n\n return null;\n}\n\n/**\n * Query Crossref and return findings plus metadata.\n */\nasync function checkCrossref(doi: string, config?: CheckConfig): Promise<CrossrefCheckResult> {\n const { queryCrossref } = await import(\"./crossref-client.js\");\n const crossrefConfig = config?.email ? { email: config.email } : undefined;\n const result = await queryCrossref(doi, crossrefConfig);\n if (!result.success) return { findings: [] };\n\n const findings: CheckFinding[] = [];\n for (const update of result.updates) {\n const finding = mapCrossrefUpdate(update);\n if (finding) {\n findings.push(finding);\n }\n }\n return result.metadata ? { findings, metadata: result.metadata } : { findings };\n}\n\n/**\n * Compare item metadata against Crossref metadata.\n */\nasync function compareItemMetadata(\n item: CslItem,\n remoteMetadata: RemoteMetadata\n): Promise<CheckFinding | null> {\n const { compareMetadata } = await import(\"./metadata-comparator.js\");\n\n const local = extractLocalMetadata(item);\n const comparison = compareMetadata(local, remoteMetadata);\n\n if (comparison.classification === \"no_change\") return null;\n\n const type = comparison.classification;\n const message =\n type === \"metadata_mismatch\"\n ? \"Local metadata significantly differs from the remote record\"\n : \"Remote metadata has been updated since import\";\n\n return {\n type,\n message,\n details: {\n updatedFields: comparison.changedFields,\n fieldDiffs: comparison.fieldDiffs,\n },\n };\n}\n\n/**\n * Fetch PubMed CSL-JSON and compare metadata for PMID-only references.\n */\nasync function comparePubmedMetadata(\n item: CslItem,\n config?: CheckConfig\n): Promise<CheckFinding | null> {\n const { fetchPmids } = await import(\"../import/fetcher.js\");\n const pubmedConfig = config?.pubmed ?? {};\n const results = await fetchPmids([item.PMID as string], pubmedConfig);\n const result = results[0];\n if (!result || !result.success) {\n console.error(\n `PubMed metadata fetch failed for PMID ${item.PMID}: ${result?.error ?? \"unknown error\"}`\n );\n return null;\n }\n\n const remoteMetadata = cslItemToRemoteMetadata(result.item);\n return compareItemMetadata(item, remoteMetadata);\n}\n\n/**\n * Convert a CslItem (from PubMed) to RemoteMetadata format for comparison.\n */\nfunction cslItemToRemoteMetadata(item: CslItem): RemoteMetadata {\n const metadata: RemoteMetadata = {};\n if (item.title !== undefined) metadata.title = item.title;\n if (item.author !== undefined) {\n metadata.author = item.author as Array<{ family?: string; given?: string }>;\n }\n if (item[\"container-title\"] !== undefined) metadata.containerTitle = item[\"container-title\"];\n if (item.type !== undefined) metadata.type = item.type;\n if (item.page !== undefined) metadata.page = item.page;\n if (item.volume !== undefined) metadata.volume = item.volume;\n if (item.issue !== undefined) metadata.issue = item.issue;\n if (item.issued !== undefined) {\n metadata.issued = item.issued as { \"date-parts\"?: number[][] };\n }\n return metadata;\n}\n\n/**\n * Extract local metadata fields from a CslItem for comparison.\n */\nfunction extractLocalMetadata(\n item: CslItem\n): import(\"./metadata-comparator.js\").LocalMetadataFields {\n const local: import(\"./metadata-comparator.js\").LocalMetadataFields = {};\n if (item.title !== undefined) local.title = item.title;\n if (item.author !== undefined)\n local.author = item.author as Array<{ family?: string; given?: string }>;\n if (item[\"container-title\"] !== undefined) local[\"container-title\"] = item[\"container-title\"];\n if (item.type !== undefined) local.type = item.type;\n if (item.page !== undefined) local.page = item.page;\n if (item.volume !== undefined) local.volume = item.volume;\n if (item.issue !== undefined) local.issue = item.issue;\n if (item.issued !== undefined) local.issued = item.issued as { \"date-parts\"?: number[][] };\n return local;\n}\n\n/**\n * Query PubMed and return findings.\n */\nasync function checkPubmed(pmid: string, config?: CheckConfig): Promise<CheckFinding[]> {\n const { queryPubmed } = await import(\"./pubmed-client.js\");\n const result = await queryPubmed(pmid, config?.pubmed);\n if (!result.success) return [];\n\n const findings: CheckFinding[] = [];\n if (result.isRetracted) {\n findings.push({\n type: \"retracted\",\n message: \"This article is marked as retracted in PubMed\",\n });\n }\n if (result.hasConcern) {\n findings.push({\n type: \"concern\",\n message: \"Expression of concern noted in PubMed\",\n });\n }\n return findings;\n}\n\n/**\n * Map a Crossref update-to entry to a CheckFinding.\n */\nfunction mapCrossrefUpdate(update: CrossrefUpdateInfo): CheckFinding | null {\n const doiDetail = update.doi ? { retractionDoi: update.doi } : {};\n const dateDetail = update.date ? { retractionDate: update.date } : {};\n const newDoiDetail = update.doi ? { newDoi: update.doi } : {};\n\n switch (update.type) {\n case \"retraction\":\n return {\n type: \"retracted\",\n message: update.date\n ? `This article was retracted on ${update.date}`\n : \"This article was retracted\",\n details: { ...doiDetail, ...dateDetail },\n };\n case \"expression-of-concern\":\n return {\n type: \"concern\",\n message: update.date\n ? `Expression of concern issued on ${update.date}`\n : \"Expression of concern issued\",\n details: { ...doiDetail, ...dateDetail },\n };\n case \"new_version\":\n return {\n type: \"version_changed\",\n message: update.doi\n ? `Published version available: ${update.doi}`\n : \"Published version available\",\n details: newDoiDetail,\n };\n default:\n return null;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { g as getRateLimiter } from "./index-
|
|
1
|
+
import { g as getRateLimiter } from "./index-BdLVfbj0.js";
|
|
2
2
|
const CROSSREF_API_BASE = "https://api.crossref.org/works";
|
|
3
3
|
function formatDateParts(updated) {
|
|
4
4
|
if (!updated || typeof updated !== "object") return {};
|
|
@@ -91,4 +91,4 @@ async function queryCrossref(doi, config) {
|
|
|
91
91
|
export {
|
|
92
92
|
queryCrossref
|
|
93
93
|
};
|
|
94
|
-
//# sourceMappingURL=crossref-client-
|
|
94
|
+
//# sourceMappingURL=crossref-client-CXw0mw4n.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crossref-client-
|
|
1
|
+
{"version":3,"file":"crossref-client-CXw0mw4n.js","sources":["../../src/features/check/crossref-client.ts"],"sourcesContent":["import { getRateLimiter } from \"../import/rate-limiter.js\";\n\nconst CROSSREF_API_BASE = \"https://api.crossref.org/works\";\n\nexport interface CrossrefUpdateInfo {\n type: string;\n doi?: string;\n label?: string;\n date?: string;\n}\n\nexport interface RemoteMetadata {\n title?: string;\n author?: Array<{ family?: string; given?: string }>;\n containerTitle?: string;\n type?: string;\n page?: string;\n volume?: string;\n issue?: string;\n issued?: { \"date-parts\"?: number[][] };\n}\n\nexport type CrossrefResult =\n | { success: true; updates: CrossrefUpdateInfo[]; metadata?: RemoteMetadata }\n | { success: false; error: string };\n\n/**\n * Format date-parts from Crossref API response to ISO date string.\n */\nfunction formatDateParts(updated: unknown): { date?: string } {\n if (!updated || typeof updated !== \"object\") return {};\n const dateParts = (updated as Record<string, unknown>)[\"date-parts\"];\n if (!Array.isArray(dateParts) || dateParts.length === 0) return {};\n const parts = dateParts[0] as number[];\n if (!Array.isArray(parts) || parts.length === 0) return {};\n const [year, month, day] = parts;\n const m = String(month ?? 1).padStart(2, \"0\");\n const d = String(day ?? 1).padStart(2, \"0\");\n return { date: `${year}-${m}-${d}` };\n}\n\n/**\n * Extract comparable metadata fields from a Crossref message object.\n */\nfunction extractMetadata(message: Record<string, unknown> | undefined): RemoteMetadata | undefined {\n if (!message) return undefined;\n\n const metadata: RemoteMetadata = {};\n\n // Title: array in Crossref, take first element\n const titleArr = message.title as string[] | undefined;\n const firstTitle = Array.isArray(titleArr) ? titleArr[0] : undefined;\n if (firstTitle) {\n metadata.title = firstTitle;\n }\n\n // Author\n const authorArr = message.author as Array<{ family?: string; given?: string }> | undefined;\n if (Array.isArray(authorArr) && authorArr.length > 0) {\n metadata.author = authorArr.map((a) => ({\n ...(a.family ? { family: a.family } : {}),\n ...(a.given ? { given: a.given } : {}),\n }));\n }\n\n // Container title: array in Crossref, take first\n const containerArr = message[\"container-title\"] as string[] | undefined;\n const firstContainer = Array.isArray(containerArr) ? containerArr[0] : undefined;\n if (firstContainer) {\n metadata.containerTitle = firstContainer;\n }\n\n // Type\n if (typeof message.type === \"string\") {\n metadata.type = message.type;\n }\n\n // Page, volume, issue\n if (typeof message.page === \"string\") metadata.page = message.page;\n if (typeof message.volume === \"string\") metadata.volume = message.volume;\n if (typeof message.issue === \"string\") metadata.issue = message.issue;\n\n // Issued\n const issued = message.issued as { \"date-parts\"?: number[][] } | undefined;\n if (issued && Array.isArray(issued[\"date-parts\"])) {\n metadata.issued = issued;\n }\n\n // Return undefined if no metadata fields were populated (prevents spurious diffs)\n if (Object.keys(metadata).length === 0) return undefined;\n\n return metadata;\n}\n\n/**\n * Query Crossref REST API for a DOI and extract update-to information.\n *\n * @param doi - The DOI to query\n * @param config - Optional config with email for polite pool\n * @returns Crossref result with update information\n */\nexport async function queryCrossref(\n doi: string,\n config?: { email?: string }\n): Promise<CrossrefResult> {\n const rateLimiter = getRateLimiter(\"crossref\", {});\n await rateLimiter.acquire();\n\n try {\n const url = new URL(`${CROSSREF_API_BASE}/${encodeURIComponent(doi)}`);\n if (config?.email) {\n url.searchParams.set(\"mailto\", config.email);\n }\n const response = await fetch(url.toString());\n\n if (!response.ok) {\n return {\n success: false,\n error: `Crossref API returned ${response.status} ${response.statusText}`,\n };\n }\n\n const data = (await response.json()) as Record<string, unknown>;\n const message = data.message as Record<string, unknown> | undefined;\n\n const updateTo = (message?.[\"update-to\"] ?? []) as Record<string, unknown>[];\n const updatedBy = (message?.[\"updated-by\"] ?? []) as Record<string, unknown>[];\n const allEntries: CrossrefUpdateInfo[] = [...updateTo, ...updatedBy].map((e) => {\n const datePart = formatDateParts(e.updated);\n return {\n type: String(e.type ?? \"\"),\n ...(e.DOI ? { doi: String(e.DOI) } : {}),\n ...(e.label ? { label: String(e.label) } : {}),\n ...(datePart.date ? { date: datePart.date } : {}),\n };\n });\n\n const seen = new Set<string>();\n const updates = allEntries.filter((e) => {\n const key = `${e.type}:${e.doi ?? \"\"}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n\n const metadata = extractMetadata(message);\n\n return metadata ? { success: true, updates, metadata } : { success: true, updates };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { render } from "ink";
|
|
2
2
|
import { createElement } from "react";
|
|
3
|
-
import { S as Select, r as restoreStdinAfterInk } from "./index-
|
|
3
|
+
import { S as Select, r as restoreStdinAfterInk } from "./index-DkJT7s7N.js";
|
|
4
4
|
function getFixActionsForFinding(finding) {
|
|
5
5
|
switch (finding.type) {
|
|
6
6
|
case "retracted":
|
|
@@ -77,7 +77,7 @@ async function applyUpdateFromPublished(library, item, finding) {
|
|
|
77
77
|
if (!newDoi) {
|
|
78
78
|
return { applied: false, message: "No published DOI available in finding details" };
|
|
79
79
|
}
|
|
80
|
-
const { fetchDoi } = await import("./index-
|
|
80
|
+
const { fetchDoi } = await import("./index-BdLVfbj0.js").then((n) => n.y);
|
|
81
81
|
const fetchResult = await fetchDoi(newDoi);
|
|
82
82
|
if (!fetchResult.success) {
|
|
83
83
|
return {
|
|
@@ -93,7 +93,7 @@ async function applyUpdateFromPublished(library, item, finding) {
|
|
|
93
93
|
async function applyUpdateAllFields(library, item, finding) {
|
|
94
94
|
let fetchedItem;
|
|
95
95
|
if (item.DOI) {
|
|
96
|
-
const { fetchDoi } = await import("./index-
|
|
96
|
+
const { fetchDoi } = await import("./index-BdLVfbj0.js").then((n) => n.y);
|
|
97
97
|
const fetchResult = await fetchDoi(item.DOI);
|
|
98
98
|
if (!fetchResult.success) {
|
|
99
99
|
return {
|
|
@@ -103,7 +103,7 @@ async function applyUpdateAllFields(library, item, finding) {
|
|
|
103
103
|
}
|
|
104
104
|
fetchedItem = fetchResult.item;
|
|
105
105
|
} else if (item.PMID) {
|
|
106
|
-
const { fetchPmids } = await import("./index-
|
|
106
|
+
const { fetchPmids } = await import("./index-BdLVfbj0.js").then((n) => n.y);
|
|
107
107
|
const results = await fetchPmids([item.PMID], {});
|
|
108
108
|
const result = results[0];
|
|
109
109
|
if (!result || !result.success) {
|
|
@@ -259,4 +259,4 @@ async function runFixInteraction(results, library, findItem) {
|
|
|
259
259
|
export {
|
|
260
260
|
runFixInteraction
|
|
261
261
|
};
|
|
262
|
-
//# sourceMappingURL=fix-interaction-
|
|
262
|
+
//# sourceMappingURL=fix-interaction-BCtx4Q9F.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fix-interaction-CTIvq9t4.js","sources":["../../src/features/check/fix-actions.ts","../../src/features/check/fix-interaction.ts"],"sourcesContent":["import type { CslItem } from \"../../core/csl-json/types.js\";\nimport type { ILibrary } from \"../../core/library-interface.js\";\nimport type { CheckFinding } from \"./types.js\";\n\nexport type FixActionType =\n | \"add_retracted_tag\"\n | \"add_retraction_note\"\n | \"remove_from_library\"\n | \"update_from_published\"\n | \"add_version_tag\"\n | \"add_concern_tag\"\n | \"add_concern_note\"\n | \"update_all_fields\"\n | \"update_selected_fields\"\n | \"skip\";\n\nexport interface FixAction {\n type: FixActionType;\n label: string;\n}\n\nexport interface FixActionResult {\n applied: boolean;\n message: string;\n removed?: boolean;\n}\n\nexport function getFixActionsForFinding(finding: CheckFinding): FixAction[] {\n switch (finding.type) {\n case \"retracted\":\n return [\n { type: \"add_retracted_tag\", label: 'Add tag \"retracted\"' },\n { type: \"add_retraction_note\", label: \"Add note with retraction details\" },\n { type: \"remove_from_library\", label: \"Remove from library\" },\n { type: \"skip\", label: \"Skip\" },\n ];\n case \"version_changed\":\n return [\n { type: \"update_from_published\", label: \"Update metadata from published version\" },\n { type: \"add_version_tag\", label: 'Add tag \"has-published-version\"' },\n { type: \"skip\", label: \"Skip\" },\n ];\n case \"concern\":\n return [\n { type: \"add_concern_tag\", label: 'Add tag \"expression-of-concern\"' },\n { type: \"add_concern_note\", label: \"Add note with concern details\" },\n { type: \"skip\", label: \"Skip\" },\n ];\n case \"metadata_mismatch\":\n case \"metadata_outdated\":\n return [\n { type: \"update_all_fields\", label: \"Update all changed fields from remote\" },\n { type: \"skip\", label: \"Skip\" },\n ];\n default:\n return [];\n }\n}\n\nfunction addTag(item: CslItem, tag: string): string[] {\n const existing = (item.custom?.tags as string[] | undefined) ?? [];\n if (existing.includes(tag)) {\n return existing;\n }\n return [...existing, tag];\n}\n\nfunction buildNoteText(prefix: string, finding: CheckFinding): string {\n const parts = [prefix];\n if (finding.details?.retractionDate) {\n parts.push(`Date: ${finding.details.retractionDate}`);\n }\n if (finding.details?.retractionDoi) {\n parts.push(`DOI: ${finding.details.retractionDoi}`);\n }\n return parts.join(\". \");\n}\n\nfunction appendNote(existingNote: string | undefined, newNote: string): string {\n if (existingNote) {\n return `${existingNote}\\n\\n${newNote}`;\n }\n return newNote;\n}\n\nasync function applyTagAction(\n library: ILibrary,\n item: CslItem,\n tag: string\n): Promise<FixActionResult> {\n const tags = addTag(item, tag);\n await library.update(item.id, { custom: { ...item.custom, tags } } as Partial<CslItem>, {\n idType: \"id\",\n });\n await library.save();\n return { applied: true, message: `Added tag \"${tag}\"` };\n}\n\nasync function applyNoteAction(\n library: ILibrary,\n item: CslItem,\n prefix: string,\n finding: CheckFinding\n): Promise<FixActionResult> {\n const noteText = buildNoteText(prefix, finding);\n const note = appendNote(item.note, noteText);\n await library.update(item.id, { note } as Partial<CslItem>, { idType: \"id\" });\n await library.save();\n return { applied: true, message: `Added note: ${noteText}` };\n}\n\nasync function applyUpdateFromPublished(\n library: ILibrary,\n item: CslItem,\n finding: CheckFinding\n): Promise<FixActionResult> {\n const newDoi = finding.details?.newDoi;\n if (!newDoi) {\n return { applied: false, message: \"No published DOI available in finding details\" };\n }\n const { fetchDoi } = await import(\"../import/fetcher.js\");\n const fetchResult = await fetchDoi(newDoi);\n if (!fetchResult.success) {\n return {\n applied: false,\n message: `Failed to fetch metadata for ${newDoi}: ${fetchResult.error}`,\n };\n }\n const { id: _id, custom: _custom, ...metadata } = fetchResult.item;\n await library.update(item.id, metadata as Partial<CslItem>, { idType: \"id\" });\n await library.save();\n return { applied: true, message: `Updated metadata from ${newDoi}` };\n}\n\nasync function applyUpdateAllFields(\n library: ILibrary,\n item: CslItem,\n finding: CheckFinding\n): Promise<FixActionResult> {\n let fetchedItem: CslItem;\n\n if (item.DOI) {\n const { fetchDoi } = await import(\"../import/fetcher.js\");\n const fetchResult = await fetchDoi(item.DOI);\n if (!fetchResult.success) {\n return {\n applied: false,\n message: `Failed to fetch metadata for DOI ${item.DOI}: ${fetchResult.error}`,\n };\n }\n fetchedItem = fetchResult.item;\n } else if (item.PMID) {\n const { fetchPmids } = await import(\"../import/fetcher.js\");\n const results = await fetchPmids([item.PMID], {});\n const result = results[0];\n if (!result || !result.success) {\n return {\n applied: false,\n message: `Failed to fetch metadata for PMID ${item.PMID}: ${result?.error ?? \"unknown error\"}`,\n };\n }\n fetchedItem = result.item;\n } else {\n return { applied: false, message: \"No DOI or PMID available for metadata update\" };\n }\n\n const updatedFields = finding.details?.updatedFields ?? [];\n const updates: Partial<CslItem> = {};\n for (const field of updatedFields) {\n if (field in fetchedItem) {\n (updates as Record<string, unknown>)[field] = (fetchedItem as Record<string, unknown>)[field];\n }\n }\n if (Object.keys(updates).length === 0) {\n return { applied: false, message: \"No fields to update\" };\n }\n await library.update(item.id, updates, { idType: \"id\" });\n await library.save();\n return { applied: true, message: `Updated fields: ${updatedFields.join(\", \")}` };\n}\n\nexport async function applyFixAction(\n library: ILibrary,\n item: CslItem,\n finding: CheckFinding,\n actionType: FixActionType\n): Promise<FixActionResult> {\n switch (actionType) {\n case \"add_retracted_tag\":\n return applyTagAction(library, item, \"retracted\");\n\n case \"add_retraction_note\":\n return applyNoteAction(library, item, \"RETRACTED\", finding);\n\n case \"add_concern_tag\":\n return applyTagAction(library, item, \"expression-of-concern\");\n\n case \"add_concern_note\":\n return applyNoteAction(library, item, \"EXPRESSION OF CONCERN\", finding);\n\n case \"add_version_tag\":\n return applyTagAction(library, item, \"has-published-version\");\n\n case \"remove_from_library\": {\n const removeResult = await library.remove(item.id, { idType: \"id\" });\n if (!removeResult.removed) {\n return { applied: false, message: `Failed to remove ${item.id}` };\n }\n await library.save();\n return { applied: true, message: `Removed ${item.id}`, removed: true };\n }\n\n case \"update_from_published\":\n return applyUpdateFromPublished(library, item, finding);\n\n case \"update_all_fields\":\n return applyUpdateAllFields(library, item, finding);\n\n // Placeholder for programmatic API (MCP/server) use.\n // Individual field selection is not available in interactive CLI mode,\n // but can be used via the MCP tool or HTTP server API.\n case \"update_selected_fields\":\n return { applied: false, message: \"Field selection not available in CLI mode\" };\n\n case \"skip\":\n return { applied: true, message: \"Skipped\" };\n\n default:\n return { applied: false, message: `Unknown action: ${actionType}` };\n }\n}\n","import { render } from \"ink\";\nimport { createElement } from \"react\";\nimport type { CslItem } from \"../../core/csl-json/types.js\";\nimport type { ILibrary } from \"../../core/library-interface.js\";\nimport { restoreStdinAfterInk } from \"../interactive/alternate-screen.js\";\nimport { Select } from \"../interactive/components/index.js\";\nimport type { SelectOption } from \"../interactive/components/index.js\";\nimport { type FixActionType, applyFixAction, getFixActionsForFinding } from \"./fix-actions.js\";\nimport type { CheckFinding, CheckResult } from \"./types.js\";\n\nexport interface FixInteractionResult {\n totalFindings: number;\n applied: number;\n skipped: number;\n removed: string[];\n}\n\nfunction selectFixAction(\n message: string,\n options: SelectOption<FixActionType>[]\n): Promise<FixActionType | null> {\n return new Promise<FixActionType | null>((resolve) => {\n let selected: FixActionType | null = null;\n\n const { waitUntilExit } = render(\n createElement(Select<FixActionType>, {\n options,\n message,\n onSelect: (value: FixActionType) => {\n selected = value;\n },\n onCancel: () => {\n selected = null;\n },\n })\n );\n\n waitUntilExit()\n .then(() => {\n restoreStdinAfterInk();\n resolve(selected);\n })\n .catch(() => {\n restoreStdinAfterInk();\n resolve(null);\n });\n });\n}\n\nfunction getStatusLabel(type: string): string {\n switch (type) {\n case \"retracted\":\n return \"RETRACTED\";\n case \"concern\":\n return \"CONCERN\";\n case \"version_changed\":\n return \"VERSION\";\n case \"metadata_mismatch\":\n return \"MISMATCH\";\n case \"metadata_outdated\":\n return \"OUTDATED\";\n default:\n return \"WARNING\";\n }\n}\n\nfunction buildSelectOptions(finding: CheckFinding): SelectOption<FixActionType>[] {\n return getFixActionsForFinding(finding).map((a) => ({\n label: a.label,\n value: a.type,\n }));\n}\n\nasync function processFinding(\n result: FixInteractionResult,\n library: ILibrary,\n item: CslItem,\n resultId: string,\n finding: CheckFinding\n): Promise<void> {\n result.totalFindings++;\n const options = buildSelectOptions(finding);\n if (options.length === 0) return;\n\n const label = getStatusLabel(finding.type);\n const message = `[${label}] ${resultId}: ${finding.message}`;\n const selectedAction = await selectFixAction(message, options);\n\n if (selectedAction === null) {\n result.skipped++;\n return;\n }\n\n const actionResult = await applyFixAction(library, item, finding, selectedAction);\n\n if (!actionResult.applied) {\n process.stderr.write(` Error: ${actionResult.message}\\n`);\n return;\n }\n\n process.stderr.write(` ${actionResult.message}\\n`);\n if (selectedAction === \"skip\") {\n result.skipped++;\n } else {\n result.applied++;\n if (actionResult.removed) {\n result.removed.push(resultId);\n }\n }\n}\n\nexport async function runFixInteraction(\n results: CheckResult[],\n library: ILibrary,\n findItem: (id: string) => CslItem | undefined\n): Promise<FixInteractionResult> {\n const interactionResult: FixInteractionResult = {\n totalFindings: 0,\n applied: 0,\n skipped: 0,\n removed: [],\n };\n\n const warningResults = results.filter((r) => r.status === \"warning\");\n\n for (const checkResult of warningResults) {\n const item = findItem(checkResult.id);\n if (!item) continue;\n\n for (const finding of checkResult.findings) {\n await processFinding(interactionResult, library, item, checkResult.id, finding);\n }\n }\n\n return interactionResult;\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fix-interaction-BCtx4Q9F.js","sources":["../../src/features/check/fix-actions.ts","../../src/features/check/fix-interaction.ts"],"sourcesContent":["import type { CslItem } from \"../../core/csl-json/types.js\";\nimport type { ILibrary } from \"../../core/library-interface.js\";\nimport type { CheckFinding } from \"./types.js\";\n\nexport type FixActionType =\n | \"add_retracted_tag\"\n | \"add_retraction_note\"\n | \"remove_from_library\"\n | \"update_from_published\"\n | \"add_version_tag\"\n | \"add_concern_tag\"\n | \"add_concern_note\"\n | \"update_all_fields\"\n | \"update_selected_fields\"\n | \"skip\";\n\nexport interface FixAction {\n type: FixActionType;\n label: string;\n}\n\nexport interface FixActionResult {\n applied: boolean;\n message: string;\n removed?: boolean;\n}\n\nexport function getFixActionsForFinding(finding: CheckFinding): FixAction[] {\n switch (finding.type) {\n case \"retracted\":\n return [\n { type: \"add_retracted_tag\", label: 'Add tag \"retracted\"' },\n { type: \"add_retraction_note\", label: \"Add note with retraction details\" },\n { type: \"remove_from_library\", label: \"Remove from library\" },\n { type: \"skip\", label: \"Skip\" },\n ];\n case \"version_changed\":\n return [\n { type: \"update_from_published\", label: \"Update metadata from published version\" },\n { type: \"add_version_tag\", label: 'Add tag \"has-published-version\"' },\n { type: \"skip\", label: \"Skip\" },\n ];\n case \"concern\":\n return [\n { type: \"add_concern_tag\", label: 'Add tag \"expression-of-concern\"' },\n { type: \"add_concern_note\", label: \"Add note with concern details\" },\n { type: \"skip\", label: \"Skip\" },\n ];\n case \"metadata_mismatch\":\n case \"metadata_outdated\":\n return [\n { type: \"update_all_fields\", label: \"Update all changed fields from remote\" },\n { type: \"skip\", label: \"Skip\" },\n ];\n default:\n return [];\n }\n}\n\nfunction addTag(item: CslItem, tag: string): string[] {\n const existing = (item.custom?.tags as string[] | undefined) ?? [];\n if (existing.includes(tag)) {\n return existing;\n }\n return [...existing, tag];\n}\n\nfunction buildNoteText(prefix: string, finding: CheckFinding): string {\n const parts = [prefix];\n if (finding.details?.retractionDate) {\n parts.push(`Date: ${finding.details.retractionDate}`);\n }\n if (finding.details?.retractionDoi) {\n parts.push(`DOI: ${finding.details.retractionDoi}`);\n }\n return parts.join(\". \");\n}\n\nfunction appendNote(existingNote: string | undefined, newNote: string): string {\n if (existingNote) {\n return `${existingNote}\\n\\n${newNote}`;\n }\n return newNote;\n}\n\nasync function applyTagAction(\n library: ILibrary,\n item: CslItem,\n tag: string\n): Promise<FixActionResult> {\n const tags = addTag(item, tag);\n await library.update(item.id, { custom: { ...item.custom, tags } } as Partial<CslItem>, {\n idType: \"id\",\n });\n await library.save();\n return { applied: true, message: `Added tag \"${tag}\"` };\n}\n\nasync function applyNoteAction(\n library: ILibrary,\n item: CslItem,\n prefix: string,\n finding: CheckFinding\n): Promise<FixActionResult> {\n const noteText = buildNoteText(prefix, finding);\n const note = appendNote(item.note, noteText);\n await library.update(item.id, { note } as Partial<CslItem>, { idType: \"id\" });\n await library.save();\n return { applied: true, message: `Added note: ${noteText}` };\n}\n\nasync function applyUpdateFromPublished(\n library: ILibrary,\n item: CslItem,\n finding: CheckFinding\n): Promise<FixActionResult> {\n const newDoi = finding.details?.newDoi;\n if (!newDoi) {\n return { applied: false, message: \"No published DOI available in finding details\" };\n }\n const { fetchDoi } = await import(\"../import/fetcher.js\");\n const fetchResult = await fetchDoi(newDoi);\n if (!fetchResult.success) {\n return {\n applied: false,\n message: `Failed to fetch metadata for ${newDoi}: ${fetchResult.error}`,\n };\n }\n const { id: _id, custom: _custom, ...metadata } = fetchResult.item;\n await library.update(item.id, metadata as Partial<CslItem>, { idType: \"id\" });\n await library.save();\n return { applied: true, message: `Updated metadata from ${newDoi}` };\n}\n\nasync function applyUpdateAllFields(\n library: ILibrary,\n item: CslItem,\n finding: CheckFinding\n): Promise<FixActionResult> {\n let fetchedItem: CslItem;\n\n if (item.DOI) {\n const { fetchDoi } = await import(\"../import/fetcher.js\");\n const fetchResult = await fetchDoi(item.DOI);\n if (!fetchResult.success) {\n return {\n applied: false,\n message: `Failed to fetch metadata for DOI ${item.DOI}: ${fetchResult.error}`,\n };\n }\n fetchedItem = fetchResult.item;\n } else if (item.PMID) {\n const { fetchPmids } = await import(\"../import/fetcher.js\");\n const results = await fetchPmids([item.PMID], {});\n const result = results[0];\n if (!result || !result.success) {\n return {\n applied: false,\n message: `Failed to fetch metadata for PMID ${item.PMID}: ${result?.error ?? \"unknown error\"}`,\n };\n }\n fetchedItem = result.item;\n } else {\n return { applied: false, message: \"No DOI or PMID available for metadata update\" };\n }\n\n const updatedFields = finding.details?.updatedFields ?? [];\n const updates: Partial<CslItem> = {};\n for (const field of updatedFields) {\n if (field in fetchedItem) {\n (updates as Record<string, unknown>)[field] = (fetchedItem as Record<string, unknown>)[field];\n }\n }\n if (Object.keys(updates).length === 0) {\n return { applied: false, message: \"No fields to update\" };\n }\n await library.update(item.id, updates, { idType: \"id\" });\n await library.save();\n return { applied: true, message: `Updated fields: ${updatedFields.join(\", \")}` };\n}\n\nexport async function applyFixAction(\n library: ILibrary,\n item: CslItem,\n finding: CheckFinding,\n actionType: FixActionType\n): Promise<FixActionResult> {\n switch (actionType) {\n case \"add_retracted_tag\":\n return applyTagAction(library, item, \"retracted\");\n\n case \"add_retraction_note\":\n return applyNoteAction(library, item, \"RETRACTED\", finding);\n\n case \"add_concern_tag\":\n return applyTagAction(library, item, \"expression-of-concern\");\n\n case \"add_concern_note\":\n return applyNoteAction(library, item, \"EXPRESSION OF CONCERN\", finding);\n\n case \"add_version_tag\":\n return applyTagAction(library, item, \"has-published-version\");\n\n case \"remove_from_library\": {\n const removeResult = await library.remove(item.id, { idType: \"id\" });\n if (!removeResult.removed) {\n return { applied: false, message: `Failed to remove ${item.id}` };\n }\n await library.save();\n return { applied: true, message: `Removed ${item.id}`, removed: true };\n }\n\n case \"update_from_published\":\n return applyUpdateFromPublished(library, item, finding);\n\n case \"update_all_fields\":\n return applyUpdateAllFields(library, item, finding);\n\n // Placeholder for programmatic API (MCP/server) use.\n // Individual field selection is not available in interactive CLI mode,\n // but can be used via the MCP tool or HTTP server API.\n case \"update_selected_fields\":\n return { applied: false, message: \"Field selection not available in CLI mode\" };\n\n case \"skip\":\n return { applied: true, message: \"Skipped\" };\n\n default:\n return { applied: false, message: `Unknown action: ${actionType}` };\n }\n}\n","import { render } from \"ink\";\nimport { createElement } from \"react\";\nimport type { CslItem } from \"../../core/csl-json/types.js\";\nimport type { ILibrary } from \"../../core/library-interface.js\";\nimport { restoreStdinAfterInk } from \"../interactive/alternate-screen.js\";\nimport { Select } from \"../interactive/components/index.js\";\nimport type { SelectOption } from \"../interactive/components/index.js\";\nimport { type FixActionType, applyFixAction, getFixActionsForFinding } from \"./fix-actions.js\";\nimport type { CheckFinding, CheckResult } from \"./types.js\";\n\nexport interface FixInteractionResult {\n totalFindings: number;\n applied: number;\n skipped: number;\n removed: string[];\n}\n\nfunction selectFixAction(\n message: string,\n options: SelectOption<FixActionType>[]\n): Promise<FixActionType | null> {\n return new Promise<FixActionType | null>((resolve) => {\n let selected: FixActionType | null = null;\n\n const { waitUntilExit } = render(\n createElement(Select<FixActionType>, {\n options,\n message,\n onSelect: (value: FixActionType) => {\n selected = value;\n },\n onCancel: () => {\n selected = null;\n },\n })\n );\n\n waitUntilExit()\n .then(() => {\n restoreStdinAfterInk();\n resolve(selected);\n })\n .catch(() => {\n restoreStdinAfterInk();\n resolve(null);\n });\n });\n}\n\nfunction getStatusLabel(type: string): string {\n switch (type) {\n case \"retracted\":\n return \"RETRACTED\";\n case \"concern\":\n return \"CONCERN\";\n case \"version_changed\":\n return \"VERSION\";\n case \"metadata_mismatch\":\n return \"MISMATCH\";\n case \"metadata_outdated\":\n return \"OUTDATED\";\n default:\n return \"WARNING\";\n }\n}\n\nfunction buildSelectOptions(finding: CheckFinding): SelectOption<FixActionType>[] {\n return getFixActionsForFinding(finding).map((a) => ({\n label: a.label,\n value: a.type,\n }));\n}\n\nasync function processFinding(\n result: FixInteractionResult,\n library: ILibrary,\n item: CslItem,\n resultId: string,\n finding: CheckFinding\n): Promise<void> {\n result.totalFindings++;\n const options = buildSelectOptions(finding);\n if (options.length === 0) return;\n\n const label = getStatusLabel(finding.type);\n const message = `[${label}] ${resultId}: ${finding.message}`;\n const selectedAction = await selectFixAction(message, options);\n\n if (selectedAction === null) {\n result.skipped++;\n return;\n }\n\n const actionResult = await applyFixAction(library, item, finding, selectedAction);\n\n if (!actionResult.applied) {\n process.stderr.write(` Error: ${actionResult.message}\\n`);\n return;\n }\n\n process.stderr.write(` ${actionResult.message}\\n`);\n if (selectedAction === \"skip\") {\n result.skipped++;\n } else {\n result.applied++;\n if (actionResult.removed) {\n result.removed.push(resultId);\n }\n }\n}\n\nexport async function runFixInteraction(\n results: CheckResult[],\n library: ILibrary,\n findItem: (id: string) => CslItem | undefined\n): Promise<FixInteractionResult> {\n const interactionResult: FixInteractionResult = {\n totalFindings: 0,\n applied: 0,\n skipped: 0,\n removed: [],\n };\n\n const warningResults = results.filter((r) => r.status === \"warning\");\n\n for (const checkResult of warningResults) {\n const item = findItem(checkResult.id);\n if (!item) continue;\n\n for (const finding of checkResult.findings) {\n await processFinding(interactionResult, library, item, checkResult.id, finding);\n }\n }\n\n return interactionResult;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -770,12 +770,14 @@ async function checkUnpaywallDetailed(doi, email) {
|
|
|
770
770
|
const DEFAULT_SOURCE_ORDER = ["pmc", "arxiv", "unpaywall", "core"];
|
|
771
771
|
function checkArxivSource(article) {
|
|
772
772
|
if (!article.arxivId)
|
|
773
|
-
return
|
|
773
|
+
return { skipped: "no arXiv ID available" };
|
|
774
774
|
return checkArxiv(article.arxivId);
|
|
775
775
|
}
|
|
776
776
|
async function checkCoreSource(article, config) {
|
|
777
|
-
if (!config.coreApiKey
|
|
778
|
-
return
|
|
777
|
+
if (!config.coreApiKey)
|
|
778
|
+
return { skipped: "coreApiKey not configured" };
|
|
779
|
+
if (!article.doi)
|
|
780
|
+
return { skipped: "no DOI available" };
|
|
779
781
|
return await checkCore(article.doi, config.coreApiKey);
|
|
780
782
|
}
|
|
781
783
|
const sourceCheckers = {
|
|
@@ -819,9 +821,16 @@ async function enrichArticleIds(article, config) {
|
|
|
819
821
|
return { enriched: article, discoveredIds };
|
|
820
822
|
}
|
|
821
823
|
async function checkUnpaywallSource(enriched, config, state) {
|
|
822
|
-
if (!config.unpaywallEmail
|
|
824
|
+
if (!config.unpaywallEmail) {
|
|
825
|
+
state.skipped.push({ source: "unpaywall", reason: "unpaywallEmail not configured" });
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
if (!enriched.doi) {
|
|
829
|
+
state.skipped.push({ source: "unpaywall", reason: "no DOI available" });
|
|
823
830
|
return;
|
|
831
|
+
}
|
|
824
832
|
state.sourcesChecked++;
|
|
833
|
+
state.checkedSources.push("unpaywall");
|
|
825
834
|
try {
|
|
826
835
|
const detailed = await checkUnpaywallDetailed(enriched.doi, config.unpaywallEmail);
|
|
827
836
|
if (!detailed)
|
|
@@ -835,14 +844,17 @@ async function checkUnpaywallSource(enriched, config, state) {
|
|
|
835
844
|
}
|
|
836
845
|
}
|
|
837
846
|
async function checkPmcSourceWithIds(enriched, state, discoveredIds) {
|
|
838
|
-
if (!enriched.pmid && !enriched.pmcid)
|
|
847
|
+
if (!enriched.pmid && !enriched.pmcid) {
|
|
848
|
+
state.skipped.push({ source: "pmc", reason: "no PMCID or PMID available" });
|
|
839
849
|
return;
|
|
850
|
+
}
|
|
840
851
|
const ids = {};
|
|
841
852
|
if (enriched.pmid)
|
|
842
853
|
ids.pmid = enriched.pmid;
|
|
843
854
|
if (enriched.pmcid)
|
|
844
855
|
ids.pmcid = enriched.pmcid;
|
|
845
856
|
state.sourcesChecked++;
|
|
857
|
+
state.checkedSources.push("pmc");
|
|
846
858
|
try {
|
|
847
859
|
const result = await checkPmc(ids);
|
|
848
860
|
if (!result)
|
|
@@ -860,9 +872,12 @@ async function checkGenericSource(source, enriched, config, state) {
|
|
|
860
872
|
if (!checker)
|
|
861
873
|
return;
|
|
862
874
|
const result = await runSourceChecker(checker, enriched, config);
|
|
863
|
-
if (result.
|
|
875
|
+
if (result.skipReason) {
|
|
876
|
+
state.skipped.push({ source, reason: result.skipReason });
|
|
864
877
|
return;
|
|
878
|
+
}
|
|
865
879
|
state.sourcesChecked++;
|
|
880
|
+
state.checkedSources.push(source);
|
|
866
881
|
if (result.error) {
|
|
867
882
|
state.errors.push({ source, error: result.error });
|
|
868
883
|
} else if (result.locations) {
|
|
@@ -873,6 +888,7 @@ async function lazyPmcCheck(enriched, state, discoveredIds) {
|
|
|
873
888
|
if (!state.unpaywallPmcid || enriched.pmcid)
|
|
874
889
|
return;
|
|
875
890
|
discoveredIds.pmcid = discoveredIds.pmcid ?? state.unpaywallPmcid;
|
|
891
|
+
state.checkedSources.push("pmc-lazy");
|
|
876
892
|
try {
|
|
877
893
|
const pmcResult = await checkPmc({ pmcid: state.unpaywallPmcid });
|
|
878
894
|
if (pmcResult) {
|
|
@@ -884,7 +900,13 @@ async function lazyPmcCheck(enriched, state, discoveredIds) {
|
|
|
884
900
|
}
|
|
885
901
|
async function discoverOA(article, config) {
|
|
886
902
|
const { enriched, discoveredIds } = await enrichArticleIds(article, config);
|
|
887
|
-
const state = {
|
|
903
|
+
const state = {
|
|
904
|
+
locations: [],
|
|
905
|
+
errors: [],
|
|
906
|
+
skipped: [],
|
|
907
|
+
checkedSources: [],
|
|
908
|
+
sourcesChecked: 0
|
|
909
|
+
};
|
|
888
910
|
const sourceOrder = config.preferSources.length > 0 ? config.preferSources : DEFAULT_SOURCE_ORDER;
|
|
889
911
|
for (const source of sourceOrder) {
|
|
890
912
|
if (source === "unpaywall") {
|
|
@@ -897,16 +919,24 @@ async function discoverOA(article, config) {
|
|
|
897
919
|
}
|
|
898
920
|
await lazyPmcCheck(enriched, state, discoveredIds);
|
|
899
921
|
const oaStatus = determineOAStatus(state.locations, state.errors, state.sourcesChecked);
|
|
900
|
-
return {
|
|
922
|
+
return {
|
|
923
|
+
oaStatus,
|
|
924
|
+
locations: state.locations,
|
|
925
|
+
errors: state.errors,
|
|
926
|
+
skipped: state.skipped,
|
|
927
|
+
checkedSources: state.checkedSources,
|
|
928
|
+
discoveredIds
|
|
929
|
+
};
|
|
901
930
|
}
|
|
902
931
|
async function runSourceChecker(checker, article, config) {
|
|
903
932
|
try {
|
|
904
933
|
const result = await checker(article, config);
|
|
905
|
-
if (result ===
|
|
906
|
-
return {
|
|
907
|
-
|
|
934
|
+
if (result !== null && typeof result === "object" && "skipped" in result) {
|
|
935
|
+
return { skipReason: result.skipped };
|
|
936
|
+
}
|
|
937
|
+
return { locations: result ?? [] };
|
|
908
938
|
} catch (err) {
|
|
909
|
-
return {
|
|
939
|
+
return { error: String(err) };
|
|
910
940
|
}
|
|
911
941
|
}
|
|
912
942
|
const NON_RETRYABLE_STATUSES = /* @__PURE__ */ new Set([400, 401, 403, 404, 405, 410]);
|
|
@@ -10287,13 +10317,22 @@ function buildDiscoveryConfig(fulltextConfig) {
|
|
|
10287
10317
|
if (fulltextConfig.sources.ncbiTool) config.ncbiTool = fulltextConfig.sources.ncbiTool;
|
|
10288
10318
|
return config;
|
|
10289
10319
|
}
|
|
10290
|
-
async function tryDownloadPdf(locations, tempDir, ctx) {
|
|
10320
|
+
async function tryDownloadPdf(locations, tempDir, ctx, attempts) {
|
|
10291
10321
|
const pdfLocations = locations.filter((loc) => loc.urlType === "pdf");
|
|
10292
10322
|
if (pdfLocations.length === 0) return { attached: false, source: "" };
|
|
10293
10323
|
const pdfPath = join(tempDir, "fulltext.pdf");
|
|
10294
10324
|
for (const pdfLocation of pdfLocations) {
|
|
10295
10325
|
const pdfResult = await downloadPdf(pdfLocation.url, pdfPath);
|
|
10296
|
-
if (!pdfResult.success)
|
|
10326
|
+
if (!pdfResult.success) {
|
|
10327
|
+
attempts.push({
|
|
10328
|
+
source: pdfLocation.source,
|
|
10329
|
+
phase: "download",
|
|
10330
|
+
url: pdfLocation.url,
|
|
10331
|
+
fileType: "pdf",
|
|
10332
|
+
error: pdfResult.error ?? "Download failed"
|
|
10333
|
+
});
|
|
10334
|
+
continue;
|
|
10335
|
+
}
|
|
10297
10336
|
const attachResult = await fulltextAttach(ctx.library, {
|
|
10298
10337
|
identifier: ctx.identifier,
|
|
10299
10338
|
idType: ctx.idType,
|
|
@@ -10306,16 +10345,39 @@ async function tryDownloadPdf(locations, tempDir, ctx) {
|
|
|
10306
10345
|
if (attachResult.success) {
|
|
10307
10346
|
return { attached: true, source: pdfLocation.source };
|
|
10308
10347
|
}
|
|
10348
|
+
attempts.push({
|
|
10349
|
+
source: pdfLocation.source,
|
|
10350
|
+
phase: "attach",
|
|
10351
|
+
url: pdfLocation.url,
|
|
10352
|
+
fileType: "pdf",
|
|
10353
|
+
error: "Failed to attach file"
|
|
10354
|
+
});
|
|
10309
10355
|
}
|
|
10310
10356
|
return { attached: false, source: pdfLocations[0]?.source ?? "" };
|
|
10311
10357
|
}
|
|
10312
|
-
async function tryDownloadPmcXmlAndConvert(pmcid, tempDir, ctx) {
|
|
10358
|
+
async function tryDownloadPmcXmlAndConvert(pmcid, tempDir, ctx, attempts) {
|
|
10313
10359
|
const xmlPath = join(tempDir, "fulltext.xml");
|
|
10314
10360
|
const xmlResult = await downloadPmcXml(pmcid, xmlPath);
|
|
10315
|
-
if (!xmlResult.success)
|
|
10361
|
+
if (!xmlResult.success) {
|
|
10362
|
+
attempts.push({
|
|
10363
|
+
source: "pmc",
|
|
10364
|
+
phase: "download",
|
|
10365
|
+
fileType: "xml",
|
|
10366
|
+
error: xmlResult.error ?? "Download failed"
|
|
10367
|
+
});
|
|
10368
|
+
return false;
|
|
10369
|
+
}
|
|
10316
10370
|
const mdPath = join(tempDir, "fulltext.md");
|
|
10317
10371
|
const convertResult2 = await convertPmcXmlToMarkdown(xmlPath, mdPath);
|
|
10318
|
-
if (!convertResult2.success)
|
|
10372
|
+
if (!convertResult2.success) {
|
|
10373
|
+
attempts.push({
|
|
10374
|
+
source: "pmc",
|
|
10375
|
+
phase: "convert",
|
|
10376
|
+
fileType: "xml",
|
|
10377
|
+
error: convertResult2.error ?? "Conversion failed"
|
|
10378
|
+
});
|
|
10379
|
+
return false;
|
|
10380
|
+
}
|
|
10319
10381
|
const attachResult = await fulltextAttach(ctx.library, {
|
|
10320
10382
|
identifier: ctx.identifier,
|
|
10321
10383
|
idType: ctx.idType,
|
|
@@ -10325,19 +10387,43 @@ async function tryDownloadPmcXmlAndConvert(pmcid, tempDir, ctx) {
|
|
|
10325
10387
|
move: true,
|
|
10326
10388
|
fulltextDirectory: ctx.fulltextDirectory
|
|
10327
10389
|
});
|
|
10390
|
+
if (!attachResult.success) {
|
|
10391
|
+
attempts.push({
|
|
10392
|
+
source: "pmc",
|
|
10393
|
+
phase: "attach",
|
|
10394
|
+
fileType: "markdown",
|
|
10395
|
+
error: "Failed to attach file"
|
|
10396
|
+
});
|
|
10397
|
+
}
|
|
10328
10398
|
return attachResult.success;
|
|
10329
10399
|
}
|
|
10330
10400
|
function extractArxivId(url) {
|
|
10331
10401
|
const match = url.match(/arxiv\.org\/(?:abs|html|pdf)\/(\d{4}\.\d{4,5}(?:v\d+)?)/);
|
|
10332
10402
|
return match?.[1];
|
|
10333
10403
|
}
|
|
10334
|
-
async function tryDownloadArxivHtmlAndConvert(arxivId, tempDir, ctx) {
|
|
10404
|
+
async function tryDownloadArxivHtmlAndConvert(arxivId, tempDir, ctx, attempts) {
|
|
10335
10405
|
const htmlPath = join(tempDir, "fulltext.html");
|
|
10336
10406
|
const htmlResult = await downloadArxivHtml(arxivId, htmlPath);
|
|
10337
|
-
if (!htmlResult.success)
|
|
10407
|
+
if (!htmlResult.success) {
|
|
10408
|
+
attempts.push({
|
|
10409
|
+
source: "arxiv",
|
|
10410
|
+
phase: "download",
|
|
10411
|
+
fileType: "html",
|
|
10412
|
+
error: htmlResult.error ?? "Download failed"
|
|
10413
|
+
});
|
|
10414
|
+
return false;
|
|
10415
|
+
}
|
|
10338
10416
|
const mdPath = join(tempDir, "fulltext.md");
|
|
10339
10417
|
const convertResult2 = await convertArxivHtmlToMarkdown(htmlPath, mdPath);
|
|
10340
|
-
if (!convertResult2.success)
|
|
10418
|
+
if (!convertResult2.success) {
|
|
10419
|
+
attempts.push({
|
|
10420
|
+
source: "arxiv",
|
|
10421
|
+
phase: "convert",
|
|
10422
|
+
fileType: "html",
|
|
10423
|
+
error: convertResult2.error ?? "Conversion failed"
|
|
10424
|
+
});
|
|
10425
|
+
return false;
|
|
10426
|
+
}
|
|
10341
10427
|
const attachResult = await fulltextAttach(ctx.library, {
|
|
10342
10428
|
identifier: ctx.identifier,
|
|
10343
10429
|
idType: ctx.idType,
|
|
@@ -10347,12 +10433,36 @@ async function tryDownloadArxivHtmlAndConvert(arxivId, tempDir, ctx) {
|
|
|
10347
10433
|
move: true,
|
|
10348
10434
|
fulltextDirectory: ctx.fulltextDirectory
|
|
10349
10435
|
});
|
|
10436
|
+
if (!attachResult.success) {
|
|
10437
|
+
attempts.push({
|
|
10438
|
+
source: "arxiv",
|
|
10439
|
+
phase: "attach",
|
|
10440
|
+
fileType: "markdown",
|
|
10441
|
+
error: "Failed to attach file"
|
|
10442
|
+
});
|
|
10443
|
+
}
|
|
10350
10444
|
return attachResult.success;
|
|
10351
10445
|
}
|
|
10352
10446
|
async function checkExistingFulltext(library, identifier, idType, fulltextDirectory) {
|
|
10353
10447
|
const existing = await fulltextGet(library, { identifier, idType, fulltextDirectory });
|
|
10354
10448
|
return existing.success && existing.paths !== void 0;
|
|
10355
10449
|
}
|
|
10450
|
+
function buildHintUrls(item) {
|
|
10451
|
+
const urls = [];
|
|
10452
|
+
if (item.DOI) urls.push(`https://doi.org/${item.DOI}`);
|
|
10453
|
+
if (item.PMID) urls.push(`https://pubmed.ncbi.nlm.nih.gov/${item.PMID}/`);
|
|
10454
|
+
return urls;
|
|
10455
|
+
}
|
|
10456
|
+
function formatHint(prefix, urls) {
|
|
10457
|
+
if (urls.length === 0) return prefix;
|
|
10458
|
+
if (urls.length === 1) return `${prefix}: ${urls[0]}`;
|
|
10459
|
+
return `${prefix}:
|
|
10460
|
+
${urls.map((u) => ` ${u}`).join("\n")}`;
|
|
10461
|
+
}
|
|
10462
|
+
function buildNoSourcesHint(item) {
|
|
10463
|
+
const urls = buildHintUrls(item);
|
|
10464
|
+
return urls.length > 0 ? formatHint("open to download manually", urls) : void 0;
|
|
10465
|
+
}
|
|
10356
10466
|
async function fulltextFetch(library, options) {
|
|
10357
10467
|
const { identifier, idType = "id", fulltextConfig, fulltextDirectory, source, force } = options;
|
|
10358
10468
|
const item = await library.find(identifier, { idType });
|
|
@@ -10375,12 +10485,22 @@ async function fulltextFetch(library, options) {
|
|
|
10375
10485
|
buildDiscoveryArticle(item),
|
|
10376
10486
|
buildDiscoveryConfig(fulltextConfig)
|
|
10377
10487
|
);
|
|
10488
|
+
const discoveryErrors = discovery.errors.length > 0 ? discovery.errors : void 0;
|
|
10489
|
+
const skipped = discovery.skipped.length > 0 ? discovery.skipped : void 0;
|
|
10490
|
+
const checkedSources = discovery.checkedSources.length > 0 ? discovery.checkedSources : void 0;
|
|
10378
10491
|
let locations = discovery.locations;
|
|
10379
10492
|
if (source) {
|
|
10380
10493
|
locations = locations.filter((loc) => loc.source === source);
|
|
10381
10494
|
}
|
|
10382
10495
|
if (locations.length === 0) {
|
|
10383
|
-
return {
|
|
10496
|
+
return {
|
|
10497
|
+
success: false,
|
|
10498
|
+
error: `No OA sources found for ${identifier}`,
|
|
10499
|
+
discoveryErrors,
|
|
10500
|
+
checkedSources,
|
|
10501
|
+
skipped,
|
|
10502
|
+
hint: buildNoSourcesHint(item)
|
|
10503
|
+
};
|
|
10384
10504
|
}
|
|
10385
10505
|
const effectivePmcid = item.PMCID ?? discovery.discoveredIds?.pmcid ?? extractPmcidFromLocations(locations);
|
|
10386
10506
|
const tempDir = await mkdtemp(join(tmpdir(), "ref-fulltext-"));
|
|
@@ -10392,49 +10512,61 @@ async function fulltextFetch(library, options) {
|
|
|
10392
10512
|
force: force ?? false
|
|
10393
10513
|
};
|
|
10394
10514
|
try {
|
|
10395
|
-
|
|
10515
|
+
const result = await downloadAndAttach(
|
|
10516
|
+
locations,
|
|
10517
|
+
effectivePmcid,
|
|
10518
|
+
tempDir,
|
|
10519
|
+
ctx,
|
|
10520
|
+
item.id,
|
|
10521
|
+
identifier
|
|
10522
|
+
);
|
|
10523
|
+
return { ...result, discoveryErrors, checkedSources, skipped };
|
|
10396
10524
|
} finally {
|
|
10397
10525
|
await rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
10398
10526
|
});
|
|
10399
10527
|
}
|
|
10400
10528
|
}
|
|
10401
|
-
async function tryArxivHtmlFromLocations(locations, tempDir, ctx) {
|
|
10529
|
+
async function tryArxivHtmlFromLocations(locations, tempDir, ctx, attempts) {
|
|
10402
10530
|
const arxivHtmlLocation = locations.find(
|
|
10403
10531
|
(loc) => loc.source === "arxiv" && loc.urlType === "html"
|
|
10404
10532
|
);
|
|
10405
10533
|
if (!arxivHtmlLocation) return { attached: false, source: "" };
|
|
10406
10534
|
const arxivId = extractArxivId(arxivHtmlLocation.url);
|
|
10407
10535
|
if (!arxivId) return { attached: false, source: "arxiv" };
|
|
10408
|
-
const mdAttached = await tryDownloadArxivHtmlAndConvert(arxivId, tempDir, ctx);
|
|
10536
|
+
const mdAttached = await tryDownloadArxivHtmlAndConvert(arxivId, tempDir, ctx, attempts);
|
|
10409
10537
|
return { attached: mdAttached, source: "arxiv" };
|
|
10410
10538
|
}
|
|
10411
|
-
function buildDownloadError(locations, identifier) {
|
|
10539
|
+
function buildDownloadError(locations, identifier, attempts) {
|
|
10540
|
+
const attemptUrls = attempts.filter((a) => a.url).map((a) => a.url);
|
|
10541
|
+
const hint = attemptUrls.length > 0 ? formatHint("open to download manually (may require institutional access)", attemptUrls) : void 0;
|
|
10412
10542
|
const pdfLocation = locations.find((loc) => loc.urlType === "pdf");
|
|
10413
10543
|
if (pdfLocation) {
|
|
10414
10544
|
return {
|
|
10415
10545
|
success: false,
|
|
10416
|
-
error: `Failed to download from ${pdfLocation.source}: download failed
|
|
10546
|
+
error: `Failed to download from ${pdfLocation.source}: download failed`,
|
|
10547
|
+
hint
|
|
10417
10548
|
};
|
|
10418
10549
|
}
|
|
10419
|
-
return { success: false, error: `Failed to download fulltext for ${identifier}
|
|
10550
|
+
return { success: false, error: `Failed to download fulltext for ${identifier}`, hint };
|
|
10420
10551
|
}
|
|
10421
10552
|
async function downloadAndAttach(locations, pmcid, tempDir, ctx, referenceId, identifier) {
|
|
10422
10553
|
const attachedFiles = [];
|
|
10423
10554
|
let usedSource = "";
|
|
10424
|
-
const
|
|
10555
|
+
const attempts = [];
|
|
10556
|
+
const pdfResult = await tryDownloadPdf(locations, tempDir, ctx, attempts);
|
|
10425
10557
|
if (pdfResult.attached) {
|
|
10426
10558
|
attachedFiles.push("pdf");
|
|
10427
10559
|
usedSource = pdfResult.source;
|
|
10428
10560
|
}
|
|
10429
10561
|
if (pmcid) {
|
|
10430
|
-
const mdAttached = await tryDownloadPmcXmlAndConvert(pmcid, tempDir, ctx);
|
|
10562
|
+
const mdAttached = await tryDownloadPmcXmlAndConvert(pmcid, tempDir, ctx, attempts);
|
|
10431
10563
|
if (mdAttached) {
|
|
10432
10564
|
attachedFiles.push("markdown");
|
|
10433
10565
|
if (!usedSource) usedSource = "pmc";
|
|
10434
10566
|
}
|
|
10435
10567
|
}
|
|
10436
10568
|
if (!attachedFiles.includes("markdown")) {
|
|
10437
|
-
const arxivResult = await tryArxivHtmlFromLocations(locations, tempDir, ctx);
|
|
10569
|
+
const arxivResult = await tryArxivHtmlFromLocations(locations, tempDir, ctx, attempts);
|
|
10438
10570
|
if (arxivResult.attached) {
|
|
10439
10571
|
attachedFiles.push("markdown");
|
|
10440
10572
|
if (!usedSource) usedSource = arxivResult.source;
|
|
@@ -10443,7 +10575,10 @@ async function downloadAndAttach(locations, pmcid, tempDir, ctx, referenceId, id
|
|
|
10443
10575
|
if (attachedFiles.length > 0) {
|
|
10444
10576
|
return { success: true, referenceId, source: usedSource, attachedFiles };
|
|
10445
10577
|
}
|
|
10446
|
-
return
|
|
10578
|
+
return {
|
|
10579
|
+
...buildDownloadError(locations, identifier, attempts),
|
|
10580
|
+
attempts: attempts.length > 0 ? attempts : void 0
|
|
10581
|
+
};
|
|
10447
10582
|
}
|
|
10448
10583
|
const fetch$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
10449
10584
|
__proto__: null,
|
|
@@ -12084,7 +12219,7 @@ function createAddRoute(library, config) {
|
|
|
12084
12219
|
}
|
|
12085
12220
|
const CHECK_CONCURRENCY = 5;
|
|
12086
12221
|
async function checkReferences(library, options) {
|
|
12087
|
-
const { checkReference } = await import("./checker-
|
|
12222
|
+
const { checkReference } = await import("./checker-gLAPe44T.js");
|
|
12088
12223
|
const save = options.save !== false;
|
|
12089
12224
|
const skipDays = options.skipDays ?? 7;
|
|
12090
12225
|
const items = await resolveItems(library, options);
|
|
@@ -12764,4 +12899,4 @@ export {
|
|
|
12764
12899
|
fetcher as y,
|
|
12765
12900
|
add as z
|
|
12766
12901
|
};
|
|
12767
|
-
//# sourceMappingURL=index-
|
|
12902
|
+
//# sourceMappingURL=index-BdLVfbj0.js.map
|