@ncukondo/reference-manager 0.14.1 → 0.15.1
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 +64 -16
- package/bin/reference-manager.js +0 -0
- package/dist/chunks/{action-menu-CVSizwXm.js → action-menu-DwCcc6Gt.js} +3 -3
- package/dist/chunks/{action-menu-CVSizwXm.js.map → action-menu-DwCcc6Gt.js.map} +1 -1
- package/dist/chunks/{file-watcher-D2Y-SlcE.js → file-watcher-B_WpVHSV.js} +18 -18
- package/dist/chunks/{file-watcher-D2Y-SlcE.js.map → file-watcher-B_WpVHSV.js.map} +1 -1
- package/dist/chunks/{index-CXoDLO8W.js → index-B4RmLBI1.js} +1698 -504
- package/dist/chunks/index-B4RmLBI1.js.map +1 -0
- package/dist/chunks/index-DEd6F5Rr.js +10 -0
- package/dist/chunks/index-DEd6F5Rr.js.map +1 -0
- package/dist/chunks/{index-DapYyqAC.js → index-DHgeuWGP.js} +112 -35
- package/dist/chunks/index-DHgeuWGP.js.map +1 -0
- package/dist/chunks/{loader-C1EpnyPm.js → loader-DStZe-OB.js} +82 -32
- package/dist/chunks/loader-DStZe-OB.js.map +1 -0
- package/dist/chunks/{reference-select-DSVwE9iu.js → reference-select-B9w9CLa1.js} +3 -3
- package/dist/chunks/{reference-select-DSVwE9iu.js.map → reference-select-B9w9CLa1.js.map} +1 -1
- package/dist/chunks/{style-select-CYo0O7MZ.js → style-select-BNQHC79W.js} +2 -2
- package/dist/chunks/{style-select-CYo0O7MZ.js.map → style-select-BNQHC79W.js.map} +1 -1
- package/dist/chunks/{tty-CDBIQraQ.js → tty-BMyaEOhX.js} +2 -2
- package/dist/chunks/tty-BMyaEOhX.js.map +1 -0
- package/dist/cli/commands/attach.d.ts +204 -0
- package/dist/cli/commands/attach.d.ts.map +1 -0
- package/dist/cli/commands/cite.d.ts +1 -1
- package/dist/cli/commands/cite.d.ts.map +1 -1
- package/dist/cli/commands/config.d.ts.map +1 -1
- package/dist/cli/commands/edit.d.ts.map +1 -1
- package/dist/cli/commands/export.d.ts +1 -1
- package/dist/cli/commands/fulltext.d.ts +2 -2
- package/dist/cli/commands/fulltext.d.ts.map +1 -1
- package/dist/cli/commands/list.d.ts +2 -1
- package/dist/cli/commands/list.d.ts.map +1 -1
- package/dist/cli/commands/remove.d.ts.map +1 -1
- package/dist/cli/commands/search.d.ts +3 -2
- package/dist/cli/commands/search.d.ts.map +1 -1
- package/dist/cli/commands/server.d.ts.map +1 -1
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/completion.d.ts.map +1 -1
- package/dist/cli/helpers.d.ts +36 -0
- package/dist/cli/helpers.d.ts.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/server-client.d.ts +37 -1
- package/dist/cli/server-client.d.ts.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/config/defaults.d.ts +7 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/key-parser.d.ts +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/schema.d.ts +22 -8
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/features/attachments/directory-manager.d.ts +40 -0
- package/dist/features/attachments/directory-manager.d.ts.map +1 -0
- package/dist/features/attachments/directory.d.ts +36 -0
- package/dist/features/attachments/directory.d.ts.map +1 -0
- package/dist/features/attachments/filename.d.ts +30 -0
- package/dist/features/attachments/filename.d.ts.map +1 -0
- package/dist/features/attachments/types.d.ts +38 -0
- package/dist/features/attachments/types.d.ts.map +1 -0
- package/dist/features/fulltext/manager.d.ts +1 -1
- package/dist/features/fulltext/manager.d.ts.map +1 -1
- package/dist/features/interactive/tty.d.ts +2 -2
- package/dist/features/operations/attachments/add.d.ts +42 -0
- package/dist/features/operations/attachments/add.d.ts.map +1 -0
- package/dist/features/operations/attachments/detach.d.ts +38 -0
- package/dist/features/operations/attachments/detach.d.ts.map +1 -0
- package/dist/features/operations/attachments/get.d.ts +35 -0
- package/dist/features/operations/attachments/get.d.ts.map +1 -0
- package/dist/features/operations/attachments/index.d.ts +16 -0
- package/dist/features/operations/attachments/index.d.ts.map +1 -0
- package/dist/features/operations/attachments/list.d.ts +32 -0
- package/dist/features/operations/attachments/list.d.ts.map +1 -0
- package/dist/features/operations/attachments/open.d.ts +39 -0
- package/dist/features/operations/attachments/open.d.ts.map +1 -0
- package/dist/features/operations/attachments/sync.d.ts +50 -0
- package/dist/features/operations/attachments/sync.d.ts.map +1 -0
- package/dist/features/operations/fulltext/attach.d.ts +8 -2
- package/dist/features/operations/fulltext/attach.d.ts.map +1 -1
- package/dist/features/operations/fulltext/detach.d.ts +9 -3
- package/dist/features/operations/fulltext/detach.d.ts.map +1 -1
- package/dist/features/operations/fulltext/get.d.ts +8 -2
- package/dist/features/operations/fulltext/get.d.ts.map +1 -1
- package/dist/features/operations/fulltext/open.d.ts +8 -2
- package/dist/features/operations/fulltext/open.d.ts.map +1 -1
- package/dist/features/operations/fulltext-adapter/fulltext-adapter.d.ts +39 -0
- package/dist/features/operations/fulltext-adapter/fulltext-adapter.d.ts.map +1 -0
- package/dist/features/operations/fulltext-adapter/index.d.ts +7 -0
- package/dist/features/operations/fulltext-adapter/index.d.ts.map +1 -0
- package/dist/features/operations/index.d.ts +1 -0
- package/dist/features/operations/index.d.ts.map +1 -1
- package/dist/features/operations/library-operations.d.ts +43 -0
- package/dist/features/operations/library-operations.d.ts.map +1 -1
- package/dist/features/operations/operations-library.d.ts +7 -0
- package/dist/features/operations/operations-library.d.ts.map +1 -1
- package/dist/features/operations/remove.d.ts +1 -0
- package/dist/features/operations/remove.d.ts.map +1 -1
- package/dist/index.js +15 -15
- package/dist/index.js.map +1 -1
- package/dist/server.js +3 -3
- package/dist/utils/opener.d.ts +6 -1
- package/dist/utils/opener.d.ts.map +1 -1
- package/dist/utils/path.d.ts +28 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/package.json +3 -1
- package/dist/chunks/index-CXoDLO8W.js.map +0 -1
- package/dist/chunks/index-DapYyqAC.js.map +0 -1
- package/dist/chunks/loader-C1EpnyPm.js.map +0 -1
- package/dist/chunks/tty-CDBIQraQ.js.map +0 -1
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import { L as Library, p as pickDefined,
|
|
2
|
+
import { L as Library, p as pickDefined, a as sortOrderSchema, v as paginationOptionsSchema, F as FileWatcher, b as sortFieldSchema, u as searchSortFieldSchema } from "./file-watcher-B_WpVHSV.js";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import { promises, readFileSync, existsSync, writeFileSync, mkdirSync, mkdtempSync } 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
|
-
import { join,
|
|
8
|
+
import path__default, { extname, join, basename, dirname } from "node:path";
|
|
9
|
+
import fs__default, { stat, rename, copyFile, readFile, unlink, readdir, mkdir, rm } from "node:fs/promises";
|
|
10
|
+
import { g as getExtension, i as isValidFulltextFiles, a as isReservedRole, F as FULLTEXT_ROLE, b as formatToExtension, c as findFulltextFiles, d as findFulltextFile, e as extensionToFormat, B as BUILTIN_STYLES, h as getFulltextAttachmentTypes, s as startServerWithFileWatcher } from "./index-DHgeuWGP.js";
|
|
11
|
+
import { o as openWithSystemApp, l as loadConfig, e as getDefaultCurrentDirConfigFilename, h as getDefaultUserConfigPath } from "./loader-DStZe-OB.js";
|
|
9
12
|
import { spawn, spawnSync } from "node:child_process";
|
|
10
13
|
import process$1, { stdin, stdout } from "node:process";
|
|
11
|
-
import { l as loadConfig, e as getDefaultCurrentDirConfigFilename, h as getDefaultUserConfigPath, o as openWithSystemApp } from "./loader-C1EpnyPm.js";
|
|
12
|
-
import { mkdir, unlink, rename, copyFile, rm, readFile } from "node:fs/promises";
|
|
13
14
|
import { parse as parse$2, stringify as stringify$2 } from "@iarna/toml";
|
|
14
|
-
import { u as updateReference, B as BUILTIN_STYLES, g as getFulltextAttachmentTypes, s as startServerWithFileWatcher } from "./index-DapYyqAC.js";
|
|
15
15
|
import "@citation-js/core";
|
|
16
16
|
import "@citation-js/plugin-csl";
|
|
17
17
|
import { ZodOptional as ZodOptional$2, z } from "zod";
|
|
18
18
|
import { serve } from "@hono/node-server";
|
|
19
19
|
const name = "@ncukondo/reference-manager";
|
|
20
|
-
const version$1 = "0.
|
|
20
|
+
const version$1 = "0.15.1";
|
|
21
21
|
const description$1 = "A local reference management tool using CSL-JSON as the single source of truth";
|
|
22
22
|
const packageJson = {
|
|
23
23
|
name,
|
|
@@ -261,6 +261,598 @@ function getExitCode(result) {
|
|
|
261
261
|
}
|
|
262
262
|
return 0;
|
|
263
263
|
}
|
|
264
|
+
function normalizePathForOutput(p) {
|
|
265
|
+
return p.replace(/\\/g, "/");
|
|
266
|
+
}
|
|
267
|
+
function extractUuidPrefix(uuid2) {
|
|
268
|
+
const normalized = uuid2.replace(/-/g, "");
|
|
269
|
+
return normalized.slice(0, 8);
|
|
270
|
+
}
|
|
271
|
+
function generateDirectoryName(ref2) {
|
|
272
|
+
const uuid2 = ref2.custom?.uuid;
|
|
273
|
+
if (!uuid2) {
|
|
274
|
+
throw new Error("Reference must have custom.uuid");
|
|
275
|
+
}
|
|
276
|
+
const uuidPrefix = extractUuidPrefix(uuid2);
|
|
277
|
+
const pmid = ref2.PMID?.trim();
|
|
278
|
+
if (pmid) {
|
|
279
|
+
return `${ref2.id}-PMID${pmid}-${uuidPrefix}`;
|
|
280
|
+
}
|
|
281
|
+
return `${ref2.id}-${uuidPrefix}`;
|
|
282
|
+
}
|
|
283
|
+
function getDirectoryPath(ref2, baseDir) {
|
|
284
|
+
const existingDir = ref2.custom?.attachments?.directory;
|
|
285
|
+
if (existingDir) {
|
|
286
|
+
return normalizePathForOutput(path__default.join(baseDir, existingDir));
|
|
287
|
+
}
|
|
288
|
+
const dirName = generateDirectoryName(ref2);
|
|
289
|
+
return normalizePathForOutput(path__default.join(baseDir, dirName));
|
|
290
|
+
}
|
|
291
|
+
async function ensureDirectory(ref2, baseDir) {
|
|
292
|
+
const dirPath = getDirectoryPath(ref2, baseDir);
|
|
293
|
+
try {
|
|
294
|
+
await fs__default.mkdir(dirPath, { recursive: true });
|
|
295
|
+
} catch (error) {
|
|
296
|
+
if (error.code !== "EEXIST") {
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return dirPath;
|
|
301
|
+
}
|
|
302
|
+
async function deleteDirectoryIfEmpty(dirPath) {
|
|
303
|
+
try {
|
|
304
|
+
const files = await fs__default.readdir(dirPath);
|
|
305
|
+
if (files.length === 0) {
|
|
306
|
+
await fs__default.rmdir(dirPath);
|
|
307
|
+
}
|
|
308
|
+
} catch (error) {
|
|
309
|
+
if (error.code !== "ENOENT") {
|
|
310
|
+
throw error;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function slugifyLabel(label) {
|
|
315
|
+
return label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
316
|
+
}
|
|
317
|
+
function generateFilename(role, ext, label) {
|
|
318
|
+
if (label) {
|
|
319
|
+
const slug = slugifyLabel(label);
|
|
320
|
+
return `${role}-${slug}.${ext}`;
|
|
321
|
+
}
|
|
322
|
+
return `${role}.${ext}`;
|
|
323
|
+
}
|
|
324
|
+
function parseFilename(filename) {
|
|
325
|
+
if (!filename) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
const ext = path__default.extname(filename);
|
|
329
|
+
const extWithoutDot = ext.startsWith(".") ? ext.slice(1) : ext;
|
|
330
|
+
const basename2 = ext ? filename.slice(0, -ext.length) : filename;
|
|
331
|
+
const firstHyphenIndex = basename2.indexOf("-");
|
|
332
|
+
if (firstHyphenIndex === -1) {
|
|
333
|
+
return {
|
|
334
|
+
role: basename2,
|
|
335
|
+
ext: extWithoutDot
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
const role = basename2.slice(0, firstHyphenIndex);
|
|
339
|
+
const label = basename2.slice(firstHyphenIndex + 1);
|
|
340
|
+
if (label) {
|
|
341
|
+
return {
|
|
342
|
+
role,
|
|
343
|
+
ext: extWithoutDot,
|
|
344
|
+
label
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
role,
|
|
349
|
+
ext: extWithoutDot
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
async function checkSourceFile(filePath) {
|
|
353
|
+
try {
|
|
354
|
+
await stat(filePath);
|
|
355
|
+
return null;
|
|
356
|
+
} catch {
|
|
357
|
+
return `Source file not found: ${filePath}`;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function validateFulltextConstraint(existingFiles, newFile) {
|
|
361
|
+
if (newFile.role !== "fulltext") {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
const newExt = getExtension(newFile.filename);
|
|
365
|
+
const existingFulltexts = existingFiles.filter((f) => f.role === "fulltext");
|
|
366
|
+
for (const existing of existingFulltexts) {
|
|
367
|
+
const existingExt = getExtension(existing.filename);
|
|
368
|
+
if (existingExt === newExt) {
|
|
369
|
+
return `A fulltext ${newExt.toUpperCase()} already exists. Use --force to overwrite.`;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const testFiles = [...existingFiles, newFile];
|
|
373
|
+
if (!isValidFulltextFiles(testFiles)) {
|
|
374
|
+
return "fulltext role allows max 2 files (1 PDF + 1 Markdown)";
|
|
375
|
+
}
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
function findExistingFile(files, filename) {
|
|
379
|
+
return files.find((f) => f.filename === filename);
|
|
380
|
+
}
|
|
381
|
+
async function copyOrMoveFile(sourcePath, destPath, move) {
|
|
382
|
+
try {
|
|
383
|
+
if (move) {
|
|
384
|
+
await rename(sourcePath, destPath);
|
|
385
|
+
} else {
|
|
386
|
+
await copyFile(sourcePath, destPath);
|
|
387
|
+
}
|
|
388
|
+
return null;
|
|
389
|
+
} catch (error) {
|
|
390
|
+
return `Failed to ${move ? "move" : "copy"} file: ${error instanceof Error ? error.message : String(error)}`;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
async function updateAttachmentMetadata$1(library, item, updatedAttachments) {
|
|
394
|
+
await library.update(item.id, {
|
|
395
|
+
custom: {
|
|
396
|
+
...item.custom,
|
|
397
|
+
attachments: updatedAttachments
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
function buildUpdatedFiles$1(existingFiles, newFile, existingFile) {
|
|
402
|
+
if (existingFile) {
|
|
403
|
+
return existingFiles.map((f) => f.filename === newFile.filename ? newFile : f);
|
|
404
|
+
}
|
|
405
|
+
return [...existingFiles, newFile];
|
|
406
|
+
}
|
|
407
|
+
async function addAttachment(library, options) {
|
|
408
|
+
const {
|
|
409
|
+
identifier,
|
|
410
|
+
filePath,
|
|
411
|
+
role,
|
|
412
|
+
label,
|
|
413
|
+
move = false,
|
|
414
|
+
force = false,
|
|
415
|
+
idType = "id",
|
|
416
|
+
attachmentsDirectory
|
|
417
|
+
} = options;
|
|
418
|
+
const item = await library.find(identifier, { idType });
|
|
419
|
+
if (!item) {
|
|
420
|
+
return { success: false, error: `Reference '${identifier}' not found` };
|
|
421
|
+
}
|
|
422
|
+
const uuid2 = item.custom?.uuid;
|
|
423
|
+
if (!uuid2) {
|
|
424
|
+
return { success: false, error: "Reference has no UUID. Cannot create attachment directory." };
|
|
425
|
+
}
|
|
426
|
+
const sourceError = await checkSourceFile(filePath);
|
|
427
|
+
if (sourceError) {
|
|
428
|
+
return { success: false, error: sourceError };
|
|
429
|
+
}
|
|
430
|
+
const ext = extname(filePath).slice(1).toLowerCase();
|
|
431
|
+
const filename = generateFilename(role, ext, label);
|
|
432
|
+
const existingAttachments = item.custom?.attachments;
|
|
433
|
+
const existingFiles = existingAttachments?.files ?? [];
|
|
434
|
+
const newFile = {
|
|
435
|
+
filename,
|
|
436
|
+
role,
|
|
437
|
+
...label && { label }
|
|
438
|
+
};
|
|
439
|
+
const existingFile = findExistingFile(existingFiles, filename);
|
|
440
|
+
if (!existingFile || !force) {
|
|
441
|
+
const constraintError = validateFulltextConstraint(existingFiles, newFile);
|
|
442
|
+
if (constraintError) {
|
|
443
|
+
return { success: false, error: constraintError };
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (existingFile && !force) {
|
|
447
|
+
return {
|
|
448
|
+
success: false,
|
|
449
|
+
existingFile: filename,
|
|
450
|
+
requiresConfirmation: true
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
const ref2 = item;
|
|
454
|
+
const dirPath = await ensureDirectory(ref2, attachmentsDirectory);
|
|
455
|
+
const dirName = existingAttachments?.directory ?? generateDirectoryName(ref2);
|
|
456
|
+
const destPath = join(dirPath, filename);
|
|
457
|
+
const copyError = await copyOrMoveFile(filePath, destPath, move);
|
|
458
|
+
if (copyError) {
|
|
459
|
+
return { success: false, error: copyError };
|
|
460
|
+
}
|
|
461
|
+
const updatedFiles = buildUpdatedFiles$1(existingFiles, newFile, existingFile);
|
|
462
|
+
const updatedAttachments = {
|
|
463
|
+
directory: dirName,
|
|
464
|
+
files: updatedFiles
|
|
465
|
+
};
|
|
466
|
+
await updateAttachmentMetadata$1(library, item, updatedAttachments);
|
|
467
|
+
await library.save();
|
|
468
|
+
return {
|
|
469
|
+
success: true,
|
|
470
|
+
filename,
|
|
471
|
+
directory: dirName,
|
|
472
|
+
overwritten: !!existingFile
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
async function listAttachments(library, options) {
|
|
476
|
+
const { identifier, idType = "id", role } = options;
|
|
477
|
+
const item = await library.find(identifier, { idType });
|
|
478
|
+
if (!item) {
|
|
479
|
+
return { success: false, files: [], error: `Reference '${identifier}' not found` };
|
|
480
|
+
}
|
|
481
|
+
const attachments = item.custom?.attachments;
|
|
482
|
+
if (!attachments || attachments.files.length === 0) {
|
|
483
|
+
return { success: false, files: [], error: `No attachments for reference '${identifier}'` };
|
|
484
|
+
}
|
|
485
|
+
let files = attachments.files;
|
|
486
|
+
if (role) {
|
|
487
|
+
files = files.filter((f) => f.role === role);
|
|
488
|
+
if (files.length === 0) {
|
|
489
|
+
return {
|
|
490
|
+
success: false,
|
|
491
|
+
files: [],
|
|
492
|
+
error: `No ${role} attachments for reference '${identifier}'`
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return {
|
|
497
|
+
success: true,
|
|
498
|
+
directory: attachments.directory,
|
|
499
|
+
files
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
function findAttachment(files, filename, role) {
|
|
503
|
+
if (filename) {
|
|
504
|
+
return files.find((f) => f.filename === filename);
|
|
505
|
+
}
|
|
506
|
+
if (role) {
|
|
507
|
+
return files.find((f) => f.role === role);
|
|
508
|
+
}
|
|
509
|
+
return void 0;
|
|
510
|
+
}
|
|
511
|
+
async function getAttachment(library, options) {
|
|
512
|
+
const {
|
|
513
|
+
identifier,
|
|
514
|
+
filename,
|
|
515
|
+
role,
|
|
516
|
+
idType = "id",
|
|
517
|
+
stdout: stdout2 = false,
|
|
518
|
+
attachmentsDirectory
|
|
519
|
+
} = options;
|
|
520
|
+
const item = await library.find(identifier, { idType });
|
|
521
|
+
if (!item) {
|
|
522
|
+
return { success: false, error: `Reference '${identifier}' not found` };
|
|
523
|
+
}
|
|
524
|
+
const attachments = item.custom?.attachments;
|
|
525
|
+
if (!attachments || attachments.files.length === 0) {
|
|
526
|
+
return { success: false, error: `No attachments for reference '${identifier}'` };
|
|
527
|
+
}
|
|
528
|
+
const attachment = findAttachment(attachments.files, filename, role);
|
|
529
|
+
if (!attachment) {
|
|
530
|
+
if (filename) {
|
|
531
|
+
return { success: false, error: `Attachment '${filename}' not found` };
|
|
532
|
+
}
|
|
533
|
+
if (role) {
|
|
534
|
+
return { success: false, error: `No ${role} attachment found` };
|
|
535
|
+
}
|
|
536
|
+
return { success: false, error: "No filename or role specified" };
|
|
537
|
+
}
|
|
538
|
+
const filePath = join(attachmentsDirectory, attachments.directory, attachment.filename);
|
|
539
|
+
const normalizedPath = normalizePathForOutput(filePath);
|
|
540
|
+
if (stdout2) {
|
|
541
|
+
try {
|
|
542
|
+
const content = await readFile(filePath);
|
|
543
|
+
return { success: true, path: normalizedPath, content };
|
|
544
|
+
} catch {
|
|
545
|
+
return {
|
|
546
|
+
success: false,
|
|
547
|
+
error: `File not found on disk: ${normalizedPath}`
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return { success: true, path: normalizedPath };
|
|
552
|
+
}
|
|
553
|
+
function findFilesToDetach(files, filename, role, all) {
|
|
554
|
+
if (filename) {
|
|
555
|
+
const file = files.find((f) => f.filename === filename);
|
|
556
|
+
return file ? [file] : [];
|
|
557
|
+
}
|
|
558
|
+
if (role && all) {
|
|
559
|
+
return files.filter((f) => f.role === role);
|
|
560
|
+
}
|
|
561
|
+
return [];
|
|
562
|
+
}
|
|
563
|
+
async function deleteFiles(dirPath, filenames) {
|
|
564
|
+
const deleted = [];
|
|
565
|
+
for (const filename of filenames) {
|
|
566
|
+
try {
|
|
567
|
+
await unlink(join(dirPath, filename));
|
|
568
|
+
deleted.push(filename);
|
|
569
|
+
} catch {
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return deleted;
|
|
573
|
+
}
|
|
574
|
+
async function updateMetadata(library, item, attachments, remainingFiles) {
|
|
575
|
+
const updatedAttachments = remainingFiles.length > 0 ? { directory: attachments.directory, files: remainingFiles } : void 0;
|
|
576
|
+
await library.update(item.id, {
|
|
577
|
+
custom: {
|
|
578
|
+
...item.custom,
|
|
579
|
+
attachments: updatedAttachments
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
async function tryDeleteEmptyDirectory(dirPath) {
|
|
584
|
+
try {
|
|
585
|
+
await deleteDirectoryIfEmpty(dirPath);
|
|
586
|
+
try {
|
|
587
|
+
await stat(dirPath);
|
|
588
|
+
return false;
|
|
589
|
+
} catch {
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
} catch {
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
function errorResult$1(error) {
|
|
597
|
+
return { success: false, detached: [], deleted: [], error };
|
|
598
|
+
}
|
|
599
|
+
async function detachAttachment(library, options) {
|
|
600
|
+
const {
|
|
601
|
+
identifier,
|
|
602
|
+
filename,
|
|
603
|
+
role,
|
|
604
|
+
all = false,
|
|
605
|
+
removeFiles = false,
|
|
606
|
+
idType = "id",
|
|
607
|
+
attachmentsDirectory
|
|
608
|
+
} = options;
|
|
609
|
+
if (!filename && !role) {
|
|
610
|
+
return errorResult$1("Either filename or role must be specified");
|
|
611
|
+
}
|
|
612
|
+
const item = await library.find(identifier, { idType });
|
|
613
|
+
if (!item) {
|
|
614
|
+
return errorResult$1(`Reference '${identifier}' not found`);
|
|
615
|
+
}
|
|
616
|
+
const attachments = item.custom?.attachments;
|
|
617
|
+
if (!attachments || attachments.files.length === 0) {
|
|
618
|
+
return errorResult$1(`No attachments for reference '${identifier}'`);
|
|
619
|
+
}
|
|
620
|
+
const filesToDetach = findFilesToDetach(attachments.files, filename, role, all);
|
|
621
|
+
if (filesToDetach.length === 0) {
|
|
622
|
+
if (filename) {
|
|
623
|
+
return errorResult$1(`Attachment '${filename}' not found`);
|
|
624
|
+
}
|
|
625
|
+
return errorResult$1(`No ${role} attachments found`);
|
|
626
|
+
}
|
|
627
|
+
const detachedFilenames = filesToDetach.map((f) => f.filename);
|
|
628
|
+
const dirPath = join(attachmentsDirectory, attachments.directory);
|
|
629
|
+
const deletedFiles = removeFiles ? await deleteFiles(dirPath, detachedFilenames) : [];
|
|
630
|
+
const remainingFiles = attachments.files.filter((f) => !detachedFilenames.includes(f.filename));
|
|
631
|
+
await updateMetadata(library, item, attachments, remainingFiles);
|
|
632
|
+
await library.save();
|
|
633
|
+
const directoryDeleted = removeFiles && remainingFiles.length === 0 ? await tryDeleteEmptyDirectory(dirPath) : false;
|
|
634
|
+
return {
|
|
635
|
+
success: true,
|
|
636
|
+
detached: detachedFilenames,
|
|
637
|
+
deleted: deletedFiles,
|
|
638
|
+
directoryDeleted
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
function errorResult(error) {
|
|
642
|
+
return { success: false, newFiles: [], missingFiles: [], applied: false, error };
|
|
643
|
+
}
|
|
644
|
+
function inferFromFilename(filename) {
|
|
645
|
+
const parsed = parseFilename(filename);
|
|
646
|
+
if (!parsed) {
|
|
647
|
+
return { filename, role: "other", label: filename };
|
|
648
|
+
}
|
|
649
|
+
const { role, label } = parsed;
|
|
650
|
+
if (isReservedRole(role)) {
|
|
651
|
+
return label ? { filename, role, label } : { filename, role };
|
|
652
|
+
}
|
|
653
|
+
const basename2 = label ? `${role}-${label}` : role;
|
|
654
|
+
return { filename, role: "other", label: basename2 };
|
|
655
|
+
}
|
|
656
|
+
async function getFilesOnDisk(dirPath) {
|
|
657
|
+
try {
|
|
658
|
+
const entries = await readdir(dirPath);
|
|
659
|
+
const files = [];
|
|
660
|
+
for (const entry of entries) {
|
|
661
|
+
const entryPath = join(dirPath, entry);
|
|
662
|
+
const stats = await stat(entryPath);
|
|
663
|
+
if (stats.isFile()) {
|
|
664
|
+
files.push(entry);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return files;
|
|
668
|
+
} catch {
|
|
669
|
+
return [];
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
async function directoryExists(dirPath) {
|
|
673
|
+
try {
|
|
674
|
+
const stats = await stat(dirPath);
|
|
675
|
+
return stats.isDirectory();
|
|
676
|
+
} catch {
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
function findNewFiles(diskFiles, metadataFilenames) {
|
|
681
|
+
return diskFiles.filter((f) => !metadataFilenames.has(f)).map(inferFromFilename);
|
|
682
|
+
}
|
|
683
|
+
function findMissingFiles(metadataFiles, diskFilenames) {
|
|
684
|
+
return metadataFiles.filter((f) => !diskFilenames.has(f.filename)).map((f) => f.filename);
|
|
685
|
+
}
|
|
686
|
+
function buildUpdatedFiles(metadataFiles, newFiles, missingFiles, shouldApplyNew, shouldApplyFix) {
|
|
687
|
+
let updatedFiles = [...metadataFiles];
|
|
688
|
+
if (shouldApplyNew) {
|
|
689
|
+
for (const newFile of newFiles) {
|
|
690
|
+
const attachmentFile = {
|
|
691
|
+
filename: newFile.filename,
|
|
692
|
+
role: newFile.role,
|
|
693
|
+
...newFile.label && { label: newFile.label }
|
|
694
|
+
};
|
|
695
|
+
updatedFiles.push(attachmentFile);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
if (shouldApplyFix) {
|
|
699
|
+
const missingSet = new Set(missingFiles);
|
|
700
|
+
updatedFiles = updatedFiles.filter((f) => !missingSet.has(f.filename));
|
|
701
|
+
}
|
|
702
|
+
return updatedFiles;
|
|
703
|
+
}
|
|
704
|
+
async function updateAttachmentMetadata(library, item, attachments, updatedFiles) {
|
|
705
|
+
await library.update(item.id, {
|
|
706
|
+
custom: {
|
|
707
|
+
...item.custom,
|
|
708
|
+
attachments: {
|
|
709
|
+
...attachments,
|
|
710
|
+
files: updatedFiles
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
async function syncAttachments(library, options) {
|
|
716
|
+
const { identifier, yes = false, fix = false, idType = "id", attachmentsDirectory } = options;
|
|
717
|
+
const item = await library.find(identifier, { idType });
|
|
718
|
+
if (!item) {
|
|
719
|
+
return errorResult(`Reference '${identifier}' not found`);
|
|
720
|
+
}
|
|
721
|
+
const attachments = item.custom?.attachments;
|
|
722
|
+
if (!attachments?.directory) {
|
|
723
|
+
return errorResult(`No attachments for reference: ${identifier}`);
|
|
724
|
+
}
|
|
725
|
+
const dirPath = join(attachmentsDirectory, attachments.directory);
|
|
726
|
+
if (!await directoryExists(dirPath)) {
|
|
727
|
+
return errorResult(`Attachments directory does not exist: ${attachments.directory}`);
|
|
728
|
+
}
|
|
729
|
+
const metadataFiles = attachments.files || [];
|
|
730
|
+
const metadataFilenames = new Set(metadataFiles.map((f) => f.filename));
|
|
731
|
+
const diskFiles = await getFilesOnDisk(dirPath);
|
|
732
|
+
const diskFilenames = new Set(diskFiles);
|
|
733
|
+
const newFiles = findNewFiles(diskFiles, metadataFilenames);
|
|
734
|
+
const missingFiles = findMissingFiles(metadataFiles, diskFilenames);
|
|
735
|
+
const shouldApplyNew = yes && newFiles.length > 0;
|
|
736
|
+
const shouldApplyFix = fix && missingFiles.length > 0;
|
|
737
|
+
const shouldApply = shouldApplyNew || shouldApplyFix;
|
|
738
|
+
if (shouldApply) {
|
|
739
|
+
const updatedFiles = buildUpdatedFiles(
|
|
740
|
+
metadataFiles,
|
|
741
|
+
newFiles,
|
|
742
|
+
missingFiles,
|
|
743
|
+
shouldApplyNew,
|
|
744
|
+
shouldApplyFix
|
|
745
|
+
);
|
|
746
|
+
await updateAttachmentMetadata(library, item, attachments, updatedFiles);
|
|
747
|
+
await library.save();
|
|
748
|
+
}
|
|
749
|
+
return { success: true, newFiles, missingFiles, applied: shouldApply };
|
|
750
|
+
}
|
|
751
|
+
async function pathExists(path2) {
|
|
752
|
+
try {
|
|
753
|
+
await stat(path2);
|
|
754
|
+
return true;
|
|
755
|
+
} catch {
|
|
756
|
+
return false;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
function findFileByRole(attachments, role) {
|
|
760
|
+
if (!attachments?.files) {
|
|
761
|
+
return null;
|
|
762
|
+
}
|
|
763
|
+
const file = attachments.files.find((f) => f.role === role);
|
|
764
|
+
return file?.filename ?? null;
|
|
765
|
+
}
|
|
766
|
+
async function resolveDirectory(ref2, attachmentsDirectory) {
|
|
767
|
+
const attachments = ref2.custom?.attachments;
|
|
768
|
+
let dirPath;
|
|
769
|
+
let directoryCreated = false;
|
|
770
|
+
if (attachments?.directory) {
|
|
771
|
+
dirPath = join(attachmentsDirectory, attachments.directory);
|
|
772
|
+
} else {
|
|
773
|
+
dirPath = await ensureDirectory(ref2, attachmentsDirectory);
|
|
774
|
+
directoryCreated = true;
|
|
775
|
+
}
|
|
776
|
+
if (!await pathExists(dirPath)) {
|
|
777
|
+
dirPath = await ensureDirectory(ref2, attachmentsDirectory);
|
|
778
|
+
directoryCreated = true;
|
|
779
|
+
}
|
|
780
|
+
return { dirPath, directoryCreated };
|
|
781
|
+
}
|
|
782
|
+
async function updateDirectoryMetadata(library, ref2, dirPath) {
|
|
783
|
+
const dirName = basename(dirPath);
|
|
784
|
+
const item = ref2;
|
|
785
|
+
await library.update(ref2.id, {
|
|
786
|
+
custom: {
|
|
787
|
+
...item.custom,
|
|
788
|
+
attachments: {
|
|
789
|
+
directory: dirName,
|
|
790
|
+
files: []
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
await library.save();
|
|
795
|
+
}
|
|
796
|
+
async function resolveTargetPath(dirPath, filename, role, attachments) {
|
|
797
|
+
if (filename) {
|
|
798
|
+
const targetPath = join(dirPath, filename);
|
|
799
|
+
if (!await pathExists(targetPath)) {
|
|
800
|
+
return { error: `Attachment file not found: ${filename}` };
|
|
801
|
+
}
|
|
802
|
+
return { path: targetPath };
|
|
803
|
+
}
|
|
804
|
+
if (role) {
|
|
805
|
+
const foundFilename = findFileByRole(attachments, role);
|
|
806
|
+
if (!foundFilename) {
|
|
807
|
+
return { error: `No ${role} attachment found` };
|
|
808
|
+
}
|
|
809
|
+
const targetPath = join(dirPath, foundFilename);
|
|
810
|
+
if (!await pathExists(targetPath)) {
|
|
811
|
+
return { error: `Attachment file not found: ${foundFilename}` };
|
|
812
|
+
}
|
|
813
|
+
return { path: targetPath };
|
|
814
|
+
}
|
|
815
|
+
return { path: dirPath };
|
|
816
|
+
}
|
|
817
|
+
async function openAttachment(library, options) {
|
|
818
|
+
const {
|
|
819
|
+
identifier,
|
|
820
|
+
filename,
|
|
821
|
+
role,
|
|
822
|
+
print = false,
|
|
823
|
+
idType = "id",
|
|
824
|
+
attachmentsDirectory
|
|
825
|
+
} = options;
|
|
826
|
+
const item = await library.find(identifier, { idType });
|
|
827
|
+
if (!item) {
|
|
828
|
+
return { success: false, error: `Reference '${identifier}' not found` };
|
|
829
|
+
}
|
|
830
|
+
const ref2 = item;
|
|
831
|
+
if (!ref2.custom?.uuid) {
|
|
832
|
+
return {
|
|
833
|
+
success: false,
|
|
834
|
+
error: "Reference has no UUID. Cannot determine attachment directory."
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
const attachments = ref2.custom?.attachments;
|
|
838
|
+
const { dirPath, directoryCreated } = await resolveDirectory(ref2, attachmentsDirectory);
|
|
839
|
+
if (directoryCreated && !attachments?.directory) {
|
|
840
|
+
await updateDirectoryMetadata(library, ref2, dirPath);
|
|
841
|
+
}
|
|
842
|
+
const targetResult = await resolveTargetPath(dirPath, filename, role, attachments);
|
|
843
|
+
if ("error" in targetResult) {
|
|
844
|
+
return { success: false, error: targetResult.error };
|
|
845
|
+
}
|
|
846
|
+
if (!print) {
|
|
847
|
+
await openWithSystemApp(targetResult.path);
|
|
848
|
+
}
|
|
849
|
+
return {
|
|
850
|
+
success: true,
|
|
851
|
+
// Normalize for output (forward slashes for cross-platform consistency)
|
|
852
|
+
path: normalizePathForOutput(targetResult.path),
|
|
853
|
+
directoryCreated
|
|
854
|
+
};
|
|
855
|
+
}
|
|
264
856
|
class OperationsLibrary {
|
|
265
857
|
constructor(library, citationConfig) {
|
|
266
858
|
this.library = library;
|
|
@@ -287,15 +879,15 @@ class OperationsLibrary {
|
|
|
287
879
|
}
|
|
288
880
|
// High-level operations
|
|
289
881
|
async search(options) {
|
|
290
|
-
const { searchReferences } = await import("./index-
|
|
882
|
+
const { searchReferences } = await import("./index-DHgeuWGP.js").then((n) => n.n);
|
|
291
883
|
return searchReferences(this.library, options);
|
|
292
884
|
}
|
|
293
885
|
async list(options) {
|
|
294
|
-
const { listReferences } = await import("./index-
|
|
886
|
+
const { listReferences } = await import("./index-DHgeuWGP.js").then((n) => n.m);
|
|
295
887
|
return listReferences(this.library, options ?? {});
|
|
296
888
|
}
|
|
297
889
|
async cite(options) {
|
|
298
|
-
const { citeReferences } = await import("./index-
|
|
890
|
+
const { citeReferences } = await import("./index-DHgeuWGP.js").then((n) => n.l);
|
|
299
891
|
const defaultStyle = options.defaultStyle ?? this.citationConfig?.defaultStyle;
|
|
300
892
|
const cslDirectory = options.cslDirectory ?? this.citationConfig?.cslDirectory;
|
|
301
893
|
const mergedOptions = {
|
|
@@ -306,9 +898,34 @@ class OperationsLibrary {
|
|
|
306
898
|
return citeReferences(this.library, mergedOptions);
|
|
307
899
|
}
|
|
308
900
|
async import(inputs, options) {
|
|
309
|
-
const { addReferences } = await import("./index-
|
|
901
|
+
const { addReferences } = await import("./index-DHgeuWGP.js").then((n) => n.k);
|
|
310
902
|
return addReferences(inputs, this.library, options ?? {});
|
|
311
903
|
}
|
|
904
|
+
// Attachment operations
|
|
905
|
+
async attachAdd(options) {
|
|
906
|
+
const { addAttachment: addAttachment2 } = await import("./index-DEd6F5Rr.js");
|
|
907
|
+
return addAttachment2(this.library, options);
|
|
908
|
+
}
|
|
909
|
+
async attachList(options) {
|
|
910
|
+
const { listAttachments: listAttachments2 } = await import("./index-DEd6F5Rr.js");
|
|
911
|
+
return listAttachments2(this.library, options);
|
|
912
|
+
}
|
|
913
|
+
async attachGet(options) {
|
|
914
|
+
const { getAttachment: getAttachment2 } = await import("./index-DEd6F5Rr.js");
|
|
915
|
+
return getAttachment2(this.library, options);
|
|
916
|
+
}
|
|
917
|
+
async attachDetach(options) {
|
|
918
|
+
const { detachAttachment: detachAttachment2 } = await import("./index-DEd6F5Rr.js");
|
|
919
|
+
return detachAttachment2(this.library, options);
|
|
920
|
+
}
|
|
921
|
+
async attachSync(options) {
|
|
922
|
+
const { syncAttachments: syncAttachments2 } = await import("./index-DEd6F5Rr.js");
|
|
923
|
+
return syncAttachments2(this.library, options);
|
|
924
|
+
}
|
|
925
|
+
async attachOpen(options) {
|
|
926
|
+
const { openAttachment: openAttachment2 } = await import("./index-DEd6F5Rr.js");
|
|
927
|
+
return openAttachment2(this.library, options);
|
|
928
|
+
}
|
|
312
929
|
}
|
|
313
930
|
class ServerClient {
|
|
314
931
|
constructor(baseUrl) {
|
|
@@ -495,20 +1112,125 @@ class ServerClient {
|
|
|
495
1112
|
}
|
|
496
1113
|
return await response.json();
|
|
497
1114
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
1115
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1116
|
+
// Attachment operations
|
|
1117
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1118
|
+
/**
|
|
1119
|
+
* Add attachment to a reference.
|
|
1120
|
+
* @param options - Add attachment options
|
|
1121
|
+
* @returns Result of the add operation
|
|
1122
|
+
*/
|
|
1123
|
+
async attachAdd(options) {
|
|
1124
|
+
const url = `${this.baseUrl}/api/attachments/add`;
|
|
1125
|
+
const response = await fetch(url, {
|
|
1126
|
+
method: "POST",
|
|
1127
|
+
headers: { "Content-Type": "application/json" },
|
|
1128
|
+
body: JSON.stringify(options)
|
|
1129
|
+
});
|
|
1130
|
+
if (!response.ok) {
|
|
1131
|
+
throw new Error(await response.text());
|
|
507
1132
|
}
|
|
508
|
-
return
|
|
1133
|
+
return await response.json();
|
|
509
1134
|
}
|
|
510
|
-
|
|
511
|
-
|
|
1135
|
+
/**
|
|
1136
|
+
* List attachments for a reference.
|
|
1137
|
+
* @param options - List attachments options
|
|
1138
|
+
* @returns List of attachments
|
|
1139
|
+
*/
|
|
1140
|
+
async attachList(options) {
|
|
1141
|
+
const url = `${this.baseUrl}/api/attachments/list`;
|
|
1142
|
+
const response = await fetch(url, {
|
|
1143
|
+
method: "POST",
|
|
1144
|
+
headers: { "Content-Type": "application/json" },
|
|
1145
|
+
body: JSON.stringify(options)
|
|
1146
|
+
});
|
|
1147
|
+
if (!response.ok) {
|
|
1148
|
+
throw new Error(await response.text());
|
|
1149
|
+
}
|
|
1150
|
+
return await response.json();
|
|
1151
|
+
}
|
|
1152
|
+
/**
|
|
1153
|
+
* Get attachment file path or content.
|
|
1154
|
+
* @param options - Get attachment options
|
|
1155
|
+
* @returns Attachment file path or content
|
|
1156
|
+
*/
|
|
1157
|
+
async attachGet(options) {
|
|
1158
|
+
const url = `${this.baseUrl}/api/attachments/get`;
|
|
1159
|
+
const response = await fetch(url, {
|
|
1160
|
+
method: "POST",
|
|
1161
|
+
headers: { "Content-Type": "application/json" },
|
|
1162
|
+
body: JSON.stringify(options)
|
|
1163
|
+
});
|
|
1164
|
+
if (!response.ok) {
|
|
1165
|
+
throw new Error(await response.text());
|
|
1166
|
+
}
|
|
1167
|
+
return await response.json();
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Detach attachment from a reference.
|
|
1171
|
+
* @param options - Detach attachment options
|
|
1172
|
+
* @returns Result of the detach operation
|
|
1173
|
+
*/
|
|
1174
|
+
async attachDetach(options) {
|
|
1175
|
+
const url = `${this.baseUrl}/api/attachments/detach`;
|
|
1176
|
+
const response = await fetch(url, {
|
|
1177
|
+
method: "POST",
|
|
1178
|
+
headers: { "Content-Type": "application/json" },
|
|
1179
|
+
body: JSON.stringify(options)
|
|
1180
|
+
});
|
|
1181
|
+
if (!response.ok) {
|
|
1182
|
+
throw new Error(await response.text());
|
|
1183
|
+
}
|
|
1184
|
+
return await response.json();
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Sync attachments with files on disk.
|
|
1188
|
+
* @param options - Sync attachment options
|
|
1189
|
+
* @returns Sync result
|
|
1190
|
+
*/
|
|
1191
|
+
async attachSync(options) {
|
|
1192
|
+
const url = `${this.baseUrl}/api/attachments/sync`;
|
|
1193
|
+
const response = await fetch(url, {
|
|
1194
|
+
method: "POST",
|
|
1195
|
+
headers: { "Content-Type": "application/json" },
|
|
1196
|
+
body: JSON.stringify(options)
|
|
1197
|
+
});
|
|
1198
|
+
if (!response.ok) {
|
|
1199
|
+
throw new Error(await response.text());
|
|
1200
|
+
}
|
|
1201
|
+
return await response.json();
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Open attachment directory or file.
|
|
1205
|
+
* @param options - Open attachment options
|
|
1206
|
+
* @returns Result of the open operation
|
|
1207
|
+
*/
|
|
1208
|
+
async attachOpen(options) {
|
|
1209
|
+
const url = `${this.baseUrl}/api/attachments/open`;
|
|
1210
|
+
const response = await fetch(url, {
|
|
1211
|
+
method: "POST",
|
|
1212
|
+
headers: { "Content-Type": "application/json" },
|
|
1213
|
+
body: JSON.stringify(options)
|
|
1214
|
+
});
|
|
1215
|
+
if (!response.ok) {
|
|
1216
|
+
throw new Error(await response.text());
|
|
1217
|
+
}
|
|
1218
|
+
return await response.json();
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
async function getServerConnection(libraryPath, config2) {
|
|
1222
|
+
const portfilePath = getPortfilePath();
|
|
1223
|
+
const portfileData = await readPortfile(portfilePath);
|
|
1224
|
+
if (!portfileData) {
|
|
1225
|
+
if (config2.server.autoStart) {
|
|
1226
|
+
await startServerDaemon$1(libraryPath);
|
|
1227
|
+
await waitForPortfile(5e3);
|
|
1228
|
+
return await getServerConnection(libraryPath, config2);
|
|
1229
|
+
}
|
|
1230
|
+
return null;
|
|
1231
|
+
}
|
|
1232
|
+
if (!isProcessRunning(portfileData.pid)) {
|
|
1233
|
+
await removePortfile(portfilePath);
|
|
512
1234
|
return null;
|
|
513
1235
|
}
|
|
514
1236
|
if (!portfileData.library || portfileData.library !== libraryPath) {
|
|
@@ -615,47 +1337,566 @@ async function readIdentifiersFromStdin() {
|
|
|
615
1337
|
if (!content) {
|
|
616
1338
|
return [];
|
|
617
1339
|
}
|
|
618
|
-
return content.split(/[\s\n]+/).filter((id2) => id2.length > 0);
|
|
619
|
-
}
|
|
620
|
-
async function readIdentifierFromStdin() {
|
|
621
|
-
const content = await readStdinContent();
|
|
622
|
-
const firstLine = content.split("\n")[0]?.trim();
|
|
623
|
-
return firstLine || void 0;
|
|
1340
|
+
return content.split(/[\s\n]+/).filter((id2) => id2.length > 0);
|
|
1341
|
+
}
|
|
1342
|
+
async function readIdentifierFromStdin() {
|
|
1343
|
+
const content = await readStdinContent();
|
|
1344
|
+
const firstLine = content.split("\n")[0]?.trim();
|
|
1345
|
+
return firstLine || void 0;
|
|
1346
|
+
}
|
|
1347
|
+
async function readConfirmation(prompt) {
|
|
1348
|
+
if (!isTTY()) {
|
|
1349
|
+
return true;
|
|
1350
|
+
}
|
|
1351
|
+
const enquirer = await import("enquirer");
|
|
1352
|
+
const Confirm = enquirer.default.Confirm;
|
|
1353
|
+
const confirmPrompt = new Confirm({
|
|
1354
|
+
name: "confirm",
|
|
1355
|
+
message: prompt,
|
|
1356
|
+
initial: false
|
|
1357
|
+
});
|
|
1358
|
+
try {
|
|
1359
|
+
return await confirmPrompt.run();
|
|
1360
|
+
} catch {
|
|
1361
|
+
return false;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
async function readStdinContent() {
|
|
1365
|
+
const chunks = [];
|
|
1366
|
+
for await (const chunk of stdin) {
|
|
1367
|
+
chunks.push(chunk);
|
|
1368
|
+
}
|
|
1369
|
+
return Buffer.concat(chunks).toString("utf-8").trim();
|
|
1370
|
+
}
|
|
1371
|
+
async function readStdinBuffer() {
|
|
1372
|
+
const chunks = [];
|
|
1373
|
+
for await (const chunk of stdin) {
|
|
1374
|
+
chunks.push(chunk);
|
|
1375
|
+
}
|
|
1376
|
+
return Buffer.concat(chunks);
|
|
1377
|
+
}
|
|
1378
|
+
const ExitCode = {
|
|
1379
|
+
/** Success */
|
|
1380
|
+
SUCCESS: 0,
|
|
1381
|
+
/** General error (e.g., not found, validation failed) */
|
|
1382
|
+
ERROR: 1,
|
|
1383
|
+
/** Internal/unexpected error */
|
|
1384
|
+
INTERNAL_ERROR: 4,
|
|
1385
|
+
/** Interrupted by SIGINT */
|
|
1386
|
+
SIGINT: 130
|
|
1387
|
+
};
|
|
1388
|
+
function setExitCode(code2) {
|
|
1389
|
+
process.exitCode = code2;
|
|
1390
|
+
}
|
|
1391
|
+
function exitWithError(message, code2 = ExitCode.ERROR) {
|
|
1392
|
+
process.stderr.write(`Error: ${message}
|
|
1393
|
+
`);
|
|
1394
|
+
setExitCode(code2);
|
|
1395
|
+
}
|
|
1396
|
+
async function executeAttachOpen(options, context) {
|
|
1397
|
+
const operationOptions = {
|
|
1398
|
+
identifier: options.identifier,
|
|
1399
|
+
attachmentsDirectory: options.attachmentsDirectory,
|
|
1400
|
+
...options.filename !== void 0 && { filename: options.filename },
|
|
1401
|
+
...options.role !== void 0 && { role: options.role },
|
|
1402
|
+
...options.print !== void 0 && { print: options.print },
|
|
1403
|
+
...options.idType !== void 0 && { idType: options.idType }
|
|
1404
|
+
};
|
|
1405
|
+
return openAttachment(context.library, operationOptions);
|
|
1406
|
+
}
|
|
1407
|
+
async function executeAttachAdd(options, context) {
|
|
1408
|
+
const operationOptions = {
|
|
1409
|
+
identifier: options.identifier,
|
|
1410
|
+
filePath: options.filePath,
|
|
1411
|
+
role: options.role,
|
|
1412
|
+
attachmentsDirectory: options.attachmentsDirectory,
|
|
1413
|
+
...options.label !== void 0 && { label: options.label },
|
|
1414
|
+
...options.move !== void 0 && { move: options.move },
|
|
1415
|
+
...options.force !== void 0 && { force: options.force },
|
|
1416
|
+
...options.idType !== void 0 && { idType: options.idType }
|
|
1417
|
+
};
|
|
1418
|
+
return addAttachment(context.library, operationOptions);
|
|
1419
|
+
}
|
|
1420
|
+
async function executeAttachList(options, context) {
|
|
1421
|
+
const operationOptions = {
|
|
1422
|
+
identifier: options.identifier,
|
|
1423
|
+
...options.role !== void 0 && { role: options.role },
|
|
1424
|
+
...options.idType !== void 0 && { idType: options.idType }
|
|
1425
|
+
};
|
|
1426
|
+
return listAttachments(context.library, operationOptions);
|
|
1427
|
+
}
|
|
1428
|
+
async function executeAttachGet(options, context) {
|
|
1429
|
+
const operationOptions = {
|
|
1430
|
+
identifier: options.identifier,
|
|
1431
|
+
attachmentsDirectory: options.attachmentsDirectory,
|
|
1432
|
+
...options.filename !== void 0 && { filename: options.filename },
|
|
1433
|
+
...options.role !== void 0 && { role: options.role },
|
|
1434
|
+
...options.stdout !== void 0 && { stdout: options.stdout },
|
|
1435
|
+
...options.idType !== void 0 && { idType: options.idType }
|
|
1436
|
+
};
|
|
1437
|
+
return getAttachment(context.library, operationOptions);
|
|
1438
|
+
}
|
|
1439
|
+
async function executeAttachDetach(options, context) {
|
|
1440
|
+
const operationOptions = {
|
|
1441
|
+
identifier: options.identifier,
|
|
1442
|
+
attachmentsDirectory: options.attachmentsDirectory,
|
|
1443
|
+
...options.filename !== void 0 && { filename: options.filename },
|
|
1444
|
+
...options.role !== void 0 && { role: options.role },
|
|
1445
|
+
...options.all !== void 0 && { all: options.all },
|
|
1446
|
+
...options.removeFiles !== void 0 && { removeFiles: options.removeFiles },
|
|
1447
|
+
...options.idType !== void 0 && { idType: options.idType }
|
|
1448
|
+
};
|
|
1449
|
+
return detachAttachment(context.library, operationOptions);
|
|
1450
|
+
}
|
|
1451
|
+
async function executeAttachSync(options, context) {
|
|
1452
|
+
const operationOptions = {
|
|
1453
|
+
identifier: options.identifier,
|
|
1454
|
+
attachmentsDirectory: options.attachmentsDirectory,
|
|
1455
|
+
...options.yes !== void 0 && { yes: options.yes },
|
|
1456
|
+
...options.fix !== void 0 && { fix: options.fix },
|
|
1457
|
+
...options.idType !== void 0 && { idType: options.idType }
|
|
1458
|
+
};
|
|
1459
|
+
return syncAttachments(context.library, operationOptions);
|
|
1460
|
+
}
|
|
1461
|
+
function formatAttachOpenOutput(result) {
|
|
1462
|
+
if (!result.success) {
|
|
1463
|
+
return `Error: ${result.error}`;
|
|
1464
|
+
}
|
|
1465
|
+
if (result.directoryCreated) {
|
|
1466
|
+
return `Created and opened: ${result.path}`;
|
|
1467
|
+
}
|
|
1468
|
+
return `Opened: ${result.path}`;
|
|
1469
|
+
}
|
|
1470
|
+
function formatAttachAddOutput(result) {
|
|
1471
|
+
if (result.requiresConfirmation) {
|
|
1472
|
+
return `File already exists: ${result.existingFile}
|
|
1473
|
+
Use --force to overwrite.`;
|
|
1474
|
+
}
|
|
1475
|
+
if (!result.success) {
|
|
1476
|
+
return `Error: ${result.error}`;
|
|
1477
|
+
}
|
|
1478
|
+
if (result.overwritten) {
|
|
1479
|
+
return `Added (overwritten): ${result.filename}`;
|
|
1480
|
+
}
|
|
1481
|
+
return `Added: ${result.filename}`;
|
|
1482
|
+
}
|
|
1483
|
+
function formatAttachListOutput(result, identifier) {
|
|
1484
|
+
if (!result.success) {
|
|
1485
|
+
return `Error: ${result.error}`;
|
|
1486
|
+
}
|
|
1487
|
+
if (result.files.length === 0) {
|
|
1488
|
+
return `No attachments for reference: ${identifier}`;
|
|
1489
|
+
}
|
|
1490
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1491
|
+
for (const file of result.files) {
|
|
1492
|
+
const existing = grouped.get(file.role) ?? [];
|
|
1493
|
+
existing.push(file);
|
|
1494
|
+
grouped.set(file.role, existing);
|
|
1495
|
+
}
|
|
1496
|
+
const lines = [];
|
|
1497
|
+
lines.push(`Attachments for ${identifier} (${result.directory}/):`);
|
|
1498
|
+
lines.push("");
|
|
1499
|
+
for (const [role, files] of grouped) {
|
|
1500
|
+
lines.push(`${role}:`);
|
|
1501
|
+
for (const file of files) {
|
|
1502
|
+
if (file.label) {
|
|
1503
|
+
lines.push(` ${file.filename} - "${file.label}"`);
|
|
1504
|
+
} else {
|
|
1505
|
+
lines.push(` ${file.filename}`);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
lines.push("");
|
|
1509
|
+
}
|
|
1510
|
+
return lines.join("\n").trimEnd();
|
|
1511
|
+
}
|
|
1512
|
+
function formatAttachDetachOutput(result) {
|
|
1513
|
+
if (!result.success) {
|
|
1514
|
+
return `Error: ${result.error}`;
|
|
1515
|
+
}
|
|
1516
|
+
const lines = [];
|
|
1517
|
+
for (const filename of result.detached) {
|
|
1518
|
+
if (result.deleted.includes(filename)) {
|
|
1519
|
+
lines.push(`Detached and deleted: ${filename}`);
|
|
1520
|
+
} else {
|
|
1521
|
+
lines.push(`Detached: ${filename}`);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
if (result.directoryDeleted) {
|
|
1525
|
+
lines.push("Directory removed.");
|
|
1526
|
+
}
|
|
1527
|
+
return lines.join("\n");
|
|
1528
|
+
}
|
|
1529
|
+
function pluralize(count, singular) {
|
|
1530
|
+
return count > 1 ? `${singular}s` : singular;
|
|
1531
|
+
}
|
|
1532
|
+
function formatNewFilesSection(result, lines) {
|
|
1533
|
+
const count = result.newFiles.length;
|
|
1534
|
+
if (count === 0) return;
|
|
1535
|
+
const verb = result.applied ? "Added" : "Found";
|
|
1536
|
+
const suffix = result.applied ? "" : " new";
|
|
1537
|
+
lines.push(`${verb} ${count}${suffix} ${pluralize(count, "file")}:`);
|
|
1538
|
+
for (const file of result.newFiles) {
|
|
1539
|
+
const labelPart = file.label ? `, label: "${file.label}"` : "";
|
|
1540
|
+
lines.push(` ${file.filename} → role: ${file.role}${labelPart}`);
|
|
1541
|
+
}
|
|
1542
|
+
lines.push("");
|
|
1543
|
+
}
|
|
1544
|
+
function formatMissingFilesSection(result, lines) {
|
|
1545
|
+
const count = result.missingFiles.length;
|
|
1546
|
+
if (count === 0) return;
|
|
1547
|
+
const header = result.applied ? `Removed ${count} missing ${pluralize(count, "file")} from metadata:` : `Missing ${count} ${pluralize(count, "file")} (in metadata but not on disk):`;
|
|
1548
|
+
lines.push(header);
|
|
1549
|
+
for (const filename of result.missingFiles) {
|
|
1550
|
+
lines.push(` ${filename}`);
|
|
1551
|
+
}
|
|
1552
|
+
lines.push("");
|
|
1553
|
+
}
|
|
1554
|
+
function formatAttachSyncOutput(result) {
|
|
1555
|
+
if (!result.success) {
|
|
1556
|
+
return `Error: ${result.error}`;
|
|
1557
|
+
}
|
|
1558
|
+
const hasNewFiles = result.newFiles.length > 0;
|
|
1559
|
+
const hasMissingFiles = result.missingFiles.length > 0;
|
|
1560
|
+
if (!hasNewFiles && !hasMissingFiles) {
|
|
1561
|
+
return "Already in sync.";
|
|
1562
|
+
}
|
|
1563
|
+
const lines = [];
|
|
1564
|
+
formatNewFilesSection(result, lines);
|
|
1565
|
+
formatMissingFilesSection(result, lines);
|
|
1566
|
+
if (result.applied) {
|
|
1567
|
+
lines.push("Changes applied.");
|
|
1568
|
+
} else {
|
|
1569
|
+
lines.push("");
|
|
1570
|
+
lines.push("(dry-run: no changes made)");
|
|
1571
|
+
if (hasNewFiles) {
|
|
1572
|
+
lines.push("Run with --yes to add new files");
|
|
1573
|
+
}
|
|
1574
|
+
if (hasMissingFiles) {
|
|
1575
|
+
lines.push("Run with --fix to remove missing files from metadata");
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
return lines.join("\n").trimEnd();
|
|
1579
|
+
}
|
|
1580
|
+
function getAttachExitCode(result) {
|
|
1581
|
+
return result.success ? 0 : 1;
|
|
1582
|
+
}
|
|
1583
|
+
async function executeInteractiveSelect$1(context, config2) {
|
|
1584
|
+
const { selectReferencesOrExit } = await import("./reference-select-B9w9CLa1.js");
|
|
1585
|
+
const allReferences = await context.library.getAll();
|
|
1586
|
+
const identifiers = await selectReferencesOrExit(
|
|
1587
|
+
allReferences,
|
|
1588
|
+
{ multiSelect: false },
|
|
1589
|
+
config2.cli.tui
|
|
1590
|
+
);
|
|
1591
|
+
return identifiers[0];
|
|
1592
|
+
}
|
|
1593
|
+
async function resolveIdentifier(identifierArg, context, config2) {
|
|
1594
|
+
if (identifierArg) {
|
|
1595
|
+
return identifierArg;
|
|
1596
|
+
}
|
|
1597
|
+
if (isTTY()) {
|
|
1598
|
+
return executeInteractiveSelect$1(context, config2);
|
|
1599
|
+
}
|
|
1600
|
+
const stdinId = await readIdentifierFromStdin();
|
|
1601
|
+
if (!stdinId) {
|
|
1602
|
+
process.stderr.write(
|
|
1603
|
+
"Error: No identifier provided. Provide an ID, pipe one via stdin, or run interactively in a TTY.\n"
|
|
1604
|
+
);
|
|
1605
|
+
setExitCode(ExitCode.ERROR);
|
|
1606
|
+
return "";
|
|
1607
|
+
}
|
|
1608
|
+
return stdinId;
|
|
1609
|
+
}
|
|
1610
|
+
function displayNamingConvention(identifier, dirPath) {
|
|
1611
|
+
process.stderr.write(`
|
|
1612
|
+
Opening attachments directory for ${identifier}...
|
|
1613
|
+
|
|
1614
|
+
`);
|
|
1615
|
+
process.stderr.write("File naming convention:\n");
|
|
1616
|
+
process.stderr.write(" fulltext.pdf / fulltext.md - Paper body\n");
|
|
1617
|
+
process.stderr.write(" supplement-{label}.ext - Supplementary materials\n");
|
|
1618
|
+
process.stderr.write(" notes-{label}.ext - Your notes\n");
|
|
1619
|
+
process.stderr.write(" draft-{label}.ext - Draft versions\n");
|
|
1620
|
+
process.stderr.write(" {custom}-{label}.ext - Custom role\n\n");
|
|
1621
|
+
process.stderr.write(`Directory: ${dirPath}/
|
|
1622
|
+
|
|
1623
|
+
`);
|
|
1624
|
+
}
|
|
1625
|
+
async function waitForEnter() {
|
|
1626
|
+
return new Promise((resolve2) => {
|
|
1627
|
+
process.stderr.write("Press Enter when done editing...");
|
|
1628
|
+
process.stdin.setRawMode(true);
|
|
1629
|
+
process.stdin.resume();
|
|
1630
|
+
process.stdin.once("data", () => {
|
|
1631
|
+
process.stdin.setRawMode(false);
|
|
1632
|
+
process.stdin.pause();
|
|
1633
|
+
process.stderr.write("\n\n");
|
|
1634
|
+
resolve2();
|
|
1635
|
+
});
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
function displayInteractiveSyncResult(result, identifier) {
|
|
1639
|
+
if (result.newFiles.length === 0) {
|
|
1640
|
+
process.stderr.write("No new files detected.\n");
|
|
1641
|
+
return;
|
|
1642
|
+
}
|
|
1643
|
+
process.stderr.write("Scanning directory...\n\n");
|
|
1644
|
+
process.stderr.write(
|
|
1645
|
+
`Found ${result.newFiles.length} new file${result.newFiles.length > 1 ? "s" : ""}:
|
|
1646
|
+
`
|
|
1647
|
+
);
|
|
1648
|
+
for (const file of result.newFiles) {
|
|
1649
|
+
const labelPart = file.label ? `, label: "${file.label}"` : "";
|
|
1650
|
+
process.stderr.write(` ✓ ${file.filename} → role: ${file.role}${labelPart}
|
|
1651
|
+
`);
|
|
1652
|
+
}
|
|
1653
|
+
process.stderr.write(`
|
|
1654
|
+
Updated metadata for ${identifier}.
|
|
1655
|
+
`);
|
|
1656
|
+
}
|
|
1657
|
+
async function runInteractiveMode(identifier, dirPath, attachmentsDirectory, idType, context) {
|
|
1658
|
+
displayNamingConvention(identifier, dirPath);
|
|
1659
|
+
await waitForEnter();
|
|
1660
|
+
const syncResult = await executeAttachSync(
|
|
1661
|
+
{
|
|
1662
|
+
identifier,
|
|
1663
|
+
attachmentsDirectory,
|
|
1664
|
+
yes: true,
|
|
1665
|
+
...idType && { idType }
|
|
1666
|
+
},
|
|
1667
|
+
context
|
|
1668
|
+
);
|
|
1669
|
+
if (syncResult.success) {
|
|
1670
|
+
displayInteractiveSyncResult(syncResult, identifier);
|
|
1671
|
+
} else {
|
|
1672
|
+
process.stderr.write(`Sync error: ${syncResult.error}
|
|
1673
|
+
`);
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
function buildOpenOptions(identifier, filenameArg, options, attachmentsDirectory) {
|
|
1677
|
+
return {
|
|
1678
|
+
identifier,
|
|
1679
|
+
attachmentsDirectory,
|
|
1680
|
+
...filenameArg && { filename: filenameArg },
|
|
1681
|
+
...options.print && { print: options.print },
|
|
1682
|
+
...options.role && { role: options.role },
|
|
1683
|
+
...options.uuid && { idType: "uuid" }
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
async function handleAttachOpenAction(identifierArg, filenameArg, options, globalOpts) {
|
|
1687
|
+
try {
|
|
1688
|
+
const config2 = await loadConfigWithOverrides({ ...globalOpts, ...options });
|
|
1689
|
+
const context = await createExecutionContext(config2, Library.load);
|
|
1690
|
+
const identifier = await resolveIdentifier(identifierArg, context, config2);
|
|
1691
|
+
const isDirectoryMode = !filenameArg && !options.role;
|
|
1692
|
+
const shouldUseInteractive = isTTY() && isDirectoryMode && !options.print && !options.noSync;
|
|
1693
|
+
const openOptions = buildOpenOptions(
|
|
1694
|
+
identifier,
|
|
1695
|
+
filenameArg,
|
|
1696
|
+
options,
|
|
1697
|
+
config2.attachments.directory
|
|
1698
|
+
);
|
|
1699
|
+
const result = await executeAttachOpen(openOptions, context);
|
|
1700
|
+
if (!result.success) {
|
|
1701
|
+
process.stderr.write(`Error: ${result.error}
|
|
1702
|
+
`);
|
|
1703
|
+
setExitCode(ExitCode.ERROR);
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
if (options.print) {
|
|
1707
|
+
process.stdout.write(`${result.path}
|
|
1708
|
+
`);
|
|
1709
|
+
setExitCode(ExitCode.SUCCESS);
|
|
1710
|
+
}
|
|
1711
|
+
if (shouldUseInteractive) {
|
|
1712
|
+
await runInteractiveMode(
|
|
1713
|
+
identifier,
|
|
1714
|
+
result.path ?? "",
|
|
1715
|
+
config2.attachments.directory,
|
|
1716
|
+
options.uuid ? "uuid" : void 0,
|
|
1717
|
+
context
|
|
1718
|
+
);
|
|
1719
|
+
} else {
|
|
1720
|
+
process.stderr.write(`${formatAttachOpenOutput(result)}
|
|
1721
|
+
`);
|
|
1722
|
+
}
|
|
1723
|
+
setExitCode(ExitCode.SUCCESS);
|
|
1724
|
+
} catch (error) {
|
|
1725
|
+
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
1726
|
+
`);
|
|
1727
|
+
setExitCode(ExitCode.INTERNAL_ERROR);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
async function handleAttachAddAction(identifierArg, filePathArg, options, globalOpts) {
|
|
1731
|
+
try {
|
|
1732
|
+
const config2 = await loadConfigWithOverrides({ ...globalOpts, ...options });
|
|
1733
|
+
const context = await createExecutionContext(config2, Library.load);
|
|
1734
|
+
const identifier = await resolveIdentifier(identifierArg, context, config2);
|
|
1735
|
+
const addOptions = {
|
|
1736
|
+
identifier,
|
|
1737
|
+
filePath: filePathArg,
|
|
1738
|
+
role: options.role,
|
|
1739
|
+
attachmentsDirectory: config2.attachments.directory,
|
|
1740
|
+
...options.label && { label: options.label },
|
|
1741
|
+
...options.move && { move: options.move },
|
|
1742
|
+
...options.force && { force: options.force },
|
|
1743
|
+
...options.uuid && { idType: "uuid" }
|
|
1744
|
+
};
|
|
1745
|
+
const result = await executeAttachAdd(addOptions, context);
|
|
1746
|
+
const output = formatAttachAddOutput(result);
|
|
1747
|
+
process.stderr.write(`${output}
|
|
1748
|
+
`);
|
|
1749
|
+
setExitCode(getAttachExitCode(result));
|
|
1750
|
+
} catch (error) {
|
|
1751
|
+
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
1752
|
+
`);
|
|
1753
|
+
setExitCode(ExitCode.INTERNAL_ERROR);
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
async function handleAttachListAction(identifierArg, options, globalOpts) {
|
|
1757
|
+
try {
|
|
1758
|
+
const config2 = await loadConfigWithOverrides({ ...globalOpts, ...options });
|
|
1759
|
+
const context = await createExecutionContext(config2, Library.load);
|
|
1760
|
+
const identifier = await resolveIdentifier(identifierArg, context, config2);
|
|
1761
|
+
const listOptions = {
|
|
1762
|
+
identifier,
|
|
1763
|
+
attachmentsDirectory: config2.attachments.directory,
|
|
1764
|
+
...options.role && { role: options.role },
|
|
1765
|
+
...options.uuid && { idType: "uuid" }
|
|
1766
|
+
};
|
|
1767
|
+
const result = await executeAttachList(listOptions, context);
|
|
1768
|
+
const output = formatAttachListOutput(result, identifier);
|
|
1769
|
+
process.stdout.write(`${output}
|
|
1770
|
+
`);
|
|
1771
|
+
setExitCode(getAttachExitCode(result));
|
|
1772
|
+
} catch (error) {
|
|
1773
|
+
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
1774
|
+
`);
|
|
1775
|
+
setExitCode(ExitCode.INTERNAL_ERROR);
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
async function handleAttachGetAction(identifierArg, filenameArg, options, globalOpts) {
|
|
1779
|
+
try {
|
|
1780
|
+
const config2 = await loadConfigWithOverrides({ ...globalOpts, ...options });
|
|
1781
|
+
const context = await createExecutionContext(config2, Library.load);
|
|
1782
|
+
const identifier = await resolveIdentifier(identifierArg, context, config2);
|
|
1783
|
+
const getOptions = {
|
|
1784
|
+
identifier,
|
|
1785
|
+
attachmentsDirectory: config2.attachments.directory,
|
|
1786
|
+
...filenameArg && { filename: filenameArg },
|
|
1787
|
+
...options.role && { role: options.role },
|
|
1788
|
+
...options.stdout && { stdout: options.stdout },
|
|
1789
|
+
...options.uuid && { idType: "uuid" }
|
|
1790
|
+
};
|
|
1791
|
+
const result = await executeAttachGet(getOptions, context);
|
|
1792
|
+
if (result.success && result.content && options.stdout) {
|
|
1793
|
+
process.stdout.write(result.content);
|
|
1794
|
+
} else if (result.success) {
|
|
1795
|
+
process.stdout.write(`${result.path}
|
|
1796
|
+
`);
|
|
1797
|
+
} else {
|
|
1798
|
+
process.stderr.write(`Error: ${result.error}
|
|
1799
|
+
`);
|
|
1800
|
+
}
|
|
1801
|
+
setExitCode(getAttachExitCode(result));
|
|
1802
|
+
} catch (error) {
|
|
1803
|
+
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
1804
|
+
`);
|
|
1805
|
+
setExitCode(ExitCode.INTERNAL_ERROR);
|
|
1806
|
+
}
|
|
624
1807
|
}
|
|
625
|
-
async function
|
|
626
|
-
if (!isTTY()) {
|
|
627
|
-
return true;
|
|
628
|
-
}
|
|
629
|
-
const enquirer = await import("enquirer");
|
|
630
|
-
const Confirm = enquirer.default.Confirm;
|
|
631
|
-
const confirmPrompt = new Confirm({
|
|
632
|
-
name: "confirm",
|
|
633
|
-
message: prompt,
|
|
634
|
-
initial: false
|
|
635
|
-
});
|
|
1808
|
+
async function handleAttachDetachAction(identifierArg, filenameArg, options, globalOpts) {
|
|
636
1809
|
try {
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
1810
|
+
const config2 = await loadConfigWithOverrides({ ...globalOpts, ...options });
|
|
1811
|
+
const context = await createExecutionContext(config2, Library.load);
|
|
1812
|
+
const identifier = await resolveIdentifier(identifierArg, context, config2);
|
|
1813
|
+
const detachOptions = {
|
|
1814
|
+
identifier,
|
|
1815
|
+
attachmentsDirectory: config2.attachments.directory,
|
|
1816
|
+
...filenameArg && { filename: filenameArg },
|
|
1817
|
+
...options.role && { role: options.role },
|
|
1818
|
+
...options.all && { all: options.all },
|
|
1819
|
+
...options.removeFiles && { removeFiles: options.removeFiles },
|
|
1820
|
+
...options.uuid && { idType: "uuid" }
|
|
1821
|
+
};
|
|
1822
|
+
const result = await executeAttachDetach(detachOptions, context);
|
|
1823
|
+
const output = formatAttachDetachOutput(result);
|
|
1824
|
+
process.stderr.write(`${output}
|
|
1825
|
+
`);
|
|
1826
|
+
setExitCode(getAttachExitCode(result));
|
|
1827
|
+
} catch (error) {
|
|
1828
|
+
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
1829
|
+
`);
|
|
1830
|
+
setExitCode(ExitCode.INTERNAL_ERROR);
|
|
640
1831
|
}
|
|
641
1832
|
}
|
|
642
|
-
async function
|
|
643
|
-
const
|
|
644
|
-
|
|
645
|
-
|
|
1833
|
+
async function runInteractiveSyncMode(identifier, attachmentsDirectory, idType, context) {
|
|
1834
|
+
const dryRunOptions = {
|
|
1835
|
+
identifier,
|
|
1836
|
+
attachmentsDirectory,
|
|
1837
|
+
...idType && { idType }
|
|
1838
|
+
};
|
|
1839
|
+
const dryRunResult = await executeAttachSync(dryRunOptions, context);
|
|
1840
|
+
const hasNewFiles = dryRunResult.newFiles.length > 0;
|
|
1841
|
+
const hasMissingFiles = dryRunResult.missingFiles.length > 0;
|
|
1842
|
+
if (!dryRunResult.success || !hasNewFiles && !hasMissingFiles) {
|
|
1843
|
+
process.stderr.write(`${formatAttachSyncOutput(dryRunResult)}
|
|
1844
|
+
`);
|
|
1845
|
+
return;
|
|
646
1846
|
}
|
|
647
|
-
|
|
1847
|
+
process.stderr.write(`${formatAttachSyncOutput(dryRunResult)}
|
|
1848
|
+
|
|
1849
|
+
`);
|
|
1850
|
+
const shouldApplyNew = hasNewFiles && await readConfirmation("Add new files to metadata?");
|
|
1851
|
+
const shouldApplyFix = hasMissingFiles && shouldApplyNew && await readConfirmation("Remove missing files?");
|
|
1852
|
+
if (!shouldApplyNew && !shouldApplyFix) {
|
|
1853
|
+
process.stderr.write("No changes applied.\n");
|
|
1854
|
+
return;
|
|
1855
|
+
}
|
|
1856
|
+
const applyOptions = {
|
|
1857
|
+
identifier,
|
|
1858
|
+
attachmentsDirectory,
|
|
1859
|
+
...shouldApplyNew && { yes: true },
|
|
1860
|
+
...shouldApplyFix && { fix: true },
|
|
1861
|
+
...idType && { idType }
|
|
1862
|
+
};
|
|
1863
|
+
const result = await executeAttachSync(applyOptions, context);
|
|
1864
|
+
process.stderr.write(`${formatAttachSyncOutput(result)}
|
|
1865
|
+
`);
|
|
648
1866
|
}
|
|
649
|
-
async function
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
1867
|
+
async function handleAttachSyncAction(identifierArg, options, globalOpts) {
|
|
1868
|
+
try {
|
|
1869
|
+
const config2 = await loadConfigWithOverrides({ ...globalOpts, ...options });
|
|
1870
|
+
const context = await createExecutionContext(config2, Library.load);
|
|
1871
|
+
const identifier = await resolveIdentifier(identifierArg, context, config2);
|
|
1872
|
+
const attachmentsDirectory = config2.attachments.directory;
|
|
1873
|
+
const idType = options.uuid ? "uuid" : void 0;
|
|
1874
|
+
const shouldUseInteractive = isTTY() && !options.yes && !options.fix;
|
|
1875
|
+
if (shouldUseInteractive) {
|
|
1876
|
+
await runInteractiveSyncMode(identifier, attachmentsDirectory, idType, context);
|
|
1877
|
+
setExitCode(ExitCode.SUCCESS);
|
|
1878
|
+
return;
|
|
1879
|
+
}
|
|
1880
|
+
const syncOptions = {
|
|
1881
|
+
identifier,
|
|
1882
|
+
attachmentsDirectory,
|
|
1883
|
+
...options.yes && { yes: true },
|
|
1884
|
+
...options.fix && { fix: true },
|
|
1885
|
+
...idType && { idType }
|
|
1886
|
+
};
|
|
1887
|
+
const result = await executeAttachSync(syncOptions, context);
|
|
1888
|
+
process.stderr.write(`${formatAttachSyncOutput(result)}
|
|
1889
|
+
`);
|
|
1890
|
+
setExitCode(getAttachExitCode(result));
|
|
1891
|
+
} catch (error) {
|
|
1892
|
+
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
1893
|
+
`);
|
|
1894
|
+
setExitCode(ExitCode.INTERNAL_ERROR);
|
|
653
1895
|
}
|
|
654
|
-
return Buffer.concat(chunks);
|
|
655
1896
|
}
|
|
656
1897
|
async function validateOptions$2(options) {
|
|
657
|
-
if (options.
|
|
658
|
-
throw new Error(`Invalid format '${options.
|
|
1898
|
+
if (options.output && !["text", "html", "rtf"].includes(options.output)) {
|
|
1899
|
+
throw new Error(`Invalid output format '${options.output}'. Must be one of: text, html, rtf`);
|
|
659
1900
|
}
|
|
660
1901
|
if (options.cslFile) {
|
|
661
1902
|
const fs2 = await import("node:fs");
|
|
@@ -671,7 +1912,7 @@ function buildCiteOptions(options) {
|
|
|
671
1912
|
...options.style !== void 0 && { style: options.style },
|
|
672
1913
|
...options.cslFile !== void 0 && { cslFile: options.cslFile },
|
|
673
1914
|
...options.locale !== void 0 && { locale: options.locale },
|
|
674
|
-
...options.
|
|
1915
|
+
...options.output !== void 0 && { format: options.output },
|
|
675
1916
|
...options.inText !== void 0 && { inText: options.inText }
|
|
676
1917
|
};
|
|
677
1918
|
}
|
|
@@ -709,13 +1950,13 @@ function getCiteExitCode(result) {
|
|
|
709
1950
|
return 0;
|
|
710
1951
|
}
|
|
711
1952
|
async function executeInteractiveCite(options, context, config2) {
|
|
712
|
-
const { selectReferencesOrExit } = await import("./reference-select-
|
|
713
|
-
const { runStyleSelect } = await import("./style-select-
|
|
1953
|
+
const { selectReferencesOrExit } = await import("./reference-select-B9w9CLa1.js");
|
|
1954
|
+
const { runStyleSelect } = await import("./style-select-BNQHC79W.js");
|
|
714
1955
|
const allReferences = await context.library.getAll();
|
|
715
1956
|
const identifiers = await selectReferencesOrExit(
|
|
716
1957
|
allReferences,
|
|
717
1958
|
{ multiSelect: true },
|
|
718
|
-
config2.cli.
|
|
1959
|
+
config2.cli.tui
|
|
719
1960
|
);
|
|
720
1961
|
let style = options.style;
|
|
721
1962
|
if (!style && !options.cslFile) {
|
|
@@ -724,7 +1965,8 @@ async function executeInteractiveCite(options, context, config2) {
|
|
|
724
1965
|
defaultStyle: config2.citation.defaultStyle
|
|
725
1966
|
});
|
|
726
1967
|
if (styleResult.cancelled) {
|
|
727
|
-
|
|
1968
|
+
setExitCode(ExitCode.SUCCESS);
|
|
1969
|
+
return { results: [] };
|
|
728
1970
|
}
|
|
729
1971
|
style = styleResult.style;
|
|
730
1972
|
}
|
|
@@ -744,7 +1986,8 @@ async function handleCiteAction(identifiers, options, globalOpts) {
|
|
|
744
1986
|
process.stderr.write(
|
|
745
1987
|
"Error: No identifiers provided. Provide IDs, pipe them via stdin, or run interactively in a TTY.\n"
|
|
746
1988
|
);
|
|
747
|
-
|
|
1989
|
+
setExitCode(ExitCode.ERROR);
|
|
1990
|
+
return;
|
|
748
1991
|
}
|
|
749
1992
|
result = await executeCite({ ...options, identifiers: stdinIds }, context);
|
|
750
1993
|
}
|
|
@@ -761,16 +2004,16 @@ async function handleCiteAction(identifiers, options, globalOpts) {
|
|
|
761
2004
|
process.stderr.write(`${errors2}
|
|
762
2005
|
`);
|
|
763
2006
|
}
|
|
764
|
-
|
|
2007
|
+
setExitCode(getCiteExitCode(result));
|
|
765
2008
|
} catch (error) {
|
|
766
2009
|
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
767
2010
|
`);
|
|
768
|
-
|
|
2011
|
+
setExitCode(ExitCode.INTERNAL_ERROR);
|
|
769
2012
|
}
|
|
770
2013
|
}
|
|
771
2014
|
const ENV_OVERRIDE_MAP = {
|
|
772
2015
|
REFERENCE_MANAGER_LIBRARY: "library",
|
|
773
|
-
|
|
2016
|
+
REFERENCE_MANAGER_ATTACHMENTS_DIR: "attachments.directory",
|
|
774
2017
|
REFERENCE_MANAGER_CLI_DEFAULT_LIMIT: "cli.default_limit",
|
|
775
2018
|
REFERENCE_MANAGER_MCP_DEFAULT_LIMIT: "mcp.default_limit",
|
|
776
2019
|
PUBMED_EMAIL: "pubmed.email",
|
|
@@ -852,14 +2095,14 @@ const CONFIG_KEY_REGISTRY = [
|
|
|
852
2095
|
description: "Default sort order",
|
|
853
2096
|
enumValues: ["asc", "desc"]
|
|
854
2097
|
},
|
|
855
|
-
// cli.
|
|
2098
|
+
// cli.tui section
|
|
856
2099
|
{
|
|
857
|
-
key: "cli.
|
|
2100
|
+
key: "cli.tui.limit",
|
|
858
2101
|
type: "integer",
|
|
859
|
-
description: "Result limit in
|
|
2102
|
+
description: "Result limit in TUI mode"
|
|
860
2103
|
},
|
|
861
2104
|
{
|
|
862
|
-
key: "cli.
|
|
2105
|
+
key: "cli.tui.debounce_ms",
|
|
863
2106
|
type: "integer",
|
|
864
2107
|
description: "Search debounce delay (ms)"
|
|
865
2108
|
},
|
|
@@ -1064,7 +2307,7 @@ function createConfigTemplate() {
|
|
|
1064
2307
|
# default_sort = "updated" # created, updated, published, author, title
|
|
1065
2308
|
# default_order = "desc" # asc, desc
|
|
1066
2309
|
|
|
1067
|
-
[cli.
|
|
2310
|
+
[cli.tui]
|
|
1068
2311
|
# limit = 20
|
|
1069
2312
|
# debounce_ms = 200
|
|
1070
2313
|
|
|
@@ -1307,9 +2550,9 @@ function toSnakeCaseConfig(config2) {
|
|
|
1307
2550
|
default_limit: config2.cli.defaultLimit,
|
|
1308
2551
|
default_sort: config2.cli.defaultSort,
|
|
1309
2552
|
default_order: config2.cli.defaultOrder,
|
|
1310
|
-
|
|
1311
|
-
limit: config2.cli.
|
|
1312
|
-
debounce_ms: config2.cli.
|
|
2553
|
+
tui: {
|
|
2554
|
+
limit: config2.cli.tui.limit,
|
|
2555
|
+
debounce_ms: config2.cli.tui.debounceMs
|
|
1313
2556
|
},
|
|
1314
2557
|
edit: {
|
|
1315
2558
|
default_format: config2.cli.edit.defaultFormat
|
|
@@ -1466,21 +2709,21 @@ function resolveEditor(platform) {
|
|
|
1466
2709
|
}
|
|
1467
2710
|
function registerConfigCommand(program) {
|
|
1468
2711
|
const configCmd = program.command("config").description("Manage configuration settings");
|
|
1469
|
-
configCmd.command("show").description("Display effective configuration").option("--
|
|
2712
|
+
configCmd.command("show").description("Display effective configuration").option("-o, --output <format>", "Output format: text|json").option("--section <name>", "Show only a specific section").option("--sources", "Include source information for each value").action(async (options) => {
|
|
1470
2713
|
try {
|
|
1471
2714
|
const config2 = loadConfig();
|
|
1472
2715
|
const output = showConfig(config2, {
|
|
1473
|
-
json: options.json,
|
|
2716
|
+
json: options.output === "json",
|
|
1474
2717
|
section: options.section,
|
|
1475
2718
|
sources: options.sources
|
|
1476
2719
|
});
|
|
1477
2720
|
process.stdout.write(`${output}
|
|
1478
2721
|
`);
|
|
1479
|
-
|
|
2722
|
+
setExitCode(ExitCode.SUCCESS);
|
|
1480
2723
|
} catch (error) {
|
|
1481
2724
|
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
1482
2725
|
`);
|
|
1483
|
-
|
|
2726
|
+
setExitCode(ExitCode.ERROR);
|
|
1484
2727
|
}
|
|
1485
2728
|
});
|
|
1486
2729
|
configCmd.command("get <key>").description("Get a specific configuration value").option("--config-only", "Return only the config file value (ignore env vars)").action(async (key, options) => {
|
|
@@ -1498,14 +2741,14 @@ function registerConfigCommand(program) {
|
|
|
1498
2741
|
const { formatValue: formatValue2 } = await Promise.resolve().then(() => get);
|
|
1499
2742
|
process.stdout.write(`${formatValue2(result.value)}
|
|
1500
2743
|
`);
|
|
1501
|
-
|
|
2744
|
+
setExitCode(ExitCode.SUCCESS);
|
|
1502
2745
|
} else {
|
|
1503
|
-
|
|
2746
|
+
setExitCode(ExitCode.ERROR);
|
|
1504
2747
|
}
|
|
1505
2748
|
} catch (error) {
|
|
1506
2749
|
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
1507
2750
|
`);
|
|
1508
|
-
|
|
2751
|
+
setExitCode(ExitCode.ERROR);
|
|
1509
2752
|
}
|
|
1510
2753
|
});
|
|
1511
2754
|
configCmd.command("set <key> <value>").description("Set a configuration value").option("--local", "Write to current directory config (create if not exists)").option("--user", "Write to user config (ignore local config even if exists)").action(async (key, value, options) => {
|
|
@@ -1523,17 +2766,18 @@ function registerConfigCommand(program) {
|
|
|
1523
2766
|
if (!result.success) {
|
|
1524
2767
|
process.stderr.write(`Error: ${result.error}
|
|
1525
2768
|
`);
|
|
1526
|
-
|
|
2769
|
+
setExitCode(ExitCode.ERROR);
|
|
2770
|
+
return;
|
|
1527
2771
|
}
|
|
1528
2772
|
if (result.warning) {
|
|
1529
2773
|
process.stderr.write(`${result.warning}
|
|
1530
2774
|
`);
|
|
1531
2775
|
}
|
|
1532
|
-
|
|
2776
|
+
setExitCode(ExitCode.SUCCESS);
|
|
1533
2777
|
} catch (error) {
|
|
1534
2778
|
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
1535
2779
|
`);
|
|
1536
|
-
|
|
2780
|
+
setExitCode(ExitCode.ERROR);
|
|
1537
2781
|
}
|
|
1538
2782
|
});
|
|
1539
2783
|
configCmd.command("unset <key>").description("Remove a configuration value (revert to default)").option("--local", "Remove from current directory config").option("--user", "Remove from user config (ignore local config even if exists)").action(async (key, options) => {
|
|
@@ -1548,27 +2792,28 @@ function registerConfigCommand(program) {
|
|
|
1548
2792
|
if (!result.success) {
|
|
1549
2793
|
process.stderr.write(`Error: ${result.error}
|
|
1550
2794
|
`);
|
|
1551
|
-
|
|
2795
|
+
setExitCode(ExitCode.ERROR);
|
|
2796
|
+
return;
|
|
1552
2797
|
}
|
|
1553
|
-
|
|
2798
|
+
setExitCode(ExitCode.SUCCESS);
|
|
1554
2799
|
} catch (error) {
|
|
1555
2800
|
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
1556
2801
|
`);
|
|
1557
|
-
|
|
2802
|
+
setExitCode(ExitCode.ERROR);
|
|
1558
2803
|
}
|
|
1559
2804
|
});
|
|
1560
|
-
configCmd.command("
|
|
2805
|
+
configCmd.command("keys").description("List all available configuration keys").option("--section <name>", "List keys only in a specific section").action(async (options) => {
|
|
1561
2806
|
try {
|
|
1562
2807
|
const output = listConfigKeys({ section: options.section });
|
|
1563
2808
|
if (output) {
|
|
1564
2809
|
process.stdout.write(`${output}
|
|
1565
2810
|
`);
|
|
1566
2811
|
}
|
|
1567
|
-
|
|
2812
|
+
setExitCode(ExitCode.SUCCESS);
|
|
1568
2813
|
} catch (error) {
|
|
1569
2814
|
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
1570
2815
|
`);
|
|
1571
|
-
|
|
2816
|
+
setExitCode(ExitCode.ERROR);
|
|
1572
2817
|
}
|
|
1573
2818
|
});
|
|
1574
2819
|
configCmd.command("path").description("Show configuration file paths").option("--user", "Show only user config path").option("--local", "Show only local config path").action(async (options) => {
|
|
@@ -1576,18 +2821,19 @@ function registerConfigCommand(program) {
|
|
|
1576
2821
|
const output = showConfigPaths({ user: options.user, local: options.local });
|
|
1577
2822
|
process.stdout.write(`${output}
|
|
1578
2823
|
`);
|
|
1579
|
-
|
|
2824
|
+
setExitCode(ExitCode.SUCCESS);
|
|
1580
2825
|
} catch (error) {
|
|
1581
2826
|
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
1582
2827
|
`);
|
|
1583
|
-
|
|
2828
|
+
setExitCode(ExitCode.ERROR);
|
|
1584
2829
|
}
|
|
1585
2830
|
});
|
|
1586
2831
|
configCmd.command("edit").description("Open configuration file in editor").option("--local", "Edit current directory config").action(async (options) => {
|
|
1587
2832
|
try {
|
|
1588
2833
|
if (!process.stdin.isTTY) {
|
|
1589
2834
|
process.stderr.write("Error: config edit requires a terminal (TTY)\n");
|
|
1590
|
-
|
|
2835
|
+
setExitCode(ExitCode.ERROR);
|
|
2836
|
+
return;
|
|
1591
2837
|
}
|
|
1592
2838
|
const target = getConfigEditTarget({ local: options.local });
|
|
1593
2839
|
if (!target.exists) {
|
|
@@ -1597,11 +2843,11 @@ function registerConfigCommand(program) {
|
|
|
1597
2843
|
}
|
|
1598
2844
|
const editor = resolveEditor();
|
|
1599
2845
|
const exitCode = openEditor(editor, target.path);
|
|
1600
|
-
|
|
2846
|
+
setExitCode(exitCode);
|
|
1601
2847
|
} catch (error) {
|
|
1602
2848
|
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
1603
2849
|
`);
|
|
1604
|
-
|
|
2850
|
+
setExitCode(ExitCode.ERROR);
|
|
1605
2851
|
}
|
|
1606
2852
|
});
|
|
1607
2853
|
}
|
|
@@ -4542,12 +5788,12 @@ function formatEditOutput(result) {
|
|
|
4542
5788
|
return lines.join("\n");
|
|
4543
5789
|
}
|
|
4544
5790
|
async function executeInteractiveEdit(options, context, config2) {
|
|
4545
|
-
const { selectReferencesOrExit } = await import("./reference-select-
|
|
5791
|
+
const { selectReferencesOrExit } = await import("./reference-select-B9w9CLa1.js");
|
|
4546
5792
|
const allReferences = await context.library.getAll();
|
|
4547
5793
|
const identifiers = await selectReferencesOrExit(
|
|
4548
5794
|
allReferences,
|
|
4549
5795
|
{ multiSelect: true },
|
|
4550
|
-
config2.cli.
|
|
5796
|
+
config2.cli.tui
|
|
4551
5797
|
);
|
|
4552
5798
|
const format2 = options.format ?? config2.cli.edit.defaultFormat;
|
|
4553
5799
|
return executeEditCommand(
|
|
@@ -4572,7 +5818,7 @@ async function handleEditAction(identifiers, options, globalOpts) {
|
|
|
4572
5818
|
const output2 = formatEditOutput(result2);
|
|
4573
5819
|
process.stderr.write(`${output2}
|
|
4574
5820
|
`);
|
|
4575
|
-
|
|
5821
|
+
setExitCode(result2.success ? ExitCode.SUCCESS : ExitCode.ERROR);
|
|
4576
5822
|
return;
|
|
4577
5823
|
} else {
|
|
4578
5824
|
const stdinIds = await readIdentifiersFromStdin();
|
|
@@ -4580,13 +5826,15 @@ async function handleEditAction(identifiers, options, globalOpts) {
|
|
|
4580
5826
|
process.stderr.write(
|
|
4581
5827
|
"Error: No identifiers provided. Provide IDs, pipe them via stdin, or run interactively in a TTY.\n"
|
|
4582
5828
|
);
|
|
4583
|
-
|
|
5829
|
+
setExitCode(ExitCode.ERROR);
|
|
5830
|
+
return;
|
|
4584
5831
|
}
|
|
4585
5832
|
resolvedIdentifiers = stdinIds;
|
|
4586
5833
|
}
|
|
4587
5834
|
if (!isTTY()) {
|
|
4588
5835
|
process.stderr.write("Error: Edit command requires a TTY to open the editor.\n");
|
|
4589
|
-
|
|
5836
|
+
setExitCode(ExitCode.ERROR);
|
|
5837
|
+
return;
|
|
4590
5838
|
}
|
|
4591
5839
|
const format2 = options.format ?? config2.cli.edit.defaultFormat;
|
|
4592
5840
|
const result = await executeEditCommand(
|
|
@@ -4601,11 +5849,11 @@ async function handleEditAction(identifiers, options, globalOpts) {
|
|
|
4601
5849
|
const output = formatEditOutput(result);
|
|
4602
5850
|
process.stderr.write(`${output}
|
|
4603
5851
|
`);
|
|
4604
|
-
|
|
5852
|
+
setExitCode(result.success ? ExitCode.SUCCESS : ExitCode.ERROR);
|
|
4605
5853
|
} catch (error) {
|
|
4606
5854
|
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
4607
5855
|
`);
|
|
4608
|
-
|
|
5856
|
+
setExitCode(ExitCode.INTERNAL_ERROR);
|
|
4609
5857
|
}
|
|
4610
5858
|
}
|
|
4611
5859
|
const ALIAS = Symbol.for("yaml.alias");
|
|
@@ -7778,7 +9026,7 @@ async function executeExport(options, context) {
|
|
|
7778
9026
|
return { items: items2, notFound };
|
|
7779
9027
|
}
|
|
7780
9028
|
function formatExportOutput(result, options) {
|
|
7781
|
-
const format2 = options.
|
|
9029
|
+
const format2 = options.output ?? "json";
|
|
7782
9030
|
const singleIdRequest = (options.ids?.length ?? 0) === 1 && !options.all && !options.search;
|
|
7783
9031
|
const data = result.items.length === 1 && singleIdRequest ? result.items[0] : result.items;
|
|
7784
9032
|
if (format2 === "json") {
|
|
@@ -7795,183 +9043,6 @@ function formatExportOutput(result, options) {
|
|
|
7795
9043
|
function getExportExitCode(result) {
|
|
7796
9044
|
return result.notFound.length > 0 ? 1 : 0;
|
|
7797
9045
|
}
|
|
7798
|
-
const FULLTEXT_EXTENSIONS = {
|
|
7799
|
-
pdf: ".pdf",
|
|
7800
|
-
markdown: ".md"
|
|
7801
|
-
};
|
|
7802
|
-
function generateFulltextFilename(item, type2) {
|
|
7803
|
-
const uuid2 = item.custom?.uuid;
|
|
7804
|
-
if (!uuid2) {
|
|
7805
|
-
throw new Error("Missing uuid in custom field");
|
|
7806
|
-
}
|
|
7807
|
-
const parts = [item.id];
|
|
7808
|
-
if (item.PMID && item.PMID.length > 0) {
|
|
7809
|
-
parts.push(`PMID${item.PMID}`);
|
|
7810
|
-
}
|
|
7811
|
-
parts.push(uuid2);
|
|
7812
|
-
return parts.join("-") + FULLTEXT_EXTENSIONS[type2];
|
|
7813
|
-
}
|
|
7814
|
-
class FulltextIOError extends Error {
|
|
7815
|
-
constructor(message, cause) {
|
|
7816
|
-
super(message);
|
|
7817
|
-
this.cause = cause;
|
|
7818
|
-
this.name = "FulltextIOError";
|
|
7819
|
-
}
|
|
7820
|
-
}
|
|
7821
|
-
class FulltextNotAttachedError extends Error {
|
|
7822
|
-
constructor(itemId, type2) {
|
|
7823
|
-
super(`No ${type2} attached to reference ${itemId}`);
|
|
7824
|
-
this.itemId = itemId;
|
|
7825
|
-
this.type = type2;
|
|
7826
|
-
this.name = "FulltextNotAttachedError";
|
|
7827
|
-
}
|
|
7828
|
-
}
|
|
7829
|
-
class FulltextManager {
|
|
7830
|
-
constructor(fulltextDirectory) {
|
|
7831
|
-
this.fulltextDirectory = fulltextDirectory;
|
|
7832
|
-
}
|
|
7833
|
-
/**
|
|
7834
|
-
* Ensure the fulltext directory exists
|
|
7835
|
-
*/
|
|
7836
|
-
async ensureDirectory() {
|
|
7837
|
-
await mkdir(this.fulltextDirectory, { recursive: true });
|
|
7838
|
-
}
|
|
7839
|
-
/**
|
|
7840
|
-
* Attach a file to a reference
|
|
7841
|
-
*/
|
|
7842
|
-
async attachFile(item, sourcePath, type2, options) {
|
|
7843
|
-
const { move = false, force = false } = options ?? {};
|
|
7844
|
-
const newFilename = generateFulltextFilename(item, type2);
|
|
7845
|
-
this.validateSourceFile(sourcePath);
|
|
7846
|
-
const existingFilename = this.getExistingFilename(item, type2);
|
|
7847
|
-
if (existingFilename && !force) {
|
|
7848
|
-
return {
|
|
7849
|
-
filename: newFilename,
|
|
7850
|
-
existingFile: existingFilename,
|
|
7851
|
-
overwritten: false
|
|
7852
|
-
};
|
|
7853
|
-
}
|
|
7854
|
-
await this.ensureDirectory();
|
|
7855
|
-
const deletedOldFile = await this.deleteOldFileIfNeeded(existingFilename, newFilename, force);
|
|
7856
|
-
const destPath = join(this.fulltextDirectory, newFilename);
|
|
7857
|
-
await this.copyOrMoveFile(sourcePath, destPath, move);
|
|
7858
|
-
const result = {
|
|
7859
|
-
filename: newFilename,
|
|
7860
|
-
overwritten: existingFilename !== void 0
|
|
7861
|
-
};
|
|
7862
|
-
if (deletedOldFile) {
|
|
7863
|
-
result.deletedOldFile = deletedOldFile;
|
|
7864
|
-
}
|
|
7865
|
-
return result;
|
|
7866
|
-
}
|
|
7867
|
-
/**
|
|
7868
|
-
* Validate that source file exists
|
|
7869
|
-
*/
|
|
7870
|
-
validateSourceFile(sourcePath) {
|
|
7871
|
-
if (!existsSync(sourcePath)) {
|
|
7872
|
-
throw new FulltextIOError(`Source file not found: ${sourcePath}`);
|
|
7873
|
-
}
|
|
7874
|
-
}
|
|
7875
|
-
/**
|
|
7876
|
-
* Delete old file if force mode and filename changed
|
|
7877
|
-
* @returns Deleted filename or undefined
|
|
7878
|
-
*/
|
|
7879
|
-
async deleteOldFileIfNeeded(existingFilename, newFilename, force) {
|
|
7880
|
-
if (!force || !existingFilename || existingFilename === newFilename) {
|
|
7881
|
-
return void 0;
|
|
7882
|
-
}
|
|
7883
|
-
const oldPath = join(this.fulltextDirectory, existingFilename);
|
|
7884
|
-
try {
|
|
7885
|
-
await unlink(oldPath);
|
|
7886
|
-
} catch {
|
|
7887
|
-
}
|
|
7888
|
-
return existingFilename;
|
|
7889
|
-
}
|
|
7890
|
-
/**
|
|
7891
|
-
* Copy or move file to destination
|
|
7892
|
-
*/
|
|
7893
|
-
async copyOrMoveFile(sourcePath, destPath, move) {
|
|
7894
|
-
try {
|
|
7895
|
-
if (move) {
|
|
7896
|
-
await rename(sourcePath, destPath);
|
|
7897
|
-
} else {
|
|
7898
|
-
await copyFile(sourcePath, destPath);
|
|
7899
|
-
}
|
|
7900
|
-
} catch (error) {
|
|
7901
|
-
const operation = move ? "move" : "copy";
|
|
7902
|
-
throw new FulltextIOError(
|
|
7903
|
-
`Failed to ${operation} file to ${destPath}`,
|
|
7904
|
-
error instanceof Error ? error : void 0
|
|
7905
|
-
);
|
|
7906
|
-
}
|
|
7907
|
-
}
|
|
7908
|
-
/**
|
|
7909
|
-
* Get the full path for an attached file
|
|
7910
|
-
* @returns Full path or null if not attached
|
|
7911
|
-
*/
|
|
7912
|
-
getFilePath(item, type2) {
|
|
7913
|
-
const filename = this.getExistingFilename(item, type2);
|
|
7914
|
-
if (!filename) {
|
|
7915
|
-
return null;
|
|
7916
|
-
}
|
|
7917
|
-
return join(this.fulltextDirectory, filename);
|
|
7918
|
-
}
|
|
7919
|
-
/**
|
|
7920
|
-
* Detach a file from a reference
|
|
7921
|
-
*/
|
|
7922
|
-
async detachFile(item, type2, options) {
|
|
7923
|
-
const { delete: deleteFile = false } = options ?? {};
|
|
7924
|
-
const filename = this.getExistingFilename(item, type2);
|
|
7925
|
-
if (!filename) {
|
|
7926
|
-
throw new FulltextNotAttachedError(item.id, type2);
|
|
7927
|
-
}
|
|
7928
|
-
if (deleteFile) {
|
|
7929
|
-
const filePath = join(this.fulltextDirectory, filename);
|
|
7930
|
-
try {
|
|
7931
|
-
await unlink(filePath);
|
|
7932
|
-
} catch {
|
|
7933
|
-
}
|
|
7934
|
-
}
|
|
7935
|
-
return {
|
|
7936
|
-
filename,
|
|
7937
|
-
deleted: deleteFile
|
|
7938
|
-
};
|
|
7939
|
-
}
|
|
7940
|
-
/**
|
|
7941
|
-
* Get list of attached fulltext types
|
|
7942
|
-
*/
|
|
7943
|
-
getAttachedTypes(item) {
|
|
7944
|
-
const types2 = [];
|
|
7945
|
-
const fulltext = item.custom?.fulltext;
|
|
7946
|
-
if (fulltext?.pdf) {
|
|
7947
|
-
types2.push("pdf");
|
|
7948
|
-
}
|
|
7949
|
-
if (fulltext?.markdown) {
|
|
7950
|
-
types2.push("markdown");
|
|
7951
|
-
}
|
|
7952
|
-
return types2;
|
|
7953
|
-
}
|
|
7954
|
-
/**
|
|
7955
|
-
* Check if item has attachment
|
|
7956
|
-
* @param type Optional type to check; if omitted, checks for any attachment
|
|
7957
|
-
*/
|
|
7958
|
-
hasAttachment(item, type2) {
|
|
7959
|
-
if (type2) {
|
|
7960
|
-
return this.getExistingFilename(item, type2) !== void 0;
|
|
7961
|
-
}
|
|
7962
|
-
return this.getAttachedTypes(item).length > 0;
|
|
7963
|
-
}
|
|
7964
|
-
/**
|
|
7965
|
-
* Get existing filename from item metadata
|
|
7966
|
-
*/
|
|
7967
|
-
getExistingFilename(item, type2) {
|
|
7968
|
-
const fulltext = item.custom?.fulltext;
|
|
7969
|
-
if (!fulltext) {
|
|
7970
|
-
return void 0;
|
|
7971
|
-
}
|
|
7972
|
-
return fulltext[type2];
|
|
7973
|
-
}
|
|
7974
|
-
}
|
|
7975
9046
|
function detectType(filePath) {
|
|
7976
9047
|
const ext = extname(filePath).toLowerCase();
|
|
7977
9048
|
if (ext === ".pdf") return "pdf";
|
|
@@ -7996,8 +9067,8 @@ function resolveFileType(explicitType, filePath, stdinContent) {
|
|
|
7996
9067
|
function prepareStdinSource(stdinContent, fileType) {
|
|
7997
9068
|
try {
|
|
7998
9069
|
const tempDir = mkdtempSync(join(tmpdir(), "refmgr-"));
|
|
7999
|
-
const ext = fileType
|
|
8000
|
-
const sourcePath = join(tempDir, `stdin
|
|
9070
|
+
const ext = formatToExtension(fileType);
|
|
9071
|
+
const sourcePath = join(tempDir, `stdin.${ext}`);
|
|
8001
9072
|
writeFileSync(sourcePath, stdinContent);
|
|
8002
9073
|
return { sourcePath, tempDir };
|
|
8003
9074
|
} catch (error) {
|
|
@@ -8012,13 +9083,6 @@ async function cleanupTempDir(tempDir) {
|
|
|
8012
9083
|
});
|
|
8013
9084
|
}
|
|
8014
9085
|
}
|
|
8015
|
-
function buildNewFulltext(currentFulltext, fileType, filename) {
|
|
8016
|
-
const newFulltext = {};
|
|
8017
|
-
if (currentFulltext.pdf) newFulltext.pdf = currentFulltext.pdf;
|
|
8018
|
-
if (currentFulltext.markdown) newFulltext.markdown = currentFulltext.markdown;
|
|
8019
|
-
newFulltext[fileType] = filename;
|
|
8020
|
-
return newFulltext;
|
|
8021
|
-
}
|
|
8022
9086
|
function prepareSourcePath(filePath, stdinContent, fileType) {
|
|
8023
9087
|
if (stdinContent) {
|
|
8024
9088
|
return prepareStdinSource(stdinContent, fileType);
|
|
@@ -8028,12 +9092,26 @@ function prepareSourcePath(filePath, stdinContent, fileType) {
|
|
|
8028
9092
|
}
|
|
8029
9093
|
return { sourcePath: filePath };
|
|
8030
9094
|
}
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
9095
|
+
function convertResult(result, fileType) {
|
|
9096
|
+
if (result.success) {
|
|
9097
|
+
return {
|
|
9098
|
+
success: true,
|
|
9099
|
+
filename: result.filename,
|
|
9100
|
+
type: fileType,
|
|
9101
|
+
overwritten: result.overwritten
|
|
9102
|
+
};
|
|
9103
|
+
}
|
|
9104
|
+
if (result.requiresConfirmation) {
|
|
9105
|
+
return {
|
|
9106
|
+
success: false,
|
|
9107
|
+
existingFile: result.existingFile,
|
|
9108
|
+
requiresConfirmation: true
|
|
9109
|
+
};
|
|
9110
|
+
}
|
|
9111
|
+
return {
|
|
9112
|
+
success: false,
|
|
9113
|
+
error: result.error
|
|
8035
9114
|
};
|
|
8036
|
-
return manager.attachFile(item, sourcePath, fileType, attachOptions);
|
|
8037
9115
|
}
|
|
8038
9116
|
async function fulltextAttach(library, options) {
|
|
8039
9117
|
const {
|
|
@@ -8046,71 +9124,78 @@ async function fulltextAttach(library, options) {
|
|
|
8046
9124
|
fulltextDirectory,
|
|
8047
9125
|
stdinContent
|
|
8048
9126
|
} = options;
|
|
8049
|
-
const item = await library.find(identifier, { idType });
|
|
8050
|
-
if (!item) {
|
|
8051
|
-
return { success: false, error: `Reference '${identifier}' not found` };
|
|
8052
|
-
}
|
|
8053
9127
|
const fileTypeResult = resolveFileType(explicitType, filePath, stdinContent);
|
|
8054
9128
|
if (typeof fileTypeResult === "object" && "error" in fileTypeResult) {
|
|
9129
|
+
const item = await library.find(identifier, { idType });
|
|
9130
|
+
if (!item) {
|
|
9131
|
+
return { success: false, error: `Reference '${identifier}' not found` };
|
|
9132
|
+
}
|
|
8055
9133
|
return { success: false, error: fileTypeResult.error };
|
|
8056
9134
|
}
|
|
8057
9135
|
const fileType = fileTypeResult;
|
|
8058
9136
|
const sourceResult = prepareSourcePath(filePath, stdinContent, fileType);
|
|
8059
9137
|
if ("error" in sourceResult) {
|
|
9138
|
+
const item = await library.find(identifier, { idType });
|
|
9139
|
+
if (!item) {
|
|
9140
|
+
return { success: false, error: `Reference '${identifier}' not found` };
|
|
9141
|
+
}
|
|
8060
9142
|
return { success: false, error: sourceResult.error };
|
|
8061
9143
|
}
|
|
8062
9144
|
const { sourcePath, tempDir } = sourceResult;
|
|
8063
|
-
const manager = new FulltextManager(fulltextDirectory);
|
|
8064
9145
|
try {
|
|
8065
|
-
const result = await
|
|
8066
|
-
if (result.existingFile && !result.overwritten) {
|
|
8067
|
-
await cleanupTempDir(tempDir);
|
|
8068
|
-
return { success: false, existingFile: result.existingFile, requiresConfirmation: true };
|
|
8069
|
-
}
|
|
8070
|
-
const newFulltext = buildNewFulltext(item.custom?.fulltext ?? {}, fileType, result.filename);
|
|
8071
|
-
await updateReference(library, {
|
|
9146
|
+
const result = await addAttachment(library, {
|
|
8072
9147
|
identifier,
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8076
|
-
|
|
9148
|
+
filePath: sourcePath,
|
|
9149
|
+
role: FULLTEXT_ROLE,
|
|
9150
|
+
move: move ?? false,
|
|
9151
|
+
force: force ?? false,
|
|
9152
|
+
idType,
|
|
9153
|
+
attachmentsDirectory: fulltextDirectory
|
|
8077
9154
|
});
|
|
8078
9155
|
await cleanupTempDir(tempDir);
|
|
8079
|
-
return
|
|
8080
|
-
success: true,
|
|
8081
|
-
filename: result.filename,
|
|
8082
|
-
type: fileType,
|
|
8083
|
-
overwritten: result.overwritten
|
|
8084
|
-
};
|
|
9156
|
+
return convertResult(result, fileType);
|
|
8085
9157
|
} catch (error) {
|
|
8086
9158
|
await cleanupTempDir(tempDir);
|
|
8087
|
-
if (error instanceof FulltextIOError) {
|
|
8088
|
-
return { success: false, error: error.message };
|
|
8089
|
-
}
|
|
8090
9159
|
throw error;
|
|
8091
9160
|
}
|
|
8092
9161
|
}
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
9162
|
+
function buildFilePath$1(attachmentsDirectory, directory, filename) {
|
|
9163
|
+
return normalizePathForOutput(join(attachmentsDirectory, directory, filename));
|
|
9164
|
+
}
|
|
9165
|
+
async function getFileContent(filePath) {
|
|
9166
|
+
const content = await readFile(filePath);
|
|
9167
|
+
return { success: true, content };
|
|
9168
|
+
}
|
|
9169
|
+
async function handleStdoutMode(attachments, type2, identifier, fulltextDirectory) {
|
|
9170
|
+
const file = findFulltextFile(attachments, type2);
|
|
9171
|
+
if (!file || !attachments?.directory) {
|
|
8096
9172
|
return { success: false, error: `No ${type2} fulltext attached to '${identifier}'` };
|
|
8097
9173
|
}
|
|
9174
|
+
const filePath = buildFilePath$1(fulltextDirectory, attachments.directory, file.filename);
|
|
8098
9175
|
try {
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
return {
|
|
8103
|
-
success: false,
|
|
8104
|
-
error: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`
|
|
8105
|
-
};
|
|
9176
|
+
return await getFileContent(filePath);
|
|
9177
|
+
} catch {
|
|
9178
|
+
return { success: false, error: `No ${type2} fulltext attached to '${identifier}'` };
|
|
8106
9179
|
}
|
|
8107
9180
|
}
|
|
8108
|
-
function
|
|
9181
|
+
function getSingleTypePath(attachments, type2, identifier, fulltextDirectory) {
|
|
9182
|
+
const file = findFulltextFile(attachments, type2);
|
|
9183
|
+
if (!file || !attachments?.directory) {
|
|
9184
|
+
return { success: false, error: `No ${type2} fulltext attached to '${identifier}'` };
|
|
9185
|
+
}
|
|
9186
|
+
const filePath = buildFilePath$1(fulltextDirectory, attachments.directory, file.filename);
|
|
9187
|
+
const paths = {};
|
|
9188
|
+
paths[type2] = filePath;
|
|
9189
|
+
return { success: true, paths };
|
|
9190
|
+
}
|
|
9191
|
+
function getAllFulltextPaths(attachments, fulltextFiles, fulltextDirectory, identifier) {
|
|
8109
9192
|
const paths = {};
|
|
8110
|
-
for (const
|
|
8111
|
-
const
|
|
8112
|
-
|
|
8113
|
-
|
|
9193
|
+
for (const file of fulltextFiles) {
|
|
9194
|
+
const ext = file.filename.split(".").pop() || "";
|
|
9195
|
+
const format2 = extensionToFormat(ext);
|
|
9196
|
+
if (format2) {
|
|
9197
|
+
const filePath = buildFilePath$1(fulltextDirectory, attachments.directory, file.filename);
|
|
9198
|
+
paths[format2] = filePath;
|
|
8114
9199
|
}
|
|
8115
9200
|
}
|
|
8116
9201
|
if (Object.keys(paths).length === 0) {
|
|
@@ -8124,92 +9209,98 @@ async function fulltextGet(library, options) {
|
|
|
8124
9209
|
if (!item) {
|
|
8125
9210
|
return { success: false, error: `Reference '${identifier}' not found` };
|
|
8126
9211
|
}
|
|
8127
|
-
const
|
|
9212
|
+
const attachments = item.custom?.attachments;
|
|
8128
9213
|
if (stdout2 && type2) {
|
|
8129
|
-
return
|
|
9214
|
+
return handleStdoutMode(attachments, type2, identifier, fulltextDirectory);
|
|
9215
|
+
}
|
|
9216
|
+
const fulltextFiles = findFulltextFiles(attachments);
|
|
9217
|
+
if (fulltextFiles.length === 0) {
|
|
9218
|
+
return { success: false, error: `No fulltext attached to '${identifier}'` };
|
|
9219
|
+
}
|
|
9220
|
+
if (type2) {
|
|
9221
|
+
return getSingleTypePath(attachments, type2, identifier, fulltextDirectory);
|
|
8130
9222
|
}
|
|
8131
|
-
|
|
8132
|
-
if (attachedTypes.length === 0) {
|
|
9223
|
+
if (!attachments) {
|
|
8133
9224
|
return { success: false, error: `No fulltext attached to '${identifier}'` };
|
|
8134
9225
|
}
|
|
8135
|
-
return
|
|
9226
|
+
return getAllFulltextPaths(attachments, fulltextFiles, fulltextDirectory, identifier);
|
|
8136
9227
|
}
|
|
8137
|
-
|
|
9228
|
+
function getFilesToDetach(attachments, type2) {
|
|
9229
|
+
if (type2) {
|
|
9230
|
+
const file = findFulltextFile(attachments, type2);
|
|
9231
|
+
return file ? [file] : [];
|
|
9232
|
+
}
|
|
9233
|
+
return findFulltextFiles(attachments);
|
|
9234
|
+
}
|
|
9235
|
+
async function detachFiles(library, files, identifier, removeFiles, idType, fulltextDirectory) {
|
|
8138
9236
|
const detached = [];
|
|
8139
9237
|
const deleted = [];
|
|
8140
|
-
for (const
|
|
8141
|
-
const
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
9238
|
+
for (const file of files) {
|
|
9239
|
+
const result = await detachAttachment(library, {
|
|
9240
|
+
identifier,
|
|
9241
|
+
filename: file.filename,
|
|
9242
|
+
removeFiles: removeFiles ?? false,
|
|
9243
|
+
idType,
|
|
9244
|
+
attachmentsDirectory: fulltextDirectory
|
|
9245
|
+
});
|
|
9246
|
+
if (result.success) {
|
|
9247
|
+
const ext = file.filename.split(".").pop() || "";
|
|
9248
|
+
const format2 = extensionToFormat(ext);
|
|
9249
|
+
if (format2) {
|
|
9250
|
+
detached.push(format2);
|
|
9251
|
+
if (result.deleted.length > 0) {
|
|
9252
|
+
deleted.push(format2);
|
|
9253
|
+
}
|
|
9254
|
+
}
|
|
8146
9255
|
}
|
|
8147
9256
|
}
|
|
8148
9257
|
return { detached, deleted };
|
|
8149
9258
|
}
|
|
8150
|
-
function
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
newFulltext.pdf = currentFulltext.pdf;
|
|
8154
|
-
}
|
|
8155
|
-
if (currentFulltext.markdown && !detached.includes("markdown")) {
|
|
8156
|
-
newFulltext.markdown = currentFulltext.markdown;
|
|
9259
|
+
function buildResult(detached, deleted, identifier) {
|
|
9260
|
+
if (detached.length === 0) {
|
|
9261
|
+
return { success: false, error: `Failed to detach fulltext from '${identifier}'` };
|
|
8157
9262
|
}
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
if (error instanceof FulltextNotAttachedError || error instanceof FulltextIOError) {
|
|
8162
|
-
return { success: false, error: error.message };
|
|
9263
|
+
const result = { success: true, detached };
|
|
9264
|
+
if (deleted.length > 0) {
|
|
9265
|
+
result.deleted = deleted;
|
|
8163
9266
|
}
|
|
8164
|
-
|
|
9267
|
+
return result;
|
|
8165
9268
|
}
|
|
8166
9269
|
async function fulltextDetach(library, options) {
|
|
8167
|
-
const { identifier, type: type2,
|
|
9270
|
+
const { identifier, type: type2, removeFiles, idType = "id", fulltextDirectory } = options;
|
|
8168
9271
|
const item = await library.find(identifier, { idType });
|
|
8169
9272
|
if (!item) {
|
|
8170
9273
|
return { success: false, error: `Reference '${identifier}' not found` };
|
|
8171
9274
|
}
|
|
8172
|
-
const
|
|
8173
|
-
const
|
|
8174
|
-
if (
|
|
9275
|
+
const attachments = item.custom?.attachments;
|
|
9276
|
+
const fulltextFiles = findFulltextFiles(attachments);
|
|
9277
|
+
if (fulltextFiles.length === 0) {
|
|
8175
9278
|
return { success: false, error: `No fulltext attached to '${identifier}'` };
|
|
8176
9279
|
}
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
item,
|
|
8181
|
-
typesToDetach,
|
|
8182
|
-
deleteFile
|
|
8183
|
-
);
|
|
8184
|
-
const updatedFulltext = buildRemainingFulltext(item.custom?.fulltext ?? {}, detached);
|
|
8185
|
-
await updateReference(library, {
|
|
8186
|
-
identifier,
|
|
8187
|
-
updates: {
|
|
8188
|
-
custom: { fulltext: updatedFulltext }
|
|
8189
|
-
},
|
|
8190
|
-
idType
|
|
8191
|
-
});
|
|
8192
|
-
const resultData = { success: true, detached };
|
|
8193
|
-
if (deleted.length > 0) {
|
|
8194
|
-
resultData.deleted = deleted;
|
|
8195
|
-
}
|
|
8196
|
-
return resultData;
|
|
8197
|
-
} catch (error) {
|
|
8198
|
-
return handleDetachError(error);
|
|
9280
|
+
const filesToDetach = getFilesToDetach(attachments, type2);
|
|
9281
|
+
if (filesToDetach.length === 0) {
|
|
9282
|
+
return { success: false, error: `No ${type2} fulltext attached to '${identifier}'` };
|
|
8199
9283
|
}
|
|
8200
|
-
}
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
|
|
9284
|
+
const { detached, deleted } = await detachFiles(
|
|
9285
|
+
library,
|
|
9286
|
+
filesToDetach,
|
|
9287
|
+
identifier,
|
|
9288
|
+
removeFiles,
|
|
9289
|
+
idType,
|
|
9290
|
+
fulltextDirectory
|
|
9291
|
+
);
|
|
9292
|
+
return buildResult(detached, deleted, identifier);
|
|
9293
|
+
}
|
|
9294
|
+
function buildFilePath(attachmentsDirectory, directory, filename) {
|
|
9295
|
+
return join(attachmentsDirectory, directory, filename);
|
|
9296
|
+
}
|
|
9297
|
+
function determineTypeToOpen(attachments) {
|
|
9298
|
+
const files = findFulltextFiles(attachments);
|
|
9299
|
+
if (files.length === 0) return void 0;
|
|
9300
|
+
const pdfFile = files.find((f) => f.filename.endsWith(".pdf"));
|
|
9301
|
+
if (pdfFile) return "pdf";
|
|
9302
|
+
const mdFile = files.find((f) => f.filename.endsWith(".md"));
|
|
9303
|
+
if (mdFile) return "markdown";
|
|
8213
9304
|
return void 0;
|
|
8214
9305
|
}
|
|
8215
9306
|
async function fulltextOpen(library, options) {
|
|
@@ -8218,14 +9309,16 @@ async function fulltextOpen(library, options) {
|
|
|
8218
9309
|
if (!item) {
|
|
8219
9310
|
return { success: false, error: `Reference not found: ${identifier}` };
|
|
8220
9311
|
}
|
|
8221
|
-
const
|
|
9312
|
+
const attachments = item.custom?.attachments;
|
|
9313
|
+
const typeToOpen = type2 ?? determineTypeToOpen(attachments);
|
|
8222
9314
|
if (!typeToOpen) {
|
|
8223
9315
|
return { success: false, error: `No fulltext attached to reference: ${identifier}` };
|
|
8224
9316
|
}
|
|
8225
|
-
const
|
|
8226
|
-
if (!
|
|
9317
|
+
const file = findFulltextFile(attachments, typeToOpen);
|
|
9318
|
+
if (!file || !attachments?.directory) {
|
|
8227
9319
|
return { success: false, error: `No ${typeToOpen} attached to reference: ${identifier}` };
|
|
8228
9320
|
}
|
|
9321
|
+
const filePath = buildFilePath(fulltextDirectory, attachments.directory, file.filename);
|
|
8229
9322
|
if (!existsSync(filePath)) {
|
|
8230
9323
|
return {
|
|
8231
9324
|
success: false,
|
|
@@ -8273,7 +9366,7 @@ async function executeFulltextDetach(options, context) {
|
|
|
8273
9366
|
const operationOptions = {
|
|
8274
9367
|
identifier: options.identifier,
|
|
8275
9368
|
type: options.type,
|
|
8276
|
-
|
|
9369
|
+
removeFiles: options.removeFiles,
|
|
8277
9370
|
idType: options.idType,
|
|
8278
9371
|
fulltextDirectory: options.fulltextDirectory
|
|
8279
9372
|
};
|
|
@@ -8344,12 +9437,12 @@ function getFulltextExitCode(result) {
|
|
|
8344
9437
|
return result.success ? 0 : 1;
|
|
8345
9438
|
}
|
|
8346
9439
|
async function executeInteractiveSelect(context, config2) {
|
|
8347
|
-
const { selectReferencesOrExit } = await import("./reference-select-
|
|
9440
|
+
const { selectReferencesOrExit } = await import("./reference-select-B9w9CLa1.js");
|
|
8348
9441
|
const allReferences = await context.library.getAll();
|
|
8349
9442
|
const identifiers = await selectReferencesOrExit(
|
|
8350
9443
|
allReferences,
|
|
8351
9444
|
{ multiSelect: false },
|
|
8352
|
-
config2.cli.
|
|
9445
|
+
config2.cli.tui
|
|
8353
9446
|
);
|
|
8354
9447
|
return identifiers[0];
|
|
8355
9448
|
}
|
|
@@ -8380,7 +9473,8 @@ async function handleFulltextAttachAction(identifierArg, filePathArg, options, g
|
|
|
8380
9473
|
process.stderr.write(
|
|
8381
9474
|
"Error: No identifier provided. Provide an ID or run interactively in a TTY.\n"
|
|
8382
9475
|
);
|
|
8383
|
-
|
|
9476
|
+
setExitCode(ExitCode.ERROR);
|
|
9477
|
+
return;
|
|
8384
9478
|
}
|
|
8385
9479
|
identifier = await executeInteractiveSelect(context, config2);
|
|
8386
9480
|
}
|
|
@@ -8388,7 +9482,7 @@ async function handleFulltextAttachAction(identifierArg, filePathArg, options, g
|
|
|
8388
9482
|
const stdinContent = !filePath && type2 ? await readStdinBuffer() : void 0;
|
|
8389
9483
|
const attachOptions = {
|
|
8390
9484
|
identifier,
|
|
8391
|
-
fulltextDirectory: config2.
|
|
9485
|
+
fulltextDirectory: config2.attachments.directory,
|
|
8392
9486
|
...filePath && { filePath },
|
|
8393
9487
|
...type2 && { type: type2 },
|
|
8394
9488
|
...options.move && { move: options.move },
|
|
@@ -8400,11 +9494,11 @@ async function handleFulltextAttachAction(identifierArg, filePathArg, options, g
|
|
|
8400
9494
|
const output = formatFulltextAttachOutput(result);
|
|
8401
9495
|
process.stderr.write(`${output}
|
|
8402
9496
|
`);
|
|
8403
|
-
|
|
9497
|
+
setExitCode(getFulltextExitCode(result));
|
|
8404
9498
|
} catch (error) {
|
|
8405
9499
|
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
8406
9500
|
`);
|
|
8407
|
-
|
|
9501
|
+
setExitCode(ExitCode.INTERNAL_ERROR);
|
|
8408
9502
|
}
|
|
8409
9503
|
}
|
|
8410
9504
|
function outputFulltextGetResult(result, useStdout) {
|
|
@@ -8436,13 +9530,14 @@ async function handleFulltextGetAction(identifierArg, options, globalOpts) {
|
|
|
8436
9530
|
process.stderr.write(
|
|
8437
9531
|
"Error: No identifier provided. Provide an ID, pipe one via stdin, or run interactively in a TTY.\n"
|
|
8438
9532
|
);
|
|
8439
|
-
|
|
9533
|
+
setExitCode(ExitCode.ERROR);
|
|
9534
|
+
return;
|
|
8440
9535
|
}
|
|
8441
9536
|
identifier = stdinId;
|
|
8442
9537
|
}
|
|
8443
9538
|
const getOptions = {
|
|
8444
9539
|
identifier,
|
|
8445
|
-
fulltextDirectory: config2.
|
|
9540
|
+
fulltextDirectory: config2.attachments.directory,
|
|
8446
9541
|
...options.pdf && { type: "pdf" },
|
|
8447
9542
|
...options.markdown && { type: "markdown" },
|
|
8448
9543
|
...options.stdout && { stdout: options.stdout },
|
|
@@ -8450,11 +9545,11 @@ async function handleFulltextGetAction(identifierArg, options, globalOpts) {
|
|
|
8450
9545
|
};
|
|
8451
9546
|
const result = await executeFulltextGet(getOptions, context);
|
|
8452
9547
|
outputFulltextGetResult(result, Boolean(options.stdout));
|
|
8453
|
-
|
|
9548
|
+
setExitCode(getFulltextExitCode(result));
|
|
8454
9549
|
} catch (error) {
|
|
8455
9550
|
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
8456
9551
|
`);
|
|
8457
|
-
|
|
9552
|
+
setExitCode(ExitCode.INTERNAL_ERROR);
|
|
8458
9553
|
}
|
|
8459
9554
|
}
|
|
8460
9555
|
async function handleFulltextDetachAction(identifierArg, options, globalOpts) {
|
|
@@ -8472,16 +9567,17 @@ async function handleFulltextDetachAction(identifierArg, options, globalOpts) {
|
|
|
8472
9567
|
process.stderr.write(
|
|
8473
9568
|
"Error: No identifier provided. Provide an ID, pipe one via stdin, or run interactively in a TTY.\n"
|
|
8474
9569
|
);
|
|
8475
|
-
|
|
9570
|
+
setExitCode(ExitCode.ERROR);
|
|
9571
|
+
return;
|
|
8476
9572
|
}
|
|
8477
9573
|
identifier = stdinId;
|
|
8478
9574
|
}
|
|
8479
9575
|
const detachOptions = {
|
|
8480
9576
|
identifier,
|
|
8481
|
-
fulltextDirectory: config2.
|
|
9577
|
+
fulltextDirectory: config2.attachments.directory,
|
|
8482
9578
|
...options.pdf && { type: "pdf" },
|
|
8483
9579
|
...options.markdown && { type: "markdown" },
|
|
8484
|
-
...options.
|
|
9580
|
+
...options.removeFiles && { removeFiles: options.removeFiles },
|
|
8485
9581
|
...options.force && { force: options.force },
|
|
8486
9582
|
...options.uuid && { idType: "uuid" }
|
|
8487
9583
|
};
|
|
@@ -8489,11 +9585,11 @@ async function handleFulltextDetachAction(identifierArg, options, globalOpts) {
|
|
|
8489
9585
|
const output = formatFulltextDetachOutput(result);
|
|
8490
9586
|
process.stderr.write(`${output}
|
|
8491
9587
|
`);
|
|
8492
|
-
|
|
9588
|
+
setExitCode(getFulltextExitCode(result));
|
|
8493
9589
|
} catch (error) {
|
|
8494
9590
|
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
8495
9591
|
`);
|
|
8496
|
-
|
|
9592
|
+
setExitCode(ExitCode.INTERNAL_ERROR);
|
|
8497
9593
|
}
|
|
8498
9594
|
}
|
|
8499
9595
|
async function handleFulltextOpenAction(identifierArg, options, globalOpts) {
|
|
@@ -8511,13 +9607,14 @@ async function handleFulltextOpenAction(identifierArg, options, globalOpts) {
|
|
|
8511
9607
|
process.stderr.write(
|
|
8512
9608
|
"Error: No identifier provided. Provide an ID, pipe one via stdin, or run interactively in a TTY.\n"
|
|
8513
9609
|
);
|
|
8514
|
-
|
|
9610
|
+
setExitCode(ExitCode.ERROR);
|
|
9611
|
+
return;
|
|
8515
9612
|
}
|
|
8516
9613
|
identifier = stdinId;
|
|
8517
9614
|
}
|
|
8518
9615
|
const openOptions = {
|
|
8519
9616
|
identifier,
|
|
8520
|
-
fulltextDirectory: config2.
|
|
9617
|
+
fulltextDirectory: config2.attachments.directory,
|
|
8521
9618
|
...options.pdf && { type: "pdf" },
|
|
8522
9619
|
...options.markdown && { type: "markdown" },
|
|
8523
9620
|
...options.uuid && { idType: "uuid" }
|
|
@@ -8526,11 +9623,11 @@ async function handleFulltextOpenAction(identifierArg, options, globalOpts) {
|
|
|
8526
9623
|
const output = formatFulltextOpenOutput(result);
|
|
8527
9624
|
process.stderr.write(`${output}
|
|
8528
9625
|
`);
|
|
8529
|
-
|
|
9626
|
+
setExitCode(getFulltextExitCode(result));
|
|
8530
9627
|
} catch (error) {
|
|
8531
9628
|
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
8532
9629
|
`);
|
|
8533
|
-
|
|
9630
|
+
setExitCode(ExitCode.INTERNAL_ERROR);
|
|
8534
9631
|
}
|
|
8535
9632
|
}
|
|
8536
9633
|
function formatAuthor(author) {
|
|
@@ -8624,21 +9721,28 @@ const VALID_LIST_SORT_FIELDS = /* @__PURE__ */ new Set([
|
|
|
8624
9721
|
"pub"
|
|
8625
9722
|
]);
|
|
8626
9723
|
function getOutputFormat$1(options) {
|
|
9724
|
+
if (options.output) {
|
|
9725
|
+
if (options.output === "ids") return "ids-only";
|
|
9726
|
+
return options.output;
|
|
9727
|
+
}
|
|
8627
9728
|
if (options.json) return "json";
|
|
8628
9729
|
if (options.idsOnly) return "ids-only";
|
|
8629
|
-
if (options.
|
|
9730
|
+
if (options.uuidOnly) return "uuid";
|
|
8630
9731
|
if (options.bibtex) return "bibtex";
|
|
8631
9732
|
return "pretty";
|
|
8632
9733
|
}
|
|
8633
9734
|
function validateOptions$1(options) {
|
|
8634
|
-
const outputOptions = [options.json, options.idsOnly, options.
|
|
9735
|
+
const outputOptions = [options.json, options.idsOnly, options.uuidOnly, options.bibtex].filter(
|
|
8635
9736
|
Boolean
|
|
8636
9737
|
);
|
|
8637
9738
|
if (outputOptions.length > 1) {
|
|
8638
9739
|
throw new Error(
|
|
8639
|
-
"Multiple output formats specified. Only one of --json, --ids-only, --uuid, --bibtex can be used."
|
|
9740
|
+
"Multiple output formats specified. Only one of --json, --ids-only, --uuid-only, --bibtex can be used."
|
|
8640
9741
|
);
|
|
8641
9742
|
}
|
|
9743
|
+
if (options.output && outputOptions.length > 0) {
|
|
9744
|
+
throw new Error("Cannot combine --output with convenience flags (--json, --ids-only, etc.)");
|
|
9745
|
+
}
|
|
8642
9746
|
if (options.sort !== void 0) {
|
|
8643
9747
|
const sortStr = String(options.sort);
|
|
8644
9748
|
if (!VALID_LIST_SORT_FIELDS.has(sortStr)) {
|
|
@@ -29050,7 +30154,7 @@ function registerFulltextAttachTool(server, getLibraryOperations, getConfig) {
|
|
|
29050
30154
|
filePath: args.path,
|
|
29051
30155
|
force: true,
|
|
29052
30156
|
// MCP tools don't support interactive confirmation
|
|
29053
|
-
fulltextDirectory: config2.
|
|
30157
|
+
fulltextDirectory: config2.attachments.directory
|
|
29054
30158
|
});
|
|
29055
30159
|
if (!result.success) {
|
|
29056
30160
|
return {
|
|
@@ -29083,7 +30187,7 @@ function registerFulltextGetTool(server, getLibraryOperations, getConfig) {
|
|
|
29083
30187
|
const config2 = getConfig();
|
|
29084
30188
|
const pathResult = await fulltextGet(libraryOps, {
|
|
29085
30189
|
identifier: args.id,
|
|
29086
|
-
fulltextDirectory: config2.
|
|
30190
|
+
fulltextDirectory: config2.attachments.directory
|
|
29087
30191
|
});
|
|
29088
30192
|
if (!pathResult.success) {
|
|
29089
30193
|
return {
|
|
@@ -29097,7 +30201,7 @@ function registerFulltextGetTool(server, getLibraryOperations, getConfig) {
|
|
|
29097
30201
|
identifier: args.id,
|
|
29098
30202
|
type: "markdown",
|
|
29099
30203
|
stdout: true,
|
|
29100
|
-
fulltextDirectory: config2.
|
|
30204
|
+
fulltextDirectory: config2.attachments.directory
|
|
29101
30205
|
});
|
|
29102
30206
|
if (contentResult.success && contentResult.content) {
|
|
29103
30207
|
responses.push({
|
|
@@ -29136,7 +30240,7 @@ function registerFulltextDetachTool(server, getLibraryOperations, getConfig) {
|
|
|
29136
30240
|
const config2 = getConfig();
|
|
29137
30241
|
const result = await fulltextDetach(libraryOps, {
|
|
29138
30242
|
identifier: args.id,
|
|
29139
|
-
fulltextDirectory: config2.
|
|
30243
|
+
fulltextDirectory: config2.attachments.directory
|
|
29140
30244
|
});
|
|
29141
30245
|
if (!result.success) {
|
|
29142
30246
|
return {
|
|
@@ -29342,7 +30446,7 @@ async function mcpStart(options) {
|
|
|
29342
30446
|
async function executeRemove(options, context) {
|
|
29343
30447
|
const { identifier, idType = "id", fulltextDirectory, deleteFulltext = false } = options;
|
|
29344
30448
|
if (context.mode === "local" && deleteFulltext && fulltextDirectory) {
|
|
29345
|
-
const { removeReference } = await import("./index-
|
|
30449
|
+
const { removeReference } = await import("./index-DHgeuWGP.js").then((n) => n.r);
|
|
29346
30450
|
return removeReference(context.library, {
|
|
29347
30451
|
identifier,
|
|
29348
30452
|
idType,
|
|
@@ -29396,12 +30500,12 @@ Continue?`;
|
|
|
29396
30500
|
return readConfirmation(confirmMsg);
|
|
29397
30501
|
}
|
|
29398
30502
|
async function executeInteractiveRemove(context, config2) {
|
|
29399
|
-
const { selectReferenceItemsOrExit } = await import("./reference-select-
|
|
30503
|
+
const { selectReferenceItemsOrExit } = await import("./reference-select-B9w9CLa1.js");
|
|
29400
30504
|
const allReferences = await context.library.getAll();
|
|
29401
30505
|
const selectedItems = await selectReferenceItemsOrExit(
|
|
29402
30506
|
allReferences,
|
|
29403
30507
|
{ multiSelect: false },
|
|
29404
|
-
config2.cli.
|
|
30508
|
+
config2.cli.tui
|
|
29405
30509
|
);
|
|
29406
30510
|
const selectedItem = selectedItems[0];
|
|
29407
30511
|
return { identifier: selectedItem.id, item: selectedItem };
|
|
@@ -29452,7 +30556,7 @@ function handleRemoveError(error, identifierArg, outputFormat) {
|
|
|
29452
30556
|
`);
|
|
29453
30557
|
}
|
|
29454
30558
|
const isUserError = message.includes("not found") || message.includes("No identifier");
|
|
29455
|
-
|
|
30559
|
+
setExitCode(isUserError ? ExitCode.ERROR : ExitCode.INTERNAL_ERROR);
|
|
29456
30560
|
}
|
|
29457
30561
|
async function handleRemoveAction(identifierArg, options, globalOpts) {
|
|
29458
30562
|
const { formatRemoveJsonOutput: formatRemoveJsonOutput2 } = await Promise.resolve().then(() => jsonOutput);
|
|
@@ -29473,22 +30577,24 @@ async function handleRemoveAction(identifierArg, options, globalOpts) {
|
|
|
29473
30577
|
if (hasFulltext && !isTTY() && !force) {
|
|
29474
30578
|
process.stderr.write(`Error: ${formatFulltextWarning(fulltextTypes)}
|
|
29475
30579
|
`);
|
|
29476
|
-
|
|
30580
|
+
setExitCode(ExitCode.ERROR);
|
|
30581
|
+
return;
|
|
29477
30582
|
}
|
|
29478
30583
|
const confirmed = await confirmRemoveIfNeeded(refToRemove, hasFulltext, force);
|
|
29479
30584
|
if (!confirmed) {
|
|
29480
30585
|
process.stderr.write("Cancelled.\n");
|
|
29481
|
-
|
|
30586
|
+
setExitCode(2);
|
|
30587
|
+
return;
|
|
29482
30588
|
}
|
|
29483
30589
|
const removeOptions = {
|
|
29484
30590
|
identifier,
|
|
29485
30591
|
idType: useUuid ? "uuid" : "id",
|
|
29486
|
-
fulltextDirectory: config2.
|
|
30592
|
+
fulltextDirectory: config2.attachments.directory,
|
|
29487
30593
|
deleteFulltext: force && hasFulltext
|
|
29488
30594
|
};
|
|
29489
30595
|
const result = await executeRemove(removeOptions, context);
|
|
29490
30596
|
outputResult(result, identifier, outputFormat, options.full, formatRemoveJsonOutput2);
|
|
29491
|
-
|
|
30597
|
+
setExitCode(result.removed ? ExitCode.SUCCESS : ExitCode.ERROR);
|
|
29492
30598
|
} catch (error) {
|
|
29493
30599
|
handleRemoveError(error, identifierArg, outputFormat);
|
|
29494
30600
|
}
|
|
@@ -29506,21 +30612,28 @@ const VALID_SEARCH_SORT_FIELDS = /* @__PURE__ */ new Set([
|
|
|
29506
30612
|
"rel"
|
|
29507
30613
|
]);
|
|
29508
30614
|
function getOutputFormat(options) {
|
|
30615
|
+
if (options.output) {
|
|
30616
|
+
if (options.output === "ids") return "ids-only";
|
|
30617
|
+
return options.output;
|
|
30618
|
+
}
|
|
29509
30619
|
if (options.json) return "json";
|
|
29510
30620
|
if (options.idsOnly) return "ids-only";
|
|
29511
|
-
if (options.
|
|
30621
|
+
if (options.uuidOnly) return "uuid";
|
|
29512
30622
|
if (options.bibtex) return "bibtex";
|
|
29513
30623
|
return "pretty";
|
|
29514
30624
|
}
|
|
29515
30625
|
function validateOptions(options) {
|
|
29516
|
-
const outputOptions = [options.json, options.idsOnly, options.
|
|
30626
|
+
const outputOptions = [options.json, options.idsOnly, options.uuidOnly, options.bibtex].filter(
|
|
29517
30627
|
Boolean
|
|
29518
30628
|
);
|
|
29519
30629
|
if (outputOptions.length > 1) {
|
|
29520
30630
|
throw new Error(
|
|
29521
|
-
"Multiple output formats specified. Only one of --json, --ids-only, --uuid, --bibtex can be used."
|
|
30631
|
+
"Multiple output formats specified. Only one of --json, --ids-only, --uuid-only, --bibtex can be used."
|
|
29522
30632
|
);
|
|
29523
30633
|
}
|
|
30634
|
+
if (options.output && outputOptions.length > 0) {
|
|
30635
|
+
throw new Error("Cannot combine --output with convenience flags (--json, --ids-only, etc.)");
|
|
30636
|
+
}
|
|
29524
30637
|
if (options.sort !== void 0) {
|
|
29525
30638
|
const sortStr = String(options.sort);
|
|
29526
30639
|
if (!VALID_SEARCH_SORT_FIELDS.has(sortStr)) {
|
|
@@ -29576,35 +30689,39 @@ function formatSearchOutput(result, options) {
|
|
|
29576
30689
|
return lines.join("\n");
|
|
29577
30690
|
}
|
|
29578
30691
|
function validateInteractiveOptions(options) {
|
|
29579
|
-
const outputOptions = [
|
|
29580
|
-
|
|
29581
|
-
|
|
30692
|
+
const outputOptions = [
|
|
30693
|
+
options.output,
|
|
30694
|
+
options.json,
|
|
30695
|
+
options.idsOnly,
|
|
30696
|
+
options.uuidOnly,
|
|
30697
|
+
options.bibtex
|
|
30698
|
+
].filter(Boolean);
|
|
29582
30699
|
if (outputOptions.length > 0) {
|
|
29583
30700
|
throw new Error(
|
|
29584
|
-
"
|
|
30701
|
+
"TUI mode cannot be combined with output format options (--output, --json, --ids-only, --uuid-only, --bibtex)"
|
|
29585
30702
|
);
|
|
29586
30703
|
}
|
|
29587
30704
|
}
|
|
29588
30705
|
async function executeInteractiveSearch(options, context, config2) {
|
|
29589
30706
|
validateInteractiveOptions(options);
|
|
29590
|
-
const { checkTTY } = await import("./tty-
|
|
30707
|
+
const { checkTTY } = await import("./tty-BMyaEOhX.js");
|
|
29591
30708
|
const { runSearchPrompt } = await import("./search-prompt-BrWpOcij.js");
|
|
29592
|
-
const { runActionMenu } = await import("./action-menu-
|
|
29593
|
-
const { search } = await import("./file-watcher-
|
|
29594
|
-
const { tokenize } = await import("./file-watcher-
|
|
30709
|
+
const { runActionMenu } = await import("./action-menu-DwCcc6Gt.js");
|
|
30710
|
+
const { search } = await import("./file-watcher-B_WpVHSV.js").then((n) => n.y);
|
|
30711
|
+
const { tokenize } = await import("./file-watcher-B_WpVHSV.js").then((n) => n.x);
|
|
29595
30712
|
checkTTY();
|
|
29596
30713
|
const allReferences = await context.library.getAll();
|
|
29597
30714
|
const searchFn = (query) => {
|
|
29598
30715
|
const { tokens } = tokenize(query);
|
|
29599
30716
|
return search(allReferences, tokens);
|
|
29600
30717
|
};
|
|
29601
|
-
const
|
|
30718
|
+
const tuiConfig = config2.cli.tui;
|
|
29602
30719
|
const searchResult = await runSearchPrompt(
|
|
29603
30720
|
allReferences,
|
|
29604
30721
|
searchFn,
|
|
29605
30722
|
{
|
|
29606
|
-
limit:
|
|
29607
|
-
debounceMs:
|
|
30723
|
+
limit: tuiConfig.limit,
|
|
30724
|
+
debounceMs: tuiConfig.debounceMs
|
|
29608
30725
|
},
|
|
29609
30726
|
options.query || ""
|
|
29610
30727
|
);
|
|
@@ -29667,7 +30784,7 @@ async function startServerForeground(options) {
|
|
|
29667
30784
|
server.close();
|
|
29668
30785
|
await dispose();
|
|
29669
30786
|
await removePortfile(options.portfilePath);
|
|
29670
|
-
|
|
30787
|
+
setExitCode(ExitCode.SUCCESS);
|
|
29671
30788
|
};
|
|
29672
30789
|
process.on("SIGINT", cleanup);
|
|
29673
30790
|
process.on("SIGTERM", cleanup);
|
|
@@ -29897,12 +31014,12 @@ function formatUpdateOutput(result, identifier) {
|
|
|
29897
31014
|
return parts.join("\n");
|
|
29898
31015
|
}
|
|
29899
31016
|
async function executeInteractiveUpdate(context, config2) {
|
|
29900
|
-
const { selectReferencesOrExit } = await import("./reference-select-
|
|
31017
|
+
const { selectReferencesOrExit } = await import("./reference-select-B9w9CLa1.js");
|
|
29901
31018
|
const allReferences = await context.library.getAll();
|
|
29902
31019
|
const identifiers = await selectReferencesOrExit(
|
|
29903
31020
|
allReferences,
|
|
29904
31021
|
{ multiSelect: false },
|
|
29905
|
-
config2.cli.
|
|
31022
|
+
config2.cli.tui
|
|
29906
31023
|
);
|
|
29907
31024
|
return identifiers[0];
|
|
29908
31025
|
}
|
|
@@ -29919,14 +31036,16 @@ async function resolveUpdateIdentifier(identifierArg, hasSetOptions, context, co
|
|
|
29919
31036
|
process.stderr.write(
|
|
29920
31037
|
"Error: No identifier provided. Provide an ID, pipe one via stdin, or run interactively in a TTY.\n"
|
|
29921
31038
|
);
|
|
29922
|
-
|
|
31039
|
+
setExitCode(ExitCode.ERROR);
|
|
31040
|
+
return "";
|
|
29923
31041
|
}
|
|
29924
31042
|
return stdinId;
|
|
29925
31043
|
}
|
|
29926
31044
|
process.stderr.write(
|
|
29927
31045
|
"Error: No identifier provided. When using stdin for JSON input, identifier must be provided as argument.\n"
|
|
29928
31046
|
);
|
|
29929
|
-
|
|
31047
|
+
setExitCode(ExitCode.ERROR);
|
|
31048
|
+
return "";
|
|
29930
31049
|
}
|
|
29931
31050
|
function parseUpdateInput(setOptions, file) {
|
|
29932
31051
|
if (setOptions && setOptions.length > 0 && file) {
|
|
@@ -29947,23 +31066,25 @@ function handleUpdateError(error) {
|
|
|
29947
31066
|
if (message.includes("Parse error")) {
|
|
29948
31067
|
process.stderr.write(`Error: ${message}
|
|
29949
31068
|
`);
|
|
29950
|
-
|
|
31069
|
+
setExitCode(3);
|
|
29951
31070
|
}
|
|
29952
31071
|
if (message.includes("not found") || message.includes("validation")) {
|
|
29953
31072
|
process.stderr.write(`Error: ${message}
|
|
29954
31073
|
`);
|
|
29955
|
-
|
|
31074
|
+
setExitCode(ExitCode.ERROR);
|
|
29956
31075
|
}
|
|
29957
31076
|
process.stderr.write(`Error: ${message}
|
|
29958
31077
|
`);
|
|
29959
|
-
|
|
31078
|
+
setExitCode(ExitCode.INTERNAL_ERROR);
|
|
29960
31079
|
}
|
|
29961
31080
|
function handleUpdateErrorWithFormat(error, identifier, outputFormat) {
|
|
29962
31081
|
const message = error instanceof Error ? error.message : String(error);
|
|
29963
31082
|
if (outputFormat === "json") {
|
|
29964
31083
|
process.stdout.write(`${JSON.stringify({ success: false, id: identifier, error: message })}
|
|
29965
31084
|
`);
|
|
29966
|
-
|
|
31085
|
+
setExitCode(
|
|
31086
|
+
message.includes("not found") || message.includes("validation") ? ExitCode.ERROR : ExitCode.INTERNAL_ERROR
|
|
31087
|
+
);
|
|
29967
31088
|
}
|
|
29968
31089
|
handleUpdateError(error);
|
|
29969
31090
|
}
|
|
@@ -29997,7 +31118,7 @@ async function handleUpdateAction(identifierArg, file, options, globalOpts) {
|
|
|
29997
31118
|
process.stderr.write(`${output}
|
|
29998
31119
|
`);
|
|
29999
31120
|
}
|
|
30000
|
-
|
|
31121
|
+
setExitCode(result.updated ? ExitCode.SUCCESS : ExitCode.ERROR);
|
|
30001
31122
|
} catch (error) {
|
|
30002
31123
|
handleUpdateErrorWithFormat(error, identifierArg ?? "", outputFormat);
|
|
30003
31124
|
}
|
|
@@ -30007,8 +31128,13 @@ function collectSetOption(value, previous) {
|
|
|
30007
31128
|
}
|
|
30008
31129
|
const SEARCH_SORT_FIELDS = searchSortFieldSchema.options;
|
|
30009
31130
|
const SORT_ORDERS = sortOrderSchema.options;
|
|
30010
|
-
const
|
|
31131
|
+
const CITATION_OUTPUT_FORMATS = ["text", "html", "rtf"];
|
|
31132
|
+
const EXPORT_OUTPUT_FORMATS = ["json", "yaml", "bibtex"];
|
|
31133
|
+
const LIST_OUTPUT_FORMATS = ["pretty", "json", "bibtex", "ids", "uuid"];
|
|
31134
|
+
const MUTATION_OUTPUT_FORMATS = ["json", "text"];
|
|
31135
|
+
const CONFIG_OUTPUT_FORMATS = ["text", "json"];
|
|
30011
31136
|
const LOG_LEVELS = ["silent", "info", "debug"];
|
|
31137
|
+
const ADD_INPUT_FORMATS = ["json", "bibtex", "ris", "pmid", "doi", "isbn", "auto"];
|
|
30012
31138
|
const CONFIG_SECTIONS = [
|
|
30013
31139
|
"backup",
|
|
30014
31140
|
"citation",
|
|
@@ -30019,17 +31145,44 @@ const CONFIG_SECTIONS = [
|
|
|
30019
31145
|
"server",
|
|
30020
31146
|
"watch"
|
|
30021
31147
|
];
|
|
31148
|
+
const ATTACHMENT_ROLES = ["fulltext", "supplement", "notes", "draft"];
|
|
30022
31149
|
const OPTION_VALUES = {
|
|
30023
31150
|
"--sort": SEARCH_SORT_FIELDS,
|
|
30024
31151
|
// search includes 'relevance'
|
|
30025
31152
|
"--order": SORT_ORDERS,
|
|
30026
|
-
"--format": CITATION_FORMATS,
|
|
30027
31153
|
"--style": BUILTIN_STYLES,
|
|
30028
31154
|
"--log-level": LOG_LEVELS,
|
|
30029
|
-
"--section": CONFIG_SECTIONS
|
|
31155
|
+
"--section": CONFIG_SECTIONS,
|
|
31156
|
+
"--input": ADD_INPUT_FORMATS,
|
|
31157
|
+
"-i": ADD_INPUT_FORMATS,
|
|
31158
|
+
"--role": ATTACHMENT_ROLES,
|
|
31159
|
+
"-r": ATTACHMENT_ROLES
|
|
30030
31160
|
};
|
|
31161
|
+
const OUTPUT_VALUES_BY_COMMAND = {
|
|
31162
|
+
cite: CITATION_OUTPUT_FORMATS,
|
|
31163
|
+
export: EXPORT_OUTPUT_FORMATS,
|
|
31164
|
+
list: LIST_OUTPUT_FORMATS,
|
|
31165
|
+
search: LIST_OUTPUT_FORMATS,
|
|
31166
|
+
add: MUTATION_OUTPUT_FORMATS,
|
|
31167
|
+
remove: MUTATION_OUTPUT_FORMATS,
|
|
31168
|
+
update: MUTATION_OUTPUT_FORMATS
|
|
31169
|
+
// config show uses CONFIG_OUTPUT_FORMATS, handled specially
|
|
31170
|
+
};
|
|
31171
|
+
function getOptionValuesForCommand(option, command, subcommand) {
|
|
31172
|
+
if (option === "--output" || option === "-o") {
|
|
31173
|
+
if (command === "config" && subcommand === "show") {
|
|
31174
|
+
return CONFIG_OUTPUT_FORMATS;
|
|
31175
|
+
}
|
|
31176
|
+
if (command && OUTPUT_VALUES_BY_COMMAND[command]) {
|
|
31177
|
+
return OUTPUT_VALUES_BY_COMMAND[command];
|
|
31178
|
+
}
|
|
31179
|
+
return CITATION_OUTPUT_FORMATS;
|
|
31180
|
+
}
|
|
31181
|
+
return OPTION_VALUES[option];
|
|
31182
|
+
}
|
|
30031
31183
|
const ID_COMPLETION_COMMANDS = /* @__PURE__ */ new Set(["cite", "remove", "update"]);
|
|
30032
31184
|
const ID_COMPLETION_FULLTEXT_SUBCOMMANDS = /* @__PURE__ */ new Set(["attach", "get", "detach", "open"]);
|
|
31185
|
+
const ID_COMPLETION_ATTACH_SUBCOMMANDS = /* @__PURE__ */ new Set(["open", "add", "list", "get", "detach", "sync"]);
|
|
30033
31186
|
function toCompletionItems(values) {
|
|
30034
31187
|
return values.map((name2) => ({ name: name2 }));
|
|
30035
31188
|
}
|
|
@@ -30063,6 +31216,13 @@ function extractGlobalOptions(program) {
|
|
|
30063
31216
|
function findSubcommand(program, name2) {
|
|
30064
31217
|
return program.commands.find((cmd) => cmd.name() === name2);
|
|
30065
31218
|
}
|
|
31219
|
+
function parseCommandContext(env) {
|
|
31220
|
+
const words = env.line.trim().split(/\s+/);
|
|
31221
|
+
const args = words.slice(1);
|
|
31222
|
+
const command = args[0];
|
|
31223
|
+
const subcommand = args.length >= 2 ? args[1] : void 0;
|
|
31224
|
+
return { command, subcommand };
|
|
31225
|
+
}
|
|
30066
31226
|
function getCompletions(env, program) {
|
|
30067
31227
|
const { line, prev, last } = env;
|
|
30068
31228
|
const words = line.trim().split(/\s+/);
|
|
@@ -30074,7 +31234,8 @@ function getCompletions(env, program) {
|
|
|
30074
31234
|
}
|
|
30075
31235
|
const firstArg = args[0] ?? "";
|
|
30076
31236
|
if (prev?.startsWith("-")) {
|
|
30077
|
-
const
|
|
31237
|
+
const { command, subcommand } = parseCommandContext(env);
|
|
31238
|
+
const optionValues = getOptionValuesForCommand(prev, command, subcommand);
|
|
30078
31239
|
if (optionValues) {
|
|
30079
31240
|
return toCompletionItems(optionValues);
|
|
30080
31241
|
}
|
|
@@ -30114,6 +31275,13 @@ function needsIdCompletion(env) {
|
|
|
30114
31275
|
}
|
|
30115
31276
|
return { needs: false };
|
|
30116
31277
|
}
|
|
31278
|
+
if (command === "attach" && args.length >= 2) {
|
|
31279
|
+
const subcommand = args[1] ?? "";
|
|
31280
|
+
if (ID_COMPLETION_ATTACH_SUBCOMMANDS.has(subcommand)) {
|
|
31281
|
+
return { needs: true, command, subcommand };
|
|
31282
|
+
}
|
|
31283
|
+
return { needs: false };
|
|
31284
|
+
}
|
|
30117
31285
|
if (ID_COMPLETION_COMMANDS.has(command)) {
|
|
30118
31286
|
return { needs: true, command };
|
|
30119
31287
|
}
|
|
@@ -30235,7 +31403,7 @@ function registerCompletionCommand(program) {
|
|
|
30235
31403
|
} else {
|
|
30236
31404
|
console.error(`Unknown action: ${action}`);
|
|
30237
31405
|
console.error("Usage: ref completion [install|uninstall]");
|
|
30238
|
-
|
|
31406
|
+
setExitCode(ExitCode.ERROR);
|
|
30239
31407
|
}
|
|
30240
31408
|
});
|
|
30241
31409
|
}
|
|
@@ -30253,6 +31421,7 @@ function createProgram() {
|
|
|
30253
31421
|
registerCiteCommand(program);
|
|
30254
31422
|
registerServerCommand(program);
|
|
30255
31423
|
registerFulltextCommand(program);
|
|
31424
|
+
registerAttachCommand(program);
|
|
30256
31425
|
registerMcpCommand(program);
|
|
30257
31426
|
registerConfigCommand(program);
|
|
30258
31427
|
registerCompletionCommand(program);
|
|
@@ -30269,15 +31438,13 @@ async function handleListAction(options, program) {
|
|
|
30269
31438
|
process.stdout.write(`${output}
|
|
30270
31439
|
`);
|
|
30271
31440
|
}
|
|
30272
|
-
|
|
31441
|
+
setExitCode(ExitCode.SUCCESS);
|
|
30273
31442
|
} catch (error) {
|
|
30274
|
-
|
|
30275
|
-
`);
|
|
30276
|
-
process.exit(4);
|
|
31443
|
+
exitWithError(error instanceof Error ? error.message : String(error), ExitCode.INTERNAL_ERROR);
|
|
30277
31444
|
}
|
|
30278
31445
|
}
|
|
30279
31446
|
function registerListCommand(program) {
|
|
30280
|
-
program.command("list").description("List all references in the library").option("--
|
|
31447
|
+
program.command("list").description("List all references in the library").option("-o, --output <format>", "Output format: pretty|json|bibtex|ids|uuid").option("--json", "Alias for --output json").option("--bibtex", "Alias for --output bibtex").option("--ids-only", "Alias for --output ids").option("--uuid-only", "Alias for --output uuid").option("--sort <field>", "Sort by field: created|updated|published|author|title").option("--order <order>", "Sort order: asc|desc").option("-n, --limit <n>", "Maximum number of results", Number.parseInt).option("--offset <n>", "Number of results to skip", Number.parseInt).action(async (options) => {
|
|
30281
31448
|
await handleListAction(options, program);
|
|
30282
31449
|
});
|
|
30283
31450
|
}
|
|
@@ -30298,15 +31465,15 @@ async function handleExportAction(ids, options, program) {
|
|
|
30298
31465
|
`);
|
|
30299
31466
|
}
|
|
30300
31467
|
}
|
|
30301
|
-
|
|
31468
|
+
setExitCode(getExportExitCode(result));
|
|
30302
31469
|
} catch (error) {
|
|
30303
31470
|
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
30304
31471
|
`);
|
|
30305
|
-
|
|
31472
|
+
setExitCode(ExitCode.INTERNAL_ERROR);
|
|
30306
31473
|
}
|
|
30307
31474
|
}
|
|
30308
31475
|
function registerExportCommand(program) {
|
|
30309
|
-
program.command("export [ids...]").description("Export raw CSL-JSON for external tool integration").option("--uuid", "Interpret identifiers as UUIDs").option("--all", "Export all references").option("--search <query>", "Export references matching search query").option("-
|
|
31476
|
+
program.command("export [ids...]").description("Export raw CSL-JSON for external tool integration").option("--uuid", "Interpret identifiers as UUIDs").option("--all", "Export all references").option("--search <query>", "Export references matching search query").option("-o, --output <format>", "Output format: json (default), yaml, bibtex").action(async (ids, options) => {
|
|
30310
31477
|
await handleExportAction(ids, options, program);
|
|
30311
31478
|
});
|
|
30312
31479
|
}
|
|
@@ -30315,13 +31482,13 @@ async function handleSearchAction(query, options, program) {
|
|
|
30315
31482
|
const globalOpts = program.opts();
|
|
30316
31483
|
const config2 = await loadConfigWithOverrides({ ...globalOpts, ...options });
|
|
30317
31484
|
const context = await createExecutionContext(config2, Library.load);
|
|
30318
|
-
if (options.
|
|
31485
|
+
if (options.tui) {
|
|
30319
31486
|
const result2 = await executeInteractiveSearch({ ...options, query }, context, config2);
|
|
30320
31487
|
if (result2.output) {
|
|
30321
31488
|
process.stdout.write(`${result2.output}
|
|
30322
31489
|
`);
|
|
30323
31490
|
}
|
|
30324
|
-
|
|
31491
|
+
setExitCode(ExitCode.SUCCESS);
|
|
30325
31492
|
}
|
|
30326
31493
|
const result = await executeSearch({ ...options, query }, context);
|
|
30327
31494
|
const output = formatSearchOutput(result, { ...options, query });
|
|
@@ -30329,18 +31496,19 @@ async function handleSearchAction(query, options, program) {
|
|
|
30329
31496
|
process.stdout.write(`${output}
|
|
30330
31497
|
`);
|
|
30331
31498
|
}
|
|
30332
|
-
|
|
31499
|
+
setExitCode(ExitCode.SUCCESS);
|
|
30333
31500
|
} catch (error) {
|
|
30334
31501
|
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
30335
31502
|
`);
|
|
30336
|
-
|
|
31503
|
+
setExitCode(ExitCode.INTERNAL_ERROR);
|
|
30337
31504
|
}
|
|
30338
31505
|
}
|
|
30339
31506
|
function registerSearchCommand(program) {
|
|
30340
|
-
program.command("search").description("Search references").argument("[query]", "Search query (required unless using --
|
|
30341
|
-
if (!options.
|
|
30342
|
-
process.stderr.write("Error: Search query is required unless using --
|
|
30343
|
-
|
|
31507
|
+
program.command("search").description("Search references").argument("[query]", "Search query (required unless using --tui)").option("-t, --tui", "Enable TUI (interactive) search mode").option("-o, --output <format>", "Output format: pretty|json|bibtex|ids|uuid").option("--json", "Alias for --output json").option("--bibtex", "Alias for --output bibtex").option("--ids-only", "Alias for --output ids").option("--uuid-only", "Alias for --output uuid").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) => {
|
|
31508
|
+
if (!options.tui && !query) {
|
|
31509
|
+
process.stderr.write("Error: Search query is required unless using --tui\n");
|
|
31510
|
+
setExitCode(ExitCode.ERROR);
|
|
31511
|
+
return;
|
|
30344
31512
|
}
|
|
30345
31513
|
await handleSearchAction(query ?? "", options, program);
|
|
30346
31514
|
});
|
|
@@ -30350,8 +31518,8 @@ function buildAddOptions(inputs, options, config2, stdinContent) {
|
|
|
30350
31518
|
inputs,
|
|
30351
31519
|
force: options.force ?? false
|
|
30352
31520
|
};
|
|
30353
|
-
if (options.
|
|
30354
|
-
addOptions.format = options.
|
|
31521
|
+
if (options.input !== void 0) {
|
|
31522
|
+
addOptions.format = options.input;
|
|
30355
31523
|
}
|
|
30356
31524
|
if (options.verbose !== void 0) {
|
|
30357
31525
|
addOptions.verbose = options.verbose;
|
|
@@ -30405,7 +31573,7 @@ async function handleAddAction(inputs, options, program) {
|
|
|
30405
31573
|
process.stderr.write(`${output}
|
|
30406
31574
|
`);
|
|
30407
31575
|
}
|
|
30408
|
-
|
|
31576
|
+
setExitCode(getExitCode(result));
|
|
30409
31577
|
} catch (error) {
|
|
30410
31578
|
const message = error instanceof Error ? error.message : String(error);
|
|
30411
31579
|
if (outputFormat === "json") {
|
|
@@ -30415,15 +31583,11 @@ async function handleAddAction(inputs, options, program) {
|
|
|
30415
31583
|
process.stderr.write(`Error: ${message}
|
|
30416
31584
|
`);
|
|
30417
31585
|
}
|
|
30418
|
-
|
|
31586
|
+
setExitCode(ExitCode.ERROR);
|
|
30419
31587
|
}
|
|
30420
31588
|
}
|
|
30421
31589
|
function registerAddCommand(program) {
|
|
30422
|
-
program.command("add").description("Add new reference(s) to the library").argument("[input...]", "File paths or identifiers (PMID/DOI/ISBN), or use stdin").option("-f, --force", "Skip duplicate detection").option(
|
|
30423
|
-
"--format <format>",
|
|
30424
|
-
"Explicit input format: json|bibtex|ris|pmid|doi|isbn|auto",
|
|
30425
|
-
"auto"
|
|
30426
|
-
).option("--verbose", "Show detailed error information").option("-o, --output <format>", "Output format: json|text", "text").option("--full", "Include full CSL-JSON data in JSON output").action(async (inputs, options) => {
|
|
31590
|
+
program.command("add").description("Add new reference(s) to the library").argument("[input...]", "File paths or identifiers (PMID/DOI/ISBN), or use stdin").option("-f, --force", "Skip duplicate detection").option("-i, --input <format>", "Input format: json|bibtex|ris|pmid|doi|isbn|auto", "auto").option("--verbose", "Show detailed error information").option("-o, --output <format>", "Output format: json|text", "text").option("--full", "Include full CSL-JSON data in JSON output").action(async (inputs, options) => {
|
|
30427
31591
|
await handleAddAction(inputs, options, program);
|
|
30428
31592
|
});
|
|
30429
31593
|
}
|
|
@@ -30441,7 +31605,7 @@ function registerEditCommand(program) {
|
|
|
30441
31605
|
program.command("edit").description("Edit references interactively using an external editor").argument(
|
|
30442
31606
|
"[identifier...]",
|
|
30443
31607
|
"Citation keys or UUIDs to edit (interactive selection if omitted)"
|
|
30444
|
-
).option("--uuid", "Interpret identifiers as UUIDs").option("
|
|
31608
|
+
).option("--uuid", "Interpret identifiers as UUIDs").option("--format <format>", "Edit format: yaml (default), json").option("--editor <editor>", "Editor command (overrides $VISUAL/$EDITOR)").action(async (identifiers, options) => {
|
|
30445
31609
|
await handleEditAction(identifiers, options, program.opts());
|
|
30446
31610
|
});
|
|
30447
31611
|
}
|
|
@@ -30449,7 +31613,7 @@ function registerCiteCommand(program) {
|
|
|
30449
31613
|
program.command("cite").description("Generate formatted citations for references").argument(
|
|
30450
31614
|
"[id-or-uuid...]",
|
|
30451
31615
|
"Citation keys or UUIDs to cite (interactive selection if omitted)"
|
|
30452
|
-
).option("--uuid", "Treat arguments as UUIDs instead of IDs").option("--style <style>", "CSL style name").option("--csl-file <path>", "Path to custom CSL file").option("--locale <locale>", "Locale code (e.g., en-US, ja-JP)").option("--
|
|
31616
|
+
).option("--uuid", "Treat arguments as UUIDs instead of IDs").option("--style <style>", "CSL style name").option("--csl-file <path>", "Path to custom CSL file").option("--locale <locale>", "Locale code (e.g., en-US, ja-JP)").option("-o, --output <format>", "Output format: text|html|rtf").option("--in-text", "Generate in-text citations instead of bibliography entries").action(async (identifiers, options) => {
|
|
30453
31617
|
await handleCiteAction(identifiers, options, program.opts());
|
|
30454
31618
|
});
|
|
30455
31619
|
}
|
|
@@ -30469,18 +31633,18 @@ function registerServerCommand(program) {
|
|
|
30469
31633
|
};
|
|
30470
31634
|
await serverStart(startOptions);
|
|
30471
31635
|
if (options.daemon) {
|
|
30472
|
-
|
|
31636
|
+
setExitCode(ExitCode.SUCCESS);
|
|
30473
31637
|
}
|
|
30474
31638
|
} catch (error) {
|
|
30475
31639
|
const message = error instanceof Error ? error.message : String(error);
|
|
30476
31640
|
if (message.includes("already running") || message.includes("conflict")) {
|
|
30477
31641
|
process.stderr.write(`Error: ${message}
|
|
30478
31642
|
`);
|
|
30479
|
-
|
|
31643
|
+
setExitCode(ExitCode.ERROR);
|
|
30480
31644
|
}
|
|
30481
31645
|
process.stderr.write(`Error: ${message}
|
|
30482
31646
|
`);
|
|
30483
|
-
|
|
31647
|
+
setExitCode(ExitCode.INTERNAL_ERROR);
|
|
30484
31648
|
}
|
|
30485
31649
|
});
|
|
30486
31650
|
serverCmd.command("stop").description("Stop running server").action(async () => {
|
|
@@ -30488,17 +31652,17 @@ function registerServerCommand(program) {
|
|
|
30488
31652
|
const portfilePath = getPortfilePath();
|
|
30489
31653
|
await serverStop(portfilePath);
|
|
30490
31654
|
process.stderr.write("Server stopped.\n");
|
|
30491
|
-
|
|
31655
|
+
setExitCode(ExitCode.SUCCESS);
|
|
30492
31656
|
} catch (error) {
|
|
30493
31657
|
const message = error instanceof Error ? error.message : String(error);
|
|
30494
31658
|
if (message.includes("not running")) {
|
|
30495
31659
|
process.stderr.write(`Error: ${message}
|
|
30496
31660
|
`);
|
|
30497
|
-
|
|
31661
|
+
setExitCode(ExitCode.ERROR);
|
|
30498
31662
|
}
|
|
30499
31663
|
process.stderr.write(`Error: ${message}
|
|
30500
31664
|
`);
|
|
30501
|
-
|
|
31665
|
+
setExitCode(ExitCode.INTERNAL_ERROR);
|
|
30502
31666
|
}
|
|
30503
31667
|
});
|
|
30504
31668
|
serverCmd.command("status").description("Check server status").action(async () => {
|
|
@@ -30513,15 +31677,15 @@ PID: ${status.pid}
|
|
|
30513
31677
|
Library: ${status.library}
|
|
30514
31678
|
`
|
|
30515
31679
|
);
|
|
30516
|
-
|
|
31680
|
+
setExitCode(ExitCode.SUCCESS);
|
|
30517
31681
|
} else {
|
|
30518
31682
|
process.stdout.write("Server not running\n");
|
|
30519
|
-
|
|
31683
|
+
setExitCode(ExitCode.ERROR);
|
|
30520
31684
|
}
|
|
30521
31685
|
} catch (error) {
|
|
30522
31686
|
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
30523
31687
|
`);
|
|
30524
|
-
|
|
31688
|
+
setExitCode(ExitCode.INTERNAL_ERROR);
|
|
30525
31689
|
}
|
|
30526
31690
|
});
|
|
30527
31691
|
}
|
|
@@ -30550,10 +31714,34 @@ function registerMcpCommand(program) {
|
|
|
30550
31714
|
} catch (error) {
|
|
30551
31715
|
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
|
|
30552
31716
|
`);
|
|
30553
|
-
|
|
31717
|
+
setExitCode(ExitCode.ERROR);
|
|
30554
31718
|
}
|
|
30555
31719
|
});
|
|
30556
31720
|
}
|
|
31721
|
+
function registerAttachCommand(program) {
|
|
31722
|
+
const attachCmd = program.command("attach").description("Manage file attachments for references");
|
|
31723
|
+
attachCmd.command("open").description("Open attachments directory or specific file").argument("[identifier]", "Citation key or UUID (interactive selection if omitted)").argument("[filename]", "Specific file to open").option("--role <role>", "Open file by role").option("--print", "Output path instead of opening").option("--no-sync", "Skip interactive sync prompt").option("--uuid", "Interpret identifier as UUID").action(async (identifier, filename, options) => {
|
|
31724
|
+
await handleAttachOpenAction(identifier, filename, options, program.opts());
|
|
31725
|
+
});
|
|
31726
|
+
attachCmd.command("add").description("Add a file attachment to a reference").argument("[identifier]", "Citation key or UUID (interactive selection if omitted)").argument("<file-path>", "Path to the file to attach").requiredOption(
|
|
31727
|
+
"--role <role>",
|
|
31728
|
+
"Role for the file (fulltext, supplement, notes, draft, or custom)"
|
|
31729
|
+
).option("--label <label>", "Human-readable label").option("--move", "Move file instead of copy").option("-f, --force", "Overwrite existing attachment").option("--uuid", "Interpret identifier as UUID").action(async (identifier, filePath, options) => {
|
|
31730
|
+
await handleAttachAddAction(identifier, filePath, options, program.opts());
|
|
31731
|
+
});
|
|
31732
|
+
attachCmd.command("list").description("List attachments for a reference").argument("[identifier]", "Citation key or UUID (interactive selection if omitted)").option("--role <role>", "Filter by role").option("--uuid", "Interpret identifier as UUID").action(async (identifier, options) => {
|
|
31733
|
+
await handleAttachListAction(identifier, options, program.opts());
|
|
31734
|
+
});
|
|
31735
|
+
attachCmd.command("get").description("Get attachment file path or content").argument("[identifier]", "Citation key or UUID (interactive selection if omitted)").argument("[filename]", "Specific file to get").option("--role <role>", "Get file by role").option("--stdout", "Output file content to stdout").option("--uuid", "Interpret identifier as UUID").action(async (identifier, filename, options) => {
|
|
31736
|
+
await handleAttachGetAction(identifier, filename, options, program.opts());
|
|
31737
|
+
});
|
|
31738
|
+
attachCmd.command("detach").description("Detach file from a reference").argument("[identifier]", "Citation key or UUID (interactive selection if omitted)").argument("[filename]", "Specific file to detach").option("--role <role>", "Detach files by role").option("--all", "Detach all files of specified role").option("--remove-files", "Also delete files from disk").option("--uuid", "Interpret identifier as UUID").action(async (identifier, filename, options) => {
|
|
31739
|
+
await handleAttachDetachAction(identifier, filename, options, program.opts());
|
|
31740
|
+
});
|
|
31741
|
+
attachCmd.command("sync").description("Synchronize metadata with files on disk").argument("[identifier]", "Citation key or UUID (interactive selection if omitted)").option("--yes", "Apply changes (add new files)").option("--fix", "Remove missing files from metadata").option("--uuid", "Interpret identifier as UUID").action(async (identifier, options) => {
|
|
31742
|
+
await handleAttachSyncAction(identifier, options, program.opts());
|
|
31743
|
+
});
|
|
31744
|
+
}
|
|
30557
31745
|
function registerFulltextCommand(program) {
|
|
30558
31746
|
const fulltextCmd = program.command("fulltext").description("Manage full-text files attached to references");
|
|
30559
31747
|
fulltextCmd.command("attach").description("Attach a full-text file to a reference").argument("[identifier]", "Citation key or UUID (interactive selection if omitted)").argument("[file-path]", "Path to the file to attach").option("--pdf [path]", "Attach as PDF (path optional if provided as argument)").option("--markdown [path]", "Attach as Markdown (path optional if provided as argument)").option("--move", "Move file instead of copy").option("-f, --force", "Overwrite existing attachment").option("--uuid", "Interpret identifier as UUID").action(async (identifier, filePath, options) => {
|
|
@@ -30562,7 +31750,7 @@ function registerFulltextCommand(program) {
|
|
|
30562
31750
|
fulltextCmd.command("get").description("Get full-text file path or content").argument("[identifier]", "Citation key or UUID (interactive selection if omitted)").option("--pdf", "Get PDF file only").option("--markdown", "Get Markdown file only").option("--stdout", "Output file content to stdout").option("--uuid", "Interpret identifier as UUID").action(async (identifier, options) => {
|
|
30563
31751
|
await handleFulltextGetAction(identifier, options, program.opts());
|
|
30564
31752
|
});
|
|
30565
|
-
fulltextCmd.command("detach").description("Detach full-text file from a reference").argument("[identifier]", "Citation key or UUID (interactive selection if omitted)").option("--pdf", "Detach PDF only").option("--markdown", "Detach Markdown only").option("--
|
|
31753
|
+
fulltextCmd.command("detach").description("Detach full-text file from a reference").argument("[identifier]", "Citation key or UUID (interactive selection if omitted)").option("--pdf", "Detach PDF only").option("--markdown", "Detach Markdown only").option("--remove-files", "Also delete the file from disk").option("-f, --force", "Skip confirmation for file removal").option("--uuid", "Interpret identifier as UUID").action(async (identifier, options) => {
|
|
30566
31754
|
await handleFulltextDetachAction(identifier, options, program.opts());
|
|
30567
31755
|
});
|
|
30568
31756
|
fulltextCmd.command("open").description("Open full-text file with system default application").argument("[identifier]", "Citation key or UUID (interactive selection if omitted)").option("--pdf", "Open PDF file").option("--markdown", "Open Markdown file").option("--uuid", "Interpret identifier as UUID").action(async (identifier, options) => {
|
|
@@ -30576,16 +31764,22 @@ async function main(argv) {
|
|
|
30576
31764
|
return;
|
|
30577
31765
|
}
|
|
30578
31766
|
process.on("SIGINT", () => {
|
|
30579
|
-
|
|
31767
|
+
setExitCode(ExitCode.SIGINT);
|
|
30580
31768
|
});
|
|
30581
31769
|
process.on("SIGTERM", () => {
|
|
30582
|
-
|
|
31770
|
+
setExitCode(ExitCode.SUCCESS);
|
|
30583
31771
|
});
|
|
30584
31772
|
await program.parseAsync(argv);
|
|
30585
31773
|
}
|
|
30586
31774
|
export {
|
|
31775
|
+
addAttachment as a,
|
|
30587
31776
|
createProgram as c,
|
|
31777
|
+
detachAttachment as d,
|
|
30588
31778
|
formatBibtex as f,
|
|
30589
|
-
|
|
31779
|
+
getAttachment as g,
|
|
31780
|
+
listAttachments as l,
|
|
31781
|
+
main as m,
|
|
31782
|
+
openAttachment as o,
|
|
31783
|
+
syncAttachments as s
|
|
30590
31784
|
};
|
|
30591
|
-
//# sourceMappingURL=index-
|
|
31785
|
+
//# sourceMappingURL=index-B4RmLBI1.js.map
|