@ncukondo/reference-manager 0.5.3 → 0.7.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/README.md +93 -0
- package/dist/chunks/action-menu-CTtINmWd.js +118 -0
- package/dist/chunks/action-menu-CTtINmWd.js.map +1 -0
- package/dist/chunks/{file-watcher-CBAbblss.js → file-watcher-D7oyc-9z.js} +55 -11
- package/dist/chunks/file-watcher-D7oyc-9z.js.map +1 -0
- package/dist/chunks/{index-Bl_mOQRe.js → index-_7NEUoS7.js} +271 -136
- package/dist/chunks/index-_7NEUoS7.js.map +1 -0
- package/dist/chunks/{loader-DuzyKV70.js → loader-BItrdVWG.js} +149 -26
- package/dist/chunks/loader-BItrdVWG.js.map +1 -0
- package/dist/chunks/search-prompt-D67WyKrY.js +179 -0
- package/dist/chunks/search-prompt-D67WyKrY.js.map +1 -0
- package/dist/chunks/tty-CDBIQraQ.js +17 -0
- package/dist/chunks/tty-CDBIQraQ.js.map +1 -0
- package/dist/cli/commands/list.d.ts +7 -1
- package/dist/cli/commands/list.d.ts.map +1 -1
- package/dist/cli/commands/search.d.ts +26 -1
- package/dist/cli/commands/search.d.ts.map +1 -1
- package/dist/cli/completion.d.ts +65 -0
- package/dist/cli/completion.d.ts.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli.js +535 -119
- package/dist/cli.js.map +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/schema.d.ts +97 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/features/import/importer.d.ts.map +1 -1
- package/dist/features/interactive/action-menu.d.ts +56 -0
- package/dist/features/interactive/action-menu.d.ts.map +1 -0
- package/dist/features/interactive/debounce.d.ts +22 -0
- package/dist/features/interactive/debounce.d.ts.map +1 -0
- package/dist/features/interactive/format.d.ts +52 -0
- package/dist/features/interactive/format.d.ts.map +1 -0
- package/dist/features/interactive/search-prompt.d.ts +47 -0
- package/dist/features/interactive/search-prompt.d.ts.map +1 -0
- package/dist/features/interactive/tty.d.ts +20 -0
- package/dist/features/interactive/tty.d.ts.map +1 -0
- package/dist/features/operations/add.d.ts.map +1 -1
- package/dist/features/operations/list.d.ts +12 -3
- package/dist/features/operations/list.d.ts.map +1 -1
- package/dist/features/operations/search.d.ts +19 -3
- package/dist/features/operations/search.d.ts.map +1 -1
- package/dist/features/pagination/aliases.d.ts +14 -0
- package/dist/features/pagination/aliases.d.ts.map +1 -0
- package/dist/features/pagination/index.d.ts +8 -0
- package/dist/features/pagination/index.d.ts.map +1 -0
- package/dist/features/pagination/paginate.d.ts +20 -0
- package/dist/features/pagination/paginate.d.ts.map +1 -0
- package/dist/features/pagination/sorter.d.ts +16 -0
- package/dist/features/pagination/sorter.d.ts.map +1 -0
- package/dist/features/pagination/types.d.ts +74 -0
- package/dist/features/pagination/types.d.ts.map +1 -0
- package/dist/index.js +13 -12
- package/dist/index.js.map +1 -1
- package/dist/mcp/context.d.ts +4 -4
- package/dist/mcp/context.d.ts.map +1 -1
- package/dist/mcp/resources/index.d.ts +3 -3
- package/dist/mcp/resources/index.d.ts.map +1 -1
- package/dist/mcp/resources/library.d.ts +5 -5
- package/dist/mcp/resources/library.d.ts.map +1 -1
- package/dist/mcp/tools/add.d.ts +3 -3
- package/dist/mcp/tools/add.d.ts.map +1 -1
- package/dist/mcp/tools/cite.d.ts +3 -3
- package/dist/mcp/tools/cite.d.ts.map +1 -1
- package/dist/mcp/tools/fulltext.d.ts +7 -7
- package/dist/mcp/tools/fulltext.d.ts.map +1 -1
- package/dist/mcp/tools/index.d.ts +3 -3
- package/dist/mcp/tools/index.d.ts.map +1 -1
- package/dist/mcp/tools/list.d.ts +10 -3
- package/dist/mcp/tools/list.d.ts.map +1 -1
- package/dist/mcp/tools/remove.d.ts +3 -3
- package/dist/mcp/tools/remove.d.ts.map +1 -1
- package/dist/mcp/tools/search.d.ts +10 -3
- package/dist/mcp/tools/search.d.ts.map +1 -1
- package/dist/server/routes/list.d.ts +13 -0
- package/dist/server/routes/list.d.ts.map +1 -1
- package/dist/server/routes/search.d.ts +14 -0
- package/dist/server/routes/search.d.ts.map +1 -1
- package/dist/server.js +4 -4
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/object.d.ts +16 -0
- package/dist/utils/object.d.ts.map +1 -0
- package/package.json +4 -1
- package/dist/chunks/file-watcher-CBAbblss.js.map +0 -1
- package/dist/chunks/index-Bl_mOQRe.js.map +0 -1
- 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-
|
|
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-D7oyc-9z.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, B as BUILTIN_STYLES,
|
|
10
|
+
import { u as updateReference, B as BUILTIN_STYLES, s as startServerWithFileWatcher } from "./chunks/index-_7NEUoS7.js";
|
|
11
11
|
import process$1, { stdin, stdout } from "node:process";
|
|
12
|
-
import { l as loadConfig } from "./chunks/loader-
|
|
12
|
+
import { l as loadConfig } from "./chunks/loader-BItrdVWG.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.
|
|
16
|
+
const version$1 = "0.7.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,
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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) {
|
|
@@ -20859,12 +20934,54 @@ class StdioServerTransport {
|
|
|
20859
20934
|
});
|
|
20860
20935
|
}
|
|
20861
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-_7NEUoS7.js").then((n) => n.e);
|
|
20963
|
+
return searchReferences(this.library, options);
|
|
20964
|
+
}
|
|
20965
|
+
async list(options) {
|
|
20966
|
+
const { listReferences } = await import("./chunks/index-_7NEUoS7.js").then((n) => n.l);
|
|
20967
|
+
return listReferences(this.library, options ?? {});
|
|
20968
|
+
}
|
|
20969
|
+
async cite(options) {
|
|
20970
|
+
const { citeReferences } = await import("./chunks/index-_7NEUoS7.js").then((n) => n.d);
|
|
20971
|
+
return citeReferences(this.library, options);
|
|
20972
|
+
}
|
|
20973
|
+
async import(inputs, options) {
|
|
20974
|
+
const { addReferences } = await import("./chunks/index-_7NEUoS7.js").then((n) => n.b);
|
|
20975
|
+
return addReferences(inputs, this.library, options ?? {});
|
|
20976
|
+
}
|
|
20977
|
+
}
|
|
20862
20978
|
async function createMcpContext(options) {
|
|
20863
20979
|
const config2 = await loadConfig({
|
|
20864
20980
|
userConfigPath: options.configPath
|
|
20865
20981
|
});
|
|
20866
20982
|
const libraryPath = options.libraryPath ?? config2.library;
|
|
20867
20983
|
const library = await Library.load(libraryPath);
|
|
20984
|
+
const libraryOperations = new OperationsLibrary(library);
|
|
20868
20985
|
const fileWatcher = new FileWatcher(libraryPath, {
|
|
20869
20986
|
debounceMs: config2.watch.debounceMs,
|
|
20870
20987
|
maxRetries: config2.watch.maxRetries,
|
|
@@ -20880,13 +20997,13 @@ async function createMcpContext(options) {
|
|
|
20880
20997
|
fileWatcher.close();
|
|
20881
20998
|
};
|
|
20882
20999
|
return {
|
|
20883
|
-
|
|
21000
|
+
libraryOperations,
|
|
20884
21001
|
config: config2,
|
|
20885
21002
|
fileWatcher,
|
|
20886
21003
|
dispose
|
|
20887
21004
|
};
|
|
20888
21005
|
}
|
|
20889
|
-
function registerReferencesResource(server,
|
|
21006
|
+
function registerReferencesResource(server, getLibraryOperations) {
|
|
20890
21007
|
server.registerResource(
|
|
20891
21008
|
"references",
|
|
20892
21009
|
"library://references",
|
|
@@ -20895,8 +21012,8 @@ function registerReferencesResource(server, getLibrary) {
|
|
|
20895
21012
|
mimeType: "application/json"
|
|
20896
21013
|
},
|
|
20897
21014
|
async (uri2) => {
|
|
20898
|
-
const
|
|
20899
|
-
const items2 = await
|
|
21015
|
+
const libraryOps = getLibraryOperations();
|
|
21016
|
+
const items2 = await libraryOps.getAll();
|
|
20900
21017
|
return {
|
|
20901
21018
|
contents: [
|
|
20902
21019
|
{
|
|
@@ -20909,11 +21026,11 @@ function registerReferencesResource(server, getLibrary) {
|
|
|
20909
21026
|
}
|
|
20910
21027
|
);
|
|
20911
21028
|
}
|
|
20912
|
-
function registerReferenceResource(server,
|
|
21029
|
+
function registerReferenceResource(server, getLibraryOperations) {
|
|
20913
21030
|
const template = new ResourceTemplate("library://reference/{id}", {
|
|
20914
21031
|
list: async () => {
|
|
20915
|
-
const
|
|
20916
|
-
const items2 = await
|
|
21032
|
+
const libraryOps = getLibraryOperations();
|
|
21033
|
+
const items2 = await libraryOps.getAll();
|
|
20917
21034
|
return {
|
|
20918
21035
|
resources: items2.map((item) => ({
|
|
20919
21036
|
uri: `library://reference/${item.id}`,
|
|
@@ -20930,9 +21047,9 @@ function registerReferenceResource(server, getLibrary) {
|
|
|
20930
21047
|
mimeType: "application/json"
|
|
20931
21048
|
},
|
|
20932
21049
|
async (uri2, variables) => {
|
|
20933
|
-
const
|
|
21050
|
+
const libraryOps = getLibraryOperations();
|
|
20934
21051
|
const id2 = variables.id;
|
|
20935
|
-
const item = await
|
|
21052
|
+
const item = await libraryOps.find(id2);
|
|
20936
21053
|
if (!item) {
|
|
20937
21054
|
throw new Error(`Reference not found: ${id2}`);
|
|
20938
21055
|
}
|
|
@@ -20973,9 +21090,9 @@ function registerStylesResource(server) {
|
|
|
20973
21090
|
}
|
|
20974
21091
|
);
|
|
20975
21092
|
}
|
|
20976
|
-
function registerAllResources(server,
|
|
20977
|
-
registerReferencesResource(server,
|
|
20978
|
-
registerReferenceResource(server,
|
|
21093
|
+
function registerAllResources(server, getLibraryOperations) {
|
|
21094
|
+
registerReferencesResource(server, getLibraryOperations);
|
|
21095
|
+
registerReferenceResource(server, getLibraryOperations);
|
|
20979
21096
|
registerStylesResource(server);
|
|
20980
21097
|
}
|
|
20981
21098
|
function formatAddResult(result) {
|
|
@@ -21004,7 +21121,7 @@ function formatAddResult(result) {
|
|
|
21004
21121
|
}
|
|
21005
21122
|
return lines.join("\n");
|
|
21006
21123
|
}
|
|
21007
|
-
function registerAddTool(server,
|
|
21124
|
+
function registerAddTool(server, getLibraryOperations) {
|
|
21008
21125
|
server.registerTool(
|
|
21009
21126
|
"add",
|
|
21010
21127
|
{
|
|
@@ -21014,10 +21131,10 @@ function registerAddTool(server, getLibrary) {
|
|
|
21014
21131
|
}
|
|
21015
21132
|
},
|
|
21016
21133
|
async (args) => {
|
|
21017
|
-
const
|
|
21134
|
+
const libraryOps = getLibraryOperations();
|
|
21018
21135
|
const inputs = Array.isArray(args.input) ? args.input : [args.input];
|
|
21019
21136
|
const stdinContent = inputs.join("\n");
|
|
21020
|
-
const result = await
|
|
21137
|
+
const result = await libraryOps.import([], { stdinContent });
|
|
21021
21138
|
return {
|
|
21022
21139
|
content: [
|
|
21023
21140
|
{
|
|
@@ -21029,7 +21146,7 @@ function registerAddTool(server, getLibrary) {
|
|
|
21029
21146
|
}
|
|
21030
21147
|
);
|
|
21031
21148
|
}
|
|
21032
|
-
function registerCiteTool(server,
|
|
21149
|
+
function registerCiteTool(server, getLibraryOperations) {
|
|
21033
21150
|
server.registerTool(
|
|
21034
21151
|
"cite",
|
|
21035
21152
|
{
|
|
@@ -21041,8 +21158,8 @@ function registerCiteTool(server, getLibrary) {
|
|
|
21041
21158
|
}
|
|
21042
21159
|
},
|
|
21043
21160
|
async (args) => {
|
|
21044
|
-
const
|
|
21045
|
-
const result = await
|
|
21161
|
+
const libraryOps = getLibraryOperations();
|
|
21162
|
+
const result = await libraryOps.cite({
|
|
21046
21163
|
identifiers: args.ids,
|
|
21047
21164
|
style: args.style ?? "apa",
|
|
21048
21165
|
format: args.format ?? "text"
|
|
@@ -21056,7 +21173,7 @@ function registerCiteTool(server, getLibrary) {
|
|
|
21056
21173
|
}
|
|
21057
21174
|
);
|
|
21058
21175
|
}
|
|
21059
|
-
function registerFulltextAttachTool(server,
|
|
21176
|
+
function registerFulltextAttachTool(server, getLibraryOperations, getConfig) {
|
|
21060
21177
|
server.registerTool(
|
|
21061
21178
|
"fulltext_attach",
|
|
21062
21179
|
{
|
|
@@ -21067,9 +21184,9 @@ function registerFulltextAttachTool(server, getLibrary, getConfig) {
|
|
|
21067
21184
|
}
|
|
21068
21185
|
},
|
|
21069
21186
|
async (args) => {
|
|
21070
|
-
const
|
|
21187
|
+
const libraryOps = getLibraryOperations();
|
|
21071
21188
|
const config2 = getConfig();
|
|
21072
|
-
const result = await fulltextAttach(
|
|
21189
|
+
const result = await fulltextAttach(libraryOps, {
|
|
21073
21190
|
identifier: args.id,
|
|
21074
21191
|
filePath: args.path,
|
|
21075
21192
|
force: true,
|
|
@@ -21093,7 +21210,7 @@ function registerFulltextAttachTool(server, getLibrary, getConfig) {
|
|
|
21093
21210
|
}
|
|
21094
21211
|
);
|
|
21095
21212
|
}
|
|
21096
|
-
function registerFulltextGetTool(server,
|
|
21213
|
+
function registerFulltextGetTool(server, getLibraryOperations, getConfig) {
|
|
21097
21214
|
server.registerTool(
|
|
21098
21215
|
"fulltext_get",
|
|
21099
21216
|
{
|
|
@@ -21103,9 +21220,9 @@ function registerFulltextGetTool(server, getLibrary, getConfig) {
|
|
|
21103
21220
|
}
|
|
21104
21221
|
},
|
|
21105
21222
|
async (args) => {
|
|
21106
|
-
const
|
|
21223
|
+
const libraryOps = getLibraryOperations();
|
|
21107
21224
|
const config2 = getConfig();
|
|
21108
|
-
const pathResult = await fulltextGet(
|
|
21225
|
+
const pathResult = await fulltextGet(libraryOps, {
|
|
21109
21226
|
identifier: args.id,
|
|
21110
21227
|
fulltextDirectory: config2.fulltext.directory
|
|
21111
21228
|
});
|
|
@@ -21117,7 +21234,7 @@ function registerFulltextGetTool(server, getLibrary, getConfig) {
|
|
|
21117
21234
|
}
|
|
21118
21235
|
const responses = [];
|
|
21119
21236
|
if (pathResult.paths?.markdown) {
|
|
21120
|
-
const contentResult = await fulltextGet(
|
|
21237
|
+
const contentResult = await fulltextGet(libraryOps, {
|
|
21121
21238
|
identifier: args.id,
|
|
21122
21239
|
type: "markdown",
|
|
21123
21240
|
stdout: true,
|
|
@@ -21146,7 +21263,7 @@ function registerFulltextGetTool(server, getLibrary, getConfig) {
|
|
|
21146
21263
|
}
|
|
21147
21264
|
);
|
|
21148
21265
|
}
|
|
21149
|
-
function registerFulltextDetachTool(server,
|
|
21266
|
+
function registerFulltextDetachTool(server, getLibraryOperations, getConfig) {
|
|
21150
21267
|
server.registerTool(
|
|
21151
21268
|
"fulltext_detach",
|
|
21152
21269
|
{
|
|
@@ -21156,9 +21273,9 @@ function registerFulltextDetachTool(server, getLibrary, getConfig) {
|
|
|
21156
21273
|
}
|
|
21157
21274
|
},
|
|
21158
21275
|
async (args) => {
|
|
21159
|
-
const
|
|
21276
|
+
const libraryOps = getLibraryOperations();
|
|
21160
21277
|
const config2 = getConfig();
|
|
21161
|
-
const result = await fulltextDetach(
|
|
21278
|
+
const result = await fulltextDetach(libraryOps, {
|
|
21162
21279
|
identifier: args.id,
|
|
21163
21280
|
fulltextDirectory: config2.fulltext.directory
|
|
21164
21281
|
});
|
|
@@ -21180,30 +21297,46 @@ function registerFulltextDetachTool(server, getLibrary, getConfig) {
|
|
|
21180
21297
|
}
|
|
21181
21298
|
);
|
|
21182
21299
|
}
|
|
21183
|
-
function registerListTool(server,
|
|
21300
|
+
function registerListTool(server, getLibraryOperations, getConfig) {
|
|
21184
21301
|
server.registerTool(
|
|
21185
21302
|
"list",
|
|
21186
21303
|
{
|
|
21187
|
-
description: "List
|
|
21304
|
+
description: "List references in the library. Supports different output formats, sorting, and pagination.",
|
|
21188
21305
|
inputSchema: {
|
|
21189
|
-
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)")
|
|
21190
21311
|
}
|
|
21191
21312
|
},
|
|
21192
21313
|
async (args) => {
|
|
21193
|
-
const
|
|
21194
|
-
const
|
|
21195
|
-
|
|
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"])
|
|
21196
21321
|
});
|
|
21197
21322
|
return {
|
|
21198
|
-
content:
|
|
21199
|
-
|
|
21200
|
-
|
|
21201
|
-
|
|
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
|
+
]
|
|
21202
21335
|
};
|
|
21203
21336
|
}
|
|
21204
21337
|
);
|
|
21205
21338
|
}
|
|
21206
|
-
function registerRemoveTool(server,
|
|
21339
|
+
function registerRemoveTool(server, getLibraryOperations) {
|
|
21207
21340
|
server.registerTool(
|
|
21208
21341
|
"remove",
|
|
21209
21342
|
{
|
|
@@ -21224,8 +21357,8 @@ function registerRemoveTool(server, getLibrary) {
|
|
|
21224
21357
|
]
|
|
21225
21358
|
};
|
|
21226
21359
|
}
|
|
21227
|
-
const
|
|
21228
|
-
const result = await
|
|
21360
|
+
const libraryOps = getLibraryOperations();
|
|
21361
|
+
const result = await libraryOps.remove(args.id);
|
|
21229
21362
|
if (!result.removed) {
|
|
21230
21363
|
return {
|
|
21231
21364
|
content: [
|
|
@@ -21249,39 +21382,57 @@ Title: ${title2}`
|
|
|
21249
21382
|
}
|
|
21250
21383
|
);
|
|
21251
21384
|
}
|
|
21252
|
-
function registerSearchTool(server,
|
|
21385
|
+
function registerSearchTool(server, getLibraryOperations, getConfig) {
|
|
21253
21386
|
server.registerTool(
|
|
21254
21387
|
"search",
|
|
21255
21388
|
{
|
|
21256
|
-
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.",
|
|
21257
21390
|
inputSchema: {
|
|
21258
|
-
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)")
|
|
21259
21398
|
}
|
|
21260
21399
|
},
|
|
21261
21400
|
async (args) => {
|
|
21262
|
-
const
|
|
21263
|
-
const
|
|
21401
|
+
const libraryOps = getLibraryOperations();
|
|
21402
|
+
const config2 = getConfig();
|
|
21403
|
+
const limit2 = args.limit ?? config2.mcp.defaultLimit;
|
|
21404
|
+
const result = await libraryOps.search({
|
|
21264
21405
|
query: args.query,
|
|
21265
|
-
format: "pretty"
|
|
21406
|
+
format: "pretty",
|
|
21407
|
+
limit: limit2,
|
|
21408
|
+
...pickDefined(args, ["sort", "order", "offset"])
|
|
21266
21409
|
});
|
|
21267
21410
|
return {
|
|
21268
|
-
content:
|
|
21269
|
-
|
|
21270
|
-
|
|
21271
|
-
|
|
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
|
+
]
|
|
21272
21423
|
};
|
|
21273
21424
|
}
|
|
21274
21425
|
);
|
|
21275
21426
|
}
|
|
21276
|
-
function registerAllTools(server,
|
|
21277
|
-
registerSearchTool(server,
|
|
21278
|
-
registerListTool(server,
|
|
21279
|
-
registerCiteTool(server,
|
|
21280
|
-
registerAddTool(server,
|
|
21281
|
-
registerRemoveTool(server,
|
|
21282
|
-
registerFulltextAttachTool(server,
|
|
21283
|
-
registerFulltextGetTool(server,
|
|
21284
|
-
registerFulltextDetachTool(server,
|
|
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);
|
|
21285
21436
|
}
|
|
21286
21437
|
async function createMcpServer(options) {
|
|
21287
21438
|
const serverInfo = {
|
|
@@ -21296,10 +21447,10 @@ async function createMcpServer(options) {
|
|
|
21296
21447
|
}
|
|
21297
21448
|
const context = await createMcpContext(contextOptions);
|
|
21298
21449
|
const server = new McpServer(serverInfo);
|
|
21299
|
-
const
|
|
21450
|
+
const getLibraryOperations = () => context.libraryOperations;
|
|
21300
21451
|
const getConfig = () => context.config;
|
|
21301
|
-
registerAllTools(server,
|
|
21302
|
-
registerAllResources(server,
|
|
21452
|
+
registerAllTools(server, getLibraryOperations, getConfig);
|
|
21453
|
+
registerAllResources(server, getLibraryOperations);
|
|
21303
21454
|
const transport = new StdioServerTransport(
|
|
21304
21455
|
options.stdin ?? process.stdin,
|
|
21305
21456
|
options.stdout ?? process.stdout
|
|
@@ -21381,6 +21532,18 @@ async function deleteFulltextFiles(item, fulltextDirectory) {
|
|
|
21381
21532
|
}
|
|
21382
21533
|
}
|
|
21383
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
|
+
]);
|
|
21384
21547
|
function getSearchFormat(options) {
|
|
21385
21548
|
if (options.json) return "json";
|
|
21386
21549
|
if (options.idsOnly) return "ids-only";
|
|
@@ -21397,17 +21560,101 @@ function validateOptions(options) {
|
|
|
21397
21560
|
"Multiple output formats specified. Only one of --json, --ids-only, --uuid, --bibtex can be used."
|
|
21398
21561
|
);
|
|
21399
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
|
+
}
|
|
21400
21583
|
}
|
|
21401
21584
|
async function executeSearch(options, context) {
|
|
21402
21585
|
validateOptions(options);
|
|
21403
21586
|
const format2 = getSearchFormat(options);
|
|
21404
|
-
|
|
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
|
+
});
|
|
21405
21594
|
}
|
|
21406
|
-
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
|
+
}
|
|
21407
21605
|
if (result.items.length === 0) {
|
|
21408
21606
|
return "";
|
|
21409
21607
|
}
|
|
21410
|
-
|
|
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");
|
|
21616
|
+
}
|
|
21617
|
+
function validateInteractiveOptions(options) {
|
|
21618
|
+
const outputOptions = [options.json, options.idsOnly, options.uuid, options.bibtex].filter(
|
|
21619
|
+
Boolean
|
|
21620
|
+
);
|
|
21621
|
+
if (outputOptions.length > 0) {
|
|
21622
|
+
throw new Error(
|
|
21623
|
+
"Interactive mode cannot be combined with output format options (--json, --ids-only, --uuid, --bibtex)"
|
|
21624
|
+
);
|
|
21625
|
+
}
|
|
21626
|
+
}
|
|
21627
|
+
async function executeInteractiveSearch(options, context, config2) {
|
|
21628
|
+
validateInteractiveOptions(options);
|
|
21629
|
+
const { checkTTY } = await import("./chunks/tty-CDBIQraQ.js");
|
|
21630
|
+
const { runSearchPrompt } = await import("./chunks/search-prompt-D67WyKrY.js");
|
|
21631
|
+
const { runActionMenu } = await import("./chunks/action-menu-CTtINmWd.js");
|
|
21632
|
+
const { search } = await import("./chunks/file-watcher-D7oyc-9z.js").then((n) => n.y);
|
|
21633
|
+
const { tokenize } = await import("./chunks/file-watcher-D7oyc-9z.js").then((n) => n.x);
|
|
21634
|
+
checkTTY();
|
|
21635
|
+
const allReferences = await context.library.getAll();
|
|
21636
|
+
const searchFn = (query) => {
|
|
21637
|
+
const { tokens } = tokenize(query);
|
|
21638
|
+
return search(allReferences, tokens);
|
|
21639
|
+
};
|
|
21640
|
+
const interactiveConfig = config2.cli.interactive;
|
|
21641
|
+
const searchResult = await runSearchPrompt(
|
|
21642
|
+
allReferences,
|
|
21643
|
+
searchFn,
|
|
21644
|
+
{
|
|
21645
|
+
limit: interactiveConfig.limit,
|
|
21646
|
+
debounceMs: interactiveConfig.debounceMs
|
|
21647
|
+
},
|
|
21648
|
+
options.query || ""
|
|
21649
|
+
);
|
|
21650
|
+
if (searchResult.cancelled || searchResult.selected.length === 0) {
|
|
21651
|
+
return { output: "", cancelled: true };
|
|
21652
|
+
}
|
|
21653
|
+
const actionResult = await runActionMenu(searchResult.selected);
|
|
21654
|
+
return {
|
|
21655
|
+
output: actionResult.output,
|
|
21656
|
+
cancelled: actionResult.cancelled
|
|
21657
|
+
};
|
|
21411
21658
|
}
|
|
21412
21659
|
async function serverStart(options) {
|
|
21413
21660
|
const existingStatus = await serverStatus(options.portfilePath);
|
|
@@ -21516,47 +21763,6 @@ function formatUpdateOutput(result, identifier) {
|
|
|
21516
21763
|
}
|
|
21517
21764
|
return parts.join("\n");
|
|
21518
21765
|
}
|
|
21519
|
-
class OperationsLibrary {
|
|
21520
|
-
constructor(library) {
|
|
21521
|
-
this.library = library;
|
|
21522
|
-
}
|
|
21523
|
-
// ILibrary delegation
|
|
21524
|
-
find(identifier, options) {
|
|
21525
|
-
return this.library.find(identifier, options);
|
|
21526
|
-
}
|
|
21527
|
-
getAll() {
|
|
21528
|
-
return this.library.getAll();
|
|
21529
|
-
}
|
|
21530
|
-
add(item) {
|
|
21531
|
-
return this.library.add(item);
|
|
21532
|
-
}
|
|
21533
|
-
update(idOrUuid, updates, options) {
|
|
21534
|
-
return this.library.update(idOrUuid, updates, options);
|
|
21535
|
-
}
|
|
21536
|
-
remove(identifier, options) {
|
|
21537
|
-
return this.library.remove(identifier, options);
|
|
21538
|
-
}
|
|
21539
|
-
save() {
|
|
21540
|
-
return this.library.save();
|
|
21541
|
-
}
|
|
21542
|
-
// High-level operations
|
|
21543
|
-
async search(options) {
|
|
21544
|
-
const { searchReferences: searchReferences2 } = await import("./chunks/index-Bl_mOQRe.js").then((n) => n.h);
|
|
21545
|
-
return searchReferences2(this.library, options);
|
|
21546
|
-
}
|
|
21547
|
-
async list(options) {
|
|
21548
|
-
const { listReferences: listReferences2 } = await import("./chunks/index-Bl_mOQRe.js").then((n) => n.g);
|
|
21549
|
-
return listReferences2(this.library, options ?? {});
|
|
21550
|
-
}
|
|
21551
|
-
async cite(options) {
|
|
21552
|
-
const { citeReferences: citeReferences2 } = await import("./chunks/index-Bl_mOQRe.js").then((n) => n.f);
|
|
21553
|
-
return citeReferences2(this.library, options);
|
|
21554
|
-
}
|
|
21555
|
-
async import(inputs, options) {
|
|
21556
|
-
const { addReferences: addReferences2 } = await import("./chunks/index-Bl_mOQRe.js").then((n) => n.e);
|
|
21557
|
-
return addReferences2(inputs, this.library, options ?? {});
|
|
21558
|
-
}
|
|
21559
|
-
}
|
|
21560
21766
|
class ServerClient {
|
|
21561
21767
|
constructor(baseUrl) {
|
|
21562
21768
|
this.baseUrl = baseUrl;
|
|
@@ -21786,6 +21992,199 @@ async function waitForPortfile(timeoutMs) {
|
|
|
21786
21992
|
}
|
|
21787
21993
|
throw new Error(`Server failed to start: portfile not created within ${timeoutMs}ms`);
|
|
21788
21994
|
}
|
|
21995
|
+
const SEARCH_SORT_FIELDS = searchSortFieldSchema.options;
|
|
21996
|
+
const SORT_ORDERS = sortOrderSchema.options;
|
|
21997
|
+
const CITATION_FORMATS = ["text", "html", "rtf"];
|
|
21998
|
+
const LOG_LEVELS = ["silent", "info", "debug"];
|
|
21999
|
+
const OPTION_VALUES = {
|
|
22000
|
+
"--sort": SEARCH_SORT_FIELDS,
|
|
22001
|
+
// search includes 'relevance'
|
|
22002
|
+
"--order": SORT_ORDERS,
|
|
22003
|
+
"--format": CITATION_FORMATS,
|
|
22004
|
+
"--style": BUILTIN_STYLES,
|
|
22005
|
+
"--log-level": LOG_LEVELS
|
|
22006
|
+
};
|
|
22007
|
+
const ID_COMPLETION_COMMANDS = /* @__PURE__ */ new Set(["cite", "remove", "update"]);
|
|
22008
|
+
const ID_COMPLETION_FULLTEXT_SUBCOMMANDS = /* @__PURE__ */ new Set(["attach", "get", "detach"]);
|
|
22009
|
+
function toCompletionItems(values) {
|
|
22010
|
+
return values.map((name2) => ({ name: name2 }));
|
|
22011
|
+
}
|
|
22012
|
+
function extractSubcommands(program) {
|
|
22013
|
+
return program.commands.map((cmd) => ({
|
|
22014
|
+
name: cmd.name(),
|
|
22015
|
+
description: cmd.description()
|
|
22016
|
+
}));
|
|
22017
|
+
}
|
|
22018
|
+
function extractOptions(cmd) {
|
|
22019
|
+
const options = [];
|
|
22020
|
+
for (const opt of cmd.options) {
|
|
22021
|
+
const longFlag = opt.long;
|
|
22022
|
+
const shortFlag = opt.short;
|
|
22023
|
+
const description2 = opt.description;
|
|
22024
|
+
if (longFlag) {
|
|
22025
|
+
options.push({ name: longFlag, description: description2 });
|
|
22026
|
+
}
|
|
22027
|
+
if (shortFlag) {
|
|
22028
|
+
options.push({ name: shortFlag, description: description2 });
|
|
22029
|
+
}
|
|
22030
|
+
}
|
|
22031
|
+
return options;
|
|
22032
|
+
}
|
|
22033
|
+
function extractGlobalOptions(program) {
|
|
22034
|
+
const options = extractOptions(program);
|
|
22035
|
+
options.push({ name: "--help", description: "display help for command" });
|
|
22036
|
+
options.push({ name: "--version", description: "output the version number" });
|
|
22037
|
+
return options;
|
|
22038
|
+
}
|
|
22039
|
+
function findSubcommand(program, name2) {
|
|
22040
|
+
return program.commands.find((cmd) => cmd.name() === name2);
|
|
22041
|
+
}
|
|
22042
|
+
function getCompletions(env, program) {
|
|
22043
|
+
const { line, prev, last } = env;
|
|
22044
|
+
const words = line.trim().split(/\s+/);
|
|
22045
|
+
const args = words.slice(1);
|
|
22046
|
+
const subcommands = extractSubcommands(program);
|
|
22047
|
+
const globalOptions = extractGlobalOptions(program);
|
|
22048
|
+
if (args.length === 0) {
|
|
22049
|
+
return subcommands;
|
|
22050
|
+
}
|
|
22051
|
+
const firstArg = args[0] ?? "";
|
|
22052
|
+
if (prev?.startsWith("-")) {
|
|
22053
|
+
const optionValues = OPTION_VALUES[prev];
|
|
22054
|
+
if (optionValues) {
|
|
22055
|
+
return toCompletionItems(optionValues);
|
|
22056
|
+
}
|
|
22057
|
+
}
|
|
22058
|
+
if (last.startsWith("-")) {
|
|
22059
|
+
const subCmd = findSubcommand(program, firstArg);
|
|
22060
|
+
const commandOptions = subCmd ? extractOptions(subCmd) : [];
|
|
22061
|
+
return [...commandOptions, ...globalOptions];
|
|
22062
|
+
}
|
|
22063
|
+
const parentCmd = findSubcommand(program, firstArg);
|
|
22064
|
+
if (parentCmd && parentCmd.commands.length > 0) {
|
|
22065
|
+
const nestedSubcommands = extractSubcommands(parentCmd);
|
|
22066
|
+
if (args.length === 1 || args.length === 2 && !last.startsWith("-")) {
|
|
22067
|
+
return nestedSubcommands;
|
|
22068
|
+
}
|
|
22069
|
+
}
|
|
22070
|
+
if (args.length === 1) {
|
|
22071
|
+
return subcommands.filter((cmd) => cmd.name.startsWith(last));
|
|
22072
|
+
}
|
|
22073
|
+
return subcommands;
|
|
22074
|
+
}
|
|
22075
|
+
function needsIdCompletion(env) {
|
|
22076
|
+
const { line, prev } = env;
|
|
22077
|
+
const words = line.trim().split(/\s+/);
|
|
22078
|
+
const args = words.slice(1);
|
|
22079
|
+
if (args.length === 0) {
|
|
22080
|
+
return { needs: false };
|
|
22081
|
+
}
|
|
22082
|
+
const command = args[0] ?? "";
|
|
22083
|
+
if (prev?.startsWith("-")) {
|
|
22084
|
+
return { needs: false };
|
|
22085
|
+
}
|
|
22086
|
+
if (command === "fulltext" && args.length >= 2) {
|
|
22087
|
+
const subcommand = args[1] ?? "";
|
|
22088
|
+
if (ID_COMPLETION_FULLTEXT_SUBCOMMANDS.has(subcommand)) {
|
|
22089
|
+
return { needs: true, command, subcommand };
|
|
22090
|
+
}
|
|
22091
|
+
return { needs: false };
|
|
22092
|
+
}
|
|
22093
|
+
if (ID_COMPLETION_COMMANDS.has(command)) {
|
|
22094
|
+
return { needs: true, command };
|
|
22095
|
+
}
|
|
22096
|
+
return { needs: false };
|
|
22097
|
+
}
|
|
22098
|
+
const MAX_ID_COMPLETIONS = 100;
|
|
22099
|
+
async function getLibraryForCompletion() {
|
|
22100
|
+
try {
|
|
22101
|
+
const config2 = loadConfig();
|
|
22102
|
+
const server = await getServerConnection(config2.library, config2);
|
|
22103
|
+
if (server) {
|
|
22104
|
+
return new ServerClient(server.baseUrl);
|
|
22105
|
+
}
|
|
22106
|
+
return await Library.load(config2.library);
|
|
22107
|
+
} catch {
|
|
22108
|
+
return void 0;
|
|
22109
|
+
}
|
|
22110
|
+
}
|
|
22111
|
+
function truncateTitle(title2, maxLength = 40) {
|
|
22112
|
+
if (!title2) return "";
|
|
22113
|
+
if (title2.length <= maxLength) return title2;
|
|
22114
|
+
return `${title2.slice(0, maxLength - 3)}...`;
|
|
22115
|
+
}
|
|
22116
|
+
async function getIdCompletions(library, prefix) {
|
|
22117
|
+
try {
|
|
22118
|
+
const items2 = await library.getAll();
|
|
22119
|
+
const completions = [];
|
|
22120
|
+
for (const item of items2) {
|
|
22121
|
+
const id2 = item.id;
|
|
22122
|
+
if (!id2) continue;
|
|
22123
|
+
if (prefix && !id2.toLowerCase().startsWith(prefix.toLowerCase())) {
|
|
22124
|
+
continue;
|
|
22125
|
+
}
|
|
22126
|
+
completions.push({
|
|
22127
|
+
name: id2,
|
|
22128
|
+
description: truncateTitle(item.title)
|
|
22129
|
+
});
|
|
22130
|
+
if (completions.length >= MAX_ID_COMPLETIONS) {
|
|
22131
|
+
break;
|
|
22132
|
+
}
|
|
22133
|
+
}
|
|
22134
|
+
return completions;
|
|
22135
|
+
} catch {
|
|
22136
|
+
return [];
|
|
22137
|
+
}
|
|
22138
|
+
}
|
|
22139
|
+
async function installCompletion() {
|
|
22140
|
+
const tabtab = await import("tabtab");
|
|
22141
|
+
await tabtab.install({
|
|
22142
|
+
name: "ref",
|
|
22143
|
+
completer: "ref"
|
|
22144
|
+
});
|
|
22145
|
+
}
|
|
22146
|
+
async function uninstallCompletion() {
|
|
22147
|
+
const tabtab = await import("tabtab");
|
|
22148
|
+
await tabtab.uninstall({
|
|
22149
|
+
name: "ref"
|
|
22150
|
+
});
|
|
22151
|
+
}
|
|
22152
|
+
async function handleCompletion(program) {
|
|
22153
|
+
const tabtab = await import("tabtab");
|
|
22154
|
+
const env = tabtab.parseEnv(process.env);
|
|
22155
|
+
if (!env.complete) {
|
|
22156
|
+
return;
|
|
22157
|
+
}
|
|
22158
|
+
if (env.last.startsWith("-")) {
|
|
22159
|
+
const completions2 = getCompletions(env, program);
|
|
22160
|
+
tabtab.log(completions2);
|
|
22161
|
+
return;
|
|
22162
|
+
}
|
|
22163
|
+
const idContext = needsIdCompletion(env);
|
|
22164
|
+
if (idContext.needs) {
|
|
22165
|
+
const library = await getLibraryForCompletion();
|
|
22166
|
+
if (library) {
|
|
22167
|
+
const idCompletions = await getIdCompletions(library, env.last);
|
|
22168
|
+
tabtab.log(idCompletions);
|
|
22169
|
+
return;
|
|
22170
|
+
}
|
|
22171
|
+
}
|
|
22172
|
+
const completions = getCompletions(env, program);
|
|
22173
|
+
tabtab.log(completions);
|
|
22174
|
+
}
|
|
22175
|
+
function registerCompletionCommand(program) {
|
|
22176
|
+
program.command("completion").description("Install or uninstall shell completion").argument("[action]", "Action to perform (install or uninstall)", "install").action(async (action) => {
|
|
22177
|
+
if (action === "install") {
|
|
22178
|
+
await installCompletion();
|
|
22179
|
+
} else if (action === "uninstall") {
|
|
22180
|
+
await uninstallCompletion();
|
|
22181
|
+
} else {
|
|
22182
|
+
console.error(`Unknown action: ${action}`);
|
|
22183
|
+
console.error("Usage: ref completion [install|uninstall]");
|
|
22184
|
+
process.exit(1);
|
|
22185
|
+
}
|
|
22186
|
+
});
|
|
22187
|
+
}
|
|
21789
22188
|
async function createExecutionContext(config2, loadLibrary) {
|
|
21790
22189
|
const server = await getServerConnection(config2.library, config2);
|
|
21791
22190
|
if (server) {
|
|
@@ -21897,6 +22296,7 @@ function createProgram() {
|
|
|
21897
22296
|
registerServerCommand(program);
|
|
21898
22297
|
registerFulltextCommand(program);
|
|
21899
22298
|
registerMcpCommand(program);
|
|
22299
|
+
registerCompletionCommand(program);
|
|
21900
22300
|
return program;
|
|
21901
22301
|
}
|
|
21902
22302
|
async function handleListAction(options, program) {
|
|
@@ -21905,7 +22305,7 @@ async function handleListAction(options, program) {
|
|
|
21905
22305
|
const config2 = await loadConfigWithOverrides({ ...globalOpts, ...options });
|
|
21906
22306
|
const context = await createExecutionContext(config2, Library.load);
|
|
21907
22307
|
const result = await executeList(options, context);
|
|
21908
|
-
const output = formatListOutput(result);
|
|
22308
|
+
const output = formatListOutput(result, options.json ?? false);
|
|
21909
22309
|
if (output) {
|
|
21910
22310
|
process.stdout.write(`${output}
|
|
21911
22311
|
`);
|
|
@@ -21918,7 +22318,7 @@ async function handleListAction(options, program) {
|
|
|
21918
22318
|
}
|
|
21919
22319
|
}
|
|
21920
22320
|
function registerListCommand(program) {
|
|
21921
|
-
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) => {
|
|
22321
|
+
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) => {
|
|
21922
22322
|
await handleListAction(options, program);
|
|
21923
22323
|
});
|
|
21924
22324
|
}
|
|
@@ -21927,8 +22327,16 @@ async function handleSearchAction(query, options, program) {
|
|
|
21927
22327
|
const globalOpts = program.opts();
|
|
21928
22328
|
const config2 = await loadConfigWithOverrides({ ...globalOpts, ...options });
|
|
21929
22329
|
const context = await createExecutionContext(config2, Library.load);
|
|
22330
|
+
if (options.interactive) {
|
|
22331
|
+
const result2 = await executeInteractiveSearch({ ...options, query }, context, config2);
|
|
22332
|
+
if (result2.output) {
|
|
22333
|
+
process.stdout.write(`${result2.output}
|
|
22334
|
+
`);
|
|
22335
|
+
}
|
|
22336
|
+
process.exit(result2.cancelled ? 0 : 0);
|
|
22337
|
+
}
|
|
21930
22338
|
const result = await executeSearch({ ...options, query }, context);
|
|
21931
|
-
const output = formatSearchOutput(result);
|
|
22339
|
+
const output = formatSearchOutput(result, options.json ?? false);
|
|
21932
22340
|
if (output) {
|
|
21933
22341
|
process.stdout.write(`${output}
|
|
21934
22342
|
`);
|
|
@@ -21941,8 +22349,12 @@ async function handleSearchAction(query, options, program) {
|
|
|
21941
22349
|
}
|
|
21942
22350
|
}
|
|
21943
22351
|
function registerSearchCommand(program) {
|
|
21944
|
-
program.command("search").description("Search references").argument("
|
|
21945
|
-
|
|
22352
|
+
program.command("search").description("Search references").argument("[query]", "Search query (required unless using --interactive)").option("-i, --interactive", "Enable interactive search mode").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) => {
|
|
22353
|
+
if (!options.interactive && !query) {
|
|
22354
|
+
process.stderr.write("Error: Search query is required unless using --interactive\n");
|
|
22355
|
+
process.exit(1);
|
|
22356
|
+
}
|
|
22357
|
+
await handleSearchAction(query ?? "", options, program);
|
|
21946
22358
|
});
|
|
21947
22359
|
}
|
|
21948
22360
|
async function handleAddAction(inputs, options, program) {
|
|
@@ -22389,6 +22801,10 @@ function registerFulltextCommand(program) {
|
|
|
22389
22801
|
}
|
|
22390
22802
|
async function main(argv) {
|
|
22391
22803
|
const program = createProgram();
|
|
22804
|
+
if (process.env.COMP_LINE) {
|
|
22805
|
+
await handleCompletion(program);
|
|
22806
|
+
return;
|
|
22807
|
+
}
|
|
22392
22808
|
process.on("SIGINT", () => {
|
|
22393
22809
|
process.exit(130);
|
|
22394
22810
|
});
|