@ncukondo/search-hub 0.19.0 → 0.20.1
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/cli/commands/register.d.ts +8 -0
- package/dist/cli/commands/register.d.ts.map +1 -1
- package/dist/cli/commands/register.js +9 -0
- package/dist/cli/commands/register.js.map +1 -1
- package/dist/cli/commands/related.d.ts +66 -0
- package/dist/cli/commands/related.d.ts.map +1 -0
- package/dist/cli/commands/related.js +161 -0
- package/dist/cli/commands/related.js.map +1 -0
- package/dist/cli/commands/review/extract.d.ts.map +1 -1
- package/dist/cli/commands/review/extract.js +15 -5
- package/dist/cli/commands/review/extract.js.map +1 -1
- package/dist/cli/commands/review/init.d.ts +2 -3
- package/dist/cli/commands/review/init.d.ts.map +1 -1
- package/dist/cli/commands/review/init.js +1 -0
- package/dist/cli/commands/review/init.js.map +1 -1
- package/dist/cli/commands/review/next-steps.d.ts +3 -0
- package/dist/cli/commands/review/next-steps.d.ts.map +1 -1
- package/dist/cli/commands/review/next-steps.js +53 -19
- package/dist/cli/commands/review/next-steps.js.map +1 -1
- package/dist/cli/commands/review/schema.d.ts +8 -0
- package/dist/cli/commands/review/schema.d.ts.map +1 -1
- package/dist/cli/commands/review/schema.js +3 -0
- package/dist/cli/commands/review/schema.js.map +1 -1
- package/dist/cli/commands/review/status.d.ts +3 -1
- package/dist/cli/commands/review/status.d.ts.map +1 -1
- package/dist/cli/commands/review/status.js +3 -1
- package/dist/cli/commands/review/status.js.map +1 -1
- package/dist/cli/commands/review/types.d.ts +2 -1
- package/dist/cli/commands/review/types.d.ts.map +1 -1
- package/dist/cli/commands/review/types.js.map +1 -1
- package/dist/cli/commands/search-executor.d.ts.map +1 -1
- package/dist/cli/commands/search-executor.js +3 -2
- package/dist/cli/commands/search-executor.js.map +1 -1
- package/dist/cli/commands/search.d.ts +2 -0
- package/dist/cli/commands/search.d.ts.map +1 -1
- package/dist/cli/commands/search.js +3 -0
- package/dist/cli/commands/search.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +133 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/suggestions/rules.d.ts.map +1 -1
- package/dist/cli/suggestions/rules.js +19 -3
- package/dist/cli/suggestions/rules.js.map +1 -1
- package/dist/node_modules/dom-serializer/lib/index.js +1 -1
- package/dist/node_modules/domelementtype/lib/index.js +1 -1
- package/dist/node_modules/nth-check/lib/index.js +1 -1
- package/dist/providers/arxiv/provider.d.ts.map +1 -1
- package/dist/providers/arxiv/provider.js +7 -4
- package/dist/providers/arxiv/provider.js.map +1 -1
- package/dist/providers/base/types.d.ts +8 -0
- package/dist/providers/base/types.d.ts.map +1 -1
- package/dist/providers/base/types.js.map +1 -1
- package/dist/providers/eric/provider.d.ts +3 -0
- package/dist/providers/eric/provider.d.ts.map +1 -1
- package/dist/providers/eric/provider.js +11 -0
- package/dist/providers/eric/provider.js.map +1 -1
- package/dist/providers/pubmed/client.d.ts +15 -1
- package/dist/providers/pubmed/client.d.ts.map +1 -1
- package/dist/providers/pubmed/client.js +64 -1
- package/dist/providers/pubmed/client.js.map +1 -1
- package/dist/providers/pubmed/index.d.ts +2 -2
- package/dist/providers/pubmed/index.d.ts.map +1 -1
- package/dist/providers/pubmed/parser.d.ts +8 -1
- package/dist/providers/pubmed/parser.d.ts.map +1 -1
- package/dist/providers/pubmed/parser.js +23 -1
- package/dist/providers/pubmed/parser.js.map +1 -1
- package/dist/providers/pubmed/provider.d.ts.map +1 -1
- package/dist/providers/pubmed/provider.js +8 -2
- package/dist/providers/pubmed/provider.js.map +1 -1
- package/dist/providers/pubmed/types.d.ts +29 -0
- package/dist/providers/pubmed/types.d.ts.map +1 -1
- package/dist/providers/scopus/client.d.ts +2 -0
- package/dist/providers/scopus/client.d.ts.map +1 -1
- package/dist/providers/scopus/client.js +3 -0
- package/dist/providers/scopus/client.js.map +1 -1
- package/dist/providers/scopus/provider.d.ts.map +1 -1
- package/dist/providers/scopus/provider.js +7 -1
- package/dist/providers/scopus/provider.js.map +1 -1
- package/dist/session/types.d.ts +13 -1
- package/dist/session/types.d.ts.map +1 -1
- package/dist/session/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAC1E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAIpD,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/B,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC9B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;IAC3B,kBAAkB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC1C;AAED,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,OAAO,EAAE,kBAAkB,GAC1B,oBAAoB,
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/search.ts"],"names":[],"mappings":"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"}
|
|
@@ -32,6 +32,9 @@ function parseSearchOptions(queryFile, options) {
|
|
|
32
32
|
if (options.strict) {
|
|
33
33
|
result.strict = true;
|
|
34
34
|
}
|
|
35
|
+
if (options.sort === "relevance" || options.sort === "date") {
|
|
36
|
+
result.sort = options.sort;
|
|
37
|
+
}
|
|
35
38
|
return result;
|
|
36
39
|
}
|
|
37
40
|
function validateSearchInput(options) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.js","sources":["../../../src/cli/commands/search.ts"],"sourcesContent":["import type { ProviderName } from '../../session/types.js';\nimport type { ConnectionTestResult } from '../../providers/base/types.js';\nimport type { Config } from '../../config/index.js';\nimport { parseProviderNames } from '../utils/validation.js';\nimport { createProviderInstance } from './search-executor.js';\n\nexport interface SearchCommandOptions {\n queryFile?: string;\n directQuery?: string;\n providers?: ProviderName[];\n sessionName?: string;\n maxResults?: number;\n dryRun?: boolean;\n countOnly?: boolean;\n preview?: boolean;\n noResume?: boolean;\n strict?: boolean;\n}\n\nexport interface CommandLineOptions {\n db?: string | undefined;\n query?: string | undefined;\n name?: string | undefined;\n maxResults?: string | undefined;\n dryRun?: boolean | undefined;\n countOnly?: boolean | undefined;\n preview?: boolean | undefined;\n noResume?: boolean | undefined;\n strict?: boolean | undefined;\n}\n\nexport interface TranslationResult {\n provider: string;\n query: string;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n error?: string;\n}\n\n/**\n * Options for enhanced dry-run output.\n */\nexport interface DryRunOutputOptions {\n config?: Config;\n providers?: ProviderName[];\n skipConnectionTest?: boolean | undefined;\n}\n\nexport function parseSearchOptions(\n queryFile: string | undefined,\n options: CommandLineOptions\n): SearchCommandOptions {\n const result: SearchCommandOptions = {};\n\n if (queryFile) {\n result.queryFile = queryFile;\n }\n\n if (options.query) {\n result.directQuery = options.query;\n }\n\n if (options.db) {\n result.providers = parseProviderNames(options.db);\n }\n\n if (options.name) {\n result.sessionName = options.name;\n }\n\n if (options.maxResults) {\n result.maxResults = parseInt(options.maxResults, 10);\n }\n\n if (options.dryRun) {\n result.dryRun = true;\n }\n\n if (options.countOnly) {\n result.countOnly = true;\n }\n\n if (options.preview) {\n result.preview = true;\n }\n\n if (options.noResume) {\n result.noResume = true;\n }\n\n if (options.strict) {\n result.strict = true;\n }\n\n return result;\n}\n\nexport function validateSearchInput(options: SearchCommandOptions): ValidationResult {\n if (!options.queryFile && !options.directQuery) {\n return {\n valid: false,\n error: 'Either a query file or --query option is required',\n };\n }\n\n if (options.directQuery && (!options.providers || options.providers.length === 0)) {\n return {\n valid: false,\n error: 'Direct query (--query) requires --db option to specify the provider',\n };\n }\n\n if (options.directQuery && options.providers && options.providers.length > 1) {\n return {\n valid: false,\n error: 'Direct query (--query) can only be used with a single provider (--db)',\n };\n }\n\n if (options.preview && options.countOnly) {\n return {\n valid: false,\n error: '--preview and --count-only cannot be used together',\n };\n }\n\n return { valid: true };\n}\n\n/**\n * Test provider connections and return results.\n */\nexport async function testProviderConnections(\n providers: ProviderName[],\n config: Config\n): Promise<Record<string, ConnectionTestResult>> {\n const results: Record<string, ConnectionTestResult> = {};\n await Promise.all(\n providers.map(async (name) => {\n const provider = createProviderInstance(name, config);\n if (!provider) {\n results[name] = { ok: false, error: 'Provider configuration incomplete' };\n return;\n }\n results[name] = await provider.testConnection();\n })\n );\n return results;\n}\n\n/**\n * Format provider readiness summary for dry-run output.\n */\nexport function formatProviderReadiness(\n providers: ProviderName[],\n config: Config,\n connectionResults?: Record<string, ConnectionTestResult>\n): string {\n const lines: string[] = [];\n lines.push('Provider readiness:');\n for (const provider of providers) {\n const providerConfig = config.providers[provider];\n const connResult = connectionResults?.[provider];\n const status = getProviderStatus(provider, providerConfig, connResult);\n const mark = status.ready ? '✓' : '✗';\n lines.push(` ${mark} ${provider.padEnd(10)}${status.message}`);\n }\n return lines.join('\\n');\n}\n\nfunction getProviderStatus(\n provider: ProviderName,\n providerConfig: { email?: string; api_key?: string },\n connectionResult?: ConnectionTestResult\n): { ready: boolean; message: string } {\n switch (provider) {\n case 'pubmed': {\n if (connectionResult && !connectionResult.ok) {\n return { ready: false, message: `not ready (${connectionResult.error})` };\n }\n if (!providerConfig.email) {\n return { ready: true, message: connectionResult ? 'ready (verified, email: not configured (recommended))' : 'ready (email: not configured (recommended))' };\n }\n return { ready: true, message: connectionResult ? 'ready (verified, email: configured)' : 'ready (email: configured)' };\n }\n case 'scopus': {\n if (!providerConfig.api_key) {\n return { ready: false, message: 'missing api_key (required)' };\n }\n if (connectionResult && !connectionResult.ok) {\n return { ready: false, message: `not ready (${connectionResult.error})` };\n }\n return { ready: true, message: connectionResult ? 'ready (verified)' : 'ready' };\n }\n case 'eric':\n case 'arxiv': {\n if (connectionResult && !connectionResult.ok) {\n return { ready: false, message: `not ready (${connectionResult.error})` };\n }\n return { ready: true, message: connectionResult ? 'ready (verified)' : 'ready' };\n }\n default:\n return { ready: true, message: 'ready' };\n }\n}\n\n/**\n * Format query diagnostics warnings for dry-run output.\n */\nexport function formatQueryDiagnostics(translations: TranslationResult[]): string {\n const warnings: string[] = [];\n for (const t of translations) {\n if (t.provider === 'pubmed') {\n if (/\\bNOT\\b/.test(t.query)) {\n warnings.push(' ⚠ pubmed: query uses NOT operator (ensure correct syntax for exclusions)');\n }\n if (/\\*\\[(?:mh|mesh)\\]/i.test(t.query)) {\n warnings.push(' ⚠ pubmed: wildcard in MeSH term — PubMed does not support wildcards in MeSH fields');\n }\n }\n }\n if (warnings.length === 0) {\n return '';\n }\n const lines: string[] = [];\n lines.push('Diagnostics:');\n lines.push(...warnings);\n return lines.join('\\n');\n}\n\n/**\n * Result of a count-only query for a single provider.\n */\nexport interface CountResult {\n provider: string;\n count: number;\n error?: string;\n}\n\n\n/**\n * Preview result for a single provider\n */\nexport interface PreviewResult {\n provider: string;\n count: number;\n titles: string[];\n error?: string;\n}\n\n/**\n * Format count-only output for display.\n */\nexport function formatCountOnlyOutput(\n counts: CountResult[],\n queryLabel?: string\n): string {\n const label = queryLabel ?? 'direct-query';\n const lines: string[] = [];\n\n lines.push(`Query: ${label} (count only)`);\n lines.push('');\n\n // Find the max provider name length for alignment (including colon)\n const maxNameLen = Math.max(...counts.map((c) => c.provider.length + 1), 6);\n\n // Calculate total (excluding errors)\n let total = 0;\n\n for (const c of counts) {\n if (c.error) {\n lines.push(` ${(c.provider + ':').padEnd(maxNameLen)} error: ${c.error}`);\n } else {\n const countStr = String(c.count).padStart(6);\n lines.push(` ${(c.provider + ':').padEnd(maxNameLen)} ${countStr} hits`);\n total += c.count;\n }\n }\n\n // Separator and total\n const separatorLen = maxNameLen + 14;\n lines.push(` ${'─'.repeat(separatorLen)}`);\n const totalStr = String(total).padStart(6);\n lines.push(` ${('total:').padEnd(maxNameLen)} ${totalStr} hits (before deduplication)`);\n\n return lines.join('\\n');\n}\n\n\n/**\n * Format preview output showing counts and sample titles.\n */\nexport function formatPreviewOutput(\n results: PreviewResult[],\n queryLabel?: string\n): string {\n const label = queryLabel ?? 'direct-query';\n const lines: string[] = [];\n\n lines.push(`Query: ${label} (preview)`);\n lines.push('');\n\n // Find the max provider name length for alignment (including colon)\n const maxNameLen = Math.max(...results.map((r) => r.provider.length + 1), 6);\n\n // Calculate total (excluding errors)\n let total = 0;\n\n for (const r of results) {\n if (r.error) {\n lines.push(` ${(r.provider + ':').padEnd(maxNameLen)} error: ${r.error}`);\n } else {\n const countStr = String(r.count).padStart(6);\n lines.push(` ${(r.provider + ':').padEnd(maxNameLen)} ${countStr} hits`);\n total += r.count;\n\n // Show sample titles\n if (r.titles.length > 0) {\n for (const title of r.titles) {\n lines.push(` • ${title}`);\n }\n }\n }\n lines.push('');\n }\n\n // Separator and total\n const separatorLen = maxNameLen + 14;\n lines.push(` ${'─'.repeat(separatorLen)}`);\n const totalStr = String(total).padStart(6);\n lines.push(` ${('total:').padEnd(maxNameLen)} ${totalStr} hits (before deduplication)`);\n\n return lines.join('\\n');\n}\n\nexport async function formatDryRunOutput(\n translations: TranslationResult[],\n options?: DryRunOutputOptions\n): Promise<string> {\n if (translations.length === 0) {\n return 'No translations available.';\n }\n const sections: string[] = [];\n if (options?.config && options?.providers) {\n let connectionResults: Record<string, ConnectionTestResult> | undefined;\n if (!options.skipConnectionTest) {\n connectionResults = await testProviderConnections(options.providers, options.config);\n }\n sections.push(formatProviderReadiness(options.providers, options.config, connectionResults));\n sections.push('');\n }\n const queryLines: string[] = [];\n queryLines.push('Translated queries:');\n queryLines.push('');\n for (const t of translations) {\n queryLines.push(`[${t.provider}]`);\n queryLines.push(t.query);\n queryLines.push('');\n }\n sections.push(queryLines.join('\\n'));\n const diagnostics = formatQueryDiagnostics(translations);\n if (diagnostics) {\n sections.push(diagnostics);\n sections.push('');\n }\n return sections.join('\\n');\n}\n\n\n/**\n * Format warning for short keywords that may cause noisy results.\n */\nexport function formatShortKeywordWarning(shortKeywords: string[]): string {\n if (shortKeywords.length === 0) {\n return '';\n }\n\n const keywordList = shortKeywords.join(', ');\n return `⚠ Query contains short keywords: ${keywordList}\n Short terms may match unrelated acronyms. Consider:\n - Adding full phrases (e.g., \"Objective Structured Clinical Examination\")\n - Using exclude terms to filter false matches`;\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"search.js","sources":["../../../src/cli/commands/search.ts"],"sourcesContent":["import type { ProviderName } from '../../session/types.js';\nimport type { ConnectionTestResult } from '../../providers/base/types.js';\nimport type { Config } from '../../config/index.js';\nimport { parseProviderNames } from '../utils/validation.js';\nimport { createProviderInstance } from './search-executor.js';\n\nexport interface SearchCommandOptions {\n queryFile?: string;\n directQuery?: string;\n providers?: ProviderName[];\n sessionName?: string;\n maxResults?: number;\n sort?: 'relevance' | 'date';\n dryRun?: boolean;\n countOnly?: boolean;\n preview?: boolean;\n noResume?: boolean;\n strict?: boolean;\n}\n\nexport interface CommandLineOptions {\n db?: string | undefined;\n query?: string | undefined;\n name?: string | undefined;\n maxResults?: string | undefined;\n sort?: string | undefined;\n dryRun?: boolean | undefined;\n countOnly?: boolean | undefined;\n preview?: boolean | undefined;\n noResume?: boolean | undefined;\n strict?: boolean | undefined;\n}\n\nexport interface TranslationResult {\n provider: string;\n query: string;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n error?: string;\n}\n\n/**\n * Options for enhanced dry-run output.\n */\nexport interface DryRunOutputOptions {\n config?: Config;\n providers?: ProviderName[];\n skipConnectionTest?: boolean | undefined;\n}\n\nexport function parseSearchOptions(\n queryFile: string | undefined,\n options: CommandLineOptions\n): SearchCommandOptions {\n const result: SearchCommandOptions = {};\n\n if (queryFile) {\n result.queryFile = queryFile;\n }\n\n if (options.query) {\n result.directQuery = options.query;\n }\n\n if (options.db) {\n result.providers = parseProviderNames(options.db);\n }\n\n if (options.name) {\n result.sessionName = options.name;\n }\n\n if (options.maxResults) {\n result.maxResults = parseInt(options.maxResults, 10);\n }\n\n if (options.dryRun) {\n result.dryRun = true;\n }\n\n if (options.countOnly) {\n result.countOnly = true;\n }\n\n if (options.preview) {\n result.preview = true;\n }\n\n if (options.noResume) {\n result.noResume = true;\n }\n\n if (options.strict) {\n result.strict = true;\n }\n\n if (options.sort === 'relevance' || options.sort === 'date') {\n result.sort = options.sort;\n }\n\n return result;\n}\n\nexport function validateSearchInput(options: SearchCommandOptions): ValidationResult {\n if (!options.queryFile && !options.directQuery) {\n return {\n valid: false,\n error: 'Either a query file or --query option is required',\n };\n }\n\n if (options.directQuery && (!options.providers || options.providers.length === 0)) {\n return {\n valid: false,\n error: 'Direct query (--query) requires --db option to specify the provider',\n };\n }\n\n if (options.directQuery && options.providers && options.providers.length > 1) {\n return {\n valid: false,\n error: 'Direct query (--query) can only be used with a single provider (--db)',\n };\n }\n\n if (options.preview && options.countOnly) {\n return {\n valid: false,\n error: '--preview and --count-only cannot be used together',\n };\n }\n\n return { valid: true };\n}\n\n/**\n * Test provider connections and return results.\n */\nexport async function testProviderConnections(\n providers: ProviderName[],\n config: Config\n): Promise<Record<string, ConnectionTestResult>> {\n const results: Record<string, ConnectionTestResult> = {};\n await Promise.all(\n providers.map(async (name) => {\n const provider = createProviderInstance(name, config);\n if (!provider) {\n results[name] = { ok: false, error: 'Provider configuration incomplete' };\n return;\n }\n results[name] = await provider.testConnection();\n })\n );\n return results;\n}\n\n/**\n * Format provider readiness summary for dry-run output.\n */\nexport function formatProviderReadiness(\n providers: ProviderName[],\n config: Config,\n connectionResults?: Record<string, ConnectionTestResult>\n): string {\n const lines: string[] = [];\n lines.push('Provider readiness:');\n for (const provider of providers) {\n const providerConfig = config.providers[provider];\n const connResult = connectionResults?.[provider];\n const status = getProviderStatus(provider, providerConfig, connResult);\n const mark = status.ready ? '✓' : '✗';\n lines.push(` ${mark} ${provider.padEnd(10)}${status.message}`);\n }\n return lines.join('\\n');\n}\n\nfunction getProviderStatus(\n provider: ProviderName,\n providerConfig: { email?: string; api_key?: string },\n connectionResult?: ConnectionTestResult\n): { ready: boolean; message: string } {\n switch (provider) {\n case 'pubmed': {\n if (connectionResult && !connectionResult.ok) {\n return { ready: false, message: `not ready (${connectionResult.error})` };\n }\n if (!providerConfig.email) {\n return { ready: true, message: connectionResult ? 'ready (verified, email: not configured (recommended))' : 'ready (email: not configured (recommended))' };\n }\n return { ready: true, message: connectionResult ? 'ready (verified, email: configured)' : 'ready (email: configured)' };\n }\n case 'scopus': {\n if (!providerConfig.api_key) {\n return { ready: false, message: 'missing api_key (required)' };\n }\n if (connectionResult && !connectionResult.ok) {\n return { ready: false, message: `not ready (${connectionResult.error})` };\n }\n return { ready: true, message: connectionResult ? 'ready (verified)' : 'ready' };\n }\n case 'eric':\n case 'arxiv': {\n if (connectionResult && !connectionResult.ok) {\n return { ready: false, message: `not ready (${connectionResult.error})` };\n }\n return { ready: true, message: connectionResult ? 'ready (verified)' : 'ready' };\n }\n default:\n return { ready: true, message: 'ready' };\n }\n}\n\n/**\n * Format query diagnostics warnings for dry-run output.\n */\nexport function formatQueryDiagnostics(translations: TranslationResult[]): string {\n const warnings: string[] = [];\n for (const t of translations) {\n if (t.provider === 'pubmed') {\n if (/\\bNOT\\b/.test(t.query)) {\n warnings.push(' ⚠ pubmed: query uses NOT operator (ensure correct syntax for exclusions)');\n }\n if (/\\*\\[(?:mh|mesh)\\]/i.test(t.query)) {\n warnings.push(' ⚠ pubmed: wildcard in MeSH term — PubMed does not support wildcards in MeSH fields');\n }\n }\n }\n if (warnings.length === 0) {\n return '';\n }\n const lines: string[] = [];\n lines.push('Diagnostics:');\n lines.push(...warnings);\n return lines.join('\\n');\n}\n\n/**\n * Result of a count-only query for a single provider.\n */\nexport interface CountResult {\n provider: string;\n count: number;\n error?: string;\n}\n\n\n/**\n * Preview result for a single provider\n */\nexport interface PreviewResult {\n provider: string;\n count: number;\n titles: string[];\n error?: string;\n}\n\n/**\n * Format count-only output for display.\n */\nexport function formatCountOnlyOutput(\n counts: CountResult[],\n queryLabel?: string\n): string {\n const label = queryLabel ?? 'direct-query';\n const lines: string[] = [];\n\n lines.push(`Query: ${label} (count only)`);\n lines.push('');\n\n // Find the max provider name length for alignment (including colon)\n const maxNameLen = Math.max(...counts.map((c) => c.provider.length + 1), 6);\n\n // Calculate total (excluding errors)\n let total = 0;\n\n for (const c of counts) {\n if (c.error) {\n lines.push(` ${(c.provider + ':').padEnd(maxNameLen)} error: ${c.error}`);\n } else {\n const countStr = String(c.count).padStart(6);\n lines.push(` ${(c.provider + ':').padEnd(maxNameLen)} ${countStr} hits`);\n total += c.count;\n }\n }\n\n // Separator and total\n const separatorLen = maxNameLen + 14;\n lines.push(` ${'─'.repeat(separatorLen)}`);\n const totalStr = String(total).padStart(6);\n lines.push(` ${('total:').padEnd(maxNameLen)} ${totalStr} hits (before deduplication)`);\n\n return lines.join('\\n');\n}\n\n\n/**\n * Format preview output showing counts and sample titles.\n */\nexport function formatPreviewOutput(\n results: PreviewResult[],\n queryLabel?: string\n): string {\n const label = queryLabel ?? 'direct-query';\n const lines: string[] = [];\n\n lines.push(`Query: ${label} (preview)`);\n lines.push('');\n\n // Find the max provider name length for alignment (including colon)\n const maxNameLen = Math.max(...results.map((r) => r.provider.length + 1), 6);\n\n // Calculate total (excluding errors)\n let total = 0;\n\n for (const r of results) {\n if (r.error) {\n lines.push(` ${(r.provider + ':').padEnd(maxNameLen)} error: ${r.error}`);\n } else {\n const countStr = String(r.count).padStart(6);\n lines.push(` ${(r.provider + ':').padEnd(maxNameLen)} ${countStr} hits`);\n total += r.count;\n\n // Show sample titles\n if (r.titles.length > 0) {\n for (const title of r.titles) {\n lines.push(` • ${title}`);\n }\n }\n }\n lines.push('');\n }\n\n // Separator and total\n const separatorLen = maxNameLen + 14;\n lines.push(` ${'─'.repeat(separatorLen)}`);\n const totalStr = String(total).padStart(6);\n lines.push(` ${('total:').padEnd(maxNameLen)} ${totalStr} hits (before deduplication)`);\n\n return lines.join('\\n');\n}\n\nexport async function formatDryRunOutput(\n translations: TranslationResult[],\n options?: DryRunOutputOptions\n): Promise<string> {\n if (translations.length === 0) {\n return 'No translations available.';\n }\n const sections: string[] = [];\n if (options?.config && options?.providers) {\n let connectionResults: Record<string, ConnectionTestResult> | undefined;\n if (!options.skipConnectionTest) {\n connectionResults = await testProviderConnections(options.providers, options.config);\n }\n sections.push(formatProviderReadiness(options.providers, options.config, connectionResults));\n sections.push('');\n }\n const queryLines: string[] = [];\n queryLines.push('Translated queries:');\n queryLines.push('');\n for (const t of translations) {\n queryLines.push(`[${t.provider}]`);\n queryLines.push(t.query);\n queryLines.push('');\n }\n sections.push(queryLines.join('\\n'));\n const diagnostics = formatQueryDiagnostics(translations);\n if (diagnostics) {\n sections.push(diagnostics);\n sections.push('');\n }\n return sections.join('\\n');\n}\n\n\n/**\n * Format warning for short keywords that may cause noisy results.\n */\nexport function formatShortKeywordWarning(shortKeywords: string[]): string {\n if (shortKeywords.length === 0) {\n return '';\n }\n\n const keywordList = shortKeywords.join(', ');\n return `⚠ Query contains short keywords: ${keywordList}\n Short terms may match unrelated acronyms. Consider:\n - Adding full phrases (e.g., \"Objective Structured Clinical Examination\")\n - Using exclude terms to filter false matches`;\n}\n"],"names":[],"mappings":";;AAoDO,SAAS,mBACd,WACA,SACsB;AACtB,QAAM,SAA+B,CAAA;AAErC,MAAI,WAAW;AACb,WAAO,YAAY;AAAA,EACrB;AAEA,MAAI,QAAQ,OAAO;AACjB,WAAO,cAAc,QAAQ;AAAA,EAC/B;AAEA,MAAI,QAAQ,IAAI;AACd,WAAO,YAAY,mBAAmB,QAAQ,EAAE;AAAA,EAClD;AAEA,MAAI,QAAQ,MAAM;AAChB,WAAO,cAAc,QAAQ;AAAA,EAC/B;AAEA,MAAI,QAAQ,YAAY;AACtB,WAAO,aAAa,SAAS,QAAQ,YAAY,EAAE;AAAA,EACrD;AAEA,MAAI,QAAQ,QAAQ;AAClB,WAAO,SAAS;AAAA,EAClB;AAEA,MAAI,QAAQ,WAAW;AACrB,WAAO,YAAY;AAAA,EACrB;AAEA,MAAI,QAAQ,SAAS;AACnB,WAAO,UAAU;AAAA,EACnB;AAEA,MAAI,QAAQ,UAAU;AACpB,WAAO,WAAW;AAAA,EACpB;AAEA,MAAI,QAAQ,QAAQ;AAClB,WAAO,SAAS;AAAA,EAClB;AAEA,MAAI,QAAQ,SAAS,eAAe,QAAQ,SAAS,QAAQ;AAC3D,WAAO,OAAO,QAAQ;AAAA,EACxB;AAEA,SAAO;AACT;AAEO,SAAS,oBAAoB,SAAiD;AACnF,MAAI,CAAC,QAAQ,aAAa,CAAC,QAAQ,aAAa;AAC9C,WAAO;AAAA,MACL,OAAO;AAAA,MACP,OAAO;AAAA,IAAA;AAAA,EAEX;AAEA,MAAI,QAAQ,gBAAgB,CAAC,QAAQ,aAAa,QAAQ,UAAU,WAAW,IAAI;AACjF,WAAO;AAAA,MACL,OAAO;AAAA,MACP,OAAO;AAAA,IAAA;AAAA,EAEX;AAEA,MAAI,QAAQ,eAAe,QAAQ,aAAa,QAAQ,UAAU,SAAS,GAAG;AAC5E,WAAO;AAAA,MACL,OAAO;AAAA,MACP,OAAO;AAAA,IAAA;AAAA,EAEX;AAEA,MAAI,QAAQ,WAAW,QAAQ,WAAW;AACxC,WAAO;AAAA,MACL,OAAO;AAAA,MACP,OAAO;AAAA,IAAA;AAAA,EAEX;AAEA,SAAO,EAAE,OAAO,KAAA;AAClB;AAKA,eAAsB,wBACpB,WACA,QAC+C;AAC/C,QAAM,UAAgD,CAAA;AACtD,QAAM,QAAQ;AAAA,IACZ,UAAU,IAAI,OAAO,SAAS;AAC5B,YAAM,WAAW,uBAAuB,MAAM,MAAM;AACpD,UAAI,CAAC,UAAU;AACb,gBAAQ,IAAI,IAAI,EAAE,IAAI,OAAO,OAAO,oCAAA;AACpC;AAAA,MACF;AACA,cAAQ,IAAI,IAAI,MAAM,SAAS,eAAA;AAAA,IACjC,CAAC;AAAA,EAAA;AAEH,SAAO;AACT;AAKO,SAAS,wBACd,WACA,QACA,mBACQ;AACR,QAAM,QAAkB,CAAA;AACxB,QAAM,KAAK,qBAAqB;AAChC,aAAW,YAAY,WAAW;AAChC,UAAM,iBAAiB,OAAO,UAAU,QAAQ;AAChD,UAAM,aAAa,oBAAoB,QAAQ;AAC/C,UAAM,SAAS,kBAAkB,UAAU,gBAAgB,UAAU;AACrE,UAAM,OAAO,OAAO,QAAQ,MAAM;AAClC,UAAM,KAAK,KAAK,IAAI,IAAI,SAAS,OAAO,EAAE,CAAC,GAAG,OAAO,OAAO,EAAE;AAAA,EAChE;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,SAAS,kBACP,UACA,gBACA,kBACqC;AACrC,UAAQ,UAAA;AAAA,IACN,KAAK,UAAU;AACb,UAAI,oBAAoB,CAAC,iBAAiB,IAAI;AAC5C,eAAO,EAAE,OAAO,OAAO,SAAS,cAAc,iBAAiB,KAAK,IAAA;AAAA,MACtE;AACA,UAAI,CAAC,eAAe,OAAO;AACzB,eAAO,EAAE,OAAO,MAAM,SAAS,mBAAmB,0DAA0D,8CAAA;AAAA,MAC9G;AACA,aAAO,EAAE,OAAO,MAAM,SAAS,mBAAmB,wCAAwC,4BAAA;AAAA,IAC5F;AAAA,IACA,KAAK,UAAU;AACb,UAAI,CAAC,eAAe,SAAS;AAC3B,eAAO,EAAE,OAAO,OAAO,SAAS,6BAAA;AAAA,MAClC;AACA,UAAI,oBAAoB,CAAC,iBAAiB,IAAI;AAC5C,eAAO,EAAE,OAAO,OAAO,SAAS,cAAc,iBAAiB,KAAK,IAAA;AAAA,MACtE;AACA,aAAO,EAAE,OAAO,MAAM,SAAS,mBAAmB,qBAAqB,QAAA;AAAA,IACzE;AAAA,IACA,KAAK;AAAA,IACL,KAAK,SAAS;AACZ,UAAI,oBAAoB,CAAC,iBAAiB,IAAI;AAC5C,eAAO,EAAE,OAAO,OAAO,SAAS,cAAc,iBAAiB,KAAK,IAAA;AAAA,MACtE;AACA,aAAO,EAAE,OAAO,MAAM,SAAS,mBAAmB,qBAAqB,QAAA;AAAA,IACzE;AAAA,IACA;AACE,aAAO,EAAE,OAAO,MAAM,SAAS,QAAA;AAAA,EAAQ;AAE7C;AAKO,SAAS,uBAAuB,cAA2C;AAChF,QAAM,WAAqB,CAAA;AAC3B,aAAW,KAAK,cAAc;AAC5B,QAAI,EAAE,aAAa,UAAU;AAC3B,UAAI,UAAU,KAAK,EAAE,KAAK,GAAG;AAC3B,iBAAS,KAAK,4EAA4E;AAAA,MAC5F;AACA,UAAI,qBAAqB,KAAK,EAAE,KAAK,GAAG;AACtC,iBAAS,KAAK,sFAAsF;AAAA,MACtG;AAAA,IACF;AAAA,EACF;AACA,MAAI,SAAS,WAAW,GAAG;AACzB,WAAO;AAAA,EACT;AACA,QAAM,QAAkB,CAAA;AACxB,QAAM,KAAK,cAAc;AACzB,QAAM,KAAK,GAAG,QAAQ;AACtB,SAAO,MAAM,KAAK,IAAI;AACxB;AAyBO,SAAS,sBACd,QACA,YACQ;AACR,QAAM,QAAQ,cAAc;AAC5B,QAAM,QAAkB,CAAA;AAExB,QAAM,KAAK,UAAU,KAAK,eAAe;AACzC,QAAM,KAAK,EAAE;AAGb,QAAM,aAAa,KAAK,IAAI,GAAG,OAAO,IAAI,CAAC,MAAM,EAAE,SAAS,SAAS,CAAC,GAAG,CAAC;AAG1E,MAAI,QAAQ;AAEZ,aAAW,KAAK,QAAQ;AACtB,QAAI,EAAE,OAAO;AACX,YAAM,KAAK,MAAM,EAAE,WAAW,KAAK,OAAO,UAAU,CAAC,YAAY,EAAE,KAAK,EAAE;AAAA,IAC5E,OAAO;AACL,YAAM,WAAW,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC;AAC3C,YAAM,KAAK,MAAM,EAAE,WAAW,KAAK,OAAO,UAAU,CAAC,IAAI,QAAQ,OAAO;AACxE,eAAS,EAAE;AAAA,IACb;AAAA,EACF;AAGA,QAAM,eAAe,aAAa;AAClC,QAAM,KAAK,KAAK,IAAI,OAAO,YAAY,CAAC,EAAE;AAC1C,QAAM,WAAW,OAAO,KAAK,EAAE,SAAS,CAAC;AACzC,QAAM,KAAK,KAAM,SAAU,OAAO,UAAU,CAAC,IAAI,QAAQ,8BAA8B;AAEvF,SAAO,MAAM,KAAK,IAAI;AACxB;AAMO,SAAS,oBACd,SACA,YACQ;AACR,QAAM,QAAQ,cAAc;AAC5B,QAAM,QAAkB,CAAA;AAExB,QAAM,KAAK,UAAU,KAAK,YAAY;AACtC,QAAM,KAAK,EAAE;AAGb,QAAM,aAAa,KAAK,IAAI,GAAG,QAAQ,IAAI,CAAC,MAAM,EAAE,SAAS,SAAS,CAAC,GAAG,CAAC;AAG3E,MAAI,QAAQ;AAEZ,aAAW,KAAK,SAAS;AACvB,QAAI,EAAE,OAAO;AACX,YAAM,KAAK,MAAM,EAAE,WAAW,KAAK,OAAO,UAAU,CAAC,YAAY,EAAE,KAAK,EAAE;AAAA,IAC5E,OAAO;AACL,YAAM,WAAW,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC;AAC3C,YAAM,KAAK,MAAM,EAAE,WAAW,KAAK,OAAO,UAAU,CAAC,IAAI,QAAQ,OAAO;AACxE,eAAS,EAAE;AAGX,UAAI,EAAE,OAAO,SAAS,GAAG;AACvB,mBAAW,SAAS,EAAE,QAAQ;AAC5B,gBAAM,KAAK,SAAS,KAAK,EAAE;AAAA,QAC7B;AAAA,MACF;AAAA,IACF;AACA,UAAM,KAAK,EAAE;AAAA,EACf;AAGA,QAAM,eAAe,aAAa;AAClC,QAAM,KAAK,KAAK,IAAI,OAAO,YAAY,CAAC,EAAE;AAC1C,QAAM,WAAW,OAAO,KAAK,EAAE,SAAS,CAAC;AACzC,QAAM,KAAK,KAAM,SAAU,OAAO,UAAU,CAAC,IAAI,QAAQ,8BAA8B;AAEvF,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,eAAsB,mBACpB,cACA,SACiB;AACjB,MAAI,aAAa,WAAW,GAAG;AAC7B,WAAO;AAAA,EACT;AACA,QAAM,WAAqB,CAAA;AAC3B,MAAI,SAAS,UAAU,SAAS,WAAW;AACzC,QAAI;AACJ,QAAI,CAAC,QAAQ,oBAAoB;AAC/B,0BAAoB,MAAM,wBAAwB,QAAQ,WAAW,QAAQ,MAAM;AAAA,IACrF;AACA,aAAS,KAAK,wBAAwB,QAAQ,WAAW,QAAQ,QAAQ,iBAAiB,CAAC;AAC3F,aAAS,KAAK,EAAE;AAAA,EAClB;AACA,QAAM,aAAuB,CAAA;AAC7B,aAAW,KAAK,qBAAqB;AACrC,aAAW,KAAK,EAAE;AAClB,aAAW,KAAK,cAAc;AAC5B,eAAW,KAAK,IAAI,EAAE,QAAQ,GAAG;AACjC,eAAW,KAAK,EAAE,KAAK;AACvB,eAAW,KAAK,EAAE;AAAA,EACpB;AACA,WAAS,KAAK,WAAW,KAAK,IAAI,CAAC;AACnC,QAAM,cAAc,uBAAuB,YAAY;AACvD,MAAI,aAAa;AACf,aAAS,KAAK,WAAW;AACzB,aAAS,KAAK,EAAE;AAAA,EAClB;AACA,SAAO,SAAS,KAAK,IAAI;AAC3B;AAMO,SAAS,0BAA0B,eAAiC;AACzE,MAAI,cAAc,WAAW,GAAG;AAC9B,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,cAAc,KAAK,IAAI;AAC3C,SAAO,oCAAoC,WAAW;AAAA;AAAA;AAAA;AAIxD;"}
|
package/dist/cli/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAQA,OAAO,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAQA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAyM5C;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,0BAA0B;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gCAAgC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,wCAAwC;IACxC,KAAK,EAAE,OAAO,CAAC;IACf,qEAAqE;IACrE,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAiuFvC;AAED;;GAEG;AACH,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAG1C"}
|
package/dist/cli/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { config } from "dotenv";
|
|
3
|
-
import { Command } from "commander";
|
|
3
|
+
import { Command, Option } from "commander";
|
|
4
4
|
import { VERSION } from "../version.js";
|
|
5
5
|
import { init } from "./commands/init.js";
|
|
6
6
|
import { EXIT_CODES } from "./exit-codes.js";
|
|
@@ -30,6 +30,7 @@ import { filterByQuery } from "./commands/query-filter.js";
|
|
|
30
30
|
import { loadNotes, formatAllSessionNotes, formatNotesList, addNote, addAssessment } from "./commands/notes.js";
|
|
31
31
|
import { computeDiff, computeQueryDiff, formatDiffJson, formatDiff } from "./commands/diff.js";
|
|
32
32
|
import { validateMergeSources, mergeArticles, formatMergeJson, formatMergeOutput, createMergedSession } from "./commands/merge.js";
|
|
33
|
+
import { parseRelatedOptions, validateRelatedInput, resolveSeeds, createRelatedSession, formatRelatedOutput } from "./commands/related.js";
|
|
33
34
|
import { executeReviewInit } from "./commands/review/init.js";
|
|
34
35
|
import { executeReviewStatus, formatStatusOutput } from "./commands/review/status.js";
|
|
35
36
|
import { executeReviewList, formatListOutput } from "./commands/review/list.js";
|
|
@@ -41,7 +42,7 @@ import { executeReviewFinalize, formatFinalizeOutput } from "./commands/review/f
|
|
|
41
42
|
import "zod";
|
|
42
43
|
import "./commands/review/schema.js";
|
|
43
44
|
import { registerFulltextCommands } from "./commands/fulltext/index.js";
|
|
44
|
-
import { parseRegisterOptions, validateRegisterInput, hasReviewFile, getReviewSummary, formatNoIncludedArticlesError, formatPendingWarning, confirmPrompt, getIncludedArticles, formatReviewRequiredMessage, formatIgnoringReviewsNote, formatDryRunOutput as formatDryRunOutput$1, formatRegistrationSummary } from "./commands/register.js";
|
|
45
|
+
import { parseRegisterOptions, validateRegisterInput, hasReviewFile, getReviewSummary, formatNoIncludedArticlesError, formatPendingWarning, confirmPrompt, getIncludedArticles, formatReviewRequiredMessage, formatIgnoringReviewsNote, formatDryRunOutput as formatDryRunOutput$1, formatRegistrationSummary, formatLibraryPath, formatDefaultLibraryHint } from "./commands/register.js";
|
|
45
46
|
import { formatSuggestion } from "./suggestions/index.js";
|
|
46
47
|
import { getSuggestion } from "./suggestions/rules.js";
|
|
47
48
|
import { readLogEntries, computeQueryHash, appendLogEntry, buildPreviewLogEntry, buildCountLogEntry } from "./commands/query/iteration-log.js";
|
|
@@ -59,6 +60,7 @@ import { getSessionsDir } from "./utils/sessions-dir.js";
|
|
|
59
60
|
import { expandPath } from "../utils/path.js";
|
|
60
61
|
import { loadSessionArticles, loadSessionQuery } from "./commands/session-utils.js";
|
|
61
62
|
import { parseIdentifierFile, checkCoverage, formatCheckResultJson, formatCheckResult } from "./commands/check.js";
|
|
63
|
+
import { PubMedClient } from "../providers/pubmed/client.js";
|
|
62
64
|
config({ quiet: true });
|
|
63
65
|
function createProgram() {
|
|
64
66
|
const program = new Command();
|
|
@@ -446,7 +448,7 @@ Examples:
|
|
|
446
448
|
process.exitCode = EXIT_CODES.SESSION_ERROR;
|
|
447
449
|
}
|
|
448
450
|
});
|
|
449
|
-
program.command("search").description("Execute search across databases").argument("[query-file]", "path to query YAML file").option("--db <providers>", "target specific database(s), comma-separated").option("--query <string>", "direct query in database-native syntax (advanced; requires --db; prefer YAML files)").option("--name <string>", "session name").option("--max-results <n>", "limit results per database").option("--dry-run", "show translated queries without executing").option("--count-only", "get hit counts without downloading results").option("--preview", "get hit counts and first 5 titles without creating session").option("--skip-connection-test", "skip API connection test during dry-run").option("--no-resume", "start fresh even if session exists").option("--strict", "require all targeted databases to succeed (exit non-zero on partial failure)").addHelpText("after", `
|
|
451
|
+
program.command("search").description("Execute search across databases").argument("[query-file]", "path to query YAML file").option("--db <providers>", "target specific database(s), comma-separated").option("--query <string>", "direct query in database-native syntax (advanced; requires --db; prefer YAML files)").option("--name <string>", "session name").option("--max-results <n>", "limit results per database").addOption(new Option("--sort <method>", "sort results by relevance or date").choices(["relevance", "date"])).option("--dry-run", "show translated queries without executing").option("--count-only", "get hit counts without downloading results").option("--preview", "get hit counts and first 5 titles without creating session").option("--skip-connection-test", "skip API connection test during dry-run").option("--no-resume", "start fresh even if session exists").option("--strict", "require all targeted databases to succeed (exit non-zero on partial failure)").addHelpText("after", `
|
|
450
452
|
Workflow position:
|
|
451
453
|
query validate → [this command: search] → results / summary / diff
|
|
452
454
|
|
|
@@ -471,6 +473,7 @@ Query features (use "query init" to see full template):
|
|
|
471
473
|
query: options?.query,
|
|
472
474
|
name: options?.name,
|
|
473
475
|
maxResults: options?.maxResults,
|
|
476
|
+
sort: options?.sort,
|
|
474
477
|
dryRun: options?.dryRun,
|
|
475
478
|
countOnly: options?.countOnly,
|
|
476
479
|
preview: options?.preview,
|
|
@@ -662,6 +665,11 @@ Search completed. Session: ${result.sessionId}`);
|
|
|
662
665
|
} else {
|
|
663
666
|
console.log(` ${provider}: ${stats.retrieved} results`);
|
|
664
667
|
}
|
|
668
|
+
if (stats.warnings && stats.warnings.length > 0) {
|
|
669
|
+
for (const w of stats.warnings) {
|
|
670
|
+
console.warn(` ⚠ ${provider}: ${w}`);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
665
673
|
}
|
|
666
674
|
}
|
|
667
675
|
if (result.sessionStatus === "partial" && result.results) {
|
|
@@ -1285,6 +1293,119 @@ Input file format (one identifier per line):
|
|
|
1285
1293
|
}
|
|
1286
1294
|
}
|
|
1287
1295
|
);
|
|
1296
|
+
program.command("related").description("Find related articles from seed PMIDs using PubMed ELink").argument("[pmids...]", "seed PMIDs").option("-n, --name <string>", "session name").option("-m, --max-results <number>", "max related articles to retrieve", "20").option("-s, --from-session <id>", "load seed PMIDs from existing session").option("--pmid <pmids...>", "seed PMIDs (alternative to positional args)").option("-t, --term <filter>", 'additional PubMed filter (e.g., "review[filter]")').addHelpText("after", `
|
|
1297
|
+
Examples:
|
|
1298
|
+
$ search-hub related 12345678 23456789 # Find related articles
|
|
1299
|
+
$ search-hub related 12345678 --name my-related # Custom session name
|
|
1300
|
+
$ search-hub related 12345678 -m 50 # Get more results
|
|
1301
|
+
$ search-hub related --from-session SESSION --pmid 12345678
|
|
1302
|
+
$ search-hub related 12345678 -t "review[filter]" # Filter by review type`).action(
|
|
1303
|
+
async (pmidArgs, options) => {
|
|
1304
|
+
const globalOpts = program.opts();
|
|
1305
|
+
try {
|
|
1306
|
+
const parsedOptions = parseRelatedOptions(pmidArgs, options ?? {});
|
|
1307
|
+
const validation = validateRelatedInput(parsedOptions);
|
|
1308
|
+
if (!validation.valid) {
|
|
1309
|
+
if (!globalOpts.quiet) {
|
|
1310
|
+
console.error(`Error: ${validation.error}`);
|
|
1311
|
+
}
|
|
1312
|
+
process.exitCode = EXIT_CODES.GENERAL_ERROR;
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
const sessionsDir = await getSessionsDir(globalOpts);
|
|
1316
|
+
let seedPmids;
|
|
1317
|
+
try {
|
|
1318
|
+
seedPmids = await resolveSeeds(parsedOptions, sessionsDir);
|
|
1319
|
+
} catch (error) {
|
|
1320
|
+
if (!globalOpts.quiet) {
|
|
1321
|
+
console.error(
|
|
1322
|
+
`Error: ${error instanceof Error ? error.message : "Failed to resolve seeds"}`
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1325
|
+
process.exitCode = EXIT_CODES.SESSION_ERROR;
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
if (seedPmids.length === 0) {
|
|
1329
|
+
if (!globalOpts.quiet) {
|
|
1330
|
+
console.error("Error: No PMIDs found to use as seeds.");
|
|
1331
|
+
}
|
|
1332
|
+
process.exitCode = EXIT_CODES.GENERAL_ERROR;
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
const config2 = await loadConfig(
|
|
1336
|
+
globalOpts.config ? { explicitConfigPath: globalOpts.config } : {}
|
|
1337
|
+
);
|
|
1338
|
+
const providerConfig = config2.providers.pubmed;
|
|
1339
|
+
const pubmedConfig = {
|
|
1340
|
+
email: providerConfig.email ?? "search-hub@example.com",
|
|
1341
|
+
rateLimit: providerConfig.rate_limit,
|
|
1342
|
+
timeout: providerConfig.timeout,
|
|
1343
|
+
retries: providerConfig.retries
|
|
1344
|
+
};
|
|
1345
|
+
if (providerConfig.api_key) {
|
|
1346
|
+
pubmedConfig.apiKey = providerConfig.api_key;
|
|
1347
|
+
}
|
|
1348
|
+
const rateLimiter = new RateLimiter({
|
|
1349
|
+
tokensPerSecond: pubmedConfig.rateLimit ?? (pubmedConfig.apiKey ? 10 : 3)
|
|
1350
|
+
});
|
|
1351
|
+
const client = new PubMedClient(pubmedConfig, rateLimiter);
|
|
1352
|
+
if (!globalOpts.quiet) {
|
|
1353
|
+
console.log(`Finding related articles for ${seedPmids.length} seed PMIDs...`);
|
|
1354
|
+
}
|
|
1355
|
+
const relatedArticles = await client.findRelatedMerged({
|
|
1356
|
+
ids: seedPmids,
|
|
1357
|
+
maxResults: parsedOptions.maxResults,
|
|
1358
|
+
...parsedOptions.term && { term: parsedOptions.term }
|
|
1359
|
+
});
|
|
1360
|
+
const totalRelated = relatedArticles.length;
|
|
1361
|
+
if (totalRelated === 0) {
|
|
1362
|
+
if (!globalOpts.quiet) {
|
|
1363
|
+
console.log("No related articles found.");
|
|
1364
|
+
}
|
|
1365
|
+
process.exitCode = EXIT_CODES.SUCCESS;
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
const relatedPmids = relatedArticles.map((a) => a.id);
|
|
1369
|
+
const articles = await client.fetch(relatedPmids);
|
|
1370
|
+
const sessionName = parsedOptions.name ?? `related-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
1371
|
+
const sessionFile = await createRelatedSession({
|
|
1372
|
+
name: sessionName,
|
|
1373
|
+
seeds: {
|
|
1374
|
+
ids: seedPmids,
|
|
1375
|
+
...parsedOptions.fromSession && { sourceSession: parsedOptions.fromSession }
|
|
1376
|
+
},
|
|
1377
|
+
articles,
|
|
1378
|
+
sessionsDir
|
|
1379
|
+
});
|
|
1380
|
+
if (!globalOpts.quiet) {
|
|
1381
|
+
console.log(formatRelatedOutput({
|
|
1382
|
+
sessionId: sessionFile.id,
|
|
1383
|
+
seedCount: seedPmids.length,
|
|
1384
|
+
totalRelated,
|
|
1385
|
+
retrievedCount: articles.length,
|
|
1386
|
+
articles
|
|
1387
|
+
}));
|
|
1388
|
+
const suggestion = getSuggestion({
|
|
1389
|
+
command: "related",
|
|
1390
|
+
sessionId: sessionFile.id
|
|
1391
|
+
});
|
|
1392
|
+
const suggestionText = formatSuggestion(suggestion);
|
|
1393
|
+
if (suggestionText) {
|
|
1394
|
+
console.log(suggestionText);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
process.exitCode = EXIT_CODES.SUCCESS;
|
|
1398
|
+
} catch (error) {
|
|
1399
|
+
if (!globalOpts.quiet) {
|
|
1400
|
+
console.error(
|
|
1401
|
+
"Error:",
|
|
1402
|
+
error instanceof Error ? error.message : error
|
|
1403
|
+
);
|
|
1404
|
+
}
|
|
1405
|
+
process.exitCode = EXIT_CODES.GENERAL_ERROR;
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
);
|
|
1288
1409
|
program.command("merge").description("Merge results from multiple search sessions").argument("<session-ids...>", "two or more session IDs to merge").option("--name <string>", "name for merged session").option("--dry-run", "show what would be merged without creating session").option("--json", "output as JSON").addHelpText("after", `
|
|
1289
1410
|
Examples:
|
|
1290
1411
|
$ search-hub merge session-v4 session-v9 # Merge two sessions
|
|
@@ -1567,7 +1688,10 @@ Failed to install reference-manager: ${installError instanceof Error ? installEr
|
|
|
1567
1688
|
}
|
|
1568
1689
|
}
|
|
1569
1690
|
console.log(`
|
|
1570
|
-
|
|
1691
|
+
${formatLibraryPath(sessionDir)}`);
|
|
1692
|
+
console.log(`Results saved to: ${join(sessionDir, "registration.json")}`);
|
|
1693
|
+
console.log(`
|
|
1694
|
+
${formatDefaultLibraryHint(sessionDir)}`);
|
|
1571
1695
|
const suggestion = formatSuggestion(getSuggestion({
|
|
1572
1696
|
command: "register",
|
|
1573
1697
|
sessionId,
|
|
@@ -1595,12 +1719,16 @@ Examples:
|
|
|
1595
1719
|
$ search-hub review extract --session SESSION_ID --name title-screening # Extract for review
|
|
1596
1720
|
$ search-hub review merge --session SESSION_ID --name title-screening # Merge reviews
|
|
1597
1721
|
$ search-hub review export --session SESSION_ID --only included -o included.yaml`);
|
|
1598
|
-
reviewCommand.command("init").description("Generate reviews.yaml from deduplicated search results").requiredOption("--session <id>", "session ID").option("-f, --force", "overwrite existing reviews.yaml", false).action(async (options) => {
|
|
1722
|
+
reviewCommand.command("init").description("Generate reviews.yaml from deduplicated search results").requiredOption("--session <id>", "session ID").option("--mode <mode>", "review mode: screening (exclusion-based) or picking (inclusion-based)").option("-f, --force", "overwrite existing reviews.yaml", false).action(async (options) => {
|
|
1599
1723
|
const globalOpts = program.opts();
|
|
1600
1724
|
try {
|
|
1725
|
+
if (options.mode && options.mode !== "screening" && options.mode !== "picking") {
|
|
1726
|
+
throw new Error(`Invalid mode: "${options.mode}". Must be "screening" or "picking".`);
|
|
1727
|
+
}
|
|
1601
1728
|
const sessionsDir = await getSessionsDir(globalOpts);
|
|
1602
1729
|
const initOptions = {
|
|
1603
1730
|
sessionId: options.session,
|
|
1731
|
+
...options.mode && { mode: options.mode },
|
|
1604
1732
|
...options.force && { force: options.force }
|
|
1605
1733
|
};
|
|
1606
1734
|
const result = await executeReviewInit(initOptions, sessionsDir);
|