@ncukondo/reference-manager 0.5.2 → 0.6.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.
Files changed (111) hide show
  1. package/README.md +63 -1
  2. package/dist/chunks/{file-watcher-Dqkw6R7-.js → file-watcher-Cwfnnw92.js} +161 -28
  3. package/dist/chunks/file-watcher-Cwfnnw92.js.map +1 -0
  4. package/dist/chunks/index-CQO8hLYm.js +1788 -0
  5. package/dist/chunks/index-CQO8hLYm.js.map +1 -0
  6. package/dist/chunks/{loader-DuzyKV70.js → loader-B_ZLxCQW.js} +97 -26
  7. package/dist/chunks/loader-B_ZLxCQW.js.map +1 -0
  8. package/dist/cli/commands/fulltext.d.ts +4 -3
  9. package/dist/cli/commands/fulltext.d.ts.map +1 -1
  10. package/dist/cli/commands/list.d.ts +7 -1
  11. package/dist/cli/commands/list.d.ts.map +1 -1
  12. package/dist/cli/commands/remove.d.ts +2 -1
  13. package/dist/cli/commands/remove.d.ts.map +1 -1
  14. package/dist/cli/commands/search.d.ts +7 -1
  15. package/dist/cli/commands/search.d.ts.map +1 -1
  16. package/dist/cli/commands/update.d.ts +2 -1
  17. package/dist/cli/commands/update.d.ts.map +1 -1
  18. package/dist/cli/completion.d.ts +65 -0
  19. package/dist/cli/completion.d.ts.map +1 -0
  20. package/dist/cli/index.d.ts.map +1 -1
  21. package/dist/cli/server-client.d.ts +5 -4
  22. package/dist/cli/server-client.d.ts.map +1 -1
  23. package/dist/cli.js +522 -153
  24. package/dist/cli.js.map +1 -1
  25. package/dist/config/defaults.d.ts.map +1 -1
  26. package/dist/config/loader.d.ts.map +1 -1
  27. package/dist/config/schema.d.ts +74 -0
  28. package/dist/config/schema.d.ts.map +1 -1
  29. package/dist/core/library-interface.d.ts +22 -4
  30. package/dist/core/library-interface.d.ts.map +1 -1
  31. package/dist/core/library.d.ts +2 -10
  32. package/dist/core/library.d.ts.map +1 -1
  33. package/dist/core/reference.d.ts +1 -0
  34. package/dist/core/reference.d.ts.map +1 -1
  35. package/dist/features/duplicate/detector.d.ts.map +1 -1
  36. package/dist/features/duplicate/types.d.ts +2 -1
  37. package/dist/features/duplicate/types.d.ts.map +1 -1
  38. package/dist/features/import/cache.d.ts +8 -0
  39. package/dist/features/import/cache.d.ts.map +1 -1
  40. package/dist/features/import/detector.d.ts +11 -3
  41. package/dist/features/import/detector.d.ts.map +1 -1
  42. package/dist/features/import/fetcher.d.ts +8 -0
  43. package/dist/features/import/fetcher.d.ts.map +1 -1
  44. package/dist/features/import/importer.d.ts.map +1 -1
  45. package/dist/features/import/normalizer.d.ts +13 -0
  46. package/dist/features/import/normalizer.d.ts.map +1 -1
  47. package/dist/features/import/rate-limiter.d.ts +1 -1
  48. package/dist/features/import/rate-limiter.d.ts.map +1 -1
  49. package/dist/features/operations/cite.d.ts +3 -3
  50. package/dist/features/operations/cite.d.ts.map +1 -1
  51. package/dist/features/operations/fulltext/attach.d.ts +3 -3
  52. package/dist/features/operations/fulltext/attach.d.ts.map +1 -1
  53. package/dist/features/operations/fulltext/detach.d.ts +3 -3
  54. package/dist/features/operations/fulltext/detach.d.ts.map +1 -1
  55. package/dist/features/operations/fulltext/get.d.ts +3 -3
  56. package/dist/features/operations/fulltext/get.d.ts.map +1 -1
  57. package/dist/features/operations/list.d.ts +12 -3
  58. package/dist/features/operations/list.d.ts.map +1 -1
  59. package/dist/features/operations/remove.d.ts +3 -3
  60. package/dist/features/operations/remove.d.ts.map +1 -1
  61. package/dist/features/operations/search.d.ts +19 -3
  62. package/dist/features/operations/search.d.ts.map +1 -1
  63. package/dist/features/operations/update.d.ts +3 -3
  64. package/dist/features/operations/update.d.ts.map +1 -1
  65. package/dist/features/pagination/aliases.d.ts +14 -0
  66. package/dist/features/pagination/aliases.d.ts.map +1 -0
  67. package/dist/features/pagination/index.d.ts +8 -0
  68. package/dist/features/pagination/index.d.ts.map +1 -0
  69. package/dist/features/pagination/paginate.d.ts +20 -0
  70. package/dist/features/pagination/paginate.d.ts.map +1 -0
  71. package/dist/features/pagination/sorter.d.ts +16 -0
  72. package/dist/features/pagination/sorter.d.ts.map +1 -0
  73. package/dist/features/pagination/types.d.ts +74 -0
  74. package/dist/features/pagination/types.d.ts.map +1 -0
  75. package/dist/index.js +13 -12
  76. package/dist/index.js.map +1 -1
  77. package/dist/mcp/context.d.ts +4 -4
  78. package/dist/mcp/context.d.ts.map +1 -1
  79. package/dist/mcp/resources/index.d.ts +3 -3
  80. package/dist/mcp/resources/index.d.ts.map +1 -1
  81. package/dist/mcp/resources/library.d.ts +5 -5
  82. package/dist/mcp/resources/library.d.ts.map +1 -1
  83. package/dist/mcp/tools/add.d.ts +3 -3
  84. package/dist/mcp/tools/add.d.ts.map +1 -1
  85. package/dist/mcp/tools/cite.d.ts +3 -3
  86. package/dist/mcp/tools/cite.d.ts.map +1 -1
  87. package/dist/mcp/tools/fulltext.d.ts +7 -7
  88. package/dist/mcp/tools/fulltext.d.ts.map +1 -1
  89. package/dist/mcp/tools/index.d.ts +3 -3
  90. package/dist/mcp/tools/index.d.ts.map +1 -1
  91. package/dist/mcp/tools/list.d.ts +10 -3
  92. package/dist/mcp/tools/list.d.ts.map +1 -1
  93. package/dist/mcp/tools/remove.d.ts +3 -3
  94. package/dist/mcp/tools/remove.d.ts.map +1 -1
  95. package/dist/mcp/tools/search.d.ts +10 -3
  96. package/dist/mcp/tools/search.d.ts.map +1 -1
  97. package/dist/server/routes/list.d.ts +13 -0
  98. package/dist/server/routes/list.d.ts.map +1 -1
  99. package/dist/server/routes/references.d.ts.map +1 -1
  100. package/dist/server/routes/search.d.ts +14 -0
  101. package/dist/server/routes/search.d.ts.map +1 -1
  102. package/dist/server.js +4 -4
  103. package/dist/utils/index.d.ts +1 -0
  104. package/dist/utils/index.d.ts.map +1 -1
  105. package/dist/utils/object.d.ts +16 -0
  106. package/dist/utils/object.d.ts.map +1 -0
  107. package/package.json +4 -1
  108. package/dist/chunks/file-watcher-Dqkw6R7-.js.map +0 -1
  109. package/dist/chunks/index-D4Q13N-R.js +0 -29863
  110. package/dist/chunks/index-D4Q13N-R.js.map +0 -1
  111. package/dist/chunks/loader-DuzyKV70.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1,19 +1,19 @@
1
1
  import { Command } from "commander";
2
2
  import { ZodOptional as ZodOptional$2, z } from "zod";
3
- import { L as Library, F as FileWatcher } from "./chunks/file-watcher-Dqkw6R7-.js";
3
+ import { p as pickDefined, q as sortOrderSchema, r as paginationOptionsSchema, L as Library, F as FileWatcher, u as sortFieldSchema, v as searchSortFieldSchema } from "./chunks/file-watcher-Cwfnnw92.js";
4
4
  import { promises, existsSync, mkdtempSync, writeFileSync, readFileSync } from "node:fs";
5
5
  import * as os from "node:os";
6
6
  import { tmpdir } from "node:os";
7
7
  import * as path from "node:path";
8
8
  import { join, extname } from "node:path";
9
9
  import { mkdir, unlink, rename, copyFile, rm, readFile } from "node:fs/promises";
10
- import { u as updateReference, g as getDefaultExportFromCjs, B as BUILTIN_STYLES, a as addReferences, c as citeReferences, l as listReferences, r as removeReference, s as searchReferences, b as startServerWithFileWatcher } from "./chunks/index-D4Q13N-R.js";
10
+ import { u as updateReference, B as BUILTIN_STYLES, s as startServerWithFileWatcher } from "./chunks/index-CQO8hLYm.js";
11
11
  import process$1, { stdin, stdout } from "node:process";
12
- import { l as loadConfig } from "./chunks/loader-DuzyKV70.js";
12
+ import { l as loadConfig } from "./chunks/loader-B_ZLxCQW.js";
13
13
  import { spawn } from "node:child_process";
14
14
  import { serve } from "@hono/node-server";
15
15
  const name = "@ncukondo/reference-manager";
16
- const version$1 = "0.5.2";
16
+ const version$1 = "0.6.0";
17
17
  const description$1 = "A local reference management tool using CSL-JSON as the single source of truth";
18
18
  const packageJson = {
19
19
  name,
@@ -163,7 +163,7 @@ async function validateOptions$2(options) {
163
163
  function buildCiteOptions(options) {
164
164
  return {
165
165
  identifiers: options.identifiers,
166
- ...options.uuid !== void 0 && { byUuid: options.uuid },
166
+ ...options.uuid && { idType: "uuid" },
167
167
  ...options.style !== void 0 && { style: options.style },
168
168
  ...options.cslFile !== void 0 && { cslFile: options.cslFile },
169
169
  ...options.locale !== void 0 && { locale: options.locale },
@@ -451,11 +451,11 @@ async function fulltextAttach(library, options) {
451
451
  type: explicitType,
452
452
  move,
453
453
  force,
454
- byUuid = false,
454
+ idType = "id",
455
455
  fulltextDirectory,
456
456
  stdinContent
457
457
  } = options;
458
- const item = await library.find(identifier, { byUuid });
458
+ const item = await library.find(identifier, { idType });
459
459
  if (!item) {
460
460
  return { success: false, error: `Reference '${identifier}' not found` };
461
461
  }
@@ -482,7 +482,7 @@ async function fulltextAttach(library, options) {
482
482
  updates: {
483
483
  custom: { fulltext: newFulltext }
484
484
  },
485
- byUuid
485
+ idType
486
486
  });
487
487
  await cleanupTempDir(tempDir);
488
488
  return {
@@ -528,8 +528,8 @@ function getFilePaths(manager, item, types2, identifier) {
528
528
  return { success: true, paths };
529
529
  }
530
530
  async function fulltextGet(library, options) {
531
- const { identifier, type: type2, stdout: stdout2, byUuid = false, fulltextDirectory } = options;
532
- const item = await library.find(identifier, { byUuid });
531
+ const { identifier, type: type2, stdout: stdout2, idType = "id", fulltextDirectory } = options;
532
+ const item = await library.find(identifier, { idType });
533
533
  if (!item) {
534
534
  return { success: false, error: `Reference '${identifier}' not found` };
535
535
  }
@@ -573,8 +573,8 @@ function handleDetachError(error) {
573
573
  throw error;
574
574
  }
575
575
  async function fulltextDetach(library, options) {
576
- const { identifier, type: type2, delete: deleteFile, byUuid = false, fulltextDirectory } = options;
577
- const item = await library.find(identifier, { byUuid });
576
+ const { identifier, type: type2, delete: deleteFile, idType = "id", fulltextDirectory } = options;
577
+ const item = await library.find(identifier, { idType });
578
578
  if (!item) {
579
579
  return { success: false, error: `Reference '${identifier}' not found` };
580
580
  }
@@ -596,7 +596,7 @@ async function fulltextDetach(library, options) {
596
596
  updates: {
597
597
  custom: { fulltext: updatedFulltext }
598
598
  },
599
- byUuid
599
+ idType
600
600
  });
601
601
  const resultData = { success: true, detached };
602
602
  if (deleted.length > 0) {
@@ -614,7 +614,7 @@ async function executeFulltextAttach(options, context) {
614
614
  type: options.type,
615
615
  move: options.move,
616
616
  force: options.force,
617
- byUuid: options.byUuid,
617
+ idType: options.idType,
618
618
  fulltextDirectory: options.fulltextDirectory,
619
619
  stdinContent: options.stdinContent
620
620
  };
@@ -625,7 +625,7 @@ async function executeFulltextGet(options, context) {
625
625
  identifier: options.identifier,
626
626
  type: options.type,
627
627
  stdout: options.stdout,
628
- byUuid: options.byUuid,
628
+ idType: options.idType,
629
629
  fulltextDirectory: options.fulltextDirectory
630
630
  };
631
631
  return fulltextGet(context.library, operationOptions);
@@ -635,7 +635,7 @@ async function executeFulltextDetach(options, context) {
635
635
  identifier: options.identifier,
636
636
  type: options.type,
637
637
  delete: options.delete,
638
- byUuid: options.byUuid,
638
+ idType: options.idType,
639
639
  fulltextDirectory: options.fulltextDirectory
640
640
  };
641
641
  return fulltextDetach(context.library, operationOptions);
@@ -689,6 +689,40 @@ function formatFulltextDetachOutput(result) {
689
689
  function getFulltextExitCode(result) {
690
690
  return result.success ? 0 : 1;
691
691
  }
692
+ const SORT_ALIASES = {
693
+ pub: "published",
694
+ mod: "updated",
695
+ add: "created",
696
+ rel: "relevance"
697
+ };
698
+ const VALID_SORT_FIELDS = /* @__PURE__ */ new Set([
699
+ "created",
700
+ "updated",
701
+ "published",
702
+ "author",
703
+ "title",
704
+ "relevance"
705
+ ]);
706
+ function resolveSortAlias(alias) {
707
+ if (VALID_SORT_FIELDS.has(alias)) {
708
+ return alias;
709
+ }
710
+ const resolved = SORT_ALIASES[alias];
711
+ if (resolved) {
712
+ return resolved;
713
+ }
714
+ throw new Error(`Unknown sort field: ${alias}`);
715
+ }
716
+ const VALID_LIST_SORT_FIELDS = /* @__PURE__ */ new Set([
717
+ "created",
718
+ "updated",
719
+ "published",
720
+ "author",
721
+ "title",
722
+ "add",
723
+ "mod",
724
+ "pub"
725
+ ]);
692
726
  function getListFormat(options) {
693
727
  if (options.json) return "json";
694
728
  if (options.idsOnly) return "ids-only";
@@ -705,17 +739,58 @@ function validateOptions$1(options) {
705
739
  "Multiple output formats specified. Only one of --json, --ids-only, --uuid, --bibtex can be used."
706
740
  );
707
741
  }
742
+ if (options.sort !== void 0) {
743
+ const sortStr = String(options.sort);
744
+ if (!VALID_LIST_SORT_FIELDS.has(sortStr)) {
745
+ throw new Error(`Invalid sort field: ${sortStr}`);
746
+ }
747
+ }
748
+ if (options.order !== void 0) {
749
+ const result = sortOrderSchema.safeParse(options.order);
750
+ if (!result.success) {
751
+ throw new Error(`Invalid sort order: ${options.order}`);
752
+ }
753
+ }
754
+ const paginationResult = paginationOptionsSchema.safeParse({
755
+ limit: options.limit,
756
+ offset: options.offset
757
+ });
758
+ if (!paginationResult.success) {
759
+ const issue2 = paginationResult.error.issues[0];
760
+ throw new Error(`Invalid pagination option: ${issue2?.message ?? "unknown error"}`);
761
+ }
708
762
  }
709
763
  async function executeList(options, context) {
710
764
  validateOptions$1(options);
711
765
  const format2 = getListFormat(options);
712
- return context.library.list({ format: format2 });
766
+ const sort = options.sort ? resolveSortAlias(options.sort) : void 0;
767
+ return context.library.list({
768
+ format: format2,
769
+ ...sort !== void 0 && { sort },
770
+ ...pickDefined(options, ["order", "limit", "offset"])
771
+ });
713
772
  }
714
- function formatListOutput(result) {
773
+ function formatListOutput(result, isJson = false) {
774
+ if (isJson) {
775
+ return JSON.stringify({
776
+ items: result.items,
777
+ total: result.total,
778
+ limit: result.limit,
779
+ offset: result.offset,
780
+ nextOffset: result.nextOffset
781
+ });
782
+ }
715
783
  if (result.items.length === 0) {
716
784
  return "";
717
785
  }
718
- return result.items.join("\n");
786
+ const lines = [];
787
+ if (result.limit > 0 && result.total > 0) {
788
+ const start = result.offset + 1;
789
+ const end = result.offset + result.items.length;
790
+ lines.push(`# Showing ${start}-${end} of ${result.total} references`);
791
+ }
792
+ lines.push(...result.items);
793
+ return lines.join("\n");
719
794
  }
720
795
  var util$1;
721
796
  (function(util2) {
@@ -12494,6 +12569,9 @@ function mergeCapabilities(base, additional) {
12494
12569
  }
12495
12570
  return result;
12496
12571
  }
12572
+ function getDefaultExportFromCjs(x) {
12573
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
12574
+ }
12497
12575
  var ajv = { exports: {} };
12498
12576
  var core$1 = {};
12499
12577
  var validate = {};
@@ -20856,12 +20934,54 @@ class StdioServerTransport {
20856
20934
  });
20857
20935
  }
20858
20936
  }
20937
+ class OperationsLibrary {
20938
+ constructor(library) {
20939
+ this.library = library;
20940
+ }
20941
+ // ILibrary delegation
20942
+ find(identifier, options) {
20943
+ return this.library.find(identifier, options);
20944
+ }
20945
+ getAll() {
20946
+ return this.library.getAll();
20947
+ }
20948
+ add(item) {
20949
+ return this.library.add(item);
20950
+ }
20951
+ update(idOrUuid, updates, options) {
20952
+ return this.library.update(idOrUuid, updates, options);
20953
+ }
20954
+ remove(identifier, options) {
20955
+ return this.library.remove(identifier, options);
20956
+ }
20957
+ save() {
20958
+ return this.library.save();
20959
+ }
20960
+ // High-level operations
20961
+ async search(options) {
20962
+ const { searchReferences } = await import("./chunks/index-CQO8hLYm.js").then((n) => n.d);
20963
+ return searchReferences(this.library, options);
20964
+ }
20965
+ async list(options) {
20966
+ const { listReferences } = await import("./chunks/index-CQO8hLYm.js").then((n) => n.l);
20967
+ return listReferences(this.library, options ?? {});
20968
+ }
20969
+ async cite(options) {
20970
+ const { citeReferences } = await import("./chunks/index-CQO8hLYm.js").then((n) => n.b);
20971
+ return citeReferences(this.library, options);
20972
+ }
20973
+ async import(inputs, options) {
20974
+ const { addReferences } = await import("./chunks/index-CQO8hLYm.js").then((n) => n.a);
20975
+ return addReferences(inputs, this.library, options ?? {});
20976
+ }
20977
+ }
20859
20978
  async function createMcpContext(options) {
20860
20979
  const config2 = await loadConfig({
20861
20980
  userConfigPath: options.configPath
20862
20981
  });
20863
20982
  const libraryPath = options.libraryPath ?? config2.library;
20864
20983
  const library = await Library.load(libraryPath);
20984
+ const libraryOperations = new OperationsLibrary(library);
20865
20985
  const fileWatcher = new FileWatcher(libraryPath, {
20866
20986
  debounceMs: config2.watch.debounceMs,
20867
20987
  maxRetries: config2.watch.maxRetries,
@@ -20877,13 +20997,13 @@ async function createMcpContext(options) {
20877
20997
  fileWatcher.close();
20878
20998
  };
20879
20999
  return {
20880
- library,
21000
+ libraryOperations,
20881
21001
  config: config2,
20882
21002
  fileWatcher,
20883
21003
  dispose
20884
21004
  };
20885
21005
  }
20886
- function registerReferencesResource(server, getLibrary) {
21006
+ function registerReferencesResource(server, getLibraryOperations) {
20887
21007
  server.registerResource(
20888
21008
  "references",
20889
21009
  "library://references",
@@ -20892,8 +21012,8 @@ function registerReferencesResource(server, getLibrary) {
20892
21012
  mimeType: "application/json"
20893
21013
  },
20894
21014
  async (uri2) => {
20895
- const library = getLibrary();
20896
- const items2 = await library.getAll();
21015
+ const libraryOps = getLibraryOperations();
21016
+ const items2 = await libraryOps.getAll();
20897
21017
  return {
20898
21018
  contents: [
20899
21019
  {
@@ -20906,11 +21026,11 @@ function registerReferencesResource(server, getLibrary) {
20906
21026
  }
20907
21027
  );
20908
21028
  }
20909
- function registerReferenceResource(server, getLibrary) {
21029
+ function registerReferenceResource(server, getLibraryOperations) {
20910
21030
  const template = new ResourceTemplate("library://reference/{id}", {
20911
21031
  list: async () => {
20912
- const library = getLibrary();
20913
- const items2 = await library.getAll();
21032
+ const libraryOps = getLibraryOperations();
21033
+ const items2 = await libraryOps.getAll();
20914
21034
  return {
20915
21035
  resources: items2.map((item) => ({
20916
21036
  uri: `library://reference/${item.id}`,
@@ -20927,9 +21047,9 @@ function registerReferenceResource(server, getLibrary) {
20927
21047
  mimeType: "application/json"
20928
21048
  },
20929
21049
  async (uri2, variables) => {
20930
- const library = getLibrary();
21050
+ const libraryOps = getLibraryOperations();
20931
21051
  const id2 = variables.id;
20932
- const item = await library.find(id2);
21052
+ const item = await libraryOps.find(id2);
20933
21053
  if (!item) {
20934
21054
  throw new Error(`Reference not found: ${id2}`);
20935
21055
  }
@@ -20970,9 +21090,9 @@ function registerStylesResource(server) {
20970
21090
  }
20971
21091
  );
20972
21092
  }
20973
- function registerAllResources(server, getLibrary) {
20974
- registerReferencesResource(server, getLibrary);
20975
- registerReferenceResource(server, getLibrary);
21093
+ function registerAllResources(server, getLibraryOperations) {
21094
+ registerReferencesResource(server, getLibraryOperations);
21095
+ registerReferenceResource(server, getLibraryOperations);
20976
21096
  registerStylesResource(server);
20977
21097
  }
20978
21098
  function formatAddResult(result) {
@@ -21001,7 +21121,7 @@ function formatAddResult(result) {
21001
21121
  }
21002
21122
  return lines.join("\n");
21003
21123
  }
21004
- function registerAddTool(server, getLibrary) {
21124
+ function registerAddTool(server, getLibraryOperations) {
21005
21125
  server.registerTool(
21006
21126
  "add",
21007
21127
  {
@@ -21011,10 +21131,10 @@ function registerAddTool(server, getLibrary) {
21011
21131
  }
21012
21132
  },
21013
21133
  async (args) => {
21014
- const library = getLibrary();
21134
+ const libraryOps = getLibraryOperations();
21015
21135
  const inputs = Array.isArray(args.input) ? args.input : [args.input];
21016
21136
  const stdinContent = inputs.join("\n");
21017
- const result = await addReferences([], library, { stdinContent });
21137
+ const result = await libraryOps.import([], { stdinContent });
21018
21138
  return {
21019
21139
  content: [
21020
21140
  {
@@ -21026,7 +21146,7 @@ function registerAddTool(server, getLibrary) {
21026
21146
  }
21027
21147
  );
21028
21148
  }
21029
- function registerCiteTool(server, getLibrary) {
21149
+ function registerCiteTool(server, getLibraryOperations) {
21030
21150
  server.registerTool(
21031
21151
  "cite",
21032
21152
  {
@@ -21038,8 +21158,8 @@ function registerCiteTool(server, getLibrary) {
21038
21158
  }
21039
21159
  },
21040
21160
  async (args) => {
21041
- const library = getLibrary();
21042
- const result = await citeReferences(library, {
21161
+ const libraryOps = getLibraryOperations();
21162
+ const result = await libraryOps.cite({
21043
21163
  identifiers: args.ids,
21044
21164
  style: args.style ?? "apa",
21045
21165
  format: args.format ?? "text"
@@ -21053,7 +21173,7 @@ function registerCiteTool(server, getLibrary) {
21053
21173
  }
21054
21174
  );
21055
21175
  }
21056
- function registerFulltextAttachTool(server, getLibrary, getConfig) {
21176
+ function registerFulltextAttachTool(server, getLibraryOperations, getConfig) {
21057
21177
  server.registerTool(
21058
21178
  "fulltext_attach",
21059
21179
  {
@@ -21064,9 +21184,9 @@ function registerFulltextAttachTool(server, getLibrary, getConfig) {
21064
21184
  }
21065
21185
  },
21066
21186
  async (args) => {
21067
- const library = getLibrary();
21187
+ const libraryOps = getLibraryOperations();
21068
21188
  const config2 = getConfig();
21069
- const result = await fulltextAttach(library, {
21189
+ const result = await fulltextAttach(libraryOps, {
21070
21190
  identifier: args.id,
21071
21191
  filePath: args.path,
21072
21192
  force: true,
@@ -21090,7 +21210,7 @@ function registerFulltextAttachTool(server, getLibrary, getConfig) {
21090
21210
  }
21091
21211
  );
21092
21212
  }
21093
- function registerFulltextGetTool(server, getLibrary, getConfig) {
21213
+ function registerFulltextGetTool(server, getLibraryOperations, getConfig) {
21094
21214
  server.registerTool(
21095
21215
  "fulltext_get",
21096
21216
  {
@@ -21100,9 +21220,9 @@ function registerFulltextGetTool(server, getLibrary, getConfig) {
21100
21220
  }
21101
21221
  },
21102
21222
  async (args) => {
21103
- const library = getLibrary();
21223
+ const libraryOps = getLibraryOperations();
21104
21224
  const config2 = getConfig();
21105
- const pathResult = await fulltextGet(library, {
21225
+ const pathResult = await fulltextGet(libraryOps, {
21106
21226
  identifier: args.id,
21107
21227
  fulltextDirectory: config2.fulltext.directory
21108
21228
  });
@@ -21114,7 +21234,7 @@ function registerFulltextGetTool(server, getLibrary, getConfig) {
21114
21234
  }
21115
21235
  const responses = [];
21116
21236
  if (pathResult.paths?.markdown) {
21117
- const contentResult = await fulltextGet(library, {
21237
+ const contentResult = await fulltextGet(libraryOps, {
21118
21238
  identifier: args.id,
21119
21239
  type: "markdown",
21120
21240
  stdout: true,
@@ -21143,7 +21263,7 @@ function registerFulltextGetTool(server, getLibrary, getConfig) {
21143
21263
  }
21144
21264
  );
21145
21265
  }
21146
- function registerFulltextDetachTool(server, getLibrary, getConfig) {
21266
+ function registerFulltextDetachTool(server, getLibraryOperations, getConfig) {
21147
21267
  server.registerTool(
21148
21268
  "fulltext_detach",
21149
21269
  {
@@ -21153,9 +21273,9 @@ function registerFulltextDetachTool(server, getLibrary, getConfig) {
21153
21273
  }
21154
21274
  },
21155
21275
  async (args) => {
21156
- const library = getLibrary();
21276
+ const libraryOps = getLibraryOperations();
21157
21277
  const config2 = getConfig();
21158
- const result = await fulltextDetach(library, {
21278
+ const result = await fulltextDetach(libraryOps, {
21159
21279
  identifier: args.id,
21160
21280
  fulltextDirectory: config2.fulltext.directory
21161
21281
  });
@@ -21177,30 +21297,46 @@ function registerFulltextDetachTool(server, getLibrary, getConfig) {
21177
21297
  }
21178
21298
  );
21179
21299
  }
21180
- function registerListTool(server, getLibrary) {
21300
+ function registerListTool(server, getLibraryOperations, getConfig) {
21181
21301
  server.registerTool(
21182
21302
  "list",
21183
21303
  {
21184
- description: "List all references in the library. Supports different output formats.",
21304
+ description: "List references in the library. Supports different output formats, sorting, and pagination.",
21185
21305
  inputSchema: {
21186
- format: z.enum(["json", "bibtex", "pretty"]).optional().describe("Output format: json, bibtex, or pretty (default: pretty)")
21306
+ format: z.enum(["json", "bibtex", "pretty"]).optional().describe("Output format: json, bibtex, or pretty (default: pretty)"),
21307
+ sort: sortFieldSchema.optional().describe("Sort by field: created, updated, published, author, title (default: updated)"),
21308
+ order: sortOrderSchema.optional().describe("Sort order: asc or desc (default: desc)"),
21309
+ limit: z.number().int().min(0).optional().describe("Maximum number of results (0 = no limit)"),
21310
+ offset: z.number().int().min(0).optional().describe("Number of results to skip (default: 0)")
21187
21311
  }
21188
21312
  },
21189
21313
  async (args) => {
21190
- const library = getLibrary();
21191
- const result = await listReferences(library, {
21192
- format: args.format ?? "pretty"
21314
+ const libraryOps = getLibraryOperations();
21315
+ const config2 = getConfig();
21316
+ const limit2 = args.limit ?? config2.mcp.defaultLimit;
21317
+ const result = await libraryOps.list({
21318
+ format: args.format ?? "pretty",
21319
+ limit: limit2,
21320
+ ...pickDefined(args, ["sort", "order", "offset"])
21193
21321
  });
21194
21322
  return {
21195
- content: result.items.map((item) => ({
21196
- type: "text",
21197
- text: item
21198
- }))
21323
+ content: [
21324
+ {
21325
+ type: "text",
21326
+ text: JSON.stringify({
21327
+ total: result.total,
21328
+ limit: result.limit,
21329
+ offset: result.offset,
21330
+ nextOffset: result.nextOffset,
21331
+ items: result.items
21332
+ })
21333
+ }
21334
+ ]
21199
21335
  };
21200
21336
  }
21201
21337
  );
21202
21338
  }
21203
- function registerRemoveTool(server, getLibrary) {
21339
+ function registerRemoveTool(server, getLibraryOperations) {
21204
21340
  server.registerTool(
21205
21341
  "remove",
21206
21342
  {
@@ -21221,8 +21357,8 @@ function registerRemoveTool(server, getLibrary) {
21221
21357
  ]
21222
21358
  };
21223
21359
  }
21224
- const library = getLibrary();
21225
- const result = await removeReference(library, { identifier: args.id });
21360
+ const libraryOps = getLibraryOperations();
21361
+ const result = await libraryOps.remove(args.id);
21226
21362
  if (!result.removed) {
21227
21363
  return {
21228
21364
  content: [
@@ -21246,39 +21382,57 @@ Title: ${title2}`
21246
21382
  }
21247
21383
  );
21248
21384
  }
21249
- function registerSearchTool(server, getLibrary) {
21385
+ function registerSearchTool(server, getLibraryOperations, getConfig) {
21250
21386
  server.registerTool(
21251
21387
  "search",
21252
21388
  {
21253
- description: "Search references in the library. Supports query syntax: author:name, year:YYYY, title:text, type:article-journal, tag:name, or free text for full-text search.",
21389
+ description: "Search references in the library. Supports query syntax: author:name, year:YYYY, title:text, type:article-journal, tag:name, or free text for full-text search. Supports sorting and pagination.",
21254
21390
  inputSchema: {
21255
- query: z.string().describe("Search query string")
21391
+ query: z.string().describe("Search query string"),
21392
+ sort: searchSortFieldSchema.optional().describe(
21393
+ "Sort by field: created, updated, published, author, title, relevance (default: updated)"
21394
+ ),
21395
+ order: sortOrderSchema.optional().describe("Sort order: asc or desc (default: desc)"),
21396
+ limit: z.number().int().min(0).optional().describe("Maximum number of results (0 = no limit)"),
21397
+ offset: z.number().int().min(0).optional().describe("Number of results to skip (default: 0)")
21256
21398
  }
21257
21399
  },
21258
21400
  async (args) => {
21259
- const library = getLibrary();
21260
- const result = await searchReferences(library, {
21401
+ const libraryOps = getLibraryOperations();
21402
+ const config2 = getConfig();
21403
+ const limit2 = args.limit ?? config2.mcp.defaultLimit;
21404
+ const result = await libraryOps.search({
21261
21405
  query: args.query,
21262
- format: "pretty"
21406
+ format: "pretty",
21407
+ limit: limit2,
21408
+ ...pickDefined(args, ["sort", "order", "offset"])
21263
21409
  });
21264
21410
  return {
21265
- content: result.items.map((item) => ({
21266
- type: "text",
21267
- text: item
21268
- }))
21411
+ content: [
21412
+ {
21413
+ type: "text",
21414
+ text: JSON.stringify({
21415
+ total: result.total,
21416
+ limit: result.limit,
21417
+ offset: result.offset,
21418
+ nextOffset: result.nextOffset,
21419
+ items: result.items
21420
+ })
21421
+ }
21422
+ ]
21269
21423
  };
21270
21424
  }
21271
21425
  );
21272
21426
  }
21273
- function registerAllTools(server, getLibrary, getConfig) {
21274
- registerSearchTool(server, getLibrary);
21275
- registerListTool(server, getLibrary);
21276
- registerCiteTool(server, getLibrary);
21277
- registerAddTool(server, getLibrary);
21278
- registerRemoveTool(server, getLibrary);
21279
- registerFulltextAttachTool(server, getLibrary, getConfig);
21280
- registerFulltextGetTool(server, getLibrary, getConfig);
21281
- registerFulltextDetachTool(server, getLibrary, getConfig);
21427
+ function registerAllTools(server, getLibraryOperations, getConfig) {
21428
+ registerSearchTool(server, getLibraryOperations, getConfig);
21429
+ registerListTool(server, getLibraryOperations, getConfig);
21430
+ registerCiteTool(server, getLibraryOperations);
21431
+ registerAddTool(server, getLibraryOperations);
21432
+ registerRemoveTool(server, getLibraryOperations);
21433
+ registerFulltextAttachTool(server, getLibraryOperations, getConfig);
21434
+ registerFulltextGetTool(server, getLibraryOperations, getConfig);
21435
+ registerFulltextDetachTool(server, getLibraryOperations, getConfig);
21282
21436
  }
21283
21437
  async function createMcpServer(options) {
21284
21438
  const serverInfo = {
@@ -21293,10 +21447,10 @@ async function createMcpServer(options) {
21293
21447
  }
21294
21448
  const context = await createMcpContext(contextOptions);
21295
21449
  const server = new McpServer(serverInfo);
21296
- const getLibrary = () => context.library;
21450
+ const getLibraryOperations = () => context.libraryOperations;
21297
21451
  const getConfig = () => context.config;
21298
- registerAllTools(server, getLibrary, getConfig);
21299
- registerAllResources(server, getLibrary);
21452
+ registerAllTools(server, getLibraryOperations, getConfig);
21453
+ registerAllResources(server, getLibraryOperations);
21300
21454
  const transport = new StdioServerTransport(
21301
21455
  options.stdin ?? process.stdin,
21302
21456
  options.stdout ?? process.stdout
@@ -21330,8 +21484,8 @@ async function mcpStart(options) {
21330
21484
  return result;
21331
21485
  }
21332
21486
  async function executeRemove(options, context) {
21333
- const { identifier, byUuid = false } = options;
21334
- return context.library.remove(identifier, { byUuid });
21487
+ const { identifier, idType = "id" } = options;
21488
+ return context.library.remove(identifier, { idType });
21335
21489
  }
21336
21490
  function formatRemoveOutput(result, identifier) {
21337
21491
  if (!result.removed) {
@@ -21378,6 +21532,18 @@ async function deleteFulltextFiles(item, fulltextDirectory) {
21378
21532
  }
21379
21533
  }
21380
21534
  }
21535
+ const VALID_SEARCH_SORT_FIELDS = /* @__PURE__ */ new Set([
21536
+ "created",
21537
+ "updated",
21538
+ "published",
21539
+ "author",
21540
+ "title",
21541
+ "relevance",
21542
+ "add",
21543
+ "mod",
21544
+ "pub",
21545
+ "rel"
21546
+ ]);
21381
21547
  function getSearchFormat(options) {
21382
21548
  if (options.json) return "json";
21383
21549
  if (options.idsOnly) return "ids-only";
@@ -21394,17 +21560,59 @@ function validateOptions(options) {
21394
21560
  "Multiple output formats specified. Only one of --json, --ids-only, --uuid, --bibtex can be used."
21395
21561
  );
21396
21562
  }
21563
+ if (options.sort !== void 0) {
21564
+ const sortStr = String(options.sort);
21565
+ if (!VALID_SEARCH_SORT_FIELDS.has(sortStr)) {
21566
+ throw new Error(`Invalid sort field: ${sortStr}`);
21567
+ }
21568
+ }
21569
+ if (options.order !== void 0) {
21570
+ const result = sortOrderSchema.safeParse(options.order);
21571
+ if (!result.success) {
21572
+ throw new Error(`Invalid sort order: ${options.order}`);
21573
+ }
21574
+ }
21575
+ const paginationResult = paginationOptionsSchema.safeParse({
21576
+ limit: options.limit,
21577
+ offset: options.offset
21578
+ });
21579
+ if (!paginationResult.success) {
21580
+ const issue2 = paginationResult.error.issues[0];
21581
+ throw new Error(`Invalid pagination option: ${issue2?.message ?? "unknown error"}`);
21582
+ }
21397
21583
  }
21398
21584
  async function executeSearch(options, context) {
21399
21585
  validateOptions(options);
21400
21586
  const format2 = getSearchFormat(options);
21401
- return context.library.search({ query: options.query, format: format2 });
21587
+ const sort = options.sort ? resolveSortAlias(options.sort) : void 0;
21588
+ return context.library.search({
21589
+ query: options.query,
21590
+ format: format2,
21591
+ ...sort !== void 0 && { sort },
21592
+ ...pickDefined(options, ["order", "limit", "offset"])
21593
+ });
21402
21594
  }
21403
- function formatSearchOutput(result) {
21595
+ function formatSearchOutput(result, isJson = false) {
21596
+ if (isJson) {
21597
+ return JSON.stringify({
21598
+ items: result.items,
21599
+ total: result.total,
21600
+ limit: result.limit,
21601
+ offset: result.offset,
21602
+ nextOffset: result.nextOffset
21603
+ });
21604
+ }
21404
21605
  if (result.items.length === 0) {
21405
21606
  return "";
21406
21607
  }
21407
- return result.items.join("\n");
21608
+ const lines = [];
21609
+ if (result.limit > 0 && result.total > 0) {
21610
+ const start = result.offset + 1;
21611
+ const end = result.offset + result.items.length;
21612
+ lines.push(`# Showing ${start}-${end} of ${result.total} references`);
21613
+ }
21614
+ lines.push(...result.items);
21615
+ return lines.join("\n");
21408
21616
  }
21409
21617
  async function serverStart(options) {
21410
21618
  const existingStatus = await serverStatus(options.portfilePath);
@@ -21491,8 +21699,8 @@ async function serverStatus(portfilePath) {
21491
21699
  return result;
21492
21700
  }
21493
21701
  async function executeUpdate(options, context) {
21494
- const { identifier, updates, byUuid = false } = options;
21495
- return context.library.update(identifier, updates, { byUuid });
21702
+ const { identifier, updates, idType = "id" } = options;
21703
+ return context.library.update(identifier, updates, { idType });
21496
21704
  }
21497
21705
  function formatUpdateOutput(result, identifier) {
21498
21706
  if (!result.updated) {
@@ -21513,47 +21721,6 @@ function formatUpdateOutput(result, identifier) {
21513
21721
  }
21514
21722
  return parts.join("\n");
21515
21723
  }
21516
- class OperationsLibrary {
21517
- constructor(library) {
21518
- this.library = library;
21519
- }
21520
- // ILibrary delegation
21521
- find(identifier, options) {
21522
- return this.library.find(identifier, options);
21523
- }
21524
- getAll() {
21525
- return this.library.getAll();
21526
- }
21527
- add(item) {
21528
- return this.library.add(item);
21529
- }
21530
- update(idOrUuid, updates, options) {
21531
- return this.library.update(idOrUuid, updates, options);
21532
- }
21533
- remove(identifier, options) {
21534
- return this.library.remove(identifier, options);
21535
- }
21536
- save() {
21537
- return this.library.save();
21538
- }
21539
- // High-level operations
21540
- async search(options) {
21541
- const { searchReferences: searchReferences2 } = await import("./chunks/index-D4Q13N-R.js").then((n) => n.i);
21542
- return searchReferences2(this.library, options);
21543
- }
21544
- async list(options) {
21545
- const { listReferences: listReferences2 } = await import("./chunks/index-D4Q13N-R.js").then((n) => n.h);
21546
- return listReferences2(this.library, options ?? {});
21547
- }
21548
- async cite(options) {
21549
- const { citeReferences: citeReferences2 } = await import("./chunks/index-D4Q13N-R.js").then((n) => n.f);
21550
- return citeReferences2(this.library, options);
21551
- }
21552
- async import(inputs, options) {
21553
- const { addReferences: addReferences2 } = await import("./chunks/index-D4Q13N-R.js").then((n) => n.e);
21554
- return addReferences2(inputs, this.library, options ?? {});
21555
- }
21556
- }
21557
21724
  class ServerClient {
21558
21725
  constructor(baseUrl) {
21559
21726
  this.baseUrl = baseUrl;
@@ -21576,12 +21743,12 @@ class ServerClient {
21576
21743
  /**
21577
21744
  * Find reference by citation ID or UUID.
21578
21745
  * @param identifier - Citation ID or UUID
21579
- * @param options - Find options (byUuid to use UUID lookup)
21746
+ * @param options - Find options (idType to specify identifier type)
21580
21747
  * @returns CSL item or undefined if not found
21581
21748
  */
21582
21749
  async find(identifier, options = {}) {
21583
- const { byUuid = false } = options;
21584
- const url = byUuid ? `${this.baseUrl}/api/references/uuid/${encodeURIComponent(identifier)}` : `${this.baseUrl}/api/references/id/${encodeURIComponent(identifier)}`;
21750
+ const { idType = "id" } = options;
21751
+ const url = idType === "uuid" ? `${this.baseUrl}/api/references/uuid/${encodeURIComponent(identifier)}` : `${this.baseUrl}/api/references/id/${encodeURIComponent(identifier)}`;
21585
21752
  const response = await fetch(url);
21586
21753
  if (response.status === 404) {
21587
21754
  return void 0;
@@ -21615,12 +21782,12 @@ class ServerClient {
21615
21782
  * Update reference by citation ID or UUID.
21616
21783
  * @param identifier - Citation ID or UUID
21617
21784
  * @param updates - Partial CSL item with fields to update
21618
- * @param options - Update options (byUuid to use UUID lookup, onIdCollision for collision handling)
21785
+ * @param options - Update options (idType to specify identifier type, onIdCollision for collision handling)
21619
21786
  * @returns Update result with updated item, success status, and any ID changes
21620
21787
  */
21621
21788
  async update(identifier, updates, options) {
21622
- const { byUuid = false, onIdCollision } = options ?? {};
21623
- const url = byUuid ? `${this.baseUrl}/api/references/uuid/${encodeURIComponent(identifier)}` : `${this.baseUrl}/api/references/id/${encodeURIComponent(identifier)}`;
21789
+ const { idType = "id", onIdCollision } = options ?? {};
21790
+ const url = idType === "uuid" ? `${this.baseUrl}/api/references/uuid/${encodeURIComponent(identifier)}` : `${this.baseUrl}/api/references/id/${encodeURIComponent(identifier)}`;
21624
21791
  const response = await fetch(url, {
21625
21792
  method: "PUT",
21626
21793
  headers: { "Content-Type": "application/json" },
@@ -21640,12 +21807,12 @@ class ServerClient {
21640
21807
  /**
21641
21808
  * Remove a reference by citation ID or UUID.
21642
21809
  * @param identifier - The citation ID or UUID of the reference to remove
21643
- * @param options - Remove options (byUuid to use UUID lookup)
21810
+ * @param options - Remove options (idType to specify identifier type)
21644
21811
  * @returns Remove result with removed status and removedItem
21645
21812
  */
21646
21813
  async remove(identifier, options = {}) {
21647
- const { byUuid = false } = options;
21648
- const url = byUuid ? `${this.baseUrl}/api/references/uuid/${encodeURIComponent(identifier)}` : `${this.baseUrl}/api/references/id/${encodeURIComponent(identifier)}`;
21814
+ const { idType = "id" } = options;
21815
+ const url = idType === "uuid" ? `${this.baseUrl}/api/references/uuid/${encodeURIComponent(identifier)}` : `${this.baseUrl}/api/references/id/${encodeURIComponent(identifier)}`;
21649
21816
  const response = await fetch(url, {
21650
21817
  method: "DELETE"
21651
21818
  });
@@ -21783,6 +21950,199 @@ async function waitForPortfile(timeoutMs) {
21783
21950
  }
21784
21951
  throw new Error(`Server failed to start: portfile not created within ${timeoutMs}ms`);
21785
21952
  }
21953
+ const SEARCH_SORT_FIELDS = searchSortFieldSchema.options;
21954
+ const SORT_ORDERS = sortOrderSchema.options;
21955
+ const CITATION_FORMATS = ["text", "html", "rtf"];
21956
+ const LOG_LEVELS = ["silent", "info", "debug"];
21957
+ const OPTION_VALUES = {
21958
+ "--sort": SEARCH_SORT_FIELDS,
21959
+ // search includes 'relevance'
21960
+ "--order": SORT_ORDERS,
21961
+ "--format": CITATION_FORMATS,
21962
+ "--style": BUILTIN_STYLES,
21963
+ "--log-level": LOG_LEVELS
21964
+ };
21965
+ const ID_COMPLETION_COMMANDS = /* @__PURE__ */ new Set(["cite", "remove", "update"]);
21966
+ const ID_COMPLETION_FULLTEXT_SUBCOMMANDS = /* @__PURE__ */ new Set(["attach", "get", "detach"]);
21967
+ function toCompletionItems(values) {
21968
+ return values.map((name2) => ({ name: name2 }));
21969
+ }
21970
+ function extractSubcommands(program) {
21971
+ return program.commands.map((cmd) => ({
21972
+ name: cmd.name(),
21973
+ description: cmd.description()
21974
+ }));
21975
+ }
21976
+ function extractOptions(cmd) {
21977
+ const options = [];
21978
+ for (const opt of cmd.options) {
21979
+ const longFlag = opt.long;
21980
+ const shortFlag = opt.short;
21981
+ const description2 = opt.description;
21982
+ if (longFlag) {
21983
+ options.push({ name: longFlag, description: description2 });
21984
+ }
21985
+ if (shortFlag) {
21986
+ options.push({ name: shortFlag, description: description2 });
21987
+ }
21988
+ }
21989
+ return options;
21990
+ }
21991
+ function extractGlobalOptions(program) {
21992
+ const options = extractOptions(program);
21993
+ options.push({ name: "--help", description: "display help for command" });
21994
+ options.push({ name: "--version", description: "output the version number" });
21995
+ return options;
21996
+ }
21997
+ function findSubcommand(program, name2) {
21998
+ return program.commands.find((cmd) => cmd.name() === name2);
21999
+ }
22000
+ function getCompletions(env, program) {
22001
+ const { line, prev, last } = env;
22002
+ const words = line.trim().split(/\s+/);
22003
+ const args = words.slice(1);
22004
+ const subcommands = extractSubcommands(program);
22005
+ const globalOptions = extractGlobalOptions(program);
22006
+ if (args.length === 0) {
22007
+ return subcommands;
22008
+ }
22009
+ const firstArg = args[0] ?? "";
22010
+ if (prev?.startsWith("-")) {
22011
+ const optionValues = OPTION_VALUES[prev];
22012
+ if (optionValues) {
22013
+ return toCompletionItems(optionValues);
22014
+ }
22015
+ }
22016
+ if (last.startsWith("-")) {
22017
+ const subCmd = findSubcommand(program, firstArg);
22018
+ const commandOptions = subCmd ? extractOptions(subCmd) : [];
22019
+ return [...commandOptions, ...globalOptions];
22020
+ }
22021
+ const parentCmd = findSubcommand(program, firstArg);
22022
+ if (parentCmd && parentCmd.commands.length > 0) {
22023
+ const nestedSubcommands = extractSubcommands(parentCmd);
22024
+ if (args.length === 1 || args.length === 2 && !last.startsWith("-")) {
22025
+ return nestedSubcommands;
22026
+ }
22027
+ }
22028
+ if (args.length === 1) {
22029
+ return subcommands.filter((cmd) => cmd.name.startsWith(last));
22030
+ }
22031
+ return subcommands;
22032
+ }
22033
+ function needsIdCompletion(env) {
22034
+ const { line, prev } = env;
22035
+ const words = line.trim().split(/\s+/);
22036
+ const args = words.slice(1);
22037
+ if (args.length === 0) {
22038
+ return { needs: false };
22039
+ }
22040
+ const command = args[0] ?? "";
22041
+ if (prev?.startsWith("-")) {
22042
+ return { needs: false };
22043
+ }
22044
+ if (command === "fulltext" && args.length >= 2) {
22045
+ const subcommand = args[1] ?? "";
22046
+ if (ID_COMPLETION_FULLTEXT_SUBCOMMANDS.has(subcommand)) {
22047
+ return { needs: true, command, subcommand };
22048
+ }
22049
+ return { needs: false };
22050
+ }
22051
+ if (ID_COMPLETION_COMMANDS.has(command)) {
22052
+ return { needs: true, command };
22053
+ }
22054
+ return { needs: false };
22055
+ }
22056
+ const MAX_ID_COMPLETIONS = 100;
22057
+ async function getLibraryForCompletion() {
22058
+ try {
22059
+ const config2 = loadConfig();
22060
+ const server = await getServerConnection(config2.library, config2);
22061
+ if (server) {
22062
+ return new ServerClient(server.baseUrl);
22063
+ }
22064
+ return await Library.load(config2.library);
22065
+ } catch {
22066
+ return void 0;
22067
+ }
22068
+ }
22069
+ function truncateTitle(title2, maxLength = 40) {
22070
+ if (!title2) return "";
22071
+ if (title2.length <= maxLength) return title2;
22072
+ return `${title2.slice(0, maxLength - 3)}...`;
22073
+ }
22074
+ async function getIdCompletions(library, prefix) {
22075
+ try {
22076
+ const items2 = await library.getAll();
22077
+ const completions = [];
22078
+ for (const item of items2) {
22079
+ const id2 = item.id;
22080
+ if (!id2) continue;
22081
+ if (prefix && !id2.toLowerCase().startsWith(prefix.toLowerCase())) {
22082
+ continue;
22083
+ }
22084
+ completions.push({
22085
+ name: id2,
22086
+ description: truncateTitle(item.title)
22087
+ });
22088
+ if (completions.length >= MAX_ID_COMPLETIONS) {
22089
+ break;
22090
+ }
22091
+ }
22092
+ return completions;
22093
+ } catch {
22094
+ return [];
22095
+ }
22096
+ }
22097
+ async function installCompletion() {
22098
+ const tabtab = await import("tabtab");
22099
+ await tabtab.install({
22100
+ name: "ref",
22101
+ completer: "ref"
22102
+ });
22103
+ }
22104
+ async function uninstallCompletion() {
22105
+ const tabtab = await import("tabtab");
22106
+ await tabtab.uninstall({
22107
+ name: "ref"
22108
+ });
22109
+ }
22110
+ async function handleCompletion(program) {
22111
+ const tabtab = await import("tabtab");
22112
+ const env = tabtab.parseEnv(process.env);
22113
+ if (!env.complete) {
22114
+ return;
22115
+ }
22116
+ if (env.last.startsWith("-")) {
22117
+ const completions2 = getCompletions(env, program);
22118
+ tabtab.log(completions2);
22119
+ return;
22120
+ }
22121
+ const idContext = needsIdCompletion(env);
22122
+ if (idContext.needs) {
22123
+ const library = await getLibraryForCompletion();
22124
+ if (library) {
22125
+ const idCompletions = await getIdCompletions(library, env.last);
22126
+ tabtab.log(idCompletions);
22127
+ return;
22128
+ }
22129
+ }
22130
+ const completions = getCompletions(env, program);
22131
+ tabtab.log(completions);
22132
+ }
22133
+ function registerCompletionCommand(program) {
22134
+ program.command("completion").description("Install or uninstall shell completion").argument("[action]", "Action to perform (install or uninstall)", "install").action(async (action) => {
22135
+ if (action === "install") {
22136
+ await installCompletion();
22137
+ } else if (action === "uninstall") {
22138
+ await uninstallCompletion();
22139
+ } else {
22140
+ console.error(`Unknown action: ${action}`);
22141
+ console.error("Usage: ref completion [install|uninstall]");
22142
+ process.exit(1);
22143
+ }
22144
+ });
22145
+ }
21786
22146
  async function createExecutionContext(config2, loadLibrary) {
21787
22147
  const server = await getServerConnection(config2.library, config2);
21788
22148
  if (server) {
@@ -21894,6 +22254,7 @@ function createProgram() {
21894
22254
  registerServerCommand(program);
21895
22255
  registerFulltextCommand(program);
21896
22256
  registerMcpCommand(program);
22257
+ registerCompletionCommand(program);
21897
22258
  return program;
21898
22259
  }
21899
22260
  async function handleListAction(options, program) {
@@ -21902,7 +22263,7 @@ async function handleListAction(options, program) {
21902
22263
  const config2 = await loadConfigWithOverrides({ ...globalOpts, ...options });
21903
22264
  const context = await createExecutionContext(config2, Library.load);
21904
22265
  const result = await executeList(options, context);
21905
- const output = formatListOutput(result);
22266
+ const output = formatListOutput(result, options.json ?? false);
21906
22267
  if (output) {
21907
22268
  process.stdout.write(`${output}
21908
22269
  `);
@@ -21915,7 +22276,7 @@ async function handleListAction(options, program) {
21915
22276
  }
21916
22277
  }
21917
22278
  function registerListCommand(program) {
21918
- program.command("list").description("List all references in the library").option("--json", "Output in JSON format").option("--ids-only", "Output only citation keys").option("--uuid", "Output only UUIDs").option("--bibtex", "Output in BibTeX format").action(async (options) => {
22279
+ program.command("list").description("List all references in the library").option("--json", "Output in JSON format").option("--ids-only", "Output only citation keys").option("--uuid", "Output only UUIDs").option("--bibtex", "Output in BibTeX format").option("--sort <field>", "Sort by field: created|updated|published|author|title").option("--order <order>", "Sort order: asc|desc").option("-n, --limit <n>", "Maximum number of results", Number.parseInt).option("--offset <n>", "Number of results to skip", Number.parseInt).action(async (options) => {
21919
22280
  await handleListAction(options, program);
21920
22281
  });
21921
22282
  }
@@ -21925,7 +22286,7 @@ async function handleSearchAction(query, options, program) {
21925
22286
  const config2 = await loadConfigWithOverrides({ ...globalOpts, ...options });
21926
22287
  const context = await createExecutionContext(config2, Library.load);
21927
22288
  const result = await executeSearch({ ...options, query }, context);
21928
- const output = formatSearchOutput(result);
22289
+ const output = formatSearchOutput(result, options.json ?? false);
21929
22290
  if (output) {
21930
22291
  process.stdout.write(`${output}
21931
22292
  `);
@@ -21938,7 +22299,7 @@ async function handleSearchAction(query, options, program) {
21938
22299
  }
21939
22300
  }
21940
22301
  function registerSearchCommand(program) {
21941
- program.command("search").description("Search references").argument("<query>", "Search query").option("--json", "Output in JSON format").option("--ids-only", "Output only citation keys").option("--uuid", "Output only UUIDs").option("--bibtex", "Output in BibTeX format").action(async (query, options) => {
22302
+ program.command("search").description("Search references").argument("<query>", "Search query").option("--json", "Output in JSON format").option("--ids-only", "Output only citation keys").option("--uuid", "Output only UUIDs").option("--bibtex", "Output in BibTeX format").option("--sort <field>", "Sort by field: created|updated|published|author|title|relevance").option("--order <order>", "Sort order: asc|desc").option("-n, --limit <n>", "Maximum number of results", Number.parseInt).option("--offset <n>", "Number of results to skip", Number.parseInt).action(async (query, options) => {
21942
22303
  await handleSearchAction(query, options, program);
21943
22304
  });
21944
22305
  }
@@ -21987,12 +22348,16 @@ async function handleAddAction(inputs, options, program) {
21987
22348
  }
21988
22349
  }
21989
22350
  function registerAddCommand(program) {
21990
- program.command("add").description("Add new reference(s) to the library").argument("[input...]", "File paths or identifiers (PMID/DOI), or use stdin").option("-f, --force", "Skip duplicate detection").option("--format <format>", "Explicit input format: json|bibtex|ris|pmid|doi|auto", "auto").option("--verbose", "Show detailed error information").action(async (inputs, options) => {
22351
+ program.command("add").description("Add new reference(s) to the library").argument("[input...]", "File paths or identifiers (PMID/DOI/ISBN), or use stdin").option("-f, --force", "Skip duplicate detection").option(
22352
+ "--format <format>",
22353
+ "Explicit input format: json|bibtex|ris|pmid|doi|isbn|auto",
22354
+ "auto"
22355
+ ).option("--verbose", "Show detailed error information").action(async (inputs, options) => {
21991
22356
  await handleAddAction(inputs, options, program);
21992
22357
  });
21993
22358
  }
21994
- async function findReferenceToRemove(identifier, byUuid, context) {
21995
- return context.library.find(identifier, { byUuid });
22359
+ async function findReferenceToRemove(identifier, useUuid, context) {
22360
+ return context.library.find(identifier, { idType: useUuid ? "uuid" : "id" });
21996
22361
  }
21997
22362
  async function confirmRemoval(refToRemove, force, fulltextWarning) {
21998
22363
  if (force || !isTTY()) {
@@ -22060,8 +22425,8 @@ async function handleRemoveAction(identifier, options, program) {
22060
22425
  const removeOptions = {
22061
22426
  identifier
22062
22427
  };
22063
- if (options.uuid !== void 0) {
22064
- removeOptions.byUuid = options.uuid;
22428
+ if (options.uuid) {
22429
+ removeOptions.idType = "uuid";
22065
22430
  }
22066
22431
  const result = await executeRemove(removeOptions, context);
22067
22432
  const output = formatRemoveOutput(result, identifier);
@@ -22115,8 +22480,8 @@ async function handleUpdateAction(identifier, file, options, program) {
22115
22480
  identifier,
22116
22481
  updates: validatedUpdates
22117
22482
  };
22118
- if (options.uuid !== void 0) {
22119
- updateOptions.byUuid = options.uuid;
22483
+ if (options.uuid) {
22484
+ updateOptions.idType = "uuid";
22120
22485
  }
22121
22486
  const result = await executeUpdate(updateOptions, context);
22122
22487
  const output = formatUpdateOutput(result, identifier);
@@ -22296,7 +22661,7 @@ async function handleFulltextAttachAction(identifier, filePathArg, options, prog
22296
22661
  ...type2 && { type: type2 },
22297
22662
  ...options.move && { move: options.move },
22298
22663
  ...options.force && { force: options.force },
22299
- ...options.uuid && { byUuid: options.uuid },
22664
+ ...options.uuid && { idType: "uuid" },
22300
22665
  ...stdinContent && { stdinContent }
22301
22666
  };
22302
22667
  const result = await executeFulltextAttach(attachOptions, context);
@@ -22321,7 +22686,7 @@ async function handleFulltextGetAction(identifier, options, program) {
22321
22686
  ...options.pdf && { type: "pdf" },
22322
22687
  ...options.markdown && { type: "markdown" },
22323
22688
  ...options.stdout && { stdout: options.stdout },
22324
- ...options.uuid && { byUuid: options.uuid }
22689
+ ...options.uuid && { idType: "uuid" }
22325
22690
  };
22326
22691
  const result = await executeFulltextGet(getOptions, context);
22327
22692
  if (result.success && result.content && options.stdout) {
@@ -22355,7 +22720,7 @@ async function handleFulltextDetachAction(identifier, options, program) {
22355
22720
  ...options.markdown && { type: "markdown" },
22356
22721
  ...options.delete && { delete: options.delete },
22357
22722
  ...options.force && { force: options.force },
22358
- ...options.uuid && { byUuid: options.uuid }
22723
+ ...options.uuid && { idType: "uuid" }
22359
22724
  };
22360
22725
  const result = await executeFulltextDetach(detachOptions, context);
22361
22726
  const output = formatFulltextDetachOutput(result);
@@ -22382,6 +22747,10 @@ function registerFulltextCommand(program) {
22382
22747
  }
22383
22748
  async function main(argv) {
22384
22749
  const program = createProgram();
22750
+ if (process.env.COMP_LINE) {
22751
+ await handleCompletion(program);
22752
+ return;
22753
+ }
22385
22754
  process.on("SIGINT", () => {
22386
22755
  process.exit(130);
22387
22756
  });