@ncukondo/search-hub 0.1.3 → 0.1.4
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/query/init.d.ts +22 -0
- package/dist/cli/commands/query/init.d.ts.map +1 -0
- package/dist/cli/commands/query/init.js +67 -0
- package/dist/cli/commands/query/init.js.map +1 -0
- package/dist/cli/commands/resume-executor.d.ts +1 -0
- package/dist/cli/commands/resume-executor.d.ts.map +1 -1
- package/dist/cli/commands/resume-executor.js +27 -5
- package/dist/cli/commands/resume-executor.js.map +1 -1
- package/dist/cli/commands/search-executor.d.ts +3 -1
- package/dist/cli/commands/search-executor.d.ts.map +1 -1
- package/dist/cli/commands/search-executor.js +41 -7
- package/dist/cli/commands/search-executor.js.map +1 -1
- package/dist/cli/commands/search-utils.d.ts +21 -0
- package/dist/cli/commands/search-utils.d.ts.map +1 -0
- package/dist/cli/commands/search-utils.js +29 -0
- package/dist/cli/commands/search-utils.js.map +1 -0
- package/dist/cli/commands/search.d.ts +17 -1
- package/dist/cli/commands/search.d.ts.map +1 -1
- package/dist/cli/commands/search.js +73 -8
- package/dist/cli/commands/search.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +70 -18
- package/dist/cli/index.js.map +1 -1
- package/dist/config/env.d.ts.map +1 -1
- package/dist/config/env.js +2 -0
- package/dist/config/env.js.map +1 -1
- package/dist/config/schema.d.ts +21 -21
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +5 -3
- package/dist/config/schema.js.map +1 -1
- package/dist/providers/pubmed/client.d.ts +2 -1
- package/dist/providers/pubmed/client.d.ts.map +1 -1
- package/dist/providers/pubmed/client.js +5 -9
- package/dist/providers/pubmed/client.js.map +1 -1
- package/dist/providers/pubmed/parser.d.ts.map +1 -1
- package/dist/providers/pubmed/parser.js +18 -1
- package/dist/providers/pubmed/parser.js.map +1 -1
- package/dist/providers/pubmed/provider.d.ts +6 -0
- package/dist/providers/pubmed/provider.d.ts.map +1 -1
- package/dist/providers/pubmed/provider.js +33 -19
- package/dist/providers/pubmed/provider.js.map +1 -1
- package/dist/providers/pubmed/translator.d.ts.map +1 -1
- package/dist/providers/pubmed/translator.js +17 -3
- package/dist/providers/pubmed/translator.js.map +1 -1
- package/dist/providers/pubmed/types.d.ts +2 -0
- package/dist/providers/pubmed/types.d.ts.map +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate the query template YAML string.
|
|
3
|
+
*
|
|
4
|
+
* @returns The YAML template string with comments
|
|
5
|
+
*/
|
|
6
|
+
export declare function generateQueryTemplate(): string;
|
|
7
|
+
/**
|
|
8
|
+
* Write the query template to a file or return it as a message.
|
|
9
|
+
*
|
|
10
|
+
* @param options - Output options
|
|
11
|
+
* @param options.output - File path to write to (if omitted, returns template in message)
|
|
12
|
+
* @param options.force - Whether to overwrite existing files
|
|
13
|
+
* @returns Result with success status and message
|
|
14
|
+
*/
|
|
15
|
+
export declare function writeQueryTemplate(options: {
|
|
16
|
+
output?: string;
|
|
17
|
+
force?: boolean;
|
|
18
|
+
}): Promise<{
|
|
19
|
+
success: boolean;
|
|
20
|
+
message: string;
|
|
21
|
+
}>;
|
|
22
|
+
//# sourceMappingURL=init.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/query/init.ts"],"names":[],"mappings":"AAkDA;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CA4BjD"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { access, writeFile } from "node:fs/promises";
|
|
2
|
+
const QUERY_TEMPLATE = `name: my_search
|
|
3
|
+
description: ""
|
|
4
|
+
|
|
5
|
+
query:
|
|
6
|
+
- field: title_abstract # title, abstract, title_abstract, author, keyword, all
|
|
7
|
+
terms:
|
|
8
|
+
keywords:
|
|
9
|
+
- "search term 1"
|
|
10
|
+
- "search term 2"
|
|
11
|
+
# mesh: # PubMed MeSH terms (optional)
|
|
12
|
+
# - "MeSH Heading"
|
|
13
|
+
operator: OR # How to combine terms within this block
|
|
14
|
+
|
|
15
|
+
# Add more blocks — blocks are AND'd together
|
|
16
|
+
# - field: title_abstract
|
|
17
|
+
# terms:
|
|
18
|
+
# keywords:
|
|
19
|
+
# - "another term"
|
|
20
|
+
# operator: OR
|
|
21
|
+
|
|
22
|
+
# filters: # Optional: apply to all databases
|
|
23
|
+
# year_from: 2020
|
|
24
|
+
# year_to: 2026
|
|
25
|
+
# language:
|
|
26
|
+
# - en
|
|
27
|
+
# publication_types:
|
|
28
|
+
# exclude:
|
|
29
|
+
# - "Review"
|
|
30
|
+
# - "Comment"
|
|
31
|
+
|
|
32
|
+
# overrides: # Optional: database-specific settings
|
|
33
|
+
# pubmed:
|
|
34
|
+
# filters:
|
|
35
|
+
# publication_types:
|
|
36
|
+
# exclude:
|
|
37
|
+
# - "Letter"
|
|
38
|
+
`;
|
|
39
|
+
function generateQueryTemplate() {
|
|
40
|
+
return QUERY_TEMPLATE;
|
|
41
|
+
}
|
|
42
|
+
async function writeQueryTemplate(options) {
|
|
43
|
+
const template = generateQueryTemplate();
|
|
44
|
+
if (!options.output) {
|
|
45
|
+
return { success: true, message: template };
|
|
46
|
+
}
|
|
47
|
+
if (!options.force) {
|
|
48
|
+
try {
|
|
49
|
+
await access(options.output);
|
|
50
|
+
return {
|
|
51
|
+
success: false,
|
|
52
|
+
message: `File already exists: ${options.output}. Use --force to overwrite.`
|
|
53
|
+
};
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
await writeFile(options.output, template, "utf-8");
|
|
58
|
+
return {
|
|
59
|
+
success: true,
|
|
60
|
+
message: `Template written to ${options.output}`
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
generateQueryTemplate,
|
|
65
|
+
writeQueryTemplate
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sources":["../../../../src/cli/commands/query/init.ts"],"sourcesContent":["/**\n * Query init command implementation.\n *\n * Generates a skeleton YAML query file with helpful comments.\n */\nimport { writeFile as fsWriteFile, access } from \"node:fs/promises\";\n\n/**\n * The YAML template string with comments preserved.\n * This is a raw string (not generated by a YAML library) so comments are kept.\n */\n// prettier-ignore\nconst QUERY_TEMPLATE =\n \"name: my_search\\n\" +\n \"description: \\\"\\\"\\n\" +\n \"\\n\" +\n \"query:\\n\" +\n \" - field: title_abstract # title, abstract, title_abstract, author, keyword, all\\n\" +\n \" terms:\\n\" +\n \" keywords:\\n\" +\n \" - \\\"search term 1\\\"\\n\" +\n \" - \\\"search term 2\\\"\\n\" +\n \" # mesh: # PubMed MeSH terms (optional)\\n\" +\n \" # - \\\"MeSH Heading\\\"\\n\" +\n \" operator: OR # How to combine terms within this block\\n\" +\n \"\\n\" +\n \" # Add more blocks — blocks are AND'd together\\n\" +\n \" # - field: title_abstract\\n\" +\n \" # terms:\\n\" +\n \" # keywords:\\n\" +\n \" # - \\\"another term\\\"\\n\" +\n \" # operator: OR\\n\" +\n \"\\n\" +\n \"# filters: # Optional: apply to all databases\\n\" +\n \"# year_from: 2020\\n\" +\n \"# year_to: 2026\\n\" +\n \"# language:\\n\" +\n \"# - en\\n\" +\n \"# publication_types:\\n\" +\n \"# exclude:\\n\" +\n \"# - \\\"Review\\\"\\n\" +\n \"# - \\\"Comment\\\"\\n\" +\n \"\\n\" +\n \"# overrides: # Optional: database-specific settings\\n\" +\n \"# pubmed:\\n\" +\n \"# filters:\\n\" +\n \"# publication_types:\\n\" +\n \"# exclude:\\n\" +\n \"# - \\\"Letter\\\"\\n\";\n\n/**\n * Generate the query template YAML string.\n *\n * @returns The YAML template string with comments\n */\nexport function generateQueryTemplate(): string {\n return QUERY_TEMPLATE;\n}\n\n/**\n * Write the query template to a file or return it as a message.\n *\n * @param options - Output options\n * @param options.output - File path to write to (if omitted, returns template in message)\n * @param options.force - Whether to overwrite existing files\n * @returns Result with success status and message\n */\nexport async function writeQueryTemplate(options: {\n output?: string;\n force?: boolean;\n}): Promise<{ success: boolean; message: string }> {\n const template = generateQueryTemplate();\n\n if (!options.output) {\n // No output file specified, return template as message\n return { success: true, message: template };\n }\n\n // Check if file exists (unless force is set)\n if (!options.force) {\n try {\n await access(options.output);\n // File exists and force is not set\n return {\n success: false,\n message: `File already exists: ${options.output}. Use --force to overwrite.`,\n };\n } catch {\n // File does not exist, proceed\n }\n }\n\n // Write to file\n await fsWriteFile(options.output, template, \"utf-8\");\n return {\n success: true,\n message: `Template written to ${options.output}`,\n };\n}\n"],"names":["fsWriteFile"],"mappings":";AAYA,MAAM,iBACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0CK,SAAS,wBAAgC;AAC9C,SAAO;AACT;AAUA,eAAsB,mBAAmB,SAGU;AACjD,QAAM,WAAW,sBAAA;AAEjB,MAAI,CAAC,QAAQ,QAAQ;AAEnB,WAAO,EAAE,SAAS,MAAM,SAAS,SAAA;AAAA,EACnC;AAGA,MAAI,CAAC,QAAQ,OAAO;AAClB,QAAI;AACF,YAAM,OAAO,QAAQ,MAAM;AAE3B,aAAO;AAAA,QACL,SAAS;AAAA,QACT,SAAS,wBAAwB,QAAQ,MAAM;AAAA,MAAA;AAAA,IAEnD,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,QAAMA,UAAY,QAAQ,QAAQ,UAAU,OAAO;AACnD,SAAO;AAAA,IACL,SAAS;AAAA,IACT,SAAS,uBAAuB,QAAQ,MAAM;AAAA,EAAA;AAElD;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resume-executor.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/resume-executor.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"resume-executor.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/resume-executor.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAiBpD;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAgBD;;GAEG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,EAC7B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,YAAY,UAAO,GAClB,OAAO,CAAC,qBAAqB,CAAC,CAmPhC"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { readFile, appendFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { isProviderError } from "../../providers/base/types.js";
|
|
3
4
|
import { loadSession, getResumableProviders, updateDatabaseStatus, updateSessionStatus } from "../../session/manager.js";
|
|
4
5
|
import { MultiProviderProgress } from "../utils/progress.js";
|
|
5
6
|
import { createProviderInstance } from "./search-executor.js";
|
|
7
|
+
import { buildFailureErrorMessage } from "./search-utils.js";
|
|
6
8
|
function isResumable(provider) {
|
|
7
9
|
return "resumeSearch" in provider && typeof provider.resumeSearch === "function";
|
|
8
10
|
}
|
|
@@ -45,13 +47,33 @@ async function executeResume(options, sessionsDir, config, showProgress = true)
|
|
|
45
47
|
if (!dbStatus) continue;
|
|
46
48
|
try {
|
|
47
49
|
const provider = createProviderInstance(providerName, config);
|
|
50
|
+
if (provider === null) {
|
|
51
|
+
progress?.fail(providerName, "Provider configuration missing");
|
|
52
|
+
const configError = `${providerName}: provider configuration incomplete. See warning above for details.`;
|
|
53
|
+
results[providerName] = { hits: 0, retrieved: 0, error: configError };
|
|
54
|
+
await updateDatabaseStatus(
|
|
55
|
+
options.sessionId,
|
|
56
|
+
providerName,
|
|
57
|
+
{
|
|
58
|
+
status: "failed",
|
|
59
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
60
|
+
error: {
|
|
61
|
+
code: "CONFIG_ERROR",
|
|
62
|
+
message: configError,
|
|
63
|
+
retryable: false
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
sessionsDir
|
|
67
|
+
);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
48
70
|
const queryPath = join(sessionsDir, options.sessionId, dbStatus.files.query);
|
|
49
71
|
let nativeQuery;
|
|
50
72
|
try {
|
|
51
73
|
nativeQuery = await readFile(queryPath, "utf-8");
|
|
52
74
|
} catch {
|
|
53
75
|
progress?.fail(providerName, "Query file not found");
|
|
54
|
-
results[providerName] = { hits: 0, retrieved: 0 };
|
|
76
|
+
results[providerName] = { hits: 0, retrieved: 0, error: "Query file not found" };
|
|
55
77
|
continue;
|
|
56
78
|
}
|
|
57
79
|
const translatedQuery = {
|
|
@@ -113,7 +135,7 @@ async function executeResume(options, sessionsDir, config, showProgress = true)
|
|
|
113
135
|
results[providerName] = { hits: totalHits || retrievedCount, retrieved: retrievedCount };
|
|
114
136
|
successCount++;
|
|
115
137
|
} catch (error) {
|
|
116
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
138
|
+
const errorMessage = error instanceof Error ? error.message : isProviderError(error) ? error.message : String(error);
|
|
117
139
|
progress?.fail(providerName, errorMessage);
|
|
118
140
|
await updateDatabaseStatus(
|
|
119
141
|
options.sessionId,
|
|
@@ -129,13 +151,13 @@ async function executeResume(options, sessionsDir, config, showProgress = true)
|
|
|
129
151
|
},
|
|
130
152
|
sessionsDir
|
|
131
153
|
);
|
|
132
|
-
results[providerName] = { hits: 0, retrieved: 0 };
|
|
154
|
+
results[providerName] = { hits: 0, retrieved: 0, error: errorMessage };
|
|
133
155
|
}
|
|
134
156
|
}
|
|
135
157
|
progress?.stop();
|
|
136
158
|
const anyFailed = resumableProviders.some((p) => {
|
|
137
159
|
const r = results[p.provider];
|
|
138
|
-
return r && r.
|
|
160
|
+
return r && r.error !== void 0;
|
|
139
161
|
});
|
|
140
162
|
const anySucceeded = resumableProviders.some((p) => {
|
|
141
163
|
const r = results[p.provider];
|
|
@@ -155,7 +177,7 @@ async function executeResume(options, sessionsDir, config, showProgress = true)
|
|
|
155
177
|
success: false,
|
|
156
178
|
resumed: successCount,
|
|
157
179
|
results,
|
|
158
|
-
error:
|
|
180
|
+
error: buildFailureErrorMessage(results)
|
|
159
181
|
};
|
|
160
182
|
}
|
|
161
183
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resume-executor.js","sources":["../../../src/cli/commands/resume-executor.ts"],"sourcesContent":["/**\n * Resume executor for CLI resume command.\n *\n * Handles resuming interrupted search sessions by continuing\n * from where providers left off or retrying failed providers.\n */\nimport { readFile, appendFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { ResumeCommandOptions } from './resume.js';\nimport type { Config } from '../../config/index.js';\nimport type {\n Provider,\n TranslatedQuery,\n SearchState,\n} from '../../providers/base/types.js';\nimport {\n loadSession,\n updateDatabaseStatus,\n updateSessionStatus,\n getResumableProviders,\n} from '../../session/manager.js';\nimport { MultiProviderProgress } from '../utils/progress.js';\nimport { createProviderInstance } from './search-executor.js';\n\n/**\n * Result of a resume execution.\n */\nexport interface ResumeExecutionResult {\n success: boolean;\n resumed: number;\n results?: Record<string, { hits: number; retrieved: number }>;\n error?: string;\n}\n\n/**\n * Interface for providers that support resuming.\n */\ninterface ResumableProviderInstance extends Provider {\n resumeSearch(state: SearchState): AsyncIterable<import('../../providers/base/types.js').Article>;\n}\n\n/**\n * Check if a provider instance supports resuming.\n */\nfunction isResumable(provider: Provider): provider is ResumableProviderInstance {\n return 'resumeSearch' in provider && typeof (provider as any).resumeSearch === 'function';\n}\n\n/**\n * Execute resume for interrupted sessions.\n */\nexport async function executeResume(\n options: ResumeCommandOptions,\n sessionsDir: string,\n config: Config,\n showProgress = true\n): Promise<ResumeExecutionResult> {\n // Load session\n let session;\n try {\n session = await loadSession(options.sessionId, sessionsDir);\n } catch (error) {\n return {\n success: false,\n resumed: 0,\n error: `Failed to load session: ${error instanceof Error ? error.message : error}`,\n };\n }\n\n // Get resumable providers\n let resumableProviders = getResumableProviders(session);\n\n // Filter by specific providers if requested\n if (options.providers && options.providers.length > 0) {\n resumableProviders = resumableProviders.filter((r) =>\n options.providers!.includes(r.provider)\n );\n }\n\n // Filter to only retry strategies if retryFailed is true\n if (options.retryFailed) {\n resumableProviders = resumableProviders.filter((r) => r.strategy === 'retry');\n }\n\n if (resumableProviders.length === 0) {\n return {\n success: true,\n resumed: 0,\n error: 'No providers to resume',\n };\n }\n\n const results: Record<string, { hits: number; retrieved: number }> = {};\n\n // Create progress display if enabled\n let progress: MultiProviderProgress | undefined;\n if (showProgress && process.stdout.isTTY) {\n progress = new MultiProviderProgress(resumableProviders.map((p) => p.provider));\n }\n\n let successCount = 0;\n\n // Resume each provider\n for (const resumable of resumableProviders) {\n const providerName = resumable.provider;\n const dbStatus = session.databases[providerName];\n\n if (!dbStatus) continue;\n\n try {\n // Create provider instance\n const provider = createProviderInstance(providerName, config);\n\n // Build the translated query from stored query file\n const queryPath = join(sessionsDir, options.sessionId, dbStatus.files.query);\n let nativeQuery: string;\n try {\n nativeQuery = await readFile(queryPath, 'utf-8');\n } catch {\n // If query file doesn't exist, skip this provider\n progress?.fail(providerName, 'Query file not found');\n results[providerName] = { hits: 0, retrieved: 0 };\n continue;\n }\n\n const translatedQuery: TranslatedQuery = {\n native: nativeQuery.trim(),\n provider: providerName,\n };\n\n // Update database status to in_progress\n await updateDatabaseStatus(\n options.sessionId,\n providerName,\n {\n status: 'in_progress',\n startedAt: new Date().toISOString(),\n },\n sessionsDir\n );\n\n // Prepare results file path\n const resultsPath = join(sessionsDir, options.sessionId, dbStatus.files.results);\n\n let retrievedCount = dbStatus.retrievedCount ?? 0;\n const totalHits = dbStatus.totalHits ?? 0;\n\n progress?.update(providerName, retrievedCount, totalHits || 100, 'in_progress');\n\n // Determine how to resume\n if (resumable.strategy === 'continue' && isResumable(provider)) {\n // Build SearchState for continuing\n const searchState: SearchState = {\n provider: providerName,\n query: translatedQuery,\n totalResults: totalHits,\n retrievedCount,\n lastUpdated: new Date(),\n providerState: dbStatus.pagination\n ? {\n cursor: dbStatus.pagination.cursor,\n pageNumber: dbStatus.pagination.pageNumber,\n }\n : undefined,\n };\n\n // Resume search\n for await (const article of provider.resumeSearch(searchState)) {\n retrievedCount++;\n\n // Write article to JSONL file\n await appendFile(resultsPath, JSON.stringify(article) + '\\n', 'utf-8');\n\n progress?.update(providerName, retrievedCount, totalHits || retrievedCount, 'in_progress');\n }\n } else {\n // Fresh start or retry - use regular search\n const searchOptions = {\n maxResults: config.providers[providerName].max_results,\n };\n\n for await (const article of provider.search(translatedQuery, searchOptions)) {\n retrievedCount++;\n\n // Write article to JSONL file\n await appendFile(resultsPath, JSON.stringify(article) + '\\n', 'utf-8');\n\n progress?.update(providerName, retrievedCount, totalHits || retrievedCount * 2, 'in_progress');\n }\n }\n\n // Mark as completed\n progress?.complete(providerName);\n\n // Update database status\n await updateDatabaseStatus(\n options.sessionId,\n providerName,\n {\n status: 'completed',\n completedAt: new Date().toISOString(),\n totalHits: totalHits || retrievedCount,\n retrievedCount,\n },\n sessionsDir\n );\n\n results[providerName] = { hits: totalHits || retrievedCount, retrieved: retrievedCount };\n successCount++;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n progress?.fail(providerName, errorMessage);\n\n // Update database status with error\n await updateDatabaseStatus(\n options.sessionId,\n providerName,\n {\n status: 'failed',\n completedAt: new Date().toISOString(),\n error: {\n code: 'RESUME_ERROR',\n message: errorMessage,\n retryable: true,\n },\n },\n sessionsDir\n );\n\n results[providerName] = { hits: 0, retrieved: 0 };\n }\n }\n\n // Stop progress display\n progress?.stop();\n\n // Determine overall session status\n const anyFailed = resumableProviders.some((p) => {\n const r = results[p.provider];\n return r && r.retrieved === 0 && r.hits === 0;\n });\n const anySucceeded = resumableProviders.some((p) => {\n const r = results[p.provider];\n return r && r.retrieved > 0;\n });\n\n let sessionStatus: 'completed' | 'partial' | 'failed';\n if (!anyFailed) {\n sessionStatus = 'completed';\n } else if (anySucceeded) {\n sessionStatus = 'partial';\n } else {\n sessionStatus = 'failed';\n }\n\n // Update session status\n await updateSessionStatus(options.sessionId, sessionStatus, sessionsDir);\n\n if (sessionStatus === 'failed') {\n return {\n success: false,\n resumed: successCount,\n results,\n error: 'All providers failed',\n };\n }\n\n return {\n success: true,\n resumed: successCount,\n results,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resume-executor.js","sources":["../../../src/cli/commands/resume-executor.ts"],"sourcesContent":["/**\n * Resume executor for CLI resume command.\n *\n * Handles resuming interrupted search sessions by continuing\n * from where providers left off or retrying failed providers.\n */\nimport { readFile, appendFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { ResumeCommandOptions } from './resume.js';\nimport type { Config } from '../../config/index.js';\nimport type {\n Provider,\n TranslatedQuery,\n SearchState,\n} from '../../providers/base/types.js';\nimport { isProviderError } from '../../providers/base/types.js';\nimport {\n loadSession,\n updateDatabaseStatus,\n updateSessionStatus,\n getResumableProviders,\n} from '../../session/manager.js';\nimport { MultiProviderProgress } from '../utils/progress.js';\nimport { createProviderInstance } from './search-executor.js';\nimport { buildFailureErrorMessage } from './search-utils.js';\n\n/**\n * Result of a resume execution.\n */\nexport interface ResumeExecutionResult {\n success: boolean;\n resumed: number;\n results?: Record<string, { hits: number; retrieved: number; error?: string }>;\n error?: string;\n}\n\n/**\n * Interface for providers that support resuming.\n */\ninterface ResumableProviderInstance extends Provider {\n resumeSearch(state: SearchState): AsyncIterable<import('../../providers/base/types.js').Article>;\n}\n\n/**\n * Check if a provider instance supports resuming.\n */\nfunction isResumable(provider: Provider): provider is ResumableProviderInstance {\n return 'resumeSearch' in provider && typeof (provider as any).resumeSearch === 'function';\n}\n\n/**\n * Execute resume for interrupted sessions.\n */\nexport async function executeResume(\n options: ResumeCommandOptions,\n sessionsDir: string,\n config: Config,\n showProgress = true\n): Promise<ResumeExecutionResult> {\n // Load session\n let session;\n try {\n session = await loadSession(options.sessionId, sessionsDir);\n } catch (error) {\n return {\n success: false,\n resumed: 0,\n error: `Failed to load session: ${error instanceof Error ? error.message : error}`,\n };\n }\n\n // Get resumable providers\n let resumableProviders = getResumableProviders(session);\n\n // Filter by specific providers if requested\n if (options.providers && options.providers.length > 0) {\n resumableProviders = resumableProviders.filter((r) =>\n options.providers!.includes(r.provider)\n );\n }\n\n // Filter to only retry strategies if retryFailed is true\n if (options.retryFailed) {\n resumableProviders = resumableProviders.filter((r) => r.strategy === 'retry');\n }\n\n if (resumableProviders.length === 0) {\n return {\n success: true,\n resumed: 0,\n error: 'No providers to resume',\n };\n }\n\n const results: Record<string, { hits: number; retrieved: number; error?: string }> = {};\n\n // Create progress display if enabled\n let progress: MultiProviderProgress | undefined;\n if (showProgress && process.stdout.isTTY) {\n progress = new MultiProviderProgress(resumableProviders.map((p) => p.provider));\n }\n\n let successCount = 0;\n\n // Resume each provider\n for (const resumable of resumableProviders) {\n const providerName = resumable.provider;\n const dbStatus = session.databases[providerName];\n\n if (!dbStatus) continue;\n\n try {\n // Create provider instance\n const provider = createProviderInstance(providerName, config);\n\n // Skip provider if it could not be created (e.g. missing configuration)\n if (provider === null) {\n progress?.fail(providerName, 'Provider configuration missing');\n const configError = `${providerName}: provider configuration incomplete. See warning above for details.`;\n results[providerName] = { hits: 0, retrieved: 0, error: configError };\n await updateDatabaseStatus(\n options.sessionId,\n providerName,\n {\n status: 'failed',\n completedAt: new Date().toISOString(),\n error: {\n code: 'CONFIG_ERROR',\n message: configError,\n retryable: false,\n },\n },\n sessionsDir\n );\n continue;\n }\n\n // Build the translated query from stored query file\n const queryPath = join(sessionsDir, options.sessionId, dbStatus.files.query);\n let nativeQuery: string;\n try {\n nativeQuery = await readFile(queryPath, 'utf-8');\n } catch {\n // If query file doesn't exist, skip this provider\n progress?.fail(providerName, 'Query file not found');\n results[providerName] = { hits: 0, retrieved: 0, error: 'Query file not found' };\n continue;\n }\n\n const translatedQuery: TranslatedQuery = {\n native: nativeQuery.trim(),\n provider: providerName,\n };\n\n // Update database status to in_progress\n await updateDatabaseStatus(\n options.sessionId,\n providerName,\n {\n status: 'in_progress',\n startedAt: new Date().toISOString(),\n },\n sessionsDir\n );\n\n // Prepare results file path\n const resultsPath = join(sessionsDir, options.sessionId, dbStatus.files.results);\n\n let retrievedCount = dbStatus.retrievedCount ?? 0;\n const totalHits = dbStatus.totalHits ?? 0;\n\n progress?.update(providerName, retrievedCount, totalHits || 100, 'in_progress');\n\n // Determine how to resume\n if (resumable.strategy === 'continue' && isResumable(provider)) {\n // Build SearchState for continuing\n const searchState: SearchState = {\n provider: providerName,\n query: translatedQuery,\n totalResults: totalHits,\n retrievedCount,\n lastUpdated: new Date(),\n providerState: dbStatus.pagination\n ? {\n cursor: dbStatus.pagination.cursor,\n pageNumber: dbStatus.pagination.pageNumber,\n }\n : undefined,\n };\n\n // Resume search\n for await (const article of provider.resumeSearch(searchState)) {\n retrievedCount++;\n\n // Write article to JSONL file\n await appendFile(resultsPath, JSON.stringify(article) + '\\n', 'utf-8');\n\n progress?.update(providerName, retrievedCount, totalHits || retrievedCount, 'in_progress');\n }\n } else {\n // Fresh start or retry - use regular search\n const searchOptions = {\n maxResults: config.providers[providerName].max_results,\n };\n\n for await (const article of provider.search(translatedQuery, searchOptions)) {\n retrievedCount++;\n\n // Write article to JSONL file\n await appendFile(resultsPath, JSON.stringify(article) + '\\n', 'utf-8');\n\n progress?.update(providerName, retrievedCount, totalHits || retrievedCount * 2, 'in_progress');\n }\n }\n\n // Mark as completed\n progress?.complete(providerName);\n\n // Update database status\n await updateDatabaseStatus(\n options.sessionId,\n providerName,\n {\n status: 'completed',\n completedAt: new Date().toISOString(),\n totalHits: totalHits || retrievedCount,\n retrievedCount,\n },\n sessionsDir\n );\n\n results[providerName] = { hits: totalHits || retrievedCount, retrieved: retrievedCount };\n successCount++;\n } catch (error) {\n const errorMessage = error instanceof Error\n ? error.message\n : isProviderError(error)\n ? error.message\n : String(error);\n\n progress?.fail(providerName, errorMessage);\n\n // Update database status with error\n await updateDatabaseStatus(\n options.sessionId,\n providerName,\n {\n status: 'failed',\n completedAt: new Date().toISOString(),\n error: {\n code: 'RESUME_ERROR',\n message: errorMessage,\n retryable: true,\n },\n },\n sessionsDir\n );\n\n results[providerName] = { hits: 0, retrieved: 0, error: errorMessage };\n }\n }\n\n // Stop progress display\n progress?.stop();\n\n // Determine overall session status\n const anyFailed = resumableProviders.some((p) => {\n const r = results[p.provider];\n return r && r.error !== undefined;\n });\n const anySucceeded = resumableProviders.some((p) => {\n const r = results[p.provider];\n return r && r.retrieved > 0;\n });\n\n let sessionStatus: 'completed' | 'partial' | 'failed';\n if (!anyFailed) {\n sessionStatus = 'completed';\n } else if (anySucceeded) {\n sessionStatus = 'partial';\n } else {\n sessionStatus = 'failed';\n }\n\n // Update session status\n await updateSessionStatus(options.sessionId, sessionStatus, sessionsDir);\n\n if (sessionStatus === 'failed') {\n return {\n success: false,\n resumed: successCount,\n results,\n error: buildFailureErrorMessage(results),\n };\n }\n\n return {\n success: true,\n resumed: successCount,\n results,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -11,6 +11,8 @@ export interface SearchExecutionResult {
|
|
|
11
11
|
results?: Record<string, {
|
|
12
12
|
hits: number;
|
|
13
13
|
retrieved: number;
|
|
14
|
+
error?: string;
|
|
15
|
+
warnings?: string[];
|
|
14
16
|
}>;
|
|
15
17
|
error?: string;
|
|
16
18
|
autoRegisterResult?: RegistrationRecord;
|
|
@@ -18,7 +20,7 @@ export interface SearchExecutionResult {
|
|
|
18
20
|
/**
|
|
19
21
|
* Create a provider instance for the given provider name.
|
|
20
22
|
*/
|
|
21
|
-
export declare function createProviderInstance(name: ProviderName, config: Config): Provider;
|
|
23
|
+
export declare function createProviderInstance(name: ProviderName, config: Config): Provider | null;
|
|
22
24
|
/**
|
|
23
25
|
* Execute a search across multiple providers.
|
|
24
26
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-executor.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/search-executor.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAEV,QAAQ,EACR,YAAY,EAEb,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"search-executor.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/search-executor.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAEV,QAAQ,EACR,YAAY,EAEb,MAAM,+BAA+B,CAAC;AAwBvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAGrE;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACnG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;CACzC;AAOD;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,MAAM,GACb,QAAQ,GAAG,IAAI,CA2DjB;AAyCD;;GAEG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,EAC7B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,YAAY,UAAO,GAClB,OAAO,CAAC,qBAAqB,CAAC,CA2ShC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFile, writeFile, appendFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
|
+
import { isProviderError } from "../../providers/base/types.js";
|
|
4
5
|
import { parseQueryString } from "../../query/parser.js";
|
|
5
6
|
import "../../query/validator.js";
|
|
6
7
|
import { createSession, updateDatabaseStatus, updateSessionStatus } from "../../session/manager.js";
|
|
@@ -15,6 +16,8 @@ import { translateQuery as translateQuery$1 } from "../../providers/arxiv/transl
|
|
|
15
16
|
import { translateQuery } from "../../providers/scopus/translator.js";
|
|
16
17
|
import { stringify } from "yaml";
|
|
17
18
|
import { registerArticles, saveRegistrationRecord } from "../../integration/register.js";
|
|
19
|
+
import { buildFailureErrorMessage } from "./search-utils.js";
|
|
20
|
+
import { getConfigDir } from "../../config/paths.js";
|
|
18
21
|
import { checkRefAvailable } from "../../integration/ref-cli.js";
|
|
19
22
|
const IMPLEMENTED_PROVIDERS = ["pubmed", "eric", "arxiv", "scopus"];
|
|
20
23
|
function createProviderInstance(name, config) {
|
|
@@ -22,8 +25,11 @@ function createProviderInstance(name, config) {
|
|
|
22
25
|
switch (name) {
|
|
23
26
|
case "pubmed": {
|
|
24
27
|
if (!providerConfig.email) {
|
|
28
|
+
const configPath = getConfigDir();
|
|
25
29
|
console.warn(
|
|
26
|
-
|
|
30
|
+
`Warning: No email configured for PubMed.
|
|
31
|
+
→ Edit ${configPath}/config.toml and set providers.pubmed.email
|
|
32
|
+
→ Or run: search-hub config providers.pubmed.email "your@email.com"`
|
|
27
33
|
);
|
|
28
34
|
}
|
|
29
35
|
const pubmedOpts = {
|
|
@@ -50,8 +56,16 @@ function createProviderInstance(name, config) {
|
|
|
50
56
|
retries: providerConfig.retries
|
|
51
57
|
});
|
|
52
58
|
case "scopus": {
|
|
59
|
+
if (!providerConfig.api_key) {
|
|
60
|
+
console.warn(
|
|
61
|
+
`Warning: Scopus requires an API key. Set providers.scopus.api_key in config.
|
|
62
|
+
→ Get an API key at https://dev.elsevier.com/
|
|
63
|
+
→ Run: search-hub config providers.scopus.api_key "your-key"`
|
|
64
|
+
);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
53
67
|
const scopusOpts = {
|
|
54
|
-
apiKey: providerConfig.api_key
|
|
68
|
+
apiKey: providerConfig.api_key,
|
|
55
69
|
rateLimit: providerConfig.rate_limit,
|
|
56
70
|
timeout: providerConfig.timeout,
|
|
57
71
|
retries: providerConfig.retries
|
|
@@ -165,6 +179,25 @@ async function executeSearch(options, sessionsDir, config, showProgress = true)
|
|
|
165
179
|
for (const providerName of providers) {
|
|
166
180
|
try {
|
|
167
181
|
const provider = createProviderInstance(providerName, config);
|
|
182
|
+
if (provider === null) {
|
|
183
|
+
const configError = `${providerName}: provider configuration incomplete. See warning above for details.`;
|
|
184
|
+
results[providerName] = { hits: 0, retrieved: 0, error: configError };
|
|
185
|
+
await updateDatabaseStatus(
|
|
186
|
+
sessionId,
|
|
187
|
+
providerName,
|
|
188
|
+
{
|
|
189
|
+
status: "failed",
|
|
190
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
191
|
+
error: {
|
|
192
|
+
code: "CONFIG_ERROR",
|
|
193
|
+
message: configError,
|
|
194
|
+
retryable: false
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
sessionsDir
|
|
198
|
+
);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
168
201
|
let translatedQuery;
|
|
169
202
|
if (options.directQuery && options.providers?.length === 1) {
|
|
170
203
|
translatedQuery = {
|
|
@@ -217,9 +250,10 @@ async function executeSearch(options, sessionsDir, config, showProgress = true)
|
|
|
217
250
|
},
|
|
218
251
|
sessionsDir
|
|
219
252
|
);
|
|
220
|
-
|
|
253
|
+
const providerWarnings = "getWarnings" in provider && typeof provider.getWarnings === "function" ? provider.getWarnings() : void 0;
|
|
254
|
+
results[providerName] = { hits: totalHits, retrieved: retrievedCount, ...providerWarnings && providerWarnings.length > 0 && { warnings: providerWarnings } };
|
|
221
255
|
} catch (error) {
|
|
222
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
256
|
+
const errorMessage = error instanceof Error ? error.message : isProviderError(error) ? error.message : String(error);
|
|
223
257
|
progress?.fail(providerName, errorMessage);
|
|
224
258
|
await updateDatabaseStatus(
|
|
225
259
|
sessionId,
|
|
@@ -235,13 +269,13 @@ async function executeSearch(options, sessionsDir, config, showProgress = true)
|
|
|
235
269
|
},
|
|
236
270
|
sessionsDir
|
|
237
271
|
);
|
|
238
|
-
results[providerName] = { hits: 0, retrieved: 0 };
|
|
272
|
+
results[providerName] = { hits: 0, retrieved: 0, error: errorMessage };
|
|
239
273
|
}
|
|
240
274
|
}
|
|
241
275
|
progress?.stop();
|
|
242
276
|
const anyFailed = providers.some((p) => {
|
|
243
277
|
const r = results[p];
|
|
244
|
-
return r && r.
|
|
278
|
+
return r && r.error !== void 0;
|
|
245
279
|
});
|
|
246
280
|
const anySucceeded = providers.some((p) => {
|
|
247
281
|
const r = results[p];
|
|
@@ -261,7 +295,7 @@ async function executeSearch(options, sessionsDir, config, showProgress = true)
|
|
|
261
295
|
success: false,
|
|
262
296
|
sessionId,
|
|
263
297
|
results,
|
|
264
|
-
error:
|
|
298
|
+
error: buildFailureErrorMessage(results)
|
|
265
299
|
};
|
|
266
300
|
}
|
|
267
301
|
let autoRegisterResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-executor.js","sources":["../../../src/cli/commands/search-executor.ts"],"sourcesContent":["/**\n * Search executor for CLI search command.\n *\n * Handles the actual execution of searches across multiple providers,\n * including session creation, progress display, and result storage.\n */\nimport { readFile, writeFile, appendFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { createHash } from 'node:crypto';\nimport type { SearchCommandOptions } from './search.js';\nimport type { Config } from '../../config/index.js';\nimport type {\n Article,\n Provider,\n ProviderName,\n TranslatedQuery,\n} from '../../providers/base/types.js';\nimport type { QueryAST } from '../../query/types.js';\nimport { parseQueryString } from '../../query/index.js';\nimport {\n createSession,\n updateDatabaseStatus,\n updateSessionStatus,\n} from '../../session/manager.js';\nimport { MultiProviderProgress } from '../utils/progress.js';\nimport { PubMedProvider } from '../../providers/pubmed/provider.js';\nimport type { PubMedConfig } from '../../providers/pubmed/types.js';\nimport { ERICProvider } from '../../providers/eric/provider.js';\nimport { ArxivProvider } from '../../providers/arxiv/provider.js';\nimport { ScopusProvider } from '../../providers/scopus/provider.js';\nimport type { ScopusConfig } from '../../providers/scopus/types.js';\nimport { translateQuery as translatePubmed } from '../../providers/pubmed/translator.js';\nimport { translateQuery as translateEric } from '../../providers/eric/translator.js';\nimport { translateQuery as translateArxiv } from '../../providers/arxiv/translator.js';\nimport { translateQuery as translateScopus } from '../../providers/scopus/translator.js';\nimport { stringify as stringifyYaml } from 'yaml';\nimport { registerArticles, saveRegistrationRecord } from '../../integration/register.js';\nimport type { RegistrationRecord } from '../../integration/types.js';\nimport { checkRefAvailable } from '../../integration/ref-cli.js';\n\n/**\n * Result of a search execution.\n */\nexport interface SearchExecutionResult {\n success: boolean;\n sessionId?: string;\n results?: Record<string, { hits: number; retrieved: number }>;\n error?: string;\n autoRegisterResult?: RegistrationRecord;\n}\n\n/**\n * Available providers that are implemented.\n */\nconst IMPLEMENTED_PROVIDERS: ProviderName[] = ['pubmed', 'eric', 'arxiv', 'scopus'];\n\n/**\n * Create a provider instance for the given provider name.\n */\nexport function createProviderInstance(\n name: ProviderName,\n config: Config\n): Provider {\n const providerConfig = config.providers[name];\n\n switch (name) {\n case 'pubmed': {\n if (!providerConfig.email) {\n console.warn(\n 'Warning: No email configured for PubMed. Set providers.pubmed.email in config.'\n );\n }\n const pubmedOpts: PubMedConfig = {\n email: providerConfig.email ?? 'search-hub@example.com',\n rateLimit: providerConfig.rate_limit,\n timeout: providerConfig.timeout,\n retries: providerConfig.retries,\n };\n if (providerConfig.api_key) {\n pubmedOpts.apiKey = providerConfig.api_key;\n }\n return new PubMedProvider(pubmedOpts);\n }\n case 'eric':\n return new ERICProvider({\n rateLimit: providerConfig.rate_limit,\n timeout: providerConfig.timeout,\n retries: providerConfig.retries,\n });\n case 'arxiv':\n return new ArxivProvider({\n rateLimit: providerConfig.rate_limit,\n timeout: providerConfig.timeout,\n retries: providerConfig.retries,\n });\n case 'scopus': {\n const scopusOpts: ScopusConfig = {\n apiKey: providerConfig.api_key ?? '',\n rateLimit: providerConfig.rate_limit,\n timeout: providerConfig.timeout,\n retries: providerConfig.retries,\n };\n if (providerConfig.inst_token) {\n scopusOpts.instToken = providerConfig.inst_token;\n }\n return new ScopusProvider(scopusOpts);\n }\n default:\n throw new Error(`Provider '${name}' is not implemented`);\n }\n}\n\n/**\n * Translate a query AST for a specific provider.\n */\nfunction translateQueryForProvider(\n ast: QueryAST,\n provider: ProviderName\n): TranslatedQuery {\n switch (provider) {\n case 'pubmed':\n return translatePubmed(ast);\n case 'eric':\n return translateEric(ast);\n case 'arxiv':\n return translateArxiv(ast);\n case 'scopus':\n return translateScopus(ast);\n default:\n throw new Error(`No translator for provider '${provider}'`);\n }\n}\n\n/**\n * Get enabled providers from config, optionally filtered by user selection.\n */\nfunction getEnabledProviders(\n config: Config,\n requestedProviders?: ProviderName[]\n): ProviderName[] {\n const enabledInConfig = IMPLEMENTED_PROVIDERS.filter(\n (name) => config.providers[name].enabled\n );\n\n if (requestedProviders && requestedProviders.length > 0) {\n return requestedProviders.filter((p) => enabledInConfig.includes(p));\n }\n\n return enabledInConfig;\n}\n\n/**\n * Execute a search across multiple providers.\n */\nexport async function executeSearch(\n options: SearchCommandOptions,\n sessionsDir: string,\n config: Config,\n showProgress = true\n): Promise<SearchExecutionResult> {\n let ast: QueryAST | undefined;\n let queryContent: string;\n let queryFile: string;\n\n // Handle direct query mode\n if (options.directQuery && options.providers && options.providers.length === 1) {\n queryFile = 'direct-query';\n\n // For direct query, we create a minimal AST structure\n ast = {\n name: options.sessionName ?? 'direct-query',\n blocks: [\n {\n field: 'all',\n terms: { keywords: [options.directQuery] },\n operator: 'AND',\n },\n ],\n filters: {},\n overrides: {},\n };\n\n // Generate YAML safely using yaml library to handle special characters\n queryContent = stringifyYaml({\n name: ast.name,\n blocks: ast.blocks,\n filters: ast.filters,\n });\n } else if (options.queryFile) {\n // Parse query file\n try {\n queryContent = await readFile(options.queryFile, 'utf-8');\n ast = parseQueryString(queryContent);\n queryFile = options.queryFile;\n } catch (error) {\n return {\n success: false,\n error: `Failed to parse query file: ${error instanceof Error ? error.message : error}`,\n };\n }\n } else {\n return {\n success: false,\n error: 'Either queryFile or directQuery with provider is required',\n };\n }\n\n // Determine which providers to use\n const providers = getEnabledProviders(config, options.providers);\n\n if (providers.length === 0) {\n return {\n success: false,\n error: 'No providers enabled or selected',\n };\n }\n\n // Create query hash\n const queryHash = createHash('sha256').update(queryContent).digest('hex').slice(0, 8);\n\n // Create session\n let session;\n try {\n const sessionOpts: Parameters<typeof createSession>[0] = {\n name: options.sessionName ?? ast.name,\n queryFile,\n queryContent,\n queryHash,\n targets: providers,\n sessionsDir,\n };\n if (ast.description) {\n sessionOpts.description = ast.description;\n }\n session = await createSession(sessionOpts);\n } catch (error) {\n return {\n success: false,\n error: `Failed to create session: ${error instanceof Error ? error.message : error}`,\n };\n }\n\n const sessionId = session.id;\n const results: Record<string, { hits: number; retrieved: number }> = {};\n\n // Create progress display if enabled\n let progress: MultiProviderProgress | undefined;\n if (showProgress && process.stdout.isTTY) {\n progress = new MultiProviderProgress(providers);\n }\n\n // Execute search for each provider\n for (const providerName of providers) {\n try {\n // Create provider instance\n const provider = createProviderInstance(providerName, config);\n\n // Translate query\n let translatedQuery: TranslatedQuery;\n if (options.directQuery && options.providers?.length === 1) {\n // For direct query, use the native query string directly\n translatedQuery = {\n native: options.directQuery,\n provider: providerName,\n };\n } else {\n translatedQuery = translateQueryForProvider(ast, providerName);\n }\n\n // Write translated query to session\n const queryPath = join(sessionsDir, sessionId, `${providerName}_query.txt`);\n await writeFile(queryPath, translatedQuery.native, 'utf-8');\n\n // Update database status to in_progress\n await updateDatabaseStatus(\n sessionId,\n providerName,\n {\n status: 'in_progress',\n startedAt: new Date().toISOString(),\n },\n sessionsDir\n );\n\n // Prepare results file path\n const resultsPath = join(sessionsDir, sessionId, `${providerName}_results.jsonl`);\n\n // Execute search\n let retrievedCount = 0;\n let totalHits = 0;\n\n progress?.update(providerName, 0, 0, 'in_progress');\n\n const searchOptions = {\n maxResults: options.maxResults ?? config.providers[providerName].max_results,\n };\n\n for await (const article of provider.search(translatedQuery, searchOptions)) {\n retrievedCount++;\n\n // Write article to JSONL file\n await appendFile(resultsPath, JSON.stringify(article) + '\\n', 'utf-8');\n\n // Update progress (estimate total from first batch)\n if (totalHits === 0) {\n // Estimate total - this is provider-dependent, we'll use retrieved count as minimum\n totalHits = Math.max(retrievedCount * 10, 100);\n }\n progress?.update(providerName, retrievedCount, totalHits, 'in_progress');\n }\n\n // Update final totals\n totalHits = retrievedCount; // Use actual count as final total\n\n // Mark as completed\n progress?.complete(providerName);\n\n // Update database status\n await updateDatabaseStatus(\n sessionId,\n providerName,\n {\n status: 'completed',\n completedAt: new Date().toISOString(),\n totalHits,\n retrievedCount,\n files: {\n query: `${providerName}_query.txt`,\n results: `${providerName}_results.jsonl`,\n },\n },\n sessionsDir\n );\n\n results[providerName] = { hits: totalHits, retrieved: retrievedCount };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n progress?.fail(providerName, errorMessage);\n\n // Update database status with error\n await updateDatabaseStatus(\n sessionId,\n providerName,\n {\n status: 'failed',\n completedAt: new Date().toISOString(),\n error: {\n code: 'SEARCH_ERROR',\n message: errorMessage,\n retryable: true,\n },\n },\n sessionsDir\n );\n\n results[providerName] = { hits: 0, retrieved: 0 };\n }\n }\n\n // Stop progress display\n progress?.stop();\n\n // Determine overall session status\n const anyFailed = providers.some((p) => {\n const r = results[p];\n return r && r.retrieved === 0 && r.hits === 0;\n });\n const anySucceeded = providers.some((p) => {\n const r = results[p];\n return r && r.retrieved > 0;\n });\n\n let sessionStatus: 'completed' | 'partial' | 'failed';\n if (!anyFailed) {\n sessionStatus = 'completed';\n } else if (anySucceeded) {\n sessionStatus = 'partial';\n } else {\n sessionStatus = 'failed';\n }\n\n // Update session status\n await updateSessionStatus(sessionId, sessionStatus, sessionsDir);\n\n if (sessionStatus === 'failed') {\n return {\n success: false,\n sessionId,\n results,\n error: 'All providers failed',\n };\n }\n\n // Auto-register if enabled\n let autoRegisterResult: RegistrationRecord | undefined;\n if (\n config.integration.reference_manager.enabled &&\n config.integration.reference_manager.auto_register\n ) {\n const refAvailable = await checkRefAvailable();\n if (refAvailable) {\n // Load all articles from results files\n const allArticles = await loadArticlesFromSession(sessionsDir, sessionId, providers);\n\n if (allArticles.length > 0) {\n autoRegisterResult = await registerArticles(allArticles, {\n sessionId,\n sessionDir: join(sessionsDir, sessionId),\n withAbstracts: config.integration.reference_manager.with_abstracts,\n });\n\n // Save registration record\n await saveRegistrationRecord(join(sessionsDir, sessionId), autoRegisterResult);\n }\n }\n }\n\n const result: SearchExecutionResult = {\n success: true,\n sessionId,\n results,\n };\n\n if (autoRegisterResult) {\n result.autoRegisterResult = autoRegisterResult;\n }\n\n return result;\n}\n\n/**\n * Load all articles from a session's results files.\n */\nasync function loadArticlesFromSession(\n sessionsDir: string,\n sessionId: string,\n providers: ProviderName[]\n): Promise<Article[]> {\n const articles: Article[] = [];\n\n for (const provider of providers) {\n const resultsPath = join(sessionsDir, sessionId, `${provider}_results.jsonl`);\n try {\n const content = await readFile(resultsPath, 'utf-8');\n const lines = content.trim().split('\\n').filter((line) => line.length > 0);\n for (const line of lines) {\n try {\n const article = JSON.parse(line) as Article;\n articles.push(article);\n } catch {\n // Skip invalid JSON lines\n }\n }\n } catch {\n // Skip if file doesn't exist\n }\n }\n\n return articles;\n}\n"],"names":["translatePubmed","translateEric","translateArxiv","translateScopus","stringifyYaml"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"search-executor.js","sources":["../../../src/cli/commands/search-executor.ts"],"sourcesContent":["/**\n * Search executor for CLI search command.\n *\n * Handles the actual execution of searches across multiple providers,\n * including session creation, progress display, and result storage.\n */\nimport { readFile, writeFile, appendFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { createHash } from 'node:crypto';\nimport type { SearchCommandOptions } from './search.js';\nimport type { Config } from '../../config/index.js';\nimport type {\n Article,\n Provider,\n ProviderName,\n TranslatedQuery,\n} from '../../providers/base/types.js';\nimport { isProviderError } from '../../providers/base/types.js';\nimport type { QueryAST } from '../../query/types.js';\nimport { parseQueryString } from '../../query/index.js';\nimport {\n createSession,\n updateDatabaseStatus,\n updateSessionStatus,\n} from '../../session/manager.js';\nimport { MultiProviderProgress } from '../utils/progress.js';\nimport { PubMedProvider } from '../../providers/pubmed/provider.js';\nimport type { PubMedConfig } from '../../providers/pubmed/types.js';\nimport { ERICProvider } from '../../providers/eric/provider.js';\nimport { ArxivProvider } from '../../providers/arxiv/provider.js';\nimport { ScopusProvider } from '../../providers/scopus/provider.js';\nimport type { ScopusConfig } from '../../providers/scopus/types.js';\nimport { translateQuery as translatePubmed } from '../../providers/pubmed/translator.js';\nimport { translateQuery as translateEric } from '../../providers/eric/translator.js';\nimport { translateQuery as translateArxiv } from '../../providers/arxiv/translator.js';\nimport { translateQuery as translateScopus } from '../../providers/scopus/translator.js';\nimport { stringify as stringifyYaml } from 'yaml';\nimport { registerArticles, saveRegistrationRecord } from '../../integration/register.js';\nimport { buildFailureErrorMessage } from './search-utils.js';\nimport { getConfigDir } from '../../config/paths.js';\nimport type { RegistrationRecord } from '../../integration/types.js';\nimport { checkRefAvailable } from '../../integration/ref-cli.js';\n\n/**\n * Result of a search execution.\n */\nexport interface SearchExecutionResult {\n success: boolean;\n sessionId?: string;\n results?: Record<string, { hits: number; retrieved: number; error?: string; warnings?: string[] }>;\n error?: string;\n autoRegisterResult?: RegistrationRecord;\n}\n\n/**\n * Available providers that are implemented.\n */\nconst IMPLEMENTED_PROVIDERS: ProviderName[] = ['pubmed', 'eric', 'arxiv', 'scopus'];\n\n/**\n * Create a provider instance for the given provider name.\n */\nexport function createProviderInstance(\n name: ProviderName,\n config: Config\n): Provider | null {\n const providerConfig = config.providers[name];\n\n switch (name) {\n case 'pubmed': {\n if (!providerConfig.email) {\n const configPath = getConfigDir();\n console.warn(\n `Warning: No email configured for PubMed.\\n` +\n ` → Edit ${configPath}/config.toml and set providers.pubmed.email\\n` +\n ` → Or run: search-hub config providers.pubmed.email \"your@email.com\"`\n );\n }\n const pubmedOpts: PubMedConfig = {\n email: providerConfig.email ?? 'search-hub@example.com',\n rateLimit: providerConfig.rate_limit,\n timeout: providerConfig.timeout,\n retries: providerConfig.retries,\n };\n if (providerConfig.api_key) {\n pubmedOpts.apiKey = providerConfig.api_key;\n }\n return new PubMedProvider(pubmedOpts);\n }\n case 'eric':\n return new ERICProvider({\n rateLimit: providerConfig.rate_limit,\n timeout: providerConfig.timeout,\n retries: providerConfig.retries,\n });\n case 'arxiv':\n return new ArxivProvider({\n rateLimit: providerConfig.rate_limit,\n timeout: providerConfig.timeout,\n retries: providerConfig.retries,\n });\n case 'scopus': {\n if (!providerConfig.api_key) {\n console.warn(\n `Warning: Scopus requires an API key. Set providers.scopus.api_key in config.\\n` +\n ` → Get an API key at https://dev.elsevier.com/\\n` +\n ` → Run: search-hub config providers.scopus.api_key \"your-key\"`\n );\n return null;\n }\n const scopusOpts: ScopusConfig = {\n apiKey: providerConfig.api_key,\n rateLimit: providerConfig.rate_limit,\n timeout: providerConfig.timeout,\n retries: providerConfig.retries,\n };\n if (providerConfig.inst_token) {\n scopusOpts.instToken = providerConfig.inst_token;\n }\n return new ScopusProvider(scopusOpts);\n }\n default:\n throw new Error(`Provider '${name}' is not implemented`);\n }\n}\n\n/**\n * Translate a query AST for a specific provider.\n */\nfunction translateQueryForProvider(\n ast: QueryAST,\n provider: ProviderName\n): TranslatedQuery {\n switch (provider) {\n case 'pubmed':\n return translatePubmed(ast);\n case 'eric':\n return translateEric(ast);\n case 'arxiv':\n return translateArxiv(ast);\n case 'scopus':\n return translateScopus(ast);\n default:\n throw new Error(`No translator for provider '${provider}'`);\n }\n}\n\n/**\n * Get enabled providers from config, optionally filtered by user selection.\n */\nfunction getEnabledProviders(\n config: Config,\n requestedProviders?: ProviderName[]\n): ProviderName[] {\n const enabledInConfig = IMPLEMENTED_PROVIDERS.filter(\n (name) => config.providers[name].enabled\n );\n\n if (requestedProviders && requestedProviders.length > 0) {\n return requestedProviders.filter((p) => enabledInConfig.includes(p));\n }\n\n return enabledInConfig;\n}\n\n/**\n * Execute a search across multiple providers.\n */\nexport async function executeSearch(\n options: SearchCommandOptions,\n sessionsDir: string,\n config: Config,\n showProgress = true\n): Promise<SearchExecutionResult> {\n let ast: QueryAST | undefined;\n let queryContent: string;\n let queryFile: string;\n\n // Handle direct query mode\n if (options.directQuery && options.providers && options.providers.length === 1) {\n queryFile = 'direct-query';\n\n // For direct query, we create a minimal AST structure\n ast = {\n name: options.sessionName ?? 'direct-query',\n blocks: [\n {\n field: 'all',\n terms: { keywords: [options.directQuery] },\n operator: 'AND',\n },\n ],\n filters: {},\n overrides: {},\n };\n\n // Generate YAML safely using yaml library to handle special characters\n queryContent = stringifyYaml({\n name: ast.name,\n blocks: ast.blocks,\n filters: ast.filters,\n });\n } else if (options.queryFile) {\n // Parse query file\n try {\n queryContent = await readFile(options.queryFile, 'utf-8');\n ast = parseQueryString(queryContent);\n queryFile = options.queryFile;\n } catch (error) {\n return {\n success: false,\n error: `Failed to parse query file: ${error instanceof Error ? error.message : error}`,\n };\n }\n } else {\n return {\n success: false,\n error: 'Either queryFile or directQuery with provider is required',\n };\n }\n\n // Determine which providers to use\n const providers = getEnabledProviders(config, options.providers);\n\n if (providers.length === 0) {\n return {\n success: false,\n error: 'No providers enabled or selected',\n };\n }\n\n // Create query hash\n const queryHash = createHash('sha256').update(queryContent).digest('hex').slice(0, 8);\n\n // Create session\n let session;\n try {\n const sessionOpts: Parameters<typeof createSession>[0] = {\n name: options.sessionName ?? ast.name,\n queryFile,\n queryContent,\n queryHash,\n targets: providers,\n sessionsDir,\n };\n if (ast.description) {\n sessionOpts.description = ast.description;\n }\n session = await createSession(sessionOpts);\n } catch (error) {\n return {\n success: false,\n error: `Failed to create session: ${error instanceof Error ? error.message : error}`,\n };\n }\n\n const sessionId = session.id;\n const results: Record<string, { hits: number; retrieved: number; error?: string; warnings?: string[] }> = {};\n\n // Create progress display if enabled\n let progress: MultiProviderProgress | undefined;\n if (showProgress && process.stdout.isTTY) {\n progress = new MultiProviderProgress(providers);\n }\n\n // Execute search for each provider\n for (const providerName of providers) {\n try {\n // Create provider instance\n const provider = createProviderInstance(providerName, config);\n\n // Skip provider if it could not be created (e.g. missing configuration)\n if (provider === null) {\n const configError = `${providerName}: provider configuration incomplete. See warning above for details.`;\n results[providerName] = { hits: 0, retrieved: 0, error: configError };\n await updateDatabaseStatus(\n sessionId,\n providerName,\n {\n status: 'failed',\n completedAt: new Date().toISOString(),\n error: {\n code: 'CONFIG_ERROR',\n message: configError,\n retryable: false,\n },\n },\n sessionsDir\n );\n continue;\n }\n\n // Translate query\n let translatedQuery: TranslatedQuery;\n if (options.directQuery && options.providers?.length === 1) {\n // For direct query, use the native query string directly\n translatedQuery = {\n native: options.directQuery,\n provider: providerName,\n };\n } else {\n translatedQuery = translateQueryForProvider(ast, providerName);\n }\n\n // Write translated query to session\n const queryPath = join(sessionsDir, sessionId, `${providerName}_query.txt`);\n await writeFile(queryPath, translatedQuery.native, 'utf-8');\n\n // Update database status to in_progress\n await updateDatabaseStatus(\n sessionId,\n providerName,\n {\n status: 'in_progress',\n startedAt: new Date().toISOString(),\n },\n sessionsDir\n );\n\n // Prepare results file path\n const resultsPath = join(sessionsDir, sessionId, `${providerName}_results.jsonl`);\n\n // Execute search\n let retrievedCount = 0;\n let totalHits = 0;\n\n progress?.update(providerName, 0, 0, 'in_progress');\n\n const searchOptions = {\n maxResults: options.maxResults ?? config.providers[providerName].max_results,\n };\n\n for await (const article of provider.search(translatedQuery, searchOptions)) {\n retrievedCount++;\n\n // Write article to JSONL file\n await appendFile(resultsPath, JSON.stringify(article) + '\\n', 'utf-8');\n\n // Update progress (estimate total from first batch)\n if (totalHits === 0) {\n // Estimate total - this is provider-dependent, we'll use retrieved count as minimum\n totalHits = Math.max(retrievedCount * 10, 100);\n }\n progress?.update(providerName, retrievedCount, totalHits, 'in_progress');\n }\n\n // Update final totals\n totalHits = retrievedCount; // Use actual count as final total\n\n // Mark as completed\n progress?.complete(providerName);\n\n // Update database status\n await updateDatabaseStatus(\n sessionId,\n providerName,\n {\n status: 'completed',\n completedAt: new Date().toISOString(),\n totalHits,\n retrievedCount,\n files: {\n query: `${providerName}_query.txt`,\n results: `${providerName}_results.jsonl`,\n },\n },\n sessionsDir\n );\n\n // Collect warnings if provider supports them\n const providerWarnings = 'getWarnings' in provider && typeof (provider as any).getWarnings === 'function'\n ? (provider as any).getWarnings() as string[]\n : undefined;\n results[providerName] = { hits: totalHits, retrieved: retrievedCount, ...(providerWarnings && providerWarnings.length > 0 && { warnings: providerWarnings }) };\n } catch (error) {\n const errorMessage = error instanceof Error\n ? error.message\n : isProviderError(error)\n ? error.message\n : String(error);\n\n progress?.fail(providerName, errorMessage);\n\n // Update database status with error\n await updateDatabaseStatus(\n sessionId,\n providerName,\n {\n status: 'failed',\n completedAt: new Date().toISOString(),\n error: {\n code: 'SEARCH_ERROR',\n message: errorMessage,\n retryable: true,\n },\n },\n sessionsDir\n );\n\n results[providerName] = { hits: 0, retrieved: 0, error: errorMessage };\n }\n }\n\n // Stop progress display\n progress?.stop();\n\n // Determine overall session status\n const anyFailed = providers.some((p) => {\n const r = results[p];\n return r && r.error !== undefined;\n });\n const anySucceeded = providers.some((p) => {\n const r = results[p];\n return r && r.retrieved > 0;\n });\n\n let sessionStatus: 'completed' | 'partial' | 'failed';\n if (!anyFailed) {\n sessionStatus = 'completed';\n } else if (anySucceeded) {\n sessionStatus = 'partial';\n } else {\n sessionStatus = 'failed';\n }\n\n // Update session status\n await updateSessionStatus(sessionId, sessionStatus, sessionsDir);\n\n if (sessionStatus === 'failed') {\n return {\n success: false,\n sessionId,\n results,\n error: buildFailureErrorMessage(results),\n };\n }\n\n // Auto-register if enabled\n let autoRegisterResult: RegistrationRecord | undefined;\n if (\n config.integration.reference_manager.enabled &&\n config.integration.reference_manager.auto_register\n ) {\n const refAvailable = await checkRefAvailable();\n if (refAvailable) {\n // Load all articles from results files\n const allArticles = await loadArticlesFromSession(sessionsDir, sessionId, providers);\n\n if (allArticles.length > 0) {\n autoRegisterResult = await registerArticles(allArticles, {\n sessionId,\n sessionDir: join(sessionsDir, sessionId),\n withAbstracts: config.integration.reference_manager.with_abstracts,\n });\n\n // Save registration record\n await saveRegistrationRecord(join(sessionsDir, sessionId), autoRegisterResult);\n }\n }\n }\n\n const result: SearchExecutionResult = {\n success: true,\n sessionId,\n results,\n };\n\n if (autoRegisterResult) {\n result.autoRegisterResult = autoRegisterResult;\n }\n\n return result;\n}\n\n/**\n * Load all articles from a session's results files.\n */\nasync function loadArticlesFromSession(\n sessionsDir: string,\n sessionId: string,\n providers: ProviderName[]\n): Promise<Article[]> {\n const articles: Article[] = [];\n\n for (const provider of providers) {\n const resultsPath = join(sessionsDir, sessionId, `${provider}_results.jsonl`);\n try {\n const content = await readFile(resultsPath, 'utf-8');\n const lines = content.trim().split('\\n').filter((line) => line.length > 0);\n for (const line of lines) {\n try {\n const article = JSON.parse(line) as Article;\n articles.push(article);\n } catch {\n // Skip invalid JSON lines\n }\n }\n } catch {\n // Skip if file doesn't exist\n }\n }\n\n return articles;\n}\n"],"names":["translatePubmed","translateEric","translateArxiv","translateScopus","stringifyYaml"],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for search and resume executors.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Build a detailed error message listing per-provider failures.
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildFailureErrorMessage(results: Record<string, {
|
|
8
|
+
hits: number;
|
|
9
|
+
retrieved: number;
|
|
10
|
+
error?: string;
|
|
11
|
+
}>): string;
|
|
12
|
+
/**
|
|
13
|
+
* Format verbose per-provider details for CLI output.
|
|
14
|
+
*/
|
|
15
|
+
export declare function formatVerboseProviderDetails(results: Record<string, {
|
|
16
|
+
hits: number;
|
|
17
|
+
retrieved: number;
|
|
18
|
+
error?: string;
|
|
19
|
+
warnings?: string[];
|
|
20
|
+
}>): string;
|
|
21
|
+
//# sourceMappingURL=search-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-utils.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/search-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAC3E,MAAM,CAeR;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,GAChG,MAAM,CAeR"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
function buildFailureErrorMessage(results) {
|
|
2
|
+
const errorLines = Object.entries(results).filter(([, r]) => r.error).map(([provider, r]) => ` ${provider}: ${r.error}`);
|
|
3
|
+
const suggestedActions = "\n\nSuggested actions:\n → Run with --dry-run to inspect translated queries\n → Check provider configuration: search-hub config\n → Use --db <provider> to test a single provider";
|
|
4
|
+
if (errorLines.length === 0) {
|
|
5
|
+
return "All providers failed" + suggestedActions;
|
|
6
|
+
}
|
|
7
|
+
return "All providers failed:\n" + errorLines.join("\n") + suggestedActions;
|
|
8
|
+
}
|
|
9
|
+
function formatVerboseProviderDetails(results) {
|
|
10
|
+
const lines = ["\nPer-provider details:"];
|
|
11
|
+
for (const [provider, stats] of Object.entries(results)) {
|
|
12
|
+
if (stats.error) {
|
|
13
|
+
lines.push(` ${provider}: FAILED - ${stats.error}`);
|
|
14
|
+
} else {
|
|
15
|
+
lines.push(` ${provider}: ${stats.retrieved} results`);
|
|
16
|
+
}
|
|
17
|
+
if (stats.warnings && stats.warnings.length > 0) {
|
|
18
|
+
for (const w of stats.warnings) {
|
|
19
|
+
lines.push(` warning: ${w}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return lines.join("\n");
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
buildFailureErrorMessage,
|
|
27
|
+
formatVerboseProviderDetails
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=search-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-utils.js","sources":["../../../src/cli/commands/search-utils.ts"],"sourcesContent":["/**\n * Shared utilities for search and resume executors.\n */\n\n/**\n * Build a detailed error message listing per-provider failures.\n */\nexport function buildFailureErrorMessage(\n results: Record<string, { hits: number; retrieved: number; error?: string }>\n): string {\n const errorLines = Object.entries(results)\n .filter(([, r]) => r.error)\n .map(([provider, r]) => ` ${provider}: ${r.error}`);\n\n const suggestedActions = '\\n\\nSuggested actions:\\n' +\n ' → Run with --dry-run to inspect translated queries\\n' +\n ' → Check provider configuration: search-hub config\\n' +\n ' → Use --db <provider> to test a single provider';\n\n if (errorLines.length === 0) {\n return 'All providers failed' + suggestedActions;\n }\n\n return 'All providers failed:\\n' + errorLines.join('\\n') + suggestedActions;\n}\n\n/**\n * Format verbose per-provider details for CLI output.\n */\nexport function formatVerboseProviderDetails(\n results: Record<string, { hits: number; retrieved: number; error?: string; warnings?: string[] }>\n): string {\n const lines: string[] = ['\\nPer-provider details:'];\n for (const [provider, stats] of Object.entries(results)) {\n if (stats.error) {\n lines.push(` ${provider}: FAILED - ${stats.error}`);\n } else {\n lines.push(` ${provider}: ${stats.retrieved} results`);\n }\n if (stats.warnings && stats.warnings.length > 0) {\n for (const w of stats.warnings) {\n lines.push(` warning: ${w}`);\n }\n }\n }\n return lines.join('\\n');\n}\n"],"names":[],"mappings":"AAOO,SAAS,yBACd,SACQ;AACR,QAAM,aAAa,OAAO,QAAQ,OAAO,EACtC,OAAO,CAAC,CAAA,EAAG,CAAC,MAAM,EAAE,KAAK,EACzB,IAAI,CAAC,CAAC,UAAU,CAAC,MAAM,KAAK,QAAQ,KAAK,EAAE,KAAK,EAAE;AAErD,QAAM,mBAAmB;AAKzB,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,yBAAyB;AAAA,EAClC;AAEA,SAAO,4BAA4B,WAAW,KAAK,IAAI,IAAI;AAC7D;AAKO,SAAS,6BACd,SACQ;AACR,QAAM,QAAkB,CAAC,yBAAyB;AAClD,aAAW,CAAC,UAAU,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AACvD,QAAI,MAAM,OAAO;AACf,YAAM,KAAK,KAAK,QAAQ,cAAc,MAAM,KAAK,EAAE;AAAA,IACrD,OAAO;AACL,YAAM,KAAK,KAAK,QAAQ,KAAK,MAAM,SAAS,UAAU;AAAA,IACxD;AACA,QAAI,MAAM,YAAY,MAAM,SAAS,SAAS,GAAG;AAC/C,iBAAW,KAAK,MAAM,UAAU;AAC9B,cAAM,KAAK,gBAAgB,CAAC,EAAE;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ProviderName } from '../../session/types.js';
|
|
2
|
+
import { Config } from '../../config/index.js';
|
|
2
3
|
export interface SearchCommandOptions {
|
|
3
4
|
queryFile?: string;
|
|
4
5
|
directQuery?: string;
|
|
@@ -24,7 +25,22 @@ export interface ValidationResult {
|
|
|
24
25
|
valid: boolean;
|
|
25
26
|
error?: string;
|
|
26
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Options for enhanced dry-run output.
|
|
30
|
+
*/
|
|
31
|
+
export interface DryRunOutputOptions {
|
|
32
|
+
config?: Config;
|
|
33
|
+
providers?: ProviderName[];
|
|
34
|
+
}
|
|
27
35
|
export declare function parseSearchOptions(queryFile: string | undefined, options: CommandLineOptions): SearchCommandOptions;
|
|
28
36
|
export declare function validateSearchInput(options: SearchCommandOptions): ValidationResult;
|
|
29
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Format provider readiness summary for dry-run output.
|
|
39
|
+
*/
|
|
40
|
+
export declare function formatProviderReadiness(providers: ProviderName[], config: Config): string;
|
|
41
|
+
/**
|
|
42
|
+
* Format query diagnostics warnings for dry-run output.
|
|
43
|
+
*/
|
|
44
|
+
export declare function formatQueryDiagnostics(translations: TranslationResult[]): string;
|
|
45
|
+
export declare function formatDryRunOutput(translations: TranslationResult[], options?: DryRunOutputOptions): string;
|
|
30
46
|
//# sourceMappingURL=search.d.ts.map
|
|
@@ -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;
|
|
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,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAGpD,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,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;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,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAChC;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;CAC5B;AAED,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,OAAO,EAAE,kBAAkB,GAC1B,oBAAoB,CAgCtB;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,gBAAgB,CAuBnF;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,YAAY,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAUzF;AA2BD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAmBhF;AAED,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,iBAAiB,EAAE,EACjC,OAAO,CAAC,EAAE,mBAAmB,GAC5B,MAAM,CAwBR"}
|