@ncukondo/reference-manager 0.6.0 → 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 +36 -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-Cwfnnw92.js → file-watcher-D7oyc-9z.js} +14 -2
- package/dist/chunks/{file-watcher-Cwfnnw92.js.map → file-watcher-D7oyc-9z.js.map} +1 -1
- package/dist/chunks/{index-CQO8hLYm.js → index-_7NEUoS7.js} +13 -9
- package/dist/chunks/index-_7NEUoS7.js.map +1 -0
- package/dist/chunks/{loader-B_ZLxCQW.js → loader-BItrdVWG.js} +59 -7
- 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/search.d.ts +19 -0
- package/dist/cli/commands/search.d.ts.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli.js +64 -10
- 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 +24 -1
- 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/index.js +3 -3
- package/dist/server.js +2 -2
- package/package.json +2 -1
- package/dist/chunks/index-CQO8hLYm.js.map +0 -1
- package/dist/chunks/loader-B_ZLxCQW.js.map +0 -1
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
function formatSingleAuthor(author) {
|
|
2
|
+
if (author.literal) {
|
|
3
|
+
return author.literal;
|
|
4
|
+
}
|
|
5
|
+
if (author.family) {
|
|
6
|
+
if (author.given) {
|
|
7
|
+
const initial = author.given.charAt(0).toUpperCase();
|
|
8
|
+
return `${author.family}, ${initial}.`;
|
|
9
|
+
}
|
|
10
|
+
return author.family;
|
|
11
|
+
}
|
|
12
|
+
return "";
|
|
13
|
+
}
|
|
14
|
+
function formatAuthors(authors) {
|
|
15
|
+
if (!authors || authors.length === 0) {
|
|
16
|
+
return "";
|
|
17
|
+
}
|
|
18
|
+
if (authors.length > 3) {
|
|
19
|
+
const first = authors[0];
|
|
20
|
+
if (!first) {
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
return `${formatSingleAuthor(first)}, et al.`;
|
|
24
|
+
}
|
|
25
|
+
const formatted = authors.map(formatSingleAuthor);
|
|
26
|
+
if (formatted.length === 1) {
|
|
27
|
+
return formatted[0] ?? "";
|
|
28
|
+
}
|
|
29
|
+
const allButLast = formatted.slice(0, -1).join(", ");
|
|
30
|
+
const last = formatted[formatted.length - 1] ?? "";
|
|
31
|
+
return `${allButLast}, & ${last}`;
|
|
32
|
+
}
|
|
33
|
+
function formatTitle(title, maxWidth) {
|
|
34
|
+
if (!title) {
|
|
35
|
+
return "";
|
|
36
|
+
}
|
|
37
|
+
if (title.length <= maxWidth) {
|
|
38
|
+
return title;
|
|
39
|
+
}
|
|
40
|
+
return `${title.slice(0, maxWidth - 3)}...`;
|
|
41
|
+
}
|
|
42
|
+
function formatIdentifiers(item) {
|
|
43
|
+
const identifiers = [];
|
|
44
|
+
if (item.DOI) {
|
|
45
|
+
identifiers.push(`DOI: ${item.DOI}`);
|
|
46
|
+
}
|
|
47
|
+
if (item.PMID) {
|
|
48
|
+
identifiers.push(`PMID: ${item.PMID}`);
|
|
49
|
+
}
|
|
50
|
+
if (item.PMCID) {
|
|
51
|
+
identifiers.push(`PMCID: ${item.PMCID}`);
|
|
52
|
+
}
|
|
53
|
+
if (item.ISBN) {
|
|
54
|
+
identifiers.push(`ISBN: ${item.ISBN}`);
|
|
55
|
+
}
|
|
56
|
+
return identifiers.join(" | ");
|
|
57
|
+
}
|
|
58
|
+
function extractYear(item) {
|
|
59
|
+
const dateParts = item.issued?.["date-parts"];
|
|
60
|
+
if (!dateParts || dateParts.length === 0) {
|
|
61
|
+
return void 0;
|
|
62
|
+
}
|
|
63
|
+
const firstDatePart = dateParts[0];
|
|
64
|
+
if (!firstDatePart || firstDatePart.length === 0) {
|
|
65
|
+
return void 0;
|
|
66
|
+
}
|
|
67
|
+
return firstDatePart[0];
|
|
68
|
+
}
|
|
69
|
+
function formatSearchResult(item, index, terminalWidth) {
|
|
70
|
+
const lines = [];
|
|
71
|
+
const authors = formatAuthors(item.author);
|
|
72
|
+
const year = extractYear(item);
|
|
73
|
+
const yearPart = year !== void 0 ? ` (${year})` : "";
|
|
74
|
+
const line1 = `[${index}] ${authors}${yearPart}`;
|
|
75
|
+
lines.push(line1);
|
|
76
|
+
const indent = " ";
|
|
77
|
+
const titleMaxWidth = terminalWidth - indent.length;
|
|
78
|
+
const title = formatTitle(item.title, titleMaxWidth);
|
|
79
|
+
if (title) {
|
|
80
|
+
lines.push(`${indent}${title}`);
|
|
81
|
+
}
|
|
82
|
+
const identifiers = formatIdentifiers(item);
|
|
83
|
+
if (identifiers) {
|
|
84
|
+
lines.push(`${indent}${identifiers}`);
|
|
85
|
+
}
|
|
86
|
+
return lines.join("\n");
|
|
87
|
+
}
|
|
88
|
+
function createChoices(results, terminalWidth) {
|
|
89
|
+
return results.map((result, index) => {
|
|
90
|
+
const displayIndex = index + 1;
|
|
91
|
+
const formattedText = formatSearchResult(result.reference, displayIndex, terminalWidth);
|
|
92
|
+
return {
|
|
93
|
+
name: JSON.stringify({
|
|
94
|
+
index,
|
|
95
|
+
item: result.reference
|
|
96
|
+
}),
|
|
97
|
+
message: formattedText,
|
|
98
|
+
value: result.reference.id
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function parseSelectedValues(values) {
|
|
103
|
+
const valueArray = Array.isArray(values) ? values : [values];
|
|
104
|
+
const items = [];
|
|
105
|
+
for (const value of valueArray) {
|
|
106
|
+
if (!value) continue;
|
|
107
|
+
try {
|
|
108
|
+
const data = JSON.parse(value);
|
|
109
|
+
items.push(data.item);
|
|
110
|
+
} catch {
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return items;
|
|
114
|
+
}
|
|
115
|
+
function getTerminalWidth() {
|
|
116
|
+
return process.stdout.columns ?? 80;
|
|
117
|
+
}
|
|
118
|
+
async function runSearchPrompt(allReferences, searchFn, config, initialQuery = "") {
|
|
119
|
+
const enquirer = await import("enquirer");
|
|
120
|
+
const AutoComplete = enquirer.default.AutoComplete;
|
|
121
|
+
const terminalWidth = getTerminalWidth();
|
|
122
|
+
const initialResults = initialQuery ? searchFn(initialQuery).slice(0, config.limit) : allReferences.slice(0, config.limit).map((ref) => ({
|
|
123
|
+
reference: ref,
|
|
124
|
+
overallStrength: "exact",
|
|
125
|
+
tokenMatches: [],
|
|
126
|
+
score: 0
|
|
127
|
+
}));
|
|
128
|
+
const initialChoices = createChoices(initialResults, terminalWidth);
|
|
129
|
+
let lastQuery = initialQuery;
|
|
130
|
+
const promptOptions = {
|
|
131
|
+
name: "references",
|
|
132
|
+
message: "Search references",
|
|
133
|
+
initial: initialQuery,
|
|
134
|
+
choices: initialChoices,
|
|
135
|
+
multiple: true,
|
|
136
|
+
limit: config.limit,
|
|
137
|
+
suggest: (input, choices) => {
|
|
138
|
+
if (input === lastQuery) {
|
|
139
|
+
return choices;
|
|
140
|
+
}
|
|
141
|
+
lastQuery = input;
|
|
142
|
+
if (!input.trim()) {
|
|
143
|
+
const defaultResults = allReferences.slice(0, config.limit).map((ref) => ({
|
|
144
|
+
reference: ref,
|
|
145
|
+
overallStrength: "exact",
|
|
146
|
+
tokenMatches: [],
|
|
147
|
+
score: 0
|
|
148
|
+
}));
|
|
149
|
+
return createChoices(defaultResults, terminalWidth);
|
|
150
|
+
}
|
|
151
|
+
const results = searchFn(input).slice(0, config.limit);
|
|
152
|
+
return createChoices(results, terminalWidth);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
try {
|
|
156
|
+
const prompt = new AutoComplete(promptOptions);
|
|
157
|
+
const result = await prompt.run();
|
|
158
|
+
const selected = parseSelectedValues(result);
|
|
159
|
+
return {
|
|
160
|
+
selected,
|
|
161
|
+
cancelled: false
|
|
162
|
+
};
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if (error === "" || error instanceof Error && error.message === "") {
|
|
165
|
+
return {
|
|
166
|
+
selected: [],
|
|
167
|
+
cancelled: true
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
export {
|
|
174
|
+
createChoices,
|
|
175
|
+
getTerminalWidth,
|
|
176
|
+
parseSelectedValues,
|
|
177
|
+
runSearchPrompt
|
|
178
|
+
};
|
|
179
|
+
//# sourceMappingURL=search-prompt-D67WyKrY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-prompt-D67WyKrY.js","sources":["../../src/features/interactive/format.ts","../../src/features/interactive/search-prompt.ts"],"sourcesContent":["/**\n * Display format functions for interactive search\n */\n\nimport type { CslItem } from \"../../core/csl-json/types.js\";\n\n/**\n * CSL name type (author structure)\n */\ntype CslName = NonNullable<CslItem[\"author\"]>[number];\n\n/**\n * Format a single author name\n * - Personal: \"Smith, J.\" (family + initial of given)\n * - Institutional: \"World Health Organization\" (literal)\n */\nfunction formatSingleAuthor(author: CslName): string {\n if (author.literal) {\n return author.literal;\n }\n if (author.family) {\n if (author.given) {\n const initial = author.given.charAt(0).toUpperCase();\n return `${author.family}, ${initial}.`;\n }\n return author.family;\n }\n return \"\";\n}\n\n/**\n * Format author list for display\n * - Single author: \"Smith, J.\"\n * - Two authors: \"Smith, J., & Doe, A.\"\n * - Three authors: \"Smith, J., Doe, A., & Johnson, B.\"\n * - More than three: \"Smith, J., et al.\"\n *\n * @param authors - Array of CSL author objects\n * @returns Formatted author string\n */\nexport function formatAuthors(authors: CslName[] | undefined): string {\n if (!authors || authors.length === 0) {\n return \"\";\n }\n\n if (authors.length > 3) {\n const first = authors[0];\n if (!first) {\n return \"\";\n }\n return `${formatSingleAuthor(first)}, et al.`;\n }\n\n const formatted = authors.map(formatSingleAuthor);\n if (formatted.length === 1) {\n return formatted[0] ?? \"\";\n }\n\n // Join all but last with \", \" and append \"& \" + last\n const allButLast = formatted.slice(0, -1).join(\", \");\n const last = formatted[formatted.length - 1] ?? \"\";\n return `${allButLast}, & ${last}`;\n}\n\n/**\n * Truncate title to fit terminal width\n *\n * @param title - Title string\n * @param maxWidth - Maximum display width\n * @returns Truncated title with ellipsis if needed\n */\nexport function formatTitle(title: string | undefined, maxWidth: number): string {\n if (!title) {\n return \"\";\n }\n\n if (title.length <= maxWidth) {\n return title;\n }\n\n // Truncate and add ellipsis, keeping total length at maxWidth\n return `${title.slice(0, maxWidth - 3)}...`;\n}\n\n/**\n * Format identifiers (DOI, PMID, PMCID, ISBN) for display\n *\n * @param item - CSL item\n * @returns Formatted identifier string (e.g., \"DOI: 10.1000/example | PMID: 12345678\")\n */\nexport function formatIdentifiers(item: CslItem): string {\n const identifiers: string[] = [];\n\n if (item.DOI) {\n identifiers.push(`DOI: ${item.DOI}`);\n }\n if (item.PMID) {\n identifiers.push(`PMID: ${item.PMID}`);\n }\n if (item.PMCID) {\n identifiers.push(`PMCID: ${item.PMCID}`);\n }\n if (item.ISBN) {\n identifiers.push(`ISBN: ${item.ISBN}`);\n }\n\n return identifiers.join(\" | \");\n}\n\n/**\n * Extract year from CSL item\n */\nfunction extractYear(item: CslItem): number | undefined {\n const dateParts = item.issued?.[\"date-parts\"];\n if (!dateParts || dateParts.length === 0) {\n return undefined;\n }\n const firstDatePart = dateParts[0];\n if (!firstDatePart || firstDatePart.length === 0) {\n return undefined;\n }\n return firstDatePart[0];\n}\n\n/**\n * Compose a complete search result line\n *\n * Format:\n * ```\n * [1] Smith, J., & Doe, A. (2020)\n * Machine learning in medicine: A comprehensive review\n * DOI: 10.1000/example | PMID: 12345678\n * ```\n *\n * @param item - CSL item\n * @param index - Display index (1-based)\n * @param terminalWidth - Terminal width for title truncation\n * @returns Multi-line formatted string\n */\nexport function formatSearchResult(item: CslItem, index: number, terminalWidth: number): string {\n const lines: string[] = [];\n\n // Line 1: [index] Authors (year)\n const authors = formatAuthors(item.author);\n const year = extractYear(item);\n const yearPart = year !== undefined ? ` (${year})` : \"\";\n const line1 = `[${index}] ${authors}${yearPart}`;\n lines.push(line1);\n\n // Line 2: Title (indented, truncated)\n const indent = \" \";\n const titleMaxWidth = terminalWidth - indent.length;\n const title = formatTitle(item.title, titleMaxWidth);\n if (title) {\n lines.push(`${indent}${title}`);\n }\n\n // Line 3: Identifiers (indented)\n const identifiers = formatIdentifiers(item);\n if (identifiers) {\n lines.push(`${indent}${identifiers}`);\n }\n\n return lines.join(\"\\n\");\n}\n","/**\n * Interactive search prompt using Enquirer's AutoComplete\n *\n * Provides real-time incremental search with multiple selection support.\n */\n\nimport type { CslItem } from \"../../core/csl-json/types.js\";\nimport type { SearchResult } from \"../search/types.js\";\nimport type { AutoCompleteChoice } from \"./enquirer.js\";\nimport { formatSearchResult } from \"./format.js\";\n\n/**\n * Configuration for the search prompt\n */\nexport interface SearchPromptConfig {\n /** Maximum number of results to display */\n limit: number;\n /** Debounce delay in milliseconds */\n debounceMs: number;\n}\n\n/**\n * Search function type for filtering references\n */\nexport type SearchFunction = (query: string) => SearchResult[];\n\n/**\n * Result from the search prompt\n */\nexport interface SearchPromptResult {\n /** Selected references */\n selected: CslItem[];\n /** Whether the prompt was cancelled */\n cancelled: boolean;\n}\n\n/**\n * Maps internal choice value to CslItem\n */\ninterface ChoiceData {\n index: number;\n item: CslItem;\n}\n\n/**\n * Creates choices from search results\n */\nexport function createChoices(\n results: SearchResult[],\n terminalWidth: number\n): AutoCompleteChoice[] {\n return results.map((result, index) => {\n const displayIndex = index + 1;\n const formattedText = formatSearchResult(result.reference, displayIndex, terminalWidth);\n\n // Enquirer returns the 'name' property on selection, not 'value'\n // So we store the JSON data in 'name' and use 'message' for display\n return {\n name: JSON.stringify({\n index,\n item: result.reference,\n } satisfies ChoiceData),\n message: formattedText,\n value: result.reference.id,\n };\n });\n}\n\n/**\n * Parses selected values back to CslItems\n */\nexport function parseSelectedValues(values: string | string[]): CslItem[] {\n const valueArray = Array.isArray(values) ? values : [values];\n const items: CslItem[] = [];\n\n for (const value of valueArray) {\n if (!value) continue;\n try {\n const data = JSON.parse(value) as ChoiceData;\n items.push(data.item);\n } catch {\n // If parsing fails, the value might be just the id (name)\n // In this case, we can't recover the full item\n // This shouldn't happen in normal operation\n }\n }\n\n return items;\n}\n\n/**\n * Gets terminal width, falling back to 80 if not available\n */\nexport function getTerminalWidth(): number {\n return process.stdout.columns ?? 80;\n}\n\n/**\n * Creates and runs an interactive search prompt\n */\nexport async function runSearchPrompt(\n allReferences: CslItem[],\n searchFn: SearchFunction,\n config: SearchPromptConfig,\n initialQuery = \"\"\n): Promise<SearchPromptResult> {\n // Dynamic import to allow mocking in tests\n // enquirer is a CommonJS module, so we must use default import\n const enquirer = await import(\"enquirer\");\n const AutoComplete = (enquirer.default as unknown as Record<string, unknown>)\n .AutoComplete as new (\n options: Record<string, unknown>\n ) => {\n run(): Promise<string | string[]>;\n };\n\n const terminalWidth = getTerminalWidth();\n\n // Create initial choices from all references (limited)\n const initialResults: SearchResult[] = initialQuery\n ? searchFn(initialQuery).slice(0, config.limit)\n : allReferences.slice(0, config.limit).map((ref) => ({\n reference: ref,\n overallStrength: \"exact\" as const,\n tokenMatches: [],\n score: 0,\n }));\n\n const initialChoices = createChoices(initialResults, terminalWidth);\n\n // Track last search query to avoid redundant searches\n let lastQuery = initialQuery;\n\n const promptOptions = {\n name: \"references\",\n message: \"Search references\",\n initial: initialQuery,\n choices: initialChoices,\n multiple: true,\n limit: config.limit,\n suggest: (input: string, choices: AutoCompleteChoice[]) => {\n // If input hasn't changed, return current choices\n if (input === lastQuery) {\n return choices;\n }\n lastQuery = input;\n\n // If input is empty, show all references (limited)\n if (!input.trim()) {\n const defaultResults: SearchResult[] = allReferences.slice(0, config.limit).map((ref) => ({\n reference: ref,\n overallStrength: \"exact\" as const,\n tokenMatches: [],\n score: 0,\n }));\n return createChoices(defaultResults, terminalWidth);\n }\n\n // Search and create new choices\n const results = searchFn(input).slice(0, config.limit);\n return createChoices(results, terminalWidth);\n },\n };\n\n try {\n const prompt = new AutoComplete(promptOptions);\n const result = await prompt.run();\n\n // Handle result\n const selected = parseSelectedValues(result);\n\n return {\n selected,\n cancelled: false,\n };\n } catch (error) {\n // Enquirer throws an empty string when cancelled\n if (error === \"\" || (error instanceof Error && error.message === \"\")) {\n return {\n selected: [],\n cancelled: true,\n };\n }\n throw error;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class TTYError extends Error {
|
|
2
|
+
exitCode = 1;
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "TTYError";
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
function checkTTY() {
|
|
9
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
10
|
+
throw new TTYError("Interactive mode requires a TTY");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
TTYError,
|
|
15
|
+
checkTTY
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=tty-CDBIQraQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tty-CDBIQraQ.js","sources":["../../src/features/interactive/tty.ts"],"sourcesContent":["/**\n * TTY detection for interactive mode\n */\n\n/**\n * Error thrown when TTY is required but not available\n */\nexport class TTYError extends Error {\n readonly exitCode = 1;\n\n constructor(message: string) {\n super(message);\n this.name = \"TTYError\";\n }\n}\n\n/**\n * Check if the current environment is a TTY\n *\n * Throws TTYError if stdin or stdout is not a TTY,\n * which means interactive mode cannot be used.\n *\n * @throws {TTYError} If not running in a TTY\n */\nexport function checkTTY(): void {\n if (!process.stdin.isTTY || !process.stdout.isTTY) {\n throw new TTYError(\"Interactive mode requires a TTY\");\n }\n}\n"],"names":[],"mappings":"AAOO,MAAM,iBAAiB,MAAM;AAAA,EACzB,WAAW;AAAA,EAEpB,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAUO,SAAS,WAAiB;AAC/B,MAAI,CAAC,QAAQ,MAAM,SAAS,CAAC,QAAQ,OAAO,OAAO;AACjD,UAAM,IAAI,SAAS,iCAAiC;AAAA,EACtD;AACF;"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Config } from "../../config/schema.js";
|
|
1
2
|
import type { SearchResult } from "../../features/operations/search.js";
|
|
2
3
|
import { type SearchSortField, type SortOrder } from "../../features/pagination/index.js";
|
|
3
4
|
import type { ExecutionContext } from "../execution-context.js";
|
|
@@ -14,6 +15,7 @@ export interface SearchCommandOptions {
|
|
|
14
15
|
order?: SortOrder;
|
|
15
16
|
limit?: number;
|
|
16
17
|
offset?: number;
|
|
18
|
+
interactive?: boolean;
|
|
17
19
|
}
|
|
18
20
|
/**
|
|
19
21
|
* Result from search command execution.
|
|
@@ -36,4 +38,21 @@ export declare function executeSearch(options: SearchCommandOptions, context: Ex
|
|
|
36
38
|
* @returns Formatted output string
|
|
37
39
|
*/
|
|
38
40
|
export declare function formatSearchOutput(result: SearchCommandResult, isJson?: boolean): string;
|
|
41
|
+
/**
|
|
42
|
+
* Result from interactive search command execution.
|
|
43
|
+
*/
|
|
44
|
+
export interface InteractiveSearchResult {
|
|
45
|
+
output: string;
|
|
46
|
+
cancelled: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Execute interactive search command.
|
|
50
|
+
* Uses runSearchPrompt and runActionMenu from the interactive module.
|
|
51
|
+
*
|
|
52
|
+
* @param options - Search command options with interactive flag
|
|
53
|
+
* @param context - Execution context
|
|
54
|
+
* @param config - Application configuration
|
|
55
|
+
* @returns Interactive search result containing output and cancelled flag
|
|
56
|
+
*/
|
|
57
|
+
export declare function executeInteractiveSearch(options: SearchCommandOptions, context: ExecutionContext, config: Config): Promise<InteractiveSearchResult>;
|
|
39
58
|
//# sourceMappingURL=search.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,YAAY,EAAE,MAAM,qCAAqC,CAAC;AACtF,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,SAAS,EAIf,MAAM,oCAAoC,CAAC;AAE5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAgBhE;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EAAgB,YAAY,EAAE,MAAM,qCAAqC,CAAC;AACtF,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,SAAS,EAIf,MAAM,oCAAoC,CAAC;AAE5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAgBhE;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,YAAY,CAAC;AAwD/C;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,EAC7B,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,mBAAmB,CAAC,CAa9B;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,mBAAmB,EAAE,MAAM,UAAQ,GAAG,MAAM,CA2BtF;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,OAAO,CAAC;CACpB;AAkBD;;;;;;;;GAQG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,oBAAoB,EAC7B,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,uBAAuB,CAAC,CA+ClC"}
|
package/dist/cli/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0DpC;;GAEG;AACH,wBAAgB,aAAa,IAAI,OAAO,CA+BvC;AAgyBD;;GAEG;AACH,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBxD"}
|
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 { 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-
|
|
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, s as startServerWithFileWatcher } from "./chunks/index-
|
|
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,
|
|
@@ -20959,19 +20959,19 @@ class OperationsLibrary {
|
|
|
20959
20959
|
}
|
|
20960
20960
|
// High-level operations
|
|
20961
20961
|
async search(options) {
|
|
20962
|
-
const { searchReferences } = await import("./chunks/index-
|
|
20962
|
+
const { searchReferences } = await import("./chunks/index-_7NEUoS7.js").then((n) => n.e);
|
|
20963
20963
|
return searchReferences(this.library, options);
|
|
20964
20964
|
}
|
|
20965
20965
|
async list(options) {
|
|
20966
|
-
const { listReferences } = await import("./chunks/index-
|
|
20966
|
+
const { listReferences } = await import("./chunks/index-_7NEUoS7.js").then((n) => n.l);
|
|
20967
20967
|
return listReferences(this.library, options ?? {});
|
|
20968
20968
|
}
|
|
20969
20969
|
async cite(options) {
|
|
20970
|
-
const { citeReferences } = await import("./chunks/index-
|
|
20970
|
+
const { citeReferences } = await import("./chunks/index-_7NEUoS7.js").then((n) => n.d);
|
|
20971
20971
|
return citeReferences(this.library, options);
|
|
20972
20972
|
}
|
|
20973
20973
|
async import(inputs, options) {
|
|
20974
|
-
const { addReferences } = await import("./chunks/index-
|
|
20974
|
+
const { addReferences } = await import("./chunks/index-_7NEUoS7.js").then((n) => n.b);
|
|
20975
20975
|
return addReferences(inputs, this.library, options ?? {});
|
|
20976
20976
|
}
|
|
20977
20977
|
}
|
|
@@ -21614,6 +21614,48 @@ function formatSearchOutput(result, isJson = false) {
|
|
|
21614
21614
|
lines.push(...result.items);
|
|
21615
21615
|
return lines.join("\n");
|
|
21616
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
|
+
};
|
|
21658
|
+
}
|
|
21617
21659
|
async function serverStart(options) {
|
|
21618
21660
|
const existingStatus = await serverStatus(options.portfilePath);
|
|
21619
21661
|
if (existingStatus !== null) {
|
|
@@ -22285,6 +22327,14 @@ async function handleSearchAction(query, options, program) {
|
|
|
22285
22327
|
const globalOpts = program.opts();
|
|
22286
22328
|
const config2 = await loadConfigWithOverrides({ ...globalOpts, ...options });
|
|
22287
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
|
+
}
|
|
22288
22338
|
const result = await executeSearch({ ...options, query }, context);
|
|
22289
22339
|
const output = formatSearchOutput(result, options.json ?? false);
|
|
22290
22340
|
if (output) {
|
|
@@ -22299,8 +22349,12 @@ async function handleSearchAction(query, options, program) {
|
|
|
22299
22349
|
}
|
|
22300
22350
|
}
|
|
22301
22351
|
function registerSearchCommand(program) {
|
|
22302
|
-
program.command("search").description("Search references").argument("
|
|
22303
|
-
|
|
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);
|
|
22304
22358
|
});
|
|
22305
22359
|
}
|
|
22306
22360
|
async function handleAddAction(inputs, options, program) {
|