@ncukondo/search-hub 0.20.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -17,7 +17,8 @@ import { createEricCountValidator, createEmtreeCountValidator } from "../query/v
17
17
  import { createProviderInstance, executePreview, executeCountOnly, executeSearch } from "./commands/search-executor.js";
18
18
  import { translateQueryCommand, formatTranslateResult } from "./commands/query/translate.js";
19
19
  import { inspectQueryCommand, formatInspectOutput } from "./commands/query/inspect.js";
20
- import { writeQueryTemplate, generateQueryTemplate } from "./commands/query/init.js";
20
+ import { writeQueryTemplate } from "./commands/query/init.js";
21
+ import { resolveQueryFile } from "./commands/query/resolve.js";
21
22
  import { getSessionDetails, computeDeduplicationStats, formatSessionDetails, listSessionsForDisplay, formatSessionList } from "./commands/status.js";
22
23
  import { parseSearchOptions, validateSearchInput, formatShortKeywordWarning, formatDryRunOutput, formatPreviewOutput, formatCountOnlyOutput } from "./commands/search.js";
23
24
  import { parseResumeOptions, validateResumeInput, getResumableProvidersForCommand } from "./commands/resume.js";
@@ -42,7 +43,7 @@ import { executeReviewFinalize, formatFinalizeOutput } from "./commands/review/f
42
43
  import "zod";
43
44
  import "./commands/review/schema.js";
44
45
  import { registerFulltextCommands } from "./commands/fulltext/index.js";
45
- import { parseRegisterOptions, validateRegisterInput, hasReviewFile, getReviewSummary, formatNoIncludedArticlesError, formatPendingWarning, confirmPrompt, getIncludedArticles, formatReviewRequiredMessage, formatIgnoringReviewsNote, formatDryRunOutput as formatDryRunOutput$1, formatRegistrationSummary } from "./commands/register.js";
46
+ import { parseRegisterOptions, validateRegisterInput, hasReviewFile, getReviewSummary, formatNoIncludedArticlesError, formatPendingWarning, confirmPrompt, getIncludedArticles, formatReviewRequiredMessage, formatIgnoringReviewsNote, formatDryRunOutput as formatDryRunOutput$1, formatRegistrationSummary, formatLibraryPath, formatDefaultLibraryHint } from "./commands/register.js";
46
47
  import { formatSuggestion } from "./suggestions/index.js";
47
48
  import { getSuggestion } from "./suggestions/rules.js";
48
49
  import { readLogEntries, computeQueryHash, appendLogEntry, buildPreviewLogEntry, buildCountLogEntry } from "./commands/query/iteration-log.js";
@@ -77,10 +78,10 @@ Workflow:
77
78
  Iterate: search → results -q → check → diff Query refinement
78
79
 
79
80
  Quick Start:
80
- $ search-hub query init -o search.yaml # Create query template
81
- $ search-hub search search.yaml --count-only # Check hit counts
82
- $ search-hub search search.yaml # Execute search
83
- $ search-hub results <session> # Review titles`);
81
+ $ search-hub query init "my search" # Create query template
82
+ $ search-hub search my-search --count-only # Check hit counts
83
+ $ search-hub search my-search # Execute search
84
+ $ search-hub results <session> # Review titles`);
84
85
  program.command("init").description("Initialize configuration directory").option("-f, --force", "overwrite existing configuration", false).addHelpText("after", `
85
86
  Examples:
86
87
  $ search-hub init # Initialize with default settings
@@ -190,9 +191,10 @@ Use "search-hub query init" to generate a template.`);
190
191
  Examples:
191
192
  $ search-hub query validate ./diabetes-ai.yaml
192
193
  $ search-hub query validate ./diabetes-ai.yaml --no-vocab # Skip MeSH check
193
- $ search-hub query validate ./diabetes-ai.yaml --no-cache # Ignore cache`).action(async (file, opts) => {
194
+ $ search-hub query validate ./diabetes-ai.yaml --no-cache # Ignore cache`).action(async (fileArg, opts) => {
194
195
  const globalOpts = program.opts();
195
196
  try {
197
+ const file = await resolveQueryFile(fileArg);
196
198
  const noVocab = opts.vocab === false;
197
199
  const noCache = opts.cache === false;
198
200
  let cache;
@@ -279,9 +281,10 @@ Examples:
279
281
  queryCommand.command("translate").description("Show translated queries for each database").argument("<file>", "path to query YAML file").option("--db <provider>", "show translation for specific provider only").addHelpText("after", `
280
282
  Examples:
281
283
  $ search-hub query translate ./diabetes-ai.yaml # All databases
282
- $ search-hub query translate ./diabetes-ai.yaml --db pubmed # PubMed only`).action(async (file, options) => {
284
+ $ search-hub query translate ./diabetes-ai.yaml --db pubmed # PubMed only`).action(async (fileArg, options) => {
283
285
  const globalOpts = program.opts();
284
286
  try {
287
+ const file = await resolveQueryFile(fileArg);
285
288
  const translateOptions = options.db ? { providers: [options.db] } : {};
286
289
  const result = await translateQueryCommand(file, translateOptions);
287
290
  if (!globalOpts.quiet) {
@@ -301,9 +304,10 @@ Examples:
301
304
  queryCommand.command("inspect").description("Show how a query resolves per provider (block replacements and added filters)").argument("<file>", "path to query YAML file").option("--db <provider>", "show resolution for specific provider only").addHelpText("after", `
302
305
  Examples:
303
306
  $ search-hub query inspect ./diabetes-ai.yaml # All databases
304
- $ search-hub query inspect ./diabetes-ai.yaml --db pubmed # PubMed only`).action(async (file, options) => {
307
+ $ search-hub query inspect ./diabetes-ai.yaml --db pubmed # PubMed only`).action(async (fileArg, options) => {
305
308
  const globalOpts = program.opts();
306
309
  try {
310
+ const file = await resolveQueryFile(fileArg);
307
311
  const inspectOptions = options.db ? { providers: [options.db] } : {};
308
312
  const result = await inspectQueryCommand(file, inspectOptions);
309
313
  if (!result.success) {
@@ -328,24 +332,35 @@ Examples:
328
332
  process.exitCode = EXIT_CODES.QUERY_ERROR;
329
333
  }
330
334
  });
331
- queryCommand.command("init").description("Generate a template query YAML file").option("-o, --output <path>", "write to file (default: stdout)").option("--force", "overwrite existing file", false).action(async (options) => {
335
+ queryCommand.command("init").description("Generate a template query YAML file").argument("<title>", "query title (used for name field and filename)").option("-o, --output <path>", "write to specific file path").option("--stdout", "output to stdout instead of file").option("--force", "overwrite existing file", false).addHelpText("after", `
336
+ Examples:
337
+ $ search-hub query init "WBA pain mechanisms" # → queries/wba-pain-mechanisms.yaml
338
+ $ search-hub query init "WBA pain" -o ./custom-path.yaml # Custom output path
339
+ $ search-hub query init "WBA pain" --stdout # Print to stdout`).action(async (title, options) => {
332
340
  const globalOpts = program.opts();
333
341
  try {
334
- if (options.output) {
335
- const result = await writeQueryTemplate(options);
336
- if (!globalOpts.quiet) {
337
- if (result.success) {
338
- console.log(result.message);
339
- } else {
340
- console.error(result.message);
342
+ const result = await writeQueryTemplate({
343
+ title,
344
+ output: options.output,
345
+ stdout: options.stdout,
346
+ force: options.force
347
+ });
348
+ if (!globalOpts.quiet) {
349
+ if (result.success) {
350
+ console.log(result.message);
351
+ if (result.outputPath) {
352
+ const suggestion = formatSuggestion(getSuggestion({
353
+ command: "query init",
354
+ outputFile: result.outputPath
355
+ }));
356
+ if (suggestion) console.log("\n" + suggestion);
357
+ console.log("\nIterate: edit the same file and re-run step 3. Counts are logged automatically.");
341
358
  }
359
+ } else {
360
+ console.error(result.message);
342
361
  }
343
- process.exitCode = result.success ? EXIT_CODES.SUCCESS : EXIT_CODES.GENERAL_ERROR;
344
- } else {
345
- const template = generateQueryTemplate();
346
- console.log(template);
347
- process.exitCode = EXIT_CODES.SUCCESS;
348
362
  }
363
+ process.exitCode = result.success ? EXIT_CODES.SUCCESS : EXIT_CODES.GENERAL_ERROR;
349
364
  } catch (error) {
350
365
  if (!globalOpts.quiet) {
351
366
  console.error("Error:", error instanceof Error ? error.message : error);
@@ -356,9 +371,10 @@ Examples:
356
371
  queryCommand.command("assess").description("Record an assessment of the current query iteration").argument("<file>", "path to query YAML file").option("--verdict <verdict>", "assessment verdict (e.g., reject, good, refine)").option("--precision <precision>", "estimated precision (e.g., ~60%)").option("--comment <comment>", "free-text comment").addHelpText("after", `
357
372
  Examples:
358
373
  $ search-hub query assess query.yaml --verdict reject --comment "Too broad"
359
- $ search-hub query assess query.yaml --verdict good --precision "~60%"`).action(async (file, options) => {
374
+ $ search-hub query assess query.yaml --verdict good --precision "~60%"`).action(async (fileArg, options) => {
360
375
  const globalOpts = program.opts();
361
376
  try {
377
+ const file = await resolveQueryFile(fileArg);
362
378
  const result = await executeQueryAssess(file, options);
363
379
  if (result.success) {
364
380
  if (!globalOpts.quiet) {
@@ -386,9 +402,10 @@ Examples:
386
402
  queryCommand.command("log").description("View the query iteration history").argument("<file>", "path to query YAML file").option("--json", "output as JSON").addHelpText("after", `
387
403
  Examples:
388
404
  $ search-hub query log query.yaml
389
- $ search-hub query log query.yaml --json`).action(async (file, options) => {
405
+ $ search-hub query log query.yaml --json`).action(async (fileArg, options) => {
390
406
  const globalOpts = program.opts();
391
407
  try {
408
+ const file = await resolveQueryFile(fileArg);
392
409
  const entries = await readLogEntries(file);
393
410
  if (!globalOpts.quiet) {
394
411
  console.log(formatLogOutput(entries, { json: options?.json }));
@@ -468,7 +485,8 @@ Query features (use "query init" to see full template):
468
485
  async (queryFile, options) => {
469
486
  const globalOpts = program.opts();
470
487
  try {
471
- const searchOpts = parseSearchOptions(queryFile, {
488
+ const resolvedQueryFile = queryFile ? await resolveQueryFile(queryFile) : void 0;
489
+ const searchOpts = parseSearchOptions(resolvedQueryFile, {
472
490
  db: options?.db,
473
491
  query: options?.query,
474
492
  name: options?.name,
@@ -683,11 +701,14 @@ Warning: Some providers failed:
683
701
  if (result.sessionId) {
684
702
  const sessions = await listSessions(sessionsDir);
685
703
  const suggestionCmd = searchOpts.directQuery ? "search --query" : "search";
704
+ const currentSession = sessions.find((s) => s.id === result.sessionId);
705
+ const previousSession = currentSession ? sessions.filter((s) => s.name === currentSession.name && s.id !== result.sessionId).sort((a, b) => b.createdAt.localeCompare(a.createdAt))[0] : void 0;
686
706
  const suggestion = formatSuggestion(getSuggestion({
687
707
  command: suggestionCmd,
688
708
  sessionId: result.sessionId,
689
709
  sessionStatus: result.sessionStatus,
690
- sessionCount: sessions.length
710
+ sessionCount: sessions.length,
711
+ previousSessionId: previousSession?.id
691
712
  }));
692
713
  if (suggestion) console.log(suggestion);
693
714
  }
@@ -1688,7 +1709,10 @@ Failed to install reference-manager: ${installError instanceof Error ? installEr
1688
1709
  }
1689
1710
  }
1690
1711
  console.log(`
1691
- Results saved to: ${join(sessionDir, "registration.json")}`);
1712
+ ${formatLibraryPath(sessionDir)}`);
1713
+ console.log(`Results saved to: ${join(sessionDir, "registration.json")}`);
1714
+ console.log(`
1715
+ ${formatDefaultLibraryHint(sessionDir)}`);
1692
1716
  const suggestion = formatSuggestion(getSuggestion({
1693
1717
  command: "register",
1694
1718
  sessionId,