@ncukondo/reference-manager 0.7.0 → 0.8.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 +1 -1
- package/dist/chunks/{loader-BItrdVWG.js → loader-CLCZRS4m.js} +43 -6
- package/dist/chunks/loader-CLCZRS4m.js.map +1 -0
- package/dist/chunks/{search-prompt-D67WyKrY.js → search-prompt-RtHDJFgL.js} +40 -11
- package/dist/chunks/search-prompt-RtHDJFgL.js.map +1 -0
- package/dist/cli/commands/fulltext.d.ts +20 -3
- package/dist/cli/commands/fulltext.d.ts.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli.js +112 -4
- package/dist/cli.js.map +1 -1
- package/dist/features/interactive/search-prompt.d.ts.map +1 -1
- package/dist/features/operations/fulltext/index.d.ts +1 -0
- package/dist/features/operations/fulltext/index.d.ts.map +1 -1
- package/dist/features/operations/fulltext/open.d.ts +36 -0
- package/dist/features/operations/fulltext/open.d.ts.map +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/opener.d.ts +13 -0
- package/dist/utils/opener.d.ts.map +1 -0
- package/package.json +1 -1
- package/dist/chunks/loader-BItrdVWG.js.map +0 -1
- package/dist/chunks/search-prompt-D67WyKrY.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-prompt.d.ts","sourceRoot":"","sources":["../../../src/features/interactive/search-prompt.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGxD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,YAAY,EAAE,CAAC;AAE/D;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,0BAA0B;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,uCAAuC;IACvC,SAAS,EAAE,OAAO,CAAC;CACpB;AAUD;;GAEG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,YAAY,EAAE,EACvB,aAAa,EAAE,MAAM,GACpB,kBAAkB,EAAE,
|
|
1
|
+
{"version":3,"file":"search-prompt.d.ts","sourceRoot":"","sources":["../../../src/features/interactive/search-prompt.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGxD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,YAAY,EAAE,CAAC;AAE/D;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,0BAA0B;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,uCAAuC;IACvC,SAAS,EAAE,OAAO,CAAC;CACpB;AAUD;;GAEG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,YAAY,EAAE,EACvB,aAAa,EAAE,MAAM,GACpB,kBAAkB,EAAE,CAetB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,EAAE,CAiBxE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAoCD;;GAEG;AACH,wBAAsB,eAAe,CACnC,aAAa,EAAE,OAAO,EAAE,EACxB,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,kBAAkB,EAC1B,YAAY,SAAK,GAChB,OAAO,CAAC,kBAAkB,CAAC,CAwG7B"}
|
|
@@ -6,4 +6,5 @@
|
|
|
6
6
|
export { fulltextAttach, type FulltextAttachOptions, type FulltextAttachResult } from "./attach.js";
|
|
7
7
|
export { fulltextGet, type FulltextGetOptions, type FulltextGetResult } from "./get.js";
|
|
8
8
|
export { fulltextDetach, type FulltextDetachOptions, type FulltextDetachResult } from "./detach.js";
|
|
9
|
+
export { fulltextOpen, type FulltextOpenOptions, type FulltextOpenResult } from "./open.js";
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/features/operations/fulltext/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACpG,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,MAAM,UAAU,CAAC;AACxF,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/features/operations/fulltext/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACpG,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,MAAM,UAAU,CAAC;AACxF,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACpG,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,KAAK,kBAAkB,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fulltext open operation
|
|
3
|
+
*/
|
|
4
|
+
import type { ILibrary, IdentifierType } from "../../../core/library-interface.js";
|
|
5
|
+
import type { FulltextType } from "../../fulltext/index.js";
|
|
6
|
+
/**
|
|
7
|
+
* Options for fulltextOpen operation
|
|
8
|
+
*/
|
|
9
|
+
export interface FulltextOpenOptions {
|
|
10
|
+
/** Reference identifier (id or uuid) */
|
|
11
|
+
identifier: string;
|
|
12
|
+
/** Specific type to open (pdf or markdown) */
|
|
13
|
+
type?: FulltextType | undefined;
|
|
14
|
+
/** Identifier type: 'id' (default), 'uuid', 'doi', 'pmid', or 'isbn' */
|
|
15
|
+
idType?: IdentifierType | undefined;
|
|
16
|
+
/** Directory for fulltext files */
|
|
17
|
+
fulltextDirectory: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Result of fulltextOpen operation
|
|
21
|
+
*/
|
|
22
|
+
export interface FulltextOpenResult {
|
|
23
|
+
success: boolean;
|
|
24
|
+
openedType?: FulltextType;
|
|
25
|
+
openedPath?: string;
|
|
26
|
+
error?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Open fulltext file with system default application.
|
|
30
|
+
*
|
|
31
|
+
* @param library - The library containing the reference
|
|
32
|
+
* @param options - Open options
|
|
33
|
+
* @returns Result with opened file info
|
|
34
|
+
*/
|
|
35
|
+
export declare function fulltextOpen(library: ILibrary, options: FulltextOpenOptions): Promise<FulltextOpenResult>;
|
|
36
|
+
//# sourceMappingURL=open.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open.d.ts","sourceRoot":"","sources":["../../../../src/features/operations/fulltext/open.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEnF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE5D;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IAChC,wEAAwE;IACxE,MAAM,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACpC,mCAAmC;IACnC,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAgCD;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,QAAQ,EACjB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,kBAAkB,CAAC,CA8C7B"}
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import writeFileAtomicLib from "write-file-atomic";
|
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
-
import { b as backupConfigSchema, c as configSchema, d as defaultConfig,
|
|
9
|
+
import { g as getOpenerCommand, o as openWithSystemApp, b as backupConfigSchema, c as configSchema, d as defaultConfig, a as getDefaultBackupDirectory, e as getDefaultCurrentDirConfigFilename, f as getDefaultLibraryPath, h as getDefaultUserConfigPath, l as loadConfig, i as logLevelSchema, n as normalizePartialConfig, p as partialConfigSchema, w as watchConfigSchema } from "./chunks/loader-CLCZRS4m.js";
|
|
10
10
|
function validateCslJson(data) {
|
|
11
11
|
const parseResult = CslLibrarySchema.safeParse(data);
|
|
12
12
|
if (!parseResult.success) {
|
|
@@ -119,7 +119,9 @@ const index$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
|
|
|
119
119
|
createLogger,
|
|
120
120
|
ensureDirectoryExists,
|
|
121
121
|
getBackupDirectory,
|
|
122
|
+
getOpenerCommand,
|
|
122
123
|
listBackups,
|
|
124
|
+
openWithSystemApp,
|
|
123
125
|
pickDefined,
|
|
124
126
|
writeFileAtomic
|
|
125
127
|
}, Symbol.toStringTag, { value: "Module" }));
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/core/csl-json/validator.ts","../src/utils/logger.ts","../src/utils/file.ts","../src/utils/backup.ts","../src/features/merge/three-way.ts"],"sourcesContent":["import { CslItemSchema, CslLibrarySchema } from \"./types\";\nimport type { CslItem, CslLibrary } from \"./types\";\n\n/**\n * Validate CSL-JSON library structure\n * @param data - Data to validate (can be any type)\n * @returns Validated CSL-JSON library\n * @throws Error if validation fails\n */\nexport function validateCslJson(data: unknown): CslLibrary {\n const parseResult = CslLibrarySchema.safeParse(data);\n\n if (!parseResult.success) {\n throw new Error(`Invalid CSL-JSON structure: ${parseResult.error.message}`);\n }\n\n return parseResult.data;\n}\n\n/**\n * Validate a single CSL-JSON item\n * @param data - Data to validate (can be any type)\n * @returns Validation result with valid flag and errors\n */\nexport function validateCslItem(data: unknown): {\n valid: boolean;\n data?: CslItem;\n errors?: string[];\n} {\n const parseResult = CslItemSchema.safeParse(data);\n\n if (!parseResult.success) {\n return {\n valid: false,\n errors: parseResult.error.issues.map((issue) => issue.message),\n };\n }\n\n return {\n valid: true,\n data: parseResult.data,\n };\n}\n","export type LogLevel = \"silent\" | \"info\" | \"debug\";\n\nexport interface Logger {\n info(...args: unknown[]): void;\n debug(...args: unknown[]): void;\n error(...args: unknown[]): void;\n}\n\nexport function createLogger(level: LogLevel = \"info\"): Logger {\n const shouldLogInfo = level === \"info\" || level === \"debug\";\n const shouldLogDebug = level === \"debug\";\n\n function formatMessage(...args: unknown[]): string {\n return `${args.map((arg) => String(arg)).join(\" \")}\\n`;\n }\n\n return {\n info(...args: unknown[]): void {\n if (shouldLogInfo) {\n process.stderr.write(formatMessage(...args));\n }\n },\n\n debug(...args: unknown[]): void {\n if (shouldLogDebug) {\n process.stderr.write(formatMessage(...args));\n }\n },\n\n error(...args: unknown[]): void {\n process.stderr.write(formatMessage(...args));\n },\n };\n}\n","import { mkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport writeFileAtomicLib from \"write-file-atomic\";\n\n/**\n * Write file atomically with parent directory creation\n */\nexport async function writeFileAtomic(filePath: string, content: string): Promise<void> {\n await ensureDirectoryExists(dirname(filePath));\n await writeFileAtomicLib(filePath, content, { encoding: \"utf-8\" });\n}\n\n/**\n * Ensure directory exists, creating it recursively if necessary\n */\nexport async function ensureDirectoryExists(dirPath: string): Promise<void> {\n await mkdir(dirPath, { recursive: true });\n}\n","import { existsSync } from \"node:fs\";\nimport { copyFile, readFile, readdir, stat, unlink } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { ensureDirectoryExists } from \"./file\";\n\nexport interface BackupOptions {\n maxGenerations?: number;\n maxAgeMs?: number;\n}\n\nconst DEFAULT_MAX_GENERATIONS = 50;\nconst DEFAULT_MAX_AGE_MS = 365 * 24 * 60 * 60 * 1000; // 1 year\n\n/**\n * Get package name from package.json\n */\nasync function resolvePackageName(): Promise<string> {\n try {\n const currentFile = fileURLToPath(import.meta.url);\n let currentDir = dirname(currentFile);\n\n for (let i = 0; i < 10; i++) {\n const packageJsonPath = join(currentDir, \"package.json\");\n if (existsSync(packageJsonPath)) {\n const content = await readFile(packageJsonPath, \"utf-8\");\n const pkg = JSON.parse(content);\n return pkg.name;\n }\n currentDir = dirname(currentDir);\n }\n } catch {\n // Fall back to hardcoded name if package.json is not found\n }\n\n return \"reference-manager\";\n}\n\nconst packageName = await resolvePackageName();\n\n/**\n * Get backup directory path for a library\n */\nexport function getBackupDirectory(libraryName: string): string {\n const pkgName = packageName ?? \"reference-manager\";\n return join(tmpdir(), pkgName, \"backups\", libraryName);\n}\n\n/**\n * Create a backup of the given file\n */\nexport async function createBackup(sourceFile: string, libraryName: string): Promise<string> {\n const backupDir = getBackupDirectory(libraryName);\n await ensureDirectoryExists(backupDir);\n\n const timestamp = Date.now();\n const backupFileName = `${timestamp}.backup`;\n const backupPath = join(backupDir, backupFileName);\n\n await copyFile(sourceFile, backupPath);\n\n return backupPath;\n}\n\n/**\n * List all backups for a library (sorted by modification time, newest first)\n */\nexport async function listBackups(libraryName: string): Promise<string[]> {\n const backupDir = getBackupDirectory(libraryName);\n\n if (!existsSync(backupDir)) {\n return [];\n }\n\n const files = await readdir(backupDir);\n const backupFiles = files.filter((f) => f.endsWith(\".backup\")).map((f) => join(backupDir, f));\n\n const filesWithStats = await Promise.all(\n backupFiles.map(async (file) => {\n const stats = await stat(file);\n return { file, mtime: stats.mtimeMs };\n })\n );\n\n filesWithStats.sort((a, b) => b.mtime - a.mtime);\n\n return filesWithStats.map((f) => f.file);\n}\n\n/**\n * Clean up old backups based on generation count and age\n */\nexport async function cleanupOldBackups(\n libraryName: string,\n options?: BackupOptions\n): Promise<void> {\n const maxGenerations = options?.maxGenerations ?? DEFAULT_MAX_GENERATIONS;\n const maxAgeMs = options?.maxAgeMs ?? DEFAULT_MAX_AGE_MS;\n\n const backups = await listBackups(libraryName);\n const now = Date.now();\n\n const backupsToDelete: string[] = [];\n\n for (let i = 0; i < backups.length; i++) {\n const backupPath = backups[i];\n if (!backupPath) continue;\n\n const stats = await stat(backupPath);\n const age = now - stats.mtimeMs;\n\n if (i >= maxGenerations || age > maxAgeMs) {\n backupsToDelete.push(backupPath);\n }\n }\n\n await Promise.all(backupsToDelete.map((backup) => unlink(backup)));\n}\n","/**\n * 3-way merge implementation with Last-Write-Wins (LWW) strategy\n */\n\nimport type { CslItem } from \"../../core/csl-json/types.js\";\nimport type {\n FieldConflict,\n ItemConflict,\n MergeOptions,\n MergeResult,\n MergeStatus,\n} from \"./types.js\";\n\n/**\n * Get UUID from item, with fallback to id if uuid is missing\n */\nfunction getItemUuid(item: CslItem): string {\n return item.custom?.uuid || item.id || \"unknown\";\n}\n\n/**\n * Get timestamp from item, with fallback to created_at\n */\nfunction getTimestamp(item: CslItem): string {\n return item.custom?.timestamp || item.custom?.created_at || \"1970-01-01T00:00:00.000Z\";\n}\n\n/**\n * Deep equality check for field values\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n return JSON.stringify(a) === JSON.stringify(b);\n}\n\n/**\n * Resolve a field conflict using LWW or prefer option\n */\nfunction resolveFieldConflict(\n localValue: unknown,\n remoteValue: unknown,\n localTimestamp: string,\n remoteTimestamp: string,\n options?: MergeOptions\n): unknown {\n if (localTimestamp > remoteTimestamp) {\n return localValue;\n }\n if (remoteTimestamp > localTimestamp) {\n return remoteValue;\n }\n // Timestamps equal: use prefer option or default to local\n if (options?.prefer === \"remote\") {\n return remoteValue;\n }\n return localValue;\n}\n\n/**\n * Determine conflict resolution type\n */\nfunction determineResolution(\n fieldConflicts: FieldConflict[],\n localTimestamp: string,\n remoteTimestamp: string,\n options?: MergeOptions\n): ItemConflict[\"resolution\"] {\n const hasRealConflicts = fieldConflicts.every((fc) => fc.resolved !== undefined);\n const localIsNewer = fieldConflicts.some(\n (fc) => fc.local !== fc.remote && localTimestamp > remoteTimestamp\n );\n const remoteIsNewer = fieldConflicts.some(\n (fc) => fc.local !== fc.remote && remoteTimestamp > localTimestamp\n );\n\n if (hasRealConflicts && localIsNewer) return \"auto-lww\";\n if (hasRealConflicts && remoteIsNewer) return \"auto-lww\";\n if (options?.prefer === \"local\") return \"prefer-local\";\n if (options?.prefer === \"remote\") return \"prefer-remote\";\n return \"unresolved\";\n}\n\n/**\n * Merge a single field from base, local, and remote versions\n */\nfunction mergeField(\n key: string,\n baseValue: unknown,\n localValue: unknown,\n remoteValue: unknown,\n localTimestamp: string,\n remoteTimestamp: string,\n options?: MergeOptions\n): { value: unknown; conflict: FieldConflict | null } {\n const localChanged = !deepEqual(baseValue, localValue);\n const remoteChanged = !deepEqual(baseValue, remoteValue);\n\n if (!localChanged && !remoteChanged) {\n return { value: baseValue, conflict: null };\n }\n\n if (localChanged && !remoteChanged) {\n return { value: localValue, conflict: null };\n }\n\n if (!localChanged && remoteChanged) {\n return { value: remoteValue, conflict: null };\n }\n\n // Both changed\n if (deepEqual(localValue, remoteValue)) {\n return { value: localValue, conflict: null };\n }\n\n // Both changed to different values\n const resolved = resolveFieldConflict(\n localValue,\n remoteValue,\n localTimestamp,\n remoteTimestamp,\n options\n );\n\n // Don't record conflicts for 'custom' metadata field\n if (key === \"custom\") {\n return { value: resolved, conflict: null };\n }\n\n return {\n value: resolved,\n conflict: {\n field: key,\n base: baseValue,\n local: localValue,\n remote: remoteValue,\n resolved,\n },\n };\n}\n\n/**\n * Merge a single item from base, local, and remote versions\n */\nfunction mergeItem(\n base: CslItem,\n local: CslItem,\n remote: CslItem,\n options?: MergeOptions\n): { merged: CslItem; conflict: ItemConflict | null } {\n const uuid = getItemUuid(base);\n const localTimestamp = getTimestamp(local);\n const remoteTimestamp = getTimestamp(remote);\n\n const merged: CslItem = { ...base };\n const fieldConflicts: FieldConflict[] = [];\n\n // Get all unique keys from all three versions\n const allKeys = new Set<string>([\n ...Object.keys(base),\n ...Object.keys(local),\n ...Object.keys(remote),\n ]);\n\n for (const key of allKeys) {\n const baseValue = (base as Record<string, unknown>)[key];\n const localValue = (local as Record<string, unknown>)[key];\n const remoteValue = (remote as Record<string, unknown>)[key];\n\n const { value, conflict } = mergeField(\n key,\n baseValue,\n localValue,\n remoteValue,\n localTimestamp,\n remoteTimestamp,\n options\n );\n\n (merged as Record<string, unknown>)[key] = value;\n\n if (conflict) {\n fieldConflicts.push(conflict);\n }\n }\n\n // If there are field conflicts, create ItemConflict\n if (fieldConflicts.length > 0) {\n const resolution = determineResolution(\n fieldConflicts,\n localTimestamp,\n remoteTimestamp,\n options\n );\n\n return {\n merged,\n conflict: {\n uuid,\n id: base.id || \"unknown\",\n fields: fieldConflicts,\n localTimestamp,\n remoteTimestamp,\n resolution,\n },\n };\n }\n\n return { merged, conflict: null };\n}\n\n/**\n * Build UUID-indexed maps from item arrays\n */\nfunction buildItemMaps(base: CslItem[], local: CslItem[], remote: CslItem[]) {\n const baseMap = new Map<string, CslItem>();\n const localMap = new Map<string, CslItem>();\n const remoteMap = new Map<string, CslItem>();\n\n for (const item of base) {\n baseMap.set(getItemUuid(item), item);\n }\n for (const item of local) {\n localMap.set(getItemUuid(item), item);\n }\n for (const item of remote) {\n remoteMap.set(getItemUuid(item), item);\n }\n\n return { baseMap, localMap, remoteMap };\n}\n\n/**\n * Handle items that exist in all three versions\n */\nfunction mergeExistingItem(\n baseItem: CslItem,\n localItem: CslItem,\n remoteItem: CslItem,\n options: MergeOptions | undefined,\n merged: CslItem[],\n conflicts: ItemConflict[]\n): void {\n const { merged: mergedItem, conflict } = mergeItem(baseItem, localItem, remoteItem, options);\n merged.push(mergedItem);\n if (conflict) {\n conflicts.push(conflict);\n }\n}\n\n/**\n * Handle items added in both local and remote\n */\nfunction handleDualAddition(\n uuid: string,\n localItem: CslItem,\n remoteItem: CslItem,\n options: MergeOptions | undefined,\n merged: CslItem[],\n conflicts: ItemConflict[]\n): void {\n if (deepEqual(localItem, remoteItem)) {\n merged.push(localItem);\n } else {\n const syntheticBase: CslItem = {\n id: uuid,\n type: \"article\",\n custom: {\n uuid,\n created_at: \"1970-01-01T00:00:00.000Z\",\n timestamp: \"1970-01-01T00:00:00.000Z\",\n },\n };\n const { merged: mergedItem, conflict } = mergeItem(\n syntheticBase,\n localItem,\n remoteItem,\n options\n );\n merged.push(mergedItem);\n if (conflict) {\n conflicts.push(conflict);\n }\n }\n}\n\n/**\n * Process a single UUID across all three versions\n */\nfunction processItem(\n uuid: string,\n baseMap: Map<string, CslItem>,\n localMap: Map<string, CslItem>,\n remoteMap: Map<string, CslItem>,\n options: MergeOptions | undefined,\n result: {\n merged: CslItem[];\n conflicts: ItemConflict[];\n localOnly: CslItem[];\n remoteOnly: CslItem[];\n deletedInLocal: CslItem[];\n deletedInRemote: CslItem[];\n }\n): void {\n const baseItem = baseMap.get(uuid);\n const localItem = localMap.get(uuid);\n const remoteItem = remoteMap.get(uuid);\n\n if (baseItem && localItem && remoteItem) {\n mergeExistingItem(baseItem, localItem, remoteItem, options, result.merged, result.conflicts);\n } else if (!baseItem && localItem && remoteItem) {\n handleDualAddition(uuid, localItem, remoteItem, options, result.merged, result.conflicts);\n } else if (!baseItem && localItem && !remoteItem) {\n result.merged.push(localItem);\n result.localOnly.push(localItem);\n } else if (!baseItem && !localItem && remoteItem) {\n result.merged.push(remoteItem);\n result.remoteOnly.push(remoteItem);\n } else if (baseItem && !localItem && remoteItem) {\n result.deletedInLocal.push(baseItem);\n } else if (baseItem && localItem && !remoteItem) {\n result.deletedInRemote.push(baseItem);\n } else if (baseItem && !localItem && !remoteItem) {\n result.deletedInLocal.push(baseItem);\n result.deletedInRemote.push(baseItem);\n }\n}\n\n/**\n * Performs a 3-way merge of CSL-JSON items\n *\n * @param base - Base version (common ancestor)\n * @param local - Local version (current working copy)\n * @param remote - Remote version (incoming changes)\n * @param options - Merge options (e.g., prefer local/remote for tie-breaking)\n * @returns Merge result with merged items and conflict information\n */\nexport function threeWayMerge(\n base: CslItem[],\n local: CslItem[],\n remote: CslItem[],\n options?: MergeOptions\n): MergeResult {\n const { baseMap, localMap, remoteMap } = buildItemMaps(base, local, remote);\n\n const result = {\n merged: [] as CslItem[],\n conflicts: [] as ItemConflict[],\n localOnly: [] as CslItem[],\n remoteOnly: [] as CslItem[],\n deletedInLocal: [] as CslItem[],\n deletedInRemote: [] as CslItem[],\n };\n\n const allUuids = new Set<string>([...baseMap.keys(), ...localMap.keys(), ...remoteMap.keys()]);\n\n for (const uuid of allUuids) {\n processItem(uuid, baseMap, localMap, remoteMap, options, result);\n }\n\n // Determine overall status\n let status: MergeStatus = \"success\";\n if (result.conflicts.length > 0) {\n const hasUnresolved = result.conflicts.some((c) => c.resolution === \"unresolved\");\n status = hasUnresolved ? \"conflict\" : \"auto-resolved\";\n }\n\n return {\n status,\n ...result,\n };\n}\n"],"names":["i","f"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/core/csl-json/validator.ts","../src/utils/logger.ts","../src/utils/file.ts","../src/utils/backup.ts","../src/features/merge/three-way.ts"],"sourcesContent":["import { CslItemSchema, CslLibrarySchema } from \"./types\";\nimport type { CslItem, CslLibrary } from \"./types\";\n\n/**\n * Validate CSL-JSON library structure\n * @param data - Data to validate (can be any type)\n * @returns Validated CSL-JSON library\n * @throws Error if validation fails\n */\nexport function validateCslJson(data: unknown): CslLibrary {\n const parseResult = CslLibrarySchema.safeParse(data);\n\n if (!parseResult.success) {\n throw new Error(`Invalid CSL-JSON structure: ${parseResult.error.message}`);\n }\n\n return parseResult.data;\n}\n\n/**\n * Validate a single CSL-JSON item\n * @param data - Data to validate (can be any type)\n * @returns Validation result with valid flag and errors\n */\nexport function validateCslItem(data: unknown): {\n valid: boolean;\n data?: CslItem;\n errors?: string[];\n} {\n const parseResult = CslItemSchema.safeParse(data);\n\n if (!parseResult.success) {\n return {\n valid: false,\n errors: parseResult.error.issues.map((issue) => issue.message),\n };\n }\n\n return {\n valid: true,\n data: parseResult.data,\n };\n}\n","export type LogLevel = \"silent\" | \"info\" | \"debug\";\n\nexport interface Logger {\n info(...args: unknown[]): void;\n debug(...args: unknown[]): void;\n error(...args: unknown[]): void;\n}\n\nexport function createLogger(level: LogLevel = \"info\"): Logger {\n const shouldLogInfo = level === \"info\" || level === \"debug\";\n const shouldLogDebug = level === \"debug\";\n\n function formatMessage(...args: unknown[]): string {\n return `${args.map((arg) => String(arg)).join(\" \")}\\n`;\n }\n\n return {\n info(...args: unknown[]): void {\n if (shouldLogInfo) {\n process.stderr.write(formatMessage(...args));\n }\n },\n\n debug(...args: unknown[]): void {\n if (shouldLogDebug) {\n process.stderr.write(formatMessage(...args));\n }\n },\n\n error(...args: unknown[]): void {\n process.stderr.write(formatMessage(...args));\n },\n };\n}\n","import { mkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport writeFileAtomicLib from \"write-file-atomic\";\n\n/**\n * Write file atomically with parent directory creation\n */\nexport async function writeFileAtomic(filePath: string, content: string): Promise<void> {\n await ensureDirectoryExists(dirname(filePath));\n await writeFileAtomicLib(filePath, content, { encoding: \"utf-8\" });\n}\n\n/**\n * Ensure directory exists, creating it recursively if necessary\n */\nexport async function ensureDirectoryExists(dirPath: string): Promise<void> {\n await mkdir(dirPath, { recursive: true });\n}\n","import { existsSync } from \"node:fs\";\nimport { copyFile, readFile, readdir, stat, unlink } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { ensureDirectoryExists } from \"./file\";\n\nexport interface BackupOptions {\n maxGenerations?: number;\n maxAgeMs?: number;\n}\n\nconst DEFAULT_MAX_GENERATIONS = 50;\nconst DEFAULT_MAX_AGE_MS = 365 * 24 * 60 * 60 * 1000; // 1 year\n\n/**\n * Get package name from package.json\n */\nasync function resolvePackageName(): Promise<string> {\n try {\n const currentFile = fileURLToPath(import.meta.url);\n let currentDir = dirname(currentFile);\n\n for (let i = 0; i < 10; i++) {\n const packageJsonPath = join(currentDir, \"package.json\");\n if (existsSync(packageJsonPath)) {\n const content = await readFile(packageJsonPath, \"utf-8\");\n const pkg = JSON.parse(content);\n return pkg.name;\n }\n currentDir = dirname(currentDir);\n }\n } catch {\n // Fall back to hardcoded name if package.json is not found\n }\n\n return \"reference-manager\";\n}\n\nconst packageName = await resolvePackageName();\n\n/**\n * Get backup directory path for a library\n */\nexport function getBackupDirectory(libraryName: string): string {\n const pkgName = packageName ?? \"reference-manager\";\n return join(tmpdir(), pkgName, \"backups\", libraryName);\n}\n\n/**\n * Create a backup of the given file\n */\nexport async function createBackup(sourceFile: string, libraryName: string): Promise<string> {\n const backupDir = getBackupDirectory(libraryName);\n await ensureDirectoryExists(backupDir);\n\n const timestamp = Date.now();\n const backupFileName = `${timestamp}.backup`;\n const backupPath = join(backupDir, backupFileName);\n\n await copyFile(sourceFile, backupPath);\n\n return backupPath;\n}\n\n/**\n * List all backups for a library (sorted by modification time, newest first)\n */\nexport async function listBackups(libraryName: string): Promise<string[]> {\n const backupDir = getBackupDirectory(libraryName);\n\n if (!existsSync(backupDir)) {\n return [];\n }\n\n const files = await readdir(backupDir);\n const backupFiles = files.filter((f) => f.endsWith(\".backup\")).map((f) => join(backupDir, f));\n\n const filesWithStats = await Promise.all(\n backupFiles.map(async (file) => {\n const stats = await stat(file);\n return { file, mtime: stats.mtimeMs };\n })\n );\n\n filesWithStats.sort((a, b) => b.mtime - a.mtime);\n\n return filesWithStats.map((f) => f.file);\n}\n\n/**\n * Clean up old backups based on generation count and age\n */\nexport async function cleanupOldBackups(\n libraryName: string,\n options?: BackupOptions\n): Promise<void> {\n const maxGenerations = options?.maxGenerations ?? DEFAULT_MAX_GENERATIONS;\n const maxAgeMs = options?.maxAgeMs ?? DEFAULT_MAX_AGE_MS;\n\n const backups = await listBackups(libraryName);\n const now = Date.now();\n\n const backupsToDelete: string[] = [];\n\n for (let i = 0; i < backups.length; i++) {\n const backupPath = backups[i];\n if (!backupPath) continue;\n\n const stats = await stat(backupPath);\n const age = now - stats.mtimeMs;\n\n if (i >= maxGenerations || age > maxAgeMs) {\n backupsToDelete.push(backupPath);\n }\n }\n\n await Promise.all(backupsToDelete.map((backup) => unlink(backup)));\n}\n","/**\n * 3-way merge implementation with Last-Write-Wins (LWW) strategy\n */\n\nimport type { CslItem } from \"../../core/csl-json/types.js\";\nimport type {\n FieldConflict,\n ItemConflict,\n MergeOptions,\n MergeResult,\n MergeStatus,\n} from \"./types.js\";\n\n/**\n * Get UUID from item, with fallback to id if uuid is missing\n */\nfunction getItemUuid(item: CslItem): string {\n return item.custom?.uuid || item.id || \"unknown\";\n}\n\n/**\n * Get timestamp from item, with fallback to created_at\n */\nfunction getTimestamp(item: CslItem): string {\n return item.custom?.timestamp || item.custom?.created_at || \"1970-01-01T00:00:00.000Z\";\n}\n\n/**\n * Deep equality check for field values\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n return JSON.stringify(a) === JSON.stringify(b);\n}\n\n/**\n * Resolve a field conflict using LWW or prefer option\n */\nfunction resolveFieldConflict(\n localValue: unknown,\n remoteValue: unknown,\n localTimestamp: string,\n remoteTimestamp: string,\n options?: MergeOptions\n): unknown {\n if (localTimestamp > remoteTimestamp) {\n return localValue;\n }\n if (remoteTimestamp > localTimestamp) {\n return remoteValue;\n }\n // Timestamps equal: use prefer option or default to local\n if (options?.prefer === \"remote\") {\n return remoteValue;\n }\n return localValue;\n}\n\n/**\n * Determine conflict resolution type\n */\nfunction determineResolution(\n fieldConflicts: FieldConflict[],\n localTimestamp: string,\n remoteTimestamp: string,\n options?: MergeOptions\n): ItemConflict[\"resolution\"] {\n const hasRealConflicts = fieldConflicts.every((fc) => fc.resolved !== undefined);\n const localIsNewer = fieldConflicts.some(\n (fc) => fc.local !== fc.remote && localTimestamp > remoteTimestamp\n );\n const remoteIsNewer = fieldConflicts.some(\n (fc) => fc.local !== fc.remote && remoteTimestamp > localTimestamp\n );\n\n if (hasRealConflicts && localIsNewer) return \"auto-lww\";\n if (hasRealConflicts && remoteIsNewer) return \"auto-lww\";\n if (options?.prefer === \"local\") return \"prefer-local\";\n if (options?.prefer === \"remote\") return \"prefer-remote\";\n return \"unresolved\";\n}\n\n/**\n * Merge a single field from base, local, and remote versions\n */\nfunction mergeField(\n key: string,\n baseValue: unknown,\n localValue: unknown,\n remoteValue: unknown,\n localTimestamp: string,\n remoteTimestamp: string,\n options?: MergeOptions\n): { value: unknown; conflict: FieldConflict | null } {\n const localChanged = !deepEqual(baseValue, localValue);\n const remoteChanged = !deepEqual(baseValue, remoteValue);\n\n if (!localChanged && !remoteChanged) {\n return { value: baseValue, conflict: null };\n }\n\n if (localChanged && !remoteChanged) {\n return { value: localValue, conflict: null };\n }\n\n if (!localChanged && remoteChanged) {\n return { value: remoteValue, conflict: null };\n }\n\n // Both changed\n if (deepEqual(localValue, remoteValue)) {\n return { value: localValue, conflict: null };\n }\n\n // Both changed to different values\n const resolved = resolveFieldConflict(\n localValue,\n remoteValue,\n localTimestamp,\n remoteTimestamp,\n options\n );\n\n // Don't record conflicts for 'custom' metadata field\n if (key === \"custom\") {\n return { value: resolved, conflict: null };\n }\n\n return {\n value: resolved,\n conflict: {\n field: key,\n base: baseValue,\n local: localValue,\n remote: remoteValue,\n resolved,\n },\n };\n}\n\n/**\n * Merge a single item from base, local, and remote versions\n */\nfunction mergeItem(\n base: CslItem,\n local: CslItem,\n remote: CslItem,\n options?: MergeOptions\n): { merged: CslItem; conflict: ItemConflict | null } {\n const uuid = getItemUuid(base);\n const localTimestamp = getTimestamp(local);\n const remoteTimestamp = getTimestamp(remote);\n\n const merged: CslItem = { ...base };\n const fieldConflicts: FieldConflict[] = [];\n\n // Get all unique keys from all three versions\n const allKeys = new Set<string>([\n ...Object.keys(base),\n ...Object.keys(local),\n ...Object.keys(remote),\n ]);\n\n for (const key of allKeys) {\n const baseValue = (base as Record<string, unknown>)[key];\n const localValue = (local as Record<string, unknown>)[key];\n const remoteValue = (remote as Record<string, unknown>)[key];\n\n const { value, conflict } = mergeField(\n key,\n baseValue,\n localValue,\n remoteValue,\n localTimestamp,\n remoteTimestamp,\n options\n );\n\n (merged as Record<string, unknown>)[key] = value;\n\n if (conflict) {\n fieldConflicts.push(conflict);\n }\n }\n\n // If there are field conflicts, create ItemConflict\n if (fieldConflicts.length > 0) {\n const resolution = determineResolution(\n fieldConflicts,\n localTimestamp,\n remoteTimestamp,\n options\n );\n\n return {\n merged,\n conflict: {\n uuid,\n id: base.id || \"unknown\",\n fields: fieldConflicts,\n localTimestamp,\n remoteTimestamp,\n resolution,\n },\n };\n }\n\n return { merged, conflict: null };\n}\n\n/**\n * Build UUID-indexed maps from item arrays\n */\nfunction buildItemMaps(base: CslItem[], local: CslItem[], remote: CslItem[]) {\n const baseMap = new Map<string, CslItem>();\n const localMap = new Map<string, CslItem>();\n const remoteMap = new Map<string, CslItem>();\n\n for (const item of base) {\n baseMap.set(getItemUuid(item), item);\n }\n for (const item of local) {\n localMap.set(getItemUuid(item), item);\n }\n for (const item of remote) {\n remoteMap.set(getItemUuid(item), item);\n }\n\n return { baseMap, localMap, remoteMap };\n}\n\n/**\n * Handle items that exist in all three versions\n */\nfunction mergeExistingItem(\n baseItem: CslItem,\n localItem: CslItem,\n remoteItem: CslItem,\n options: MergeOptions | undefined,\n merged: CslItem[],\n conflicts: ItemConflict[]\n): void {\n const { merged: mergedItem, conflict } = mergeItem(baseItem, localItem, remoteItem, options);\n merged.push(mergedItem);\n if (conflict) {\n conflicts.push(conflict);\n }\n}\n\n/**\n * Handle items added in both local and remote\n */\nfunction handleDualAddition(\n uuid: string,\n localItem: CslItem,\n remoteItem: CslItem,\n options: MergeOptions | undefined,\n merged: CslItem[],\n conflicts: ItemConflict[]\n): void {\n if (deepEqual(localItem, remoteItem)) {\n merged.push(localItem);\n } else {\n const syntheticBase: CslItem = {\n id: uuid,\n type: \"article\",\n custom: {\n uuid,\n created_at: \"1970-01-01T00:00:00.000Z\",\n timestamp: \"1970-01-01T00:00:00.000Z\",\n },\n };\n const { merged: mergedItem, conflict } = mergeItem(\n syntheticBase,\n localItem,\n remoteItem,\n options\n );\n merged.push(mergedItem);\n if (conflict) {\n conflicts.push(conflict);\n }\n }\n}\n\n/**\n * Process a single UUID across all three versions\n */\nfunction processItem(\n uuid: string,\n baseMap: Map<string, CslItem>,\n localMap: Map<string, CslItem>,\n remoteMap: Map<string, CslItem>,\n options: MergeOptions | undefined,\n result: {\n merged: CslItem[];\n conflicts: ItemConflict[];\n localOnly: CslItem[];\n remoteOnly: CslItem[];\n deletedInLocal: CslItem[];\n deletedInRemote: CslItem[];\n }\n): void {\n const baseItem = baseMap.get(uuid);\n const localItem = localMap.get(uuid);\n const remoteItem = remoteMap.get(uuid);\n\n if (baseItem && localItem && remoteItem) {\n mergeExistingItem(baseItem, localItem, remoteItem, options, result.merged, result.conflicts);\n } else if (!baseItem && localItem && remoteItem) {\n handleDualAddition(uuid, localItem, remoteItem, options, result.merged, result.conflicts);\n } else if (!baseItem && localItem && !remoteItem) {\n result.merged.push(localItem);\n result.localOnly.push(localItem);\n } else if (!baseItem && !localItem && remoteItem) {\n result.merged.push(remoteItem);\n result.remoteOnly.push(remoteItem);\n } else if (baseItem && !localItem && remoteItem) {\n result.deletedInLocal.push(baseItem);\n } else if (baseItem && localItem && !remoteItem) {\n result.deletedInRemote.push(baseItem);\n } else if (baseItem && !localItem && !remoteItem) {\n result.deletedInLocal.push(baseItem);\n result.deletedInRemote.push(baseItem);\n }\n}\n\n/**\n * Performs a 3-way merge of CSL-JSON items\n *\n * @param base - Base version (common ancestor)\n * @param local - Local version (current working copy)\n * @param remote - Remote version (incoming changes)\n * @param options - Merge options (e.g., prefer local/remote for tie-breaking)\n * @returns Merge result with merged items and conflict information\n */\nexport function threeWayMerge(\n base: CslItem[],\n local: CslItem[],\n remote: CslItem[],\n options?: MergeOptions\n): MergeResult {\n const { baseMap, localMap, remoteMap } = buildItemMaps(base, local, remote);\n\n const result = {\n merged: [] as CslItem[],\n conflicts: [] as ItemConflict[],\n localOnly: [] as CslItem[],\n remoteOnly: [] as CslItem[],\n deletedInLocal: [] as CslItem[],\n deletedInRemote: [] as CslItem[],\n };\n\n const allUuids = new Set<string>([...baseMap.keys(), ...localMap.keys(), ...remoteMap.keys()]);\n\n for (const uuid of allUuids) {\n processItem(uuid, baseMap, localMap, remoteMap, options, result);\n }\n\n // Determine overall status\n let status: MergeStatus = \"success\";\n if (result.conflicts.length > 0) {\n const hasUnresolved = result.conflicts.some((c) => c.resolution === \"unresolved\");\n status = hasUnresolved ? \"conflict\" : \"auto-resolved\";\n }\n\n return {\n status,\n ...result,\n };\n}\n"],"names":["i","f"],"mappings":"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"}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -3,4 +3,5 @@ export { computeHash, computeFileHash } from "./hash";
|
|
|
3
3
|
export { writeFileAtomic, ensureDirectoryExists } from "./file";
|
|
4
4
|
export { createBackup, cleanupOldBackups, getBackupDirectory, listBackups, type BackupOptions, } from "./backup";
|
|
5
5
|
export { pickDefined } from "./object";
|
|
6
|
+
export { getOpenerCommand, openWithSystemApp } from "./opener";
|
|
6
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,MAAM,EAAE,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC;AAChE,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,KAAK,aAAa,GACnB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,MAAM,EAAE,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC;AAChE,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,KAAK,aAAa,GACnB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the system opener command for the specified platform.
|
|
3
|
+
* @param platform - The platform (darwin, linux, win32)
|
|
4
|
+
* @returns The command array to execute
|
|
5
|
+
*/
|
|
6
|
+
export declare function getOpenerCommand(platform: string): string[];
|
|
7
|
+
/**
|
|
8
|
+
* Open a file with the system's default application.
|
|
9
|
+
* @param filePath - The path to the file to open
|
|
10
|
+
* @param platform - The platform (defaults to process.platform)
|
|
11
|
+
*/
|
|
12
|
+
export declare function openWithSystemApp(filePath: string, platform?: string): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=opener.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opener.d.ts","sourceRoot":"","sources":["../../src/utils/opener.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAW3D;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAyB,GAClC,OAAO,CAAC,IAAI,CAAC,CAyBf"}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"loader-BItrdVWG.js","sources":["../../src/config/schema.ts","../../src/config/defaults.ts","../../src/config/loader.ts"],"sourcesContent":["/**\n * Configuration schema using Zod\n */\n\nimport { z } from \"zod\";\nimport { sortFieldSchema, sortOrderSchema } from \"../features/pagination/types.js\";\n\n/**\n * Log level schema\n */\nexport const logLevelSchema = z.enum([\"silent\", \"info\", \"debug\"]);\n\n/**\n * Interactive search configuration schema\n */\nexport const interactiveConfigSchema = z.object({\n limit: z.number().int().nonnegative(),\n debounceMs: z.number().int().nonnegative(),\n});\n\n/**\n * CLI configuration schema\n */\nexport const cliConfigSchema = z.object({\n defaultLimit: z.number().int().nonnegative(),\n defaultSort: sortFieldSchema,\n defaultOrder: sortOrderSchema,\n interactive: interactiveConfigSchema,\n});\n\n/**\n * MCP configuration schema\n */\nexport const mcpConfigSchema = z.object({\n defaultLimit: z.number().int().nonnegative(),\n});\n\n/**\n * Backup configuration schema\n */\nexport const backupConfigSchema = z.object({\n maxGenerations: z.number().int().positive(),\n maxAgeDays: z.number().int().positive(),\n directory: z.string().min(1),\n});\n\n/**\n * File watching configuration schema\n * Note: File watching is always enabled in server mode (HTTP/MCP).\n * CLI mode does not use file watching.\n */\nexport const watchConfigSchema = z.object({\n debounceMs: z.number().int().nonnegative(),\n pollIntervalMs: z.number().int().positive(),\n retryIntervalMs: z.number().int().positive(),\n maxRetries: z.number().int().nonnegative(),\n});\n\n/**\n * Server configuration schema\n */\nexport const serverConfigSchema = z.object({\n autoStart: z.boolean(),\n autoStopMinutes: z.number().int().nonnegative(),\n});\n\n/**\n * Citation format schema\n */\nexport const citationFormatSchema = z.enum([\"text\", \"html\", \"rtf\"]);\n\n/**\n * Citation configuration schema\n */\nexport const citationConfigSchema = z.object({\n defaultStyle: z.string(),\n cslDirectory: z.array(z.string()),\n defaultLocale: z.string(),\n defaultFormat: citationFormatSchema,\n});\n\n/**\n * PubMed API configuration schema\n */\nexport const pubmedConfigSchema = z.object({\n email: z.string().optional(),\n apiKey: z.string().optional(),\n});\n\n/**\n * Fulltext storage configuration schema\n */\nexport const fulltextConfigSchema = z.object({\n directory: z.string().min(1),\n});\n\n/**\n * Complete configuration schema\n */\nexport const configSchema = z.object({\n library: z.string().min(1),\n logLevel: logLevelSchema,\n backup: backupConfigSchema,\n watch: watchConfigSchema,\n server: serverConfigSchema,\n citation: citationConfigSchema,\n pubmed: pubmedConfigSchema,\n fulltext: fulltextConfigSchema,\n cli: cliConfigSchema,\n mcp: mcpConfigSchema,\n});\n\n/**\n * Partial configuration schema (for TOML files)\n * Supports both camelCase and snake_case field names\n */\nexport const partialConfigSchema = z\n .object({\n library: z.string().min(1).optional(),\n logLevel: logLevelSchema.optional(),\n log_level: logLevelSchema.optional(), // snake_case support\n backup: z\n .object({\n maxGenerations: z.number().int().positive().optional(),\n max_generations: z.number().int().positive().optional(),\n maxAgeDays: z.number().int().positive().optional(),\n max_age_days: z.number().int().positive().optional(),\n directory: z.string().min(1).optional(),\n })\n .optional(),\n watch: z\n .object({\n debounceMs: z.number().int().nonnegative().optional(),\n debounce_ms: z.number().int().nonnegative().optional(),\n pollIntervalMs: z.number().int().positive().optional(),\n poll_interval_ms: z.number().int().positive().optional(),\n retryIntervalMs: z.number().int().positive().optional(),\n retry_interval_ms: z.number().int().positive().optional(),\n maxRetries: z.number().int().nonnegative().optional(),\n max_retries: z.number().int().nonnegative().optional(),\n })\n .optional(),\n server: z\n .object({\n autoStart: z.boolean().optional(),\n auto_start: z.boolean().optional(),\n autoStopMinutes: z.number().int().nonnegative().optional(),\n auto_stop_minutes: z.number().int().nonnegative().optional(),\n })\n .optional(),\n citation: z\n .object({\n defaultStyle: z.string().optional(),\n default_style: z.string().optional(),\n cslDirectory: z.union([z.string(), z.array(z.string())]).optional(),\n csl_directory: z.union([z.string(), z.array(z.string())]).optional(),\n defaultLocale: z.string().optional(),\n default_locale: z.string().optional(),\n defaultFormat: citationFormatSchema.optional(),\n default_format: citationFormatSchema.optional(),\n })\n .optional(),\n pubmed: z\n .object({\n email: z.string().optional(),\n apiKey: z.string().optional(),\n api_key: z.string().optional(),\n })\n .optional(),\n fulltext: z\n .object({\n directory: z.string().min(1).optional(),\n })\n .optional(),\n cli: z\n .object({\n defaultLimit: z.number().int().nonnegative().optional(),\n default_limit: z.number().int().nonnegative().optional(),\n defaultSort: sortFieldSchema.optional(),\n default_sort: sortFieldSchema.optional(),\n defaultOrder: sortOrderSchema.optional(),\n default_order: sortOrderSchema.optional(),\n interactive: z\n .object({\n limit: z.number().int().nonnegative().optional(),\n debounceMs: z.number().int().nonnegative().optional(),\n debounce_ms: z.number().int().nonnegative().optional(),\n })\n .optional(),\n })\n .optional(),\n mcp: z\n .object({\n defaultLimit: z.number().int().nonnegative().optional(),\n default_limit: z.number().int().nonnegative().optional(),\n })\n .optional(),\n })\n .passthrough(); // Allow unknown fields in TOML files\n\n/**\n * Inferred types from schemas\n */\nexport type LogLevel = z.infer<typeof logLevelSchema>;\nexport type BackupConfig = z.infer<typeof backupConfigSchema>;\nexport type WatchConfig = z.infer<typeof watchConfigSchema>;\nexport type ServerConfig = z.infer<typeof serverConfigSchema>;\nexport type CitationFormat = z.infer<typeof citationFormatSchema>;\nexport type CitationConfig = z.infer<typeof citationConfigSchema>;\nexport type PubmedConfig = z.infer<typeof pubmedConfigSchema>;\nexport type FulltextConfig = z.infer<typeof fulltextConfigSchema>;\nexport type InteractiveConfig = z.infer<typeof interactiveConfigSchema>;\nexport type CliConfig = z.infer<typeof cliConfigSchema>;\nexport type McpConfig = z.infer<typeof mcpConfigSchema>;\nexport type Config = z.infer<typeof configSchema>;\nexport type PartialConfig = z.infer<typeof partialConfigSchema>;\n\n/**\n * Deep partial type for Config\n */\nexport type DeepPartialConfig = {\n library?: string;\n logLevel?: LogLevel;\n backup?: Partial<BackupConfig>;\n watch?: Partial<WatchConfig>;\n server?: Partial<ServerConfig>;\n citation?: Partial<CitationConfig>;\n pubmed?: Partial<PubmedConfig>;\n fulltext?: Partial<FulltextConfig>;\n cli?: Partial<Omit<CliConfig, \"interactive\">> & {\n interactive?: Partial<InteractiveConfig>;\n };\n mcp?: Partial<McpConfig>;\n};\n\n/**\n * Normalize backup configuration from snake_case to camelCase\n */\nfunction normalizeBackupConfig(\n backup: Partial<{\n maxGenerations?: number;\n max_generations?: number;\n maxAgeDays?: number;\n max_age_days?: number;\n directory?: string;\n }>\n): Partial<BackupConfig> | undefined {\n const normalized: Partial<BackupConfig> = {};\n\n const maxGenerations = backup.maxGenerations ?? backup.max_generations;\n if (maxGenerations !== undefined) {\n normalized.maxGenerations = maxGenerations;\n }\n\n const maxAgeDays = backup.maxAgeDays ?? backup.max_age_days;\n if (maxAgeDays !== undefined) {\n normalized.maxAgeDays = maxAgeDays;\n }\n\n if (backup.directory !== undefined) {\n normalized.directory = backup.directory;\n }\n\n return Object.keys(normalized).length > 0 ? normalized : undefined;\n}\n\n/**\n * Normalize watch configuration from snake_case to camelCase\n */\nfunction normalizeWatchConfig(\n watch: Partial<{\n debounceMs?: number;\n debounce_ms?: number;\n pollIntervalMs?: number;\n poll_interval_ms?: number;\n retryIntervalMs?: number;\n retry_interval_ms?: number;\n maxRetries?: number;\n max_retries?: number;\n }>\n): Partial<WatchConfig> | undefined {\n const normalized: Partial<WatchConfig> = {};\n\n const debounceMs = watch.debounceMs ?? watch.debounce_ms;\n if (debounceMs !== undefined) {\n normalized.debounceMs = debounceMs;\n }\n\n const pollIntervalMs = watch.pollIntervalMs ?? watch.poll_interval_ms;\n if (pollIntervalMs !== undefined) {\n normalized.pollIntervalMs = pollIntervalMs;\n }\n\n const retryIntervalMs = watch.retryIntervalMs ?? watch.retry_interval_ms;\n if (retryIntervalMs !== undefined) {\n normalized.retryIntervalMs = retryIntervalMs;\n }\n\n const maxRetries = watch.maxRetries ?? watch.max_retries;\n if (maxRetries !== undefined) {\n normalized.maxRetries = maxRetries;\n }\n\n return Object.keys(normalized).length > 0 ? normalized : undefined;\n}\n\n/**\n * Normalize server configuration from snake_case to camelCase\n */\nfunction normalizeServerConfig(\n server: Partial<{\n autoStart?: boolean;\n auto_start?: boolean;\n autoStopMinutes?: number;\n auto_stop_minutes?: number;\n }>\n): Partial<ServerConfig> | undefined {\n const normalized: Partial<ServerConfig> = {};\n\n const autoStart = server.autoStart ?? server.auto_start;\n if (autoStart !== undefined) {\n normalized.autoStart = autoStart;\n }\n\n const autoStopMinutes = server.autoStopMinutes ?? server.auto_stop_minutes;\n if (autoStopMinutes !== undefined) {\n normalized.autoStopMinutes = autoStopMinutes;\n }\n\n return Object.keys(normalized).length > 0 ? normalized : undefined;\n}\n\n/**\n * Normalize citation configuration from snake_case to camelCase\n */\nfunction normalizeCitationConfig(\n citation: Partial<{\n defaultStyle?: string;\n default_style?: string;\n cslDirectory?: string | string[];\n csl_directory?: string | string[];\n defaultLocale?: string;\n default_locale?: string;\n defaultFormat?: CitationFormat;\n default_format?: CitationFormat;\n }>\n): Partial<CitationConfig> | undefined {\n const normalized: Partial<CitationConfig> = {};\n\n const defaultStyle = citation.defaultStyle ?? citation.default_style;\n if (defaultStyle !== undefined) {\n normalized.defaultStyle = defaultStyle;\n }\n\n const cslDirectory = citation.cslDirectory ?? citation.csl_directory;\n if (cslDirectory !== undefined) {\n // Normalize to array: string -> [string]\n normalized.cslDirectory = Array.isArray(cslDirectory) ? cslDirectory : [cslDirectory];\n }\n\n const defaultLocale = citation.defaultLocale ?? citation.default_locale;\n if (defaultLocale !== undefined) {\n normalized.defaultLocale = defaultLocale;\n }\n\n const defaultFormat = citation.defaultFormat ?? citation.default_format;\n if (defaultFormat !== undefined) {\n normalized.defaultFormat = defaultFormat;\n }\n\n return Object.keys(normalized).length > 0 ? normalized : undefined;\n}\n\n/**\n * Normalize pubmed configuration from snake_case to camelCase\n */\nfunction normalizePubmedConfig(\n pubmed: Partial<{\n email?: string;\n apiKey?: string;\n api_key?: string;\n }>\n): Partial<PubmedConfig> | undefined {\n const normalized: Partial<PubmedConfig> = {};\n\n if (pubmed.email !== undefined) {\n normalized.email = pubmed.email;\n }\n\n const apiKey = pubmed.apiKey ?? pubmed.api_key;\n if (apiKey !== undefined) {\n normalized.apiKey = apiKey;\n }\n\n return Object.keys(normalized).length > 0 ? normalized : undefined;\n}\n\n/**\n * Section normalizers mapping\n */\nconst sectionNormalizers = {\n backup: normalizeBackupConfig,\n watch: normalizeWatchConfig,\n server: normalizeServerConfig,\n citation: normalizeCitationConfig,\n pubmed: normalizePubmedConfig,\n fulltext: normalizeFulltextConfig,\n cli: normalizeCliConfig,\n mcp: normalizeMcpConfig,\n} as const;\n\ntype SectionKey = keyof typeof sectionNormalizers;\n\n/**\n * Helper to apply a normalizer function to a config section\n */\nfunction applyNormalizer<K extends SectionKey>(\n normalized: DeepPartialConfig,\n partial: PartialConfig,\n key: K,\n normalizer: (typeof sectionNormalizers)[K]\n): void {\n const value = partial[key];\n if (value !== undefined) {\n const result = (normalizer as (input: unknown) => DeepPartialConfig[K] | undefined)(value);\n if (result) {\n normalized[key] = result;\n }\n }\n}\n\n/**\n * Normalize snake_case fields to camelCase\n */\nexport function normalizePartialConfig(partial: PartialConfig): DeepPartialConfig {\n const normalized: DeepPartialConfig = {};\n\n // Simple fields\n if (partial.library !== undefined) {\n normalized.library = partial.library;\n }\n const logLevel = partial.logLevel ?? partial.log_level;\n if (logLevel !== undefined) {\n normalized.logLevel = logLevel;\n }\n\n // Section fields\n for (const key of Object.keys(sectionNormalizers) as SectionKey[]) {\n applyNormalizer(normalized, partial, key, sectionNormalizers[key]);\n }\n\n return normalized;\n}\n\n/**\n * Normalize fulltext configuration\n */\nfunction normalizeFulltextConfig(fulltext: {\n directory?: string | undefined;\n}): Partial<FulltextConfig> | undefined {\n const normalized: Partial<FulltextConfig> = {};\n\n if (fulltext.directory !== undefined) {\n normalized.directory = fulltext.directory;\n }\n\n return Object.keys(normalized).length > 0 ? normalized : undefined;\n}\n\n/**\n * Normalize CLI configuration from snake_case to camelCase\n */\nfunction normalizeCliConfig(\n cli: Partial<{\n defaultLimit?: number;\n default_limit?: number;\n defaultSort?: CliConfig[\"defaultSort\"];\n default_sort?: CliConfig[\"defaultSort\"];\n defaultOrder?: CliConfig[\"defaultOrder\"];\n default_order?: CliConfig[\"defaultOrder\"];\n interactive?: Partial<{\n limit?: number;\n debounceMs?: number;\n debounce_ms?: number;\n }>;\n }>\n): Partial<CliConfig> | undefined {\n const normalized: Partial<CliConfig> = {};\n\n const defaultLimit = cli.defaultLimit ?? cli.default_limit;\n if (defaultLimit !== undefined) {\n normalized.defaultLimit = defaultLimit;\n }\n\n const defaultSort = cli.defaultSort ?? cli.default_sort;\n if (defaultSort !== undefined) {\n normalized.defaultSort = defaultSort;\n }\n\n const defaultOrder = cli.defaultOrder ?? cli.default_order;\n if (defaultOrder !== undefined) {\n normalized.defaultOrder = defaultOrder;\n }\n\n if (cli.interactive !== undefined) {\n const interactive: Partial<InteractiveConfig> = {};\n if (cli.interactive.limit !== undefined) {\n interactive.limit = cli.interactive.limit;\n }\n const debounceMs = cli.interactive.debounceMs ?? cli.interactive.debounce_ms;\n if (debounceMs !== undefined) {\n interactive.debounceMs = debounceMs;\n }\n if (Object.keys(interactive).length > 0) {\n normalized.interactive = interactive as InteractiveConfig;\n }\n }\n\n return Object.keys(normalized).length > 0 ? normalized : undefined;\n}\n\n/**\n * Normalize MCP configuration from snake_case to camelCase\n */\nfunction normalizeMcpConfig(\n mcp: Partial<{\n defaultLimit?: number;\n default_limit?: number;\n }>\n): Partial<McpConfig> | undefined {\n const normalized: Partial<McpConfig> = {};\n\n const defaultLimit = mcp.defaultLimit ?? mcp.default_limit;\n if (defaultLimit !== undefined) {\n normalized.defaultLimit = defaultLimit;\n }\n\n return Object.keys(normalized).length > 0 ? normalized : undefined;\n}\n","/**\n * Default configuration values\n */\n\nimport { tmpdir } from \"node:os\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Config } from \"./schema.js\";\n\n/**\n * Get the default backup directory\n * Uses $TMPDIR/reference-manager/backups/\n */\nexport function getDefaultBackupDirectory(): string {\n return join(tmpdir(), \"reference-manager\", \"backups\");\n}\n\n/**\n * Get the default library path\n * Uses ~/.reference-manager/csl.library.json\n */\nexport function getDefaultLibraryPath(): string {\n return join(homedir(), \".reference-manager\", \"csl.library.json\");\n}\n\n/**\n * Get the default user config path\n * Uses ~/.reference-manager/config.toml\n */\nexport function getDefaultUserConfigPath(): string {\n return join(homedir(), \".reference-manager\", \"config.toml\");\n}\n\n/**\n * Get the default current directory config filename\n * Uses .reference-manager.config.toml\n */\nexport function getDefaultCurrentDirConfigFilename(): string {\n return \".reference-manager.config.toml\";\n}\n\n/**\n * Get the default CSL directory\n * Uses ~/.reference-manager/csl/\n */\nexport function getDefaultCslDirectory(): string {\n return join(homedir(), \".reference-manager\", \"csl\");\n}\n\n/**\n * Get the default fulltext directory\n * Uses ~/.reference-manager/fulltext/\n */\nexport function getDefaultFulltextDirectory(): string {\n return join(homedir(), \".reference-manager\", \"fulltext\");\n}\n\n/**\n * Default configuration\n */\nexport const defaultConfig: Config = {\n library: getDefaultLibraryPath(),\n logLevel: \"info\",\n backup: {\n maxGenerations: 50,\n maxAgeDays: 365,\n directory: getDefaultBackupDirectory(),\n },\n watch: {\n debounceMs: 500,\n pollIntervalMs: 5000,\n retryIntervalMs: 200,\n maxRetries: 10,\n },\n server: {\n autoStart: false,\n autoStopMinutes: 0,\n },\n citation: {\n defaultStyle: \"apa\",\n cslDirectory: [getDefaultCslDirectory()],\n defaultLocale: \"en-US\",\n defaultFormat: \"text\",\n },\n pubmed: {\n email: undefined,\n apiKey: undefined,\n },\n fulltext: {\n directory: getDefaultFulltextDirectory(),\n },\n cli: {\n defaultLimit: 0,\n defaultSort: \"updated\",\n defaultOrder: \"desc\",\n interactive: {\n limit: 20,\n debounceMs: 200,\n },\n },\n mcp: {\n defaultLimit: 20,\n },\n};\n","/**\n * Configuration loader\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { parse as parseTOML } from \"@iarna/toml\";\nimport {\n defaultConfig,\n getDefaultCurrentDirConfigFilename,\n getDefaultUserConfigPath,\n} from \"./defaults.js\";\nimport {\n type Config,\n type DeepPartialConfig,\n type PartialConfig,\n configSchema,\n normalizePartialConfig,\n partialConfigSchema,\n} from \"./schema.js\";\n\n/**\n * Options for loading configuration\n */\nexport interface LoadConfigOptions {\n /** Current working directory (default: process.cwd()) */\n cwd?: string;\n /** User config path (default: ~/.reference-manager/config.toml) */\n userConfigPath?: string;\n /** CLI argument overrides */\n overrides?: Partial<Config>;\n}\n\n/**\n * Load and parse a TOML config file\n */\nfunction loadTOMLFile(path: string): PartialConfig | null {\n if (!existsSync(path)) {\n return null;\n }\n\n try {\n const content = readFileSync(path, \"utf-8\");\n const parsed = parseTOML(content);\n\n // Validate with partial schema\n const validated = partialConfigSchema.parse(parsed);\n return validated;\n } catch (error) {\n throw new Error(\n `Failed to load config from ${path}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n}\n\n/**\n * Merge CLI config with nested interactive section\n */\nfunction mergeCliConfig(\n base: DeepPartialConfig[\"cli\"],\n override: NonNullable<DeepPartialConfig[\"cli\"]>\n): NonNullable<DeepPartialConfig[\"cli\"]> {\n const { interactive: overrideInteractive, ...overrideCliRest } = override;\n const { interactive: baseInteractive, ...baseCliRest } = base ?? {};\n const mergedInteractive =\n overrideInteractive !== undefined\n ? { ...baseInteractive, ...overrideInteractive }\n : baseInteractive;\n return {\n ...baseCliRest,\n ...overrideCliRest,\n ...(mergedInteractive !== undefined ? { interactive: mergedInteractive } : {}),\n };\n}\n\n/**\n * Merge partial configurations\n * Later configs override earlier ones\n */\nfunction mergeConfigs(\n base: DeepPartialConfig,\n ...overrides: (DeepPartialConfig | null | undefined)[]\n): DeepPartialConfig {\n const result: DeepPartialConfig = { ...base };\n\n const sectionKeys = [\n \"backup\",\n \"watch\",\n \"server\",\n \"citation\",\n \"pubmed\",\n \"fulltext\",\n \"mcp\",\n ] as const;\n\n for (const override of overrides) {\n if (!override) continue;\n\n // Merge top-level primitive fields\n if (override.library !== undefined) {\n result.library = override.library;\n }\n if (override.logLevel !== undefined) {\n result.logLevel = override.logLevel;\n }\n\n // Merge section configs\n for (const key of sectionKeys) {\n if (override[key] !== undefined) {\n result[key] = {\n ...result[key],\n ...override[key],\n };\n }\n }\n\n // Merge cli config with nested interactive\n if (override.cli !== undefined) {\n result.cli = mergeCliConfig(result.cli, override.cli);\n }\n }\n\n return result;\n}\n\n/**\n * Fill missing fields with defaults\n */\nfunction fillDefaults(partial: DeepPartialConfig): Config {\n return {\n library: partial.library ?? defaultConfig.library,\n logLevel: partial.logLevel ?? defaultConfig.logLevel,\n backup: {\n maxGenerations: partial.backup?.maxGenerations ?? defaultConfig.backup.maxGenerations,\n maxAgeDays: partial.backup?.maxAgeDays ?? defaultConfig.backup.maxAgeDays,\n directory: partial.backup?.directory ?? defaultConfig.backup.directory,\n },\n watch: {\n debounceMs: partial.watch?.debounceMs ?? defaultConfig.watch.debounceMs,\n pollIntervalMs: partial.watch?.pollIntervalMs ?? defaultConfig.watch.pollIntervalMs,\n retryIntervalMs: partial.watch?.retryIntervalMs ?? defaultConfig.watch.retryIntervalMs,\n maxRetries: partial.watch?.maxRetries ?? defaultConfig.watch.maxRetries,\n },\n server: {\n autoStart: partial.server?.autoStart ?? defaultConfig.server.autoStart,\n autoStopMinutes: partial.server?.autoStopMinutes ?? defaultConfig.server.autoStopMinutes,\n },\n citation: fillCitationDefaults(partial.citation),\n pubmed: fillPubmedDefaults(partial.pubmed),\n fulltext: fillFulltextDefaults(partial.fulltext),\n cli: fillCliDefaults(partial.cli),\n mcp: fillMcpDefaults(partial.mcp),\n };\n}\n\n/**\n * Fill citation config with defaults\n */\nfunction fillCitationDefaults(partial: DeepPartialConfig[\"citation\"]): Config[\"citation\"] {\n return {\n defaultStyle: partial?.defaultStyle ?? defaultConfig.citation.defaultStyle,\n cslDirectory: partial?.cslDirectory ?? defaultConfig.citation.cslDirectory,\n defaultLocale: partial?.defaultLocale ?? defaultConfig.citation.defaultLocale,\n defaultFormat: partial?.defaultFormat ?? defaultConfig.citation.defaultFormat,\n };\n}\n\n/**\n * Fill pubmed config with defaults\n * Environment variables take priority over config file values\n */\nfunction fillPubmedDefaults(partial: DeepPartialConfig[\"pubmed\"]): Config[\"pubmed\"] {\n // Environment variables take priority\n const email = process.env.PUBMED_EMAIL ?? partial?.email ?? defaultConfig.pubmed.email;\n const apiKey = process.env.PUBMED_API_KEY ?? partial?.apiKey ?? defaultConfig.pubmed.apiKey;\n\n return {\n email,\n apiKey,\n };\n}\n\n/**\n * Expand ~ to home directory\n */\nfunction expandTilde(path: string): string {\n if (path.startsWith(\"~/\")) {\n return join(homedir(), path.slice(2));\n }\n return path;\n}\n\n/**\n * Fill fulltext config with defaults\n *\n * Priority:\n * 1. Environment variable REFERENCE_MANAGER_FULLTEXT_DIR\n * 2. Config file setting\n * 3. Default value\n */\nfunction fillFulltextDefaults(partial: DeepPartialConfig[\"fulltext\"]): Config[\"fulltext\"] {\n const envDir = process.env.REFERENCE_MANAGER_FULLTEXT_DIR;\n const directory = envDir ?? partial?.directory ?? defaultConfig.fulltext.directory;\n return {\n directory: expandTilde(directory),\n };\n}\n\n/**\n * Fill CLI config with defaults\n *\n * Priority:\n * 1. Environment variable REFERENCE_MANAGER_CLI_DEFAULT_LIMIT\n * 2. Config file setting\n * 3. Default value\n */\nfunction fillCliDefaults(partial: DeepPartialConfig[\"cli\"]): Config[\"cli\"] {\n const envLimit = process.env.REFERENCE_MANAGER_CLI_DEFAULT_LIMIT;\n const defaultLimit =\n envLimit !== undefined\n ? Number(envLimit)\n : (partial?.defaultLimit ?? defaultConfig.cli.defaultLimit);\n return {\n defaultLimit,\n defaultSort: partial?.defaultSort ?? defaultConfig.cli.defaultSort,\n defaultOrder: partial?.defaultOrder ?? defaultConfig.cli.defaultOrder,\n interactive: {\n limit: partial?.interactive?.limit ?? defaultConfig.cli.interactive.limit,\n debounceMs: partial?.interactive?.debounceMs ?? defaultConfig.cli.interactive.debounceMs,\n },\n };\n}\n\n/**\n * Fill MCP config with defaults\n *\n * Priority:\n * 1. Environment variable REFERENCE_MANAGER_MCP_DEFAULT_LIMIT\n * 2. Config file setting\n * 3. Default value\n */\nfunction fillMcpDefaults(partial: DeepPartialConfig[\"mcp\"]): Config[\"mcp\"] {\n const envLimit = process.env.REFERENCE_MANAGER_MCP_DEFAULT_LIMIT;\n const defaultLimit =\n envLimit !== undefined\n ? Number(envLimit)\n : (partial?.defaultLimit ?? defaultConfig.mcp.defaultLimit);\n return {\n defaultLimit,\n };\n}\n\n/**\n * Load configuration from multiple sources\n *\n * Priority (highest to lowest):\n * 1. CLI argument overrides\n * 2. Current directory config (.reference-manager.config.toml)\n * 3. Environment variable (REFERENCE_MANAGER_CONFIG)\n * 4. User config (~/.reference-manager/config.toml)\n * 5. Default values\n */\nexport function loadConfig(options: LoadConfigOptions = {}): Config {\n const cwd = options.cwd ?? process.cwd();\n const userConfigPath = options.userConfigPath ?? getDefaultUserConfigPath();\n\n // 1. Load user config (lowest priority)\n const userConfig = loadTOMLFile(userConfigPath);\n\n // 2. Load environment variable config\n const envConfigPath = process.env.REFERENCE_MANAGER_CONFIG;\n const envConfig = envConfigPath ? loadTOMLFile(envConfigPath) : null;\n\n // 3. Load current directory config (highest priority)\n const currentConfigPath = join(cwd, getDefaultCurrentDirConfigFilename());\n const currentConfig = loadTOMLFile(currentConfigPath);\n\n // Normalize snake_case to camelCase\n const normalizedUser = userConfig ? normalizePartialConfig(userConfig) : null;\n const normalizedEnv = envConfig ? normalizePartialConfig(envConfig) : null;\n const normalizedCurrent = currentConfig ? normalizePartialConfig(currentConfig) : null;\n\n // Merge configs (priority: current > env > user > defaults)\n const merged = mergeConfigs(\n {},\n normalizedUser,\n normalizedEnv,\n normalizedCurrent,\n options.overrides\n );\n\n // Fill missing fields with defaults\n const config = fillDefaults(merged);\n\n // Validate final config\n try {\n return configSchema.parse(config);\n } catch (error) {\n throw new Error(\n `Invalid configuration: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n}\n"],"names":["parseTOML"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
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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"}
|