@ncukondo/reference-manager 0.17.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/chunks/action-menu-D8gSe1YM.js +87 -0
  2. package/dist/chunks/action-menu-D8gSe1YM.js.map +1 -0
  3. package/dist/chunks/clipboard-KXwFSJ4w.js +56 -0
  4. package/dist/chunks/clipboard-KXwFSJ4w.js.map +1 -0
  5. package/dist/chunks/{format-ByjxlVm5.js → format-B52BI__9.js} +2 -2
  6. package/dist/chunks/{format-ByjxlVm5.js.map → format-B52BI__9.js.map} +1 -1
  7. package/dist/chunks/{index-BigPRCTh.js → index-B5W5srUa.js} +50 -38
  8. package/dist/chunks/index-B5W5srUa.js.map +1 -0
  9. package/dist/chunks/{index-wb8zgPJ0.js → index-BPhIwqHO.js} +561 -92
  10. package/dist/chunks/index-BPhIwqHO.js.map +1 -0
  11. package/dist/chunks/{index-DWEWWvFO.js → index-iYPq6D80.js} +2 -2
  12. package/dist/chunks/index-iYPq6D80.js.map +1 -0
  13. package/dist/chunks/{loader-Ds4s8UO0.js → loader-BtW20O32.js} +40 -8
  14. package/dist/chunks/loader-BtW20O32.js.map +1 -0
  15. package/dist/chunks/{reference-select-O0PY7CRU.js → reference-select-DcClzkw2.js} +3 -3
  16. package/dist/chunks/{reference-select-O0PY7CRU.js.map → reference-select-DcClzkw2.js.map} +1 -1
  17. package/dist/chunks/{style-select-DF5kX4G6.js → style-select-D0bgalgW.js} +2 -2
  18. package/dist/chunks/{style-select-DF5kX4G6.js.map → style-select-D0bgalgW.js.map} +1 -1
  19. package/dist/cli/commands/attach.d.ts.map +1 -1
  20. package/dist/cli/commands/cite.d.ts.map +1 -1
  21. package/dist/cli/commands/list.d.ts +6 -2
  22. package/dist/cli/commands/list.d.ts.map +1 -1
  23. package/dist/cli/commands/remove.d.ts +5 -0
  24. package/dist/cli/commands/remove.d.ts.map +1 -1
  25. package/dist/cli/commands/search.d.ts +18 -3
  26. package/dist/cli/commands/search.d.ts.map +1 -1
  27. package/dist/cli/commands/url.d.ts +58 -0
  28. package/dist/cli/commands/url.d.ts.map +1 -0
  29. package/dist/cli/helpers.d.ts +18 -0
  30. package/dist/cli/helpers.d.ts.map +1 -1
  31. package/dist/cli/index.d.ts.map +1 -1
  32. package/dist/cli.js +1 -1
  33. package/dist/config/defaults.d.ts.map +1 -1
  34. package/dist/config/env-override.d.ts.map +1 -1
  35. package/dist/config/key-parser.d.ts.map +1 -1
  36. package/dist/config/loader.d.ts +7 -4
  37. package/dist/config/loader.d.ts.map +1 -1
  38. package/dist/config/schema.d.ts +27 -0
  39. package/dist/config/schema.d.ts.map +1 -1
  40. package/dist/features/format/items.d.ts +1 -1
  41. package/dist/features/format/items.d.ts.map +1 -1
  42. package/dist/features/interactive/action-menu.d.ts +40 -5
  43. package/dist/features/interactive/action-menu.d.ts.map +1 -1
  44. package/dist/features/interactive/apps/SearchFlowApp.d.ts +8 -3
  45. package/dist/features/interactive/apps/SearchFlowApp.d.ts.map +1 -1
  46. package/dist/features/interactive/apps/runSearchFlow.d.ts +5 -0
  47. package/dist/features/interactive/apps/runSearchFlow.d.ts.map +1 -1
  48. package/dist/features/operations/url.d.ts +24 -0
  49. package/dist/features/operations/url.d.ts.map +1 -0
  50. package/dist/index.js +1 -1
  51. package/dist/utils/clipboard.d.ts +19 -0
  52. package/dist/utils/clipboard.d.ts.map +1 -0
  53. package/package.json +2 -2
  54. package/dist/chunks/index-BigPRCTh.js.map +0 -1
  55. package/dist/chunks/index-DWEWWvFO.js.map +0 -1
  56. package/dist/chunks/index-wb8zgPJ0.js.map +0 -1
  57. package/dist/chunks/loader-Ds4s8UO0.js.map +0 -1
@@ -8,7 +8,7 @@ import * as path from "node:path";
8
8
  import path__default, { extname, join, basename, dirname } from "node:path";
9
9
  import fs__default, { stat, rename, copyFile, readFile, unlink, readdir, mkdir, rm } from "node:fs/promises";
10
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-B4gr0P83.js";
11
- import { o as openWithSystemApp, l as loadConfig, e as getDefaultCurrentDirConfigFilename, h as getDefaultUserConfigPath } from "./loader-Ds4s8UO0.js";
11
+ import { o as openWithSystemApp, l as loadConfig, e as getDefaultCurrentDirConfigFilename, h as getDefaultUserConfigPath } from "./loader-BtW20O32.js";
12
12
  import { spawn, spawnSync } from "node:child_process";
13
13
  import process$1, { stdin, stdout } from "node:process";
14
14
  import * as readline from "node:readline";
@@ -20,7 +20,7 @@ import "@citation-js/plugin-csl";
20
20
  import { ZodOptional as ZodOptional$2, z } from "zod";
21
21
  import { serve } from "@hono/node-server";
22
22
  const name = "@ncukondo/reference-manager";
23
- const version$1 = "0.17.1";
23
+ const version$1 = "0.18.0";
24
24
  const description$1 = "A local reference management tool using CSL-JSON as the single source of truth";
25
25
  const packageJson = {
26
26
  name,
@@ -1056,27 +1056,27 @@ class OperationsLibrary {
1056
1056
  }
1057
1057
  // Attachment operations
1058
1058
  async attachAdd(options) {
1059
- const { addAttachment: addAttachment2 } = await import("./index-DWEWWvFO.js");
1059
+ const { addAttachment: addAttachment2 } = await import("./index-iYPq6D80.js");
1060
1060
  return addAttachment2(this.library, options);
1061
1061
  }
1062
1062
  async attachList(options) {
1063
- const { listAttachments: listAttachments2 } = await import("./index-DWEWWvFO.js");
1063
+ const { listAttachments: listAttachments2 } = await import("./index-iYPq6D80.js");
1064
1064
  return listAttachments2(this.library, options);
1065
1065
  }
1066
1066
  async attachGet(options) {
1067
- const { getAttachment: getAttachment2 } = await import("./index-DWEWWvFO.js");
1067
+ const { getAttachment: getAttachment2 } = await import("./index-iYPq6D80.js");
1068
1068
  return getAttachment2(this.library, options);
1069
1069
  }
1070
1070
  async attachDetach(options) {
1071
- const { detachAttachment: detachAttachment2 } = await import("./index-DWEWWvFO.js");
1071
+ const { detachAttachment: detachAttachment2 } = await import("./index-iYPq6D80.js");
1072
1072
  return detachAttachment2(this.library, options);
1073
1073
  }
1074
1074
  async attachSync(options) {
1075
- const { syncAttachments: syncAttachments2 } = await import("./index-DWEWWvFO.js");
1075
+ const { syncAttachments: syncAttachments2 } = await import("./index-iYPq6D80.js");
1076
1076
  return syncAttachments2(this.library, options);
1077
1077
  }
1078
1078
  async attachOpen(options) {
1079
- const { openAttachment: openAttachment2 } = await import("./index-DWEWWvFO.js");
1079
+ const { openAttachment: openAttachment2 } = await import("./index-iYPq6D80.js");
1080
1080
  return openAttachment2(this.library, options);
1081
1081
  }
1082
1082
  }
@@ -1092,8 +1092,8 @@ class ServerClient {
1092
1092
  * @returns Array of CSL items
1093
1093
  */
1094
1094
  async getAll() {
1095
- const url = `${this.baseUrl}/api/references`;
1096
- const response = await fetch(url);
1095
+ const url2 = `${this.baseUrl}/api/references`;
1096
+ const response = await fetch(url2);
1097
1097
  if (!response.ok) {
1098
1098
  throw new Error(await response.text());
1099
1099
  }
@@ -1107,8 +1107,8 @@ class ServerClient {
1107
1107
  */
1108
1108
  async find(identifier, options = {}) {
1109
1109
  const { idType = "id" } = options;
1110
- const url = idType === "uuid" ? `${this.baseUrl}/api/references/uuid/${encodeURIComponent(identifier)}` : `${this.baseUrl}/api/references/id/${encodeURIComponent(identifier)}`;
1111
- const response = await fetch(url);
1110
+ const url2 = idType === "uuid" ? `${this.baseUrl}/api/references/uuid/${encodeURIComponent(identifier)}` : `${this.baseUrl}/api/references/id/${encodeURIComponent(identifier)}`;
1111
+ const response = await fetch(url2);
1112
1112
  if (response.status === 404) {
1113
1113
  return void 0;
1114
1114
  }
@@ -1126,8 +1126,8 @@ class ServerClient {
1126
1126
  * @returns The added CSL item (with generated ID and UUID)
1127
1127
  */
1128
1128
  async add(item) {
1129
- const url = `${this.baseUrl}/api/references`;
1130
- const response = await fetch(url, {
1129
+ const url2 = `${this.baseUrl}/api/references`;
1130
+ const response = await fetch(url2, {
1131
1131
  method: "POST",
1132
1132
  headers: { "Content-Type": "application/json" },
1133
1133
  body: JSON.stringify(item)
@@ -1146,8 +1146,8 @@ class ServerClient {
1146
1146
  */
1147
1147
  async update(identifier, updates, options) {
1148
1148
  const { idType = "id", onIdCollision } = options ?? {};
1149
- const url = idType === "uuid" ? `${this.baseUrl}/api/references/uuid/${encodeURIComponent(identifier)}` : `${this.baseUrl}/api/references/id/${encodeURIComponent(identifier)}`;
1150
- const response = await fetch(url, {
1149
+ const url2 = idType === "uuid" ? `${this.baseUrl}/api/references/uuid/${encodeURIComponent(identifier)}` : `${this.baseUrl}/api/references/id/${encodeURIComponent(identifier)}`;
1150
+ const response = await fetch(url2, {
1151
1151
  method: "PUT",
1152
1152
  headers: { "Content-Type": "application/json" },
1153
1153
  body: JSON.stringify({ updates, onIdCollision })
@@ -1171,8 +1171,8 @@ class ServerClient {
1171
1171
  */
1172
1172
  async remove(identifier, options = {}) {
1173
1173
  const { idType = "id" } = options;
1174
- const url = idType === "uuid" ? `${this.baseUrl}/api/references/uuid/${encodeURIComponent(identifier)}` : `${this.baseUrl}/api/references/id/${encodeURIComponent(identifier)}`;
1175
- const response = await fetch(url, {
1174
+ const url2 = idType === "uuid" ? `${this.baseUrl}/api/references/uuid/${encodeURIComponent(identifier)}` : `${this.baseUrl}/api/references/id/${encodeURIComponent(identifier)}`;
1175
+ const response = await fetch(url2, {
1176
1176
  method: "DELETE"
1177
1177
  });
1178
1178
  if (!response.ok && response.status !== 404) {
@@ -1203,8 +1203,8 @@ class ServerClient {
1203
1203
  * @returns Result containing added, failed, and skipped items
1204
1204
  */
1205
1205
  async import(inputs, options) {
1206
- const url = `${this.baseUrl}/api/add`;
1207
- const response = await fetch(url, {
1206
+ const url2 = `${this.baseUrl}/api/add`;
1207
+ const response = await fetch(url2, {
1208
1208
  method: "POST",
1209
1209
  headers: { "Content-Type": "application/json" },
1210
1210
  body: JSON.stringify({ inputs, options })
@@ -1220,8 +1220,8 @@ class ServerClient {
1220
1220
  * @returns Cite result with per-identifier results
1221
1221
  */
1222
1222
  async cite(options) {
1223
- const url = `${this.baseUrl}/api/cite`;
1224
- const response = await fetch(url, {
1223
+ const url2 = `${this.baseUrl}/api/cite`;
1224
+ const response = await fetch(url2, {
1225
1225
  method: "POST",
1226
1226
  headers: { "Content-Type": "application/json" },
1227
1227
  body: JSON.stringify(options)
@@ -1237,8 +1237,8 @@ class ServerClient {
1237
1237
  * @returns List result with raw CslItem[]
1238
1238
  */
1239
1239
  async list(options) {
1240
- const url = `${this.baseUrl}/api/list`;
1241
- const response = await fetch(url, {
1240
+ const url2 = `${this.baseUrl}/api/list`;
1241
+ const response = await fetch(url2, {
1242
1242
  method: "POST",
1243
1243
  headers: { "Content-Type": "application/json" },
1244
1244
  body: JSON.stringify(options ?? {})
@@ -1254,8 +1254,8 @@ class ServerClient {
1254
1254
  * @returns Search result with raw CslItem[]
1255
1255
  */
1256
1256
  async search(options) {
1257
- const url = `${this.baseUrl}/api/search`;
1258
- const response = await fetch(url, {
1257
+ const url2 = `${this.baseUrl}/api/search`;
1258
+ const response = await fetch(url2, {
1259
1259
  method: "POST",
1260
1260
  headers: { "Content-Type": "application/json" },
1261
1261
  body: JSON.stringify(options)
@@ -1274,8 +1274,8 @@ class ServerClient {
1274
1274
  * @returns Result of the add operation
1275
1275
  */
1276
1276
  async attachAdd(options) {
1277
- const url = `${this.baseUrl}/api/attachments/add`;
1278
- const response = await fetch(url, {
1277
+ const url2 = `${this.baseUrl}/api/attachments/add`;
1278
+ const response = await fetch(url2, {
1279
1279
  method: "POST",
1280
1280
  headers: { "Content-Type": "application/json" },
1281
1281
  body: JSON.stringify(options)
@@ -1291,8 +1291,8 @@ class ServerClient {
1291
1291
  * @returns List of attachments
1292
1292
  */
1293
1293
  async attachList(options) {
1294
- const url = `${this.baseUrl}/api/attachments/list`;
1295
- const response = await fetch(url, {
1294
+ const url2 = `${this.baseUrl}/api/attachments/list`;
1295
+ const response = await fetch(url2, {
1296
1296
  method: "POST",
1297
1297
  headers: { "Content-Type": "application/json" },
1298
1298
  body: JSON.stringify(options)
@@ -1308,8 +1308,8 @@ class ServerClient {
1308
1308
  * @returns Attachment file path or content
1309
1309
  */
1310
1310
  async attachGet(options) {
1311
- const url = `${this.baseUrl}/api/attachments/get`;
1312
- const response = await fetch(url, {
1311
+ const url2 = `${this.baseUrl}/api/attachments/get`;
1312
+ const response = await fetch(url2, {
1313
1313
  method: "POST",
1314
1314
  headers: { "Content-Type": "application/json" },
1315
1315
  body: JSON.stringify(options)
@@ -1325,8 +1325,8 @@ class ServerClient {
1325
1325
  * @returns Result of the detach operation
1326
1326
  */
1327
1327
  async attachDetach(options) {
1328
- const url = `${this.baseUrl}/api/attachments/detach`;
1329
- const response = await fetch(url, {
1328
+ const url2 = `${this.baseUrl}/api/attachments/detach`;
1329
+ const response = await fetch(url2, {
1330
1330
  method: "POST",
1331
1331
  headers: { "Content-Type": "application/json" },
1332
1332
  body: JSON.stringify(options)
@@ -1342,8 +1342,8 @@ class ServerClient {
1342
1342
  * @returns Sync result
1343
1343
  */
1344
1344
  async attachSync(options) {
1345
- const url = `${this.baseUrl}/api/attachments/sync`;
1346
- const response = await fetch(url, {
1345
+ const url2 = `${this.baseUrl}/api/attachments/sync`;
1346
+ const response = await fetch(url2, {
1347
1347
  method: "POST",
1348
1348
  headers: { "Content-Type": "application/json" },
1349
1349
  body: JSON.stringify(options)
@@ -1359,8 +1359,8 @@ class ServerClient {
1359
1359
  * @returns Result of the open operation
1360
1360
  */
1361
1361
  async attachOpen(options) {
1362
- const url = `${this.baseUrl}/api/attachments/open`;
1363
- const response = await fetch(url, {
1362
+ const url2 = `${this.baseUrl}/api/attachments/open`;
1363
+ const response = await fetch(url2, {
1364
1364
  method: "POST",
1365
1365
  headers: { "Content-Type": "application/json" },
1366
1366
  body: JSON.stringify(options)
@@ -1458,7 +1458,9 @@ function parseJsonInput(input) {
1458
1458
  }
1459
1459
  }
1460
1460
  async function loadConfigWithOverrides(options) {
1461
- const config2 = await loadConfig();
1461
+ const config2 = await loadConfig({
1462
+ ...options.config && { configPath: options.config }
1463
+ });
1462
1464
  const overrides = {};
1463
1465
  if (options.library) {
1464
1466
  overrides.library = options.library;
@@ -1566,6 +1568,35 @@ function exitWithError(message, code2 = ExitCode.ERROR) {
1566
1568
  `);
1567
1569
  setExitCode(code2);
1568
1570
  }
1571
+ function resolveClipboardEnabled(options, config2, isTui) {
1572
+ if (options.clipboard !== void 0) {
1573
+ return options.clipboard;
1574
+ }
1575
+ const envVal = process.env.REFERENCE_MANAGER_CLIPBOARD_AUTO_COPY;
1576
+ if (envVal !== void 0) {
1577
+ return envVal === "1" || envVal === "true";
1578
+ }
1579
+ if (isTui) {
1580
+ return config2.cli.tui.clipboardAutoCopy;
1581
+ }
1582
+ return false;
1583
+ }
1584
+ async function writeOutputWithClipboard(output, clipboardEnabled, quiet) {
1585
+ process.stdout.write(`${output}
1586
+ `);
1587
+ if (clipboardEnabled) {
1588
+ const { copyToClipboard } = await import("./clipboard-KXwFSJ4w.js");
1589
+ const result = await copyToClipboard(output);
1590
+ if (!quiet) {
1591
+ if (result.success) {
1592
+ process.stderr.write("Copied to clipboard\n");
1593
+ } else {
1594
+ process.stderr.write(`Warning: Failed to copy to clipboard: ${result.error}
1595
+ `);
1596
+ }
1597
+ }
1598
+ }
1599
+ }
1569
1600
  async function executeAttachOpen(options, context) {
1570
1601
  const operationOptions = {
1571
1602
  identifier: options.identifier,
@@ -1762,9 +1793,9 @@ function formatSyncPreview(result) {
1762
1793
  function getAttachExitCode(result) {
1763
1794
  return result.success ? 0 : 1;
1764
1795
  }
1765
- async function executeInteractiveSelect$1(context, config2) {
1796
+ async function executeInteractiveSelect$2(context, config2) {
1766
1797
  const { withAlternateScreen: withAlternateScreen2 } = await Promise.resolve().then(() => alternateScreen);
1767
- const { selectReferencesOrExit } = await import("./reference-select-O0PY7CRU.js");
1798
+ const { selectReferencesOrExit } = await import("./reference-select-DcClzkw2.js");
1768
1799
  const allReferences = await context.library.getAll();
1769
1800
  const identifiers = await withAlternateScreen2(
1770
1801
  () => selectReferencesOrExit(allReferences, { multiSelect: false }, config2.cli.tui)
@@ -1776,7 +1807,7 @@ async function resolveIdentifier(identifierArg, context, config2) {
1776
1807
  return identifierArg;
1777
1808
  }
1778
1809
  if (isTTY()) {
1779
- return executeInteractiveSelect$1(context, config2);
1810
+ return executeInteractiveSelect$2(context, config2);
1780
1811
  }
1781
1812
  const stdinId = await readIdentifierFromStdin();
1782
1813
  if (!stdinId) {
@@ -1894,6 +1925,7 @@ async function handleAttachOpenAction(identifierArg, filenameArg, options, globa
1894
1925
  process.stdout.write(`${result.path}
1895
1926
  `);
1896
1927
  setExitCode(ExitCode.SUCCESS);
1928
+ return;
1897
1929
  }
1898
1930
  if (shouldUseInteractive) {
1899
1931
  await runInteractiveMode(
@@ -2080,6 +2112,27 @@ async function handleAttachSyncAction(identifierArg, options, globalOpts) {
2080
2112
  setExitCode(ExitCode.INTERNAL_ERROR);
2081
2113
  }
2082
2114
  }
2115
+ const attach = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
2116
+ __proto__: null,
2117
+ executeAttachAdd,
2118
+ executeAttachDetach,
2119
+ executeAttachGet,
2120
+ executeAttachList,
2121
+ executeAttachOpen,
2122
+ executeAttachSync,
2123
+ formatAttachAddOutput,
2124
+ formatAttachDetachOutput,
2125
+ formatAttachListOutput,
2126
+ formatAttachOpenOutput,
2127
+ formatAttachSyncOutput,
2128
+ getAttachExitCode,
2129
+ handleAttachAddAction,
2130
+ handleAttachDetachAction,
2131
+ handleAttachGetAction,
2132
+ handleAttachListAction,
2133
+ handleAttachOpenAction,
2134
+ handleAttachSyncAction
2135
+ }, Symbol.toStringTag, { value: "Module" }));
2083
2136
  async function validateOptions$2(options) {
2084
2137
  if (options.output && !["text", "html", "rtf"].includes(options.output)) {
2085
2138
  throw new Error(`Invalid output format '${options.output}'. Must be one of: text, html, rtf`);
@@ -2137,8 +2190,8 @@ function getCiteExitCode(result) {
2137
2190
  }
2138
2191
  async function executeInteractiveCite(options, context, config2) {
2139
2192
  const { withAlternateScreen: withAlternateScreen2 } = await Promise.resolve().then(() => alternateScreen);
2140
- const { runCiteFlow } = await import("./index-BigPRCTh.js");
2141
- const { buildStyleChoices, listCustomStyles } = await import("./style-select-DF5kX4G6.js");
2193
+ const { runCiteFlow } = await import("./index-B5W5srUa.js");
2194
+ const { buildStyleChoices, listCustomStyles } = await import("./style-select-D0bgalgW.js");
2142
2195
  const { search } = await import("./file-watcher-CrsNHUpz.js").then((n) => n.z);
2143
2196
  const { tokenize } = await import("./file-watcher-CrsNHUpz.js").then((n) => n.y);
2144
2197
  const { checkTTY } = await import("./tty-BMyaEOhX.js");
@@ -2180,8 +2233,10 @@ async function handleCiteAction(identifiers, options, globalOpts) {
2180
2233
  const config2 = await loadConfigWithOverrides({ ...globalOpts, ...options });
2181
2234
  const context = await createExecutionContext(config2, Library.load);
2182
2235
  let result;
2236
+ let isTuiMode = false;
2183
2237
  if (identifiers.length === 0) {
2184
2238
  if (isTTY()) {
2239
+ isTuiMode = true;
2185
2240
  result = await executeInteractiveCite(options, context, config2);
2186
2241
  } else {
2187
2242
  const stdinIds = await readIdentifiersFromStdin();
@@ -2199,8 +2254,8 @@ async function handleCiteAction(identifiers, options, globalOpts) {
2199
2254
  }
2200
2255
  const output = formatCiteOutput(result);
2201
2256
  if (output) {
2202
- process.stdout.write(`${output}
2203
- `);
2257
+ const clipboardEnabled = resolveClipboardEnabled(globalOpts, config2, isTuiMode);
2258
+ await writeOutputWithClipboard(output, clipboardEnabled, config2.logLevel === "silent");
2204
2259
  }
2205
2260
  const errors2 = formatCiteErrors(result);
2206
2261
  if (errors2) {
@@ -2219,6 +2274,7 @@ const ENV_OVERRIDE_MAP = {
2219
2274
  REFERENCE_MANAGER_ATTACHMENTS_DIR: "attachments.directory",
2220
2275
  REFERENCE_MANAGER_CLI_DEFAULT_LIMIT: "cli.default_limit",
2221
2276
  REFERENCE_MANAGER_MCP_DEFAULT_LIMIT: "mcp.default_limit",
2277
+ REFERENCE_MANAGER_CLIPBOARD_AUTO_COPY: "cli.tui.clipboard_auto_copy",
2222
2278
  PUBMED_EMAIL: "pubmed.email",
2223
2279
  PUBMED_API_KEY: "pubmed.api_key"
2224
2280
  };
@@ -2275,6 +2331,12 @@ const CONFIG_KEY_REGISTRY = [
2275
2331
  description: "Default format",
2276
2332
  enumValues: ["text", "html", "rtf"]
2277
2333
  },
2334
+ {
2335
+ key: "citation.default_key_format",
2336
+ type: "enum",
2337
+ description: "Default citation key format",
2338
+ enumValues: ["pandoc", "latex"]
2339
+ },
2278
2340
  // pubmed section
2279
2341
  { key: "pubmed.email", type: "string", description: "Email for PubMed API", optional: true },
2280
2342
  { key: "pubmed.api_key", type: "string", description: "API key for PubMed", optional: true },
@@ -2309,6 +2371,11 @@ const CONFIG_KEY_REGISTRY = [
2309
2371
  type: "integer",
2310
2372
  description: "Search debounce delay (ms)"
2311
2373
  },
2374
+ {
2375
+ key: "cli.tui.clipboard_auto_copy",
2376
+ type: "boolean",
2377
+ description: "Auto-copy TUI output to clipboard"
2378
+ },
2312
2379
  // cli.edit section
2313
2380
  {
2314
2381
  key: "cli.edit.default_format",
@@ -6681,7 +6748,7 @@ function formatEditOutput(result) {
6681
6748
  }
6682
6749
  async function executeInteractiveEdit(options, context, config2) {
6683
6750
  const { withAlternateScreen: withAlternateScreen2 } = await Promise.resolve().then(() => alternateScreen);
6684
- const { selectReferencesOrExit } = await import("./reference-select-O0PY7CRU.js");
6751
+ const { selectReferencesOrExit } = await import("./reference-select-DcClzkw2.js");
6685
6752
  const allReferences = await context.library.getAll();
6686
6753
  const identifiers = await withAlternateScreen2(
6687
6754
  () => selectReferencesOrExit(allReferences, { multiSelect: true }, config2.cli.tui)
@@ -6747,6 +6814,12 @@ async function handleEditAction(identifiers, options, globalOpts) {
6747
6814
  setExitCode(ExitCode.INTERNAL_ERROR);
6748
6815
  }
6749
6816
  }
6817
+ const edit = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
6818
+ __proto__: null,
6819
+ executeEditCommand,
6820
+ formatEditOutput,
6821
+ handleEditAction
6822
+ }, Symbol.toStringTag, { value: "Module" }));
6750
6823
  const ALIAS = Symbol.for("yaml.alias");
6751
6824
  const DOC = Symbol.for("yaml.document");
6752
6825
  const MAP = Symbol.for("yaml.map");
@@ -10327,9 +10400,9 @@ function formatFulltextOpenOutput(result) {
10327
10400
  function getFulltextExitCode(result) {
10328
10401
  return result.success ? 0 : 1;
10329
10402
  }
10330
- async function executeInteractiveSelect(context, config2) {
10403
+ async function executeInteractiveSelect$1(context, config2) {
10331
10404
  const { withAlternateScreen: withAlternateScreen2 } = await Promise.resolve().then(() => alternateScreen);
10332
- const { selectReferencesOrExit } = await import("./reference-select-O0PY7CRU.js");
10405
+ const { selectReferencesOrExit } = await import("./reference-select-DcClzkw2.js");
10333
10406
  const allReferences = await context.library.getAll();
10334
10407
  const identifiers = await withAlternateScreen2(
10335
10408
  () => selectReferencesOrExit(allReferences, { multiSelect: false }, config2.cli.tui)
@@ -10366,7 +10439,7 @@ async function handleFulltextAttachAction(identifierArg, filePathArg, options, g
10366
10439
  setExitCode(ExitCode.ERROR);
10367
10440
  return;
10368
10441
  }
10369
- identifier = await executeInteractiveSelect(context, config2);
10442
+ identifier = await executeInteractiveSelect$1(context, config2);
10370
10443
  }
10371
10444
  const { type: type2, filePath } = parseFulltextAttachTypeAndPath(filePathArg, options);
10372
10445
  const stdinContent = !filePath && type2 ? await readStdinBuffer() : void 0;
@@ -10413,7 +10486,7 @@ async function handleFulltextGetAction(identifierArg, options, globalOpts) {
10413
10486
  if (identifierArg) {
10414
10487
  identifier = identifierArg;
10415
10488
  } else if (isTTY()) {
10416
- identifier = await executeInteractiveSelect(context, config2);
10489
+ identifier = await executeInteractiveSelect$1(context, config2);
10417
10490
  } else {
10418
10491
  const stdinId = await readIdentifierFromStdin();
10419
10492
  if (!stdinId) {
@@ -10450,7 +10523,7 @@ async function handleFulltextDetachAction(identifierArg, options, globalOpts) {
10450
10523
  if (identifierArg) {
10451
10524
  identifier = identifierArg;
10452
10525
  } else if (isTTY()) {
10453
- identifier = await executeInteractiveSelect(context, config2);
10526
+ identifier = await executeInteractiveSelect$1(context, config2);
10454
10527
  } else {
10455
10528
  const stdinId = await readIdentifierFromStdin();
10456
10529
  if (!stdinId) {
@@ -10490,7 +10563,7 @@ async function handleFulltextOpenAction(identifierArg, options, globalOpts) {
10490
10563
  if (identifierArg) {
10491
10564
  identifier = identifierArg;
10492
10565
  } else if (isTTY()) {
10493
- identifier = await executeInteractiveSelect(context, config2);
10566
+ identifier = await executeInteractiveSelect$1(context, config2);
10494
10567
  } else {
10495
10568
  const stdinId = await readIdentifierFromStdin();
10496
10569
  if (!stdinId) {
@@ -10520,6 +10593,22 @@ async function handleFulltextOpenAction(identifierArg, options, globalOpts) {
10520
10593
  setExitCode(ExitCode.INTERNAL_ERROR);
10521
10594
  }
10522
10595
  }
10596
+ const fulltext = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
10597
+ __proto__: null,
10598
+ executeFulltextAttach,
10599
+ executeFulltextDetach,
10600
+ executeFulltextGet,
10601
+ executeFulltextOpen,
10602
+ formatFulltextAttachOutput,
10603
+ formatFulltextDetachOutput,
10604
+ formatFulltextGetOutput,
10605
+ formatFulltextOpenOutput,
10606
+ getFulltextExitCode,
10607
+ handleFulltextAttachAction,
10608
+ handleFulltextDetachAction,
10609
+ handleFulltextGetAction,
10610
+ handleFulltextOpenAction
10611
+ }, Symbol.toStringTag, { value: "Module" }));
10523
10612
  function formatAuthor(author) {
10524
10613
  const family = author.family || "";
10525
10614
  const givenInitial = author.given ? `${author.given.charAt(0)}.` : "";
@@ -10572,6 +10661,10 @@ function formatItems(items2, format2) {
10572
10661
  return items2.filter(
10573
10662
  (item) => Boolean(item.custom?.uuid)
10574
10663
  ).map((item) => item.custom.uuid);
10664
+ case "pandoc-key":
10665
+ return items2.map((item) => `@${item.id}`);
10666
+ case "latex-key":
10667
+ return items2.map((item) => `\\cite{${item.id}}`);
10575
10668
  default:
10576
10669
  return items2.map((item) => formatPretty([item]));
10577
10670
  }
@@ -10610,11 +10703,14 @@ const VALID_LIST_SORT_FIELDS = /* @__PURE__ */ new Set([
10610
10703
  "mod",
10611
10704
  "pub"
10612
10705
  ]);
10613
- function getOutputFormat$1(options) {
10706
+ function getOutputFormat$1(options, defaultKeyFormat) {
10614
10707
  if (options.output) {
10615
10708
  if (options.output === "ids") return "ids-only";
10616
10709
  return options.output;
10617
10710
  }
10711
+ if (options.key) return defaultKeyFormat === "latex" ? "latex-key" : "pandoc-key";
10712
+ if (options.pandocKey) return "pandoc-key";
10713
+ if (options.latexKey) return "latex-key";
10618
10714
  if (options.json) return "json";
10619
10715
  if (options.idsOnly) return "ids-only";
10620
10716
  if (options.uuidOnly) return "uuid";
@@ -10622,12 +10718,18 @@ function getOutputFormat$1(options) {
10622
10718
  return "pretty";
10623
10719
  }
10624
10720
  function validateOptions$1(options) {
10625
- const outputOptions = [options.json, options.idsOnly, options.uuidOnly, options.bibtex].filter(
10626
- Boolean
10627
- );
10721
+ const outputOptions = [
10722
+ options.json,
10723
+ options.idsOnly,
10724
+ options.uuidOnly,
10725
+ options.bibtex,
10726
+ options.key,
10727
+ options.pandocKey,
10728
+ options.latexKey
10729
+ ].filter(Boolean);
10628
10730
  if (outputOptions.length > 1) {
10629
10731
  throw new Error(
10630
- "Multiple output formats specified. Only one of --json, --ids-only, --uuid-only, --bibtex can be used."
10732
+ "Multiple output formats specified. Only one of --json, --ids-only, --uuid-only, --bibtex, --key, --pandoc-key, --latex-key can be used."
10631
10733
  );
10632
10734
  }
10633
10735
  if (options.output && outputOptions.length > 0) {
@@ -10662,8 +10764,8 @@ async function executeList(options, context) {
10662
10764
  ...pickDefined(options, ["order", "limit", "offset"])
10663
10765
  });
10664
10766
  }
10665
- function formatListOutput(result, options) {
10666
- const format2 = getOutputFormat$1(options);
10767
+ function formatListOutput(result, options, defaultKeyFormat) {
10768
+ const format2 = getOutputFormat$1(options, defaultKeyFormat);
10667
10769
  if (format2 === "json") {
10668
10770
  return JSON.stringify({
10669
10771
  items: result.items,
@@ -15480,10 +15582,10 @@ const $ZodURL = /* @__PURE__ */ $constructor("$ZodURL", (inst, def) => {
15480
15582
  inst._zod.check = (payload) => {
15481
15583
  try {
15482
15584
  const trimmed = payload.value.trim();
15483
- const url = new URL(trimmed);
15585
+ const url2 = new URL(trimmed);
15484
15586
  if (def.hostname) {
15485
15587
  def.hostname.lastIndex = 0;
15486
- if (!def.hostname.test(url.hostname)) {
15588
+ if (!def.hostname.test(url2.hostname)) {
15487
15589
  payload.issues.push({
15488
15590
  code: "invalid_format",
15489
15591
  format: "url",
@@ -15497,7 +15599,7 @@ const $ZodURL = /* @__PURE__ */ $constructor("$ZodURL", (inst, def) => {
15497
15599
  }
15498
15600
  if (def.protocol) {
15499
15601
  def.protocol.lastIndex = 0;
15500
- if (!def.protocol.test(url.protocol.endsWith(":") ? url.protocol.slice(0, -1) : url.protocol)) {
15602
+ if (!def.protocol.test(url2.protocol.endsWith(":") ? url2.protocol.slice(0, -1) : url2.protocol)) {
15501
15603
  payload.issues.push({
15502
15604
  code: "invalid_format",
15503
15605
  format: "url",
@@ -15510,7 +15612,7 @@ const $ZodURL = /* @__PURE__ */ $constructor("$ZodURL", (inst, def) => {
15510
15612
  }
15511
15613
  }
15512
15614
  if (def.normalize) {
15513
- payload.value = url.href;
15615
+ payload.value = url2.href;
15514
15616
  } else {
15515
15617
  payload.value = trimmed;
15516
15618
  }
@@ -31388,7 +31490,7 @@ Continue?`;
31388
31490
  }
31389
31491
  async function executeInteractiveRemove(context, config2) {
31390
31492
  const { withAlternateScreen: withAlternateScreen2 } = await Promise.resolve().then(() => alternateScreen);
31391
- const { selectReferenceItemsOrExit } = await import("./reference-select-O0PY7CRU.js");
31493
+ const { selectReferenceItemsOrExit } = await import("./reference-select-DcClzkw2.js");
31392
31494
  const allReferences = await context.library.getAll();
31393
31495
  const selectedItems = await withAlternateScreen2(
31394
31496
  () => selectReferenceItemsOrExit(allReferences, { multiSelect: false }, config2.cli.tui)
@@ -31485,6 +31587,15 @@ async function handleRemoveAction(identifierArg, options, globalOpts) {
31485
31587
  handleRemoveError(error, identifierArg, outputFormat);
31486
31588
  }
31487
31589
  }
31590
+ const remove = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
31591
+ __proto__: null,
31592
+ confirmRemoveIfNeeded,
31593
+ executeRemove,
31594
+ formatFulltextWarning,
31595
+ formatRemoveOutput,
31596
+ getFulltextAttachmentTypes,
31597
+ handleRemoveAction
31598
+ }, Symbol.toStringTag, { value: "Module" }));
31488
31599
  const VALID_SEARCH_SORT_FIELDS = /* @__PURE__ */ new Set([
31489
31600
  "created",
31490
31601
  "updated",
@@ -31497,11 +31608,14 @@ const VALID_SEARCH_SORT_FIELDS = /* @__PURE__ */ new Set([
31497
31608
  "pub",
31498
31609
  "rel"
31499
31610
  ]);
31500
- function getOutputFormat(options) {
31611
+ function getOutputFormat(options, defaultKeyFormat) {
31501
31612
  if (options.output) {
31502
31613
  if (options.output === "ids") return "ids-only";
31503
31614
  return options.output;
31504
31615
  }
31616
+ if (options.key) return defaultKeyFormat === "latex" ? "latex-key" : "pandoc-key";
31617
+ if (options.pandocKey) return "pandoc-key";
31618
+ if (options.latexKey) return "latex-key";
31505
31619
  if (options.json) return "json";
31506
31620
  if (options.idsOnly) return "ids-only";
31507
31621
  if (options.uuidOnly) return "uuid";
@@ -31509,12 +31623,18 @@ function getOutputFormat(options) {
31509
31623
  return "pretty";
31510
31624
  }
31511
31625
  function validateOptions(options) {
31512
- const outputOptions = [options.json, options.idsOnly, options.uuidOnly, options.bibtex].filter(
31513
- Boolean
31514
- );
31626
+ const outputOptions = [
31627
+ options.json,
31628
+ options.idsOnly,
31629
+ options.uuidOnly,
31630
+ options.bibtex,
31631
+ options.key,
31632
+ options.pandocKey,
31633
+ options.latexKey
31634
+ ].filter(Boolean);
31515
31635
  if (outputOptions.length > 1) {
31516
31636
  throw new Error(
31517
- "Multiple output formats specified. Only one of --json, --ids-only, --uuid-only, --bibtex can be used."
31637
+ "Multiple output formats specified. Only one of --json, --ids-only, --uuid-only, --bibtex, --key, --pandoc-key, --latex-key can be used."
31518
31638
  );
31519
31639
  }
31520
31640
  if (options.output && outputOptions.length > 0) {
@@ -31550,8 +31670,8 @@ async function executeSearch(options, context) {
31550
31670
  ...pickDefined(options, ["order", "limit", "offset"])
31551
31671
  });
31552
31672
  }
31553
- function formatSearchOutput(result, options) {
31554
- const format2 = getOutputFormat(options);
31673
+ function formatSearchOutput(result, options, defaultKeyFormat) {
31674
+ const format2 = getOutputFormat(options, defaultKeyFormat);
31555
31675
  if (format2 === "json") {
31556
31676
  return JSON.stringify({
31557
31677
  items: result.items,
@@ -31580,11 +31700,14 @@ function validateInteractiveOptions(options) {
31580
31700
  options.json,
31581
31701
  options.idsOnly,
31582
31702
  options.uuidOnly,
31583
- options.bibtex
31703
+ options.bibtex,
31704
+ options.key,
31705
+ options.pandocKey,
31706
+ options.latexKey
31584
31707
  ].filter(Boolean);
31585
31708
  if (outputOptions.length > 0) {
31586
31709
  throw new Error(
31587
- "TUI mode cannot be combined with output format options (--output, --json, --ids-only, --uuid-only, --bibtex)"
31710
+ "TUI mode cannot be combined with output format options (--output, --json, --ids-only, --uuid-only, --bibtex, --key, --pandoc-key, --latex-key)"
31588
31711
  );
31589
31712
  }
31590
31713
  }
@@ -31592,7 +31715,7 @@ async function executeInteractiveSearch(options, context, config2) {
31592
31715
  validateInteractiveOptions(options);
31593
31716
  const { checkTTY } = await import("./tty-BMyaEOhX.js");
31594
31717
  const { withAlternateScreen: withAlternateScreen2 } = await Promise.resolve().then(() => alternateScreen);
31595
- const { runSearchFlow } = await import("./index-BigPRCTh.js");
31718
+ const { runSearchFlow } = await import("./index-B5W5srUa.js");
31596
31719
  const { search } = await import("./file-watcher-CrsNHUpz.js").then((n) => n.z);
31597
31720
  const { tokenize } = await import("./file-watcher-CrsNHUpz.js").then((n) => n.y);
31598
31721
  checkTTY();
@@ -31605,14 +31728,110 @@ async function executeInteractiveSearch(options, context, config2) {
31605
31728
  const result = await withAlternateScreen2(
31606
31729
  () => runSearchFlow(allReferences, searchFn, {
31607
31730
  limit: tuiConfig.limit,
31608
- debounceMs: tuiConfig.debounceMs
31731
+ debounceMs: tuiConfig.debounceMs,
31732
+ defaultKeyFormat: config2.citation.defaultKeyFormat,
31733
+ defaultStyle: config2.citation.defaultStyle
31609
31734
  })
31610
31735
  );
31736
+ if (result.selectedItems && !result.cancelled) {
31737
+ const { isSideEffectAction } = await import("./action-menu-D8gSe1YM.js");
31738
+ if (isSideEffectAction(result.action)) {
31739
+ await executeSideEffectAction(result.action, result.selectedItems, context, config2);
31740
+ return { output: "", cancelled: false, action: result.action };
31741
+ }
31742
+ }
31611
31743
  return {
31612
31744
  output: result.output,
31613
- cancelled: result.cancelled
31745
+ cancelled: result.cancelled,
31746
+ action: result.action
31614
31747
  };
31615
31748
  }
31749
+ async function executeSideEffectAction(action, items2, context, config2) {
31750
+ switch (action) {
31751
+ case "open-url": {
31752
+ const { resolveDefaultUrl: resolveDefaultUrl2 } = await Promise.resolve().then(() => url);
31753
+ const { openWithSystemApp: openWithSystemApp2 } = await import("./loader-BtW20O32.js").then((n) => n.j);
31754
+ const item = items2[0];
31755
+ if (!item) return;
31756
+ const url$1 = resolveDefaultUrl2(item);
31757
+ if (url$1) {
31758
+ await openWithSystemApp2(url$1);
31759
+ } else {
31760
+ process.stderr.write(`No URL available for ${item.id}
31761
+ `);
31762
+ }
31763
+ break;
31764
+ }
31765
+ case "open-fulltext": {
31766
+ const { executeFulltextOpen: executeFulltextOpen2 } = await Promise.resolve().then(() => fulltext);
31767
+ const item = items2[0];
31768
+ if (!item) return;
31769
+ const result = await executeFulltextOpen2(
31770
+ {
31771
+ identifier: item.id,
31772
+ fulltextDirectory: config2.attachments.directory
31773
+ },
31774
+ context
31775
+ );
31776
+ if (!result.success) {
31777
+ process.stderr.write(`${result.error}
31778
+ `);
31779
+ }
31780
+ break;
31781
+ }
31782
+ case "manage-attachments": {
31783
+ const { executeAttachOpen: executeAttachOpen2 } = await Promise.resolve().then(() => attach);
31784
+ const item = items2[0];
31785
+ if (!item) return;
31786
+ await executeAttachOpen2(
31787
+ {
31788
+ identifier: item.id,
31789
+ attachmentsDirectory: config2.attachments.directory
31790
+ },
31791
+ context
31792
+ );
31793
+ break;
31794
+ }
31795
+ case "edit": {
31796
+ const { executeEditCommand: executeEditCommand2 } = await Promise.resolve().then(() => edit);
31797
+ await executeEditCommand2(
31798
+ {
31799
+ identifiers: items2.map((i) => i.id),
31800
+ format: config2.cli.edit.defaultFormat
31801
+ },
31802
+ context
31803
+ );
31804
+ break;
31805
+ }
31806
+ case "remove": {
31807
+ const {
31808
+ executeRemove: executeRemove2,
31809
+ confirmRemoveIfNeeded: confirmRemoveIfNeeded2,
31810
+ getFulltextAttachmentTypes: getFulltextAttachmentTypes2,
31811
+ formatRemoveOutput: formatRemoveOutput2
31812
+ } = await Promise.resolve().then(() => remove);
31813
+ for (const item of items2) {
31814
+ const hasFulltext = getFulltextAttachmentTypes2(item).length > 0;
31815
+ const confirmed = await confirmRemoveIfNeeded2(item, hasFulltext, false);
31816
+ if (!confirmed) {
31817
+ process.stderr.write("Cancelled.\n");
31818
+ continue;
31819
+ }
31820
+ const result = await executeRemove2(
31821
+ {
31822
+ identifier: item.id,
31823
+ fulltextDirectory: config2.attachments.directory,
31824
+ deleteFulltext: hasFulltext
31825
+ },
31826
+ context
31827
+ );
31828
+ process.stderr.write(`${formatRemoveOutput2(result, item.id)}
31829
+ `);
31830
+ }
31831
+ break;
31832
+ }
31833
+ }
31834
+ }
31616
31835
  async function serverStart(options) {
31617
31836
  const existingStatus = await serverStatus(options.portfilePath);
31618
31837
  if (existingStatus !== null) {
@@ -31906,7 +32125,7 @@ function formatUpdateOutput(result, identifier) {
31906
32125
  }
31907
32126
  async function executeInteractiveUpdate(context, config2) {
31908
32127
  const { withAlternateScreen: withAlternateScreen2 } = await Promise.resolve().then(() => alternateScreen);
31909
- const { selectReferencesOrExit } = await import("./reference-select-O0PY7CRU.js");
32128
+ const { selectReferencesOrExit } = await import("./reference-select-DcClzkw2.js");
31910
32129
  const allReferences = await context.library.getAll();
31911
32130
  const identifiers = await withAlternateScreen2(
31912
32131
  () => selectReferencesOrExit(allReferences, { multiSelect: false }, config2.cli.tui)
@@ -32021,6 +32240,231 @@ async function handleUpdateAction(identifierArg, file, options, globalOpts) {
32021
32240
  function collectSetOption(value, previous) {
32022
32241
  return previous.concat([value]);
32023
32242
  }
32243
+ function buildDoiUrl(doi) {
32244
+ return `https://doi.org/${doi}`;
32245
+ }
32246
+ function buildPubmedUrl(pmid) {
32247
+ return `https://pubmed.ncbi.nlm.nih.gov/${pmid}/`;
32248
+ }
32249
+ function buildPmcUrl(pmcid) {
32250
+ return `https://www.ncbi.nlm.nih.gov/pmc/articles/${pmcid}/`;
32251
+ }
32252
+ function resolveAllUrls(item) {
32253
+ const urls = [];
32254
+ if (item.DOI) {
32255
+ urls.push(buildDoiUrl(item.DOI));
32256
+ }
32257
+ if (item.URL) {
32258
+ urls.push(item.URL);
32259
+ }
32260
+ if (item.PMID) {
32261
+ urls.push(buildPubmedUrl(item.PMID));
32262
+ }
32263
+ if (item.PMCID) {
32264
+ urls.push(buildPmcUrl(item.PMCID));
32265
+ }
32266
+ const additionalUrls = item.custom?.additional_urls;
32267
+ if (additionalUrls && additionalUrls.length > 0) {
32268
+ urls.push(...additionalUrls);
32269
+ }
32270
+ return urls;
32271
+ }
32272
+ function resolveDefaultUrl(item) {
32273
+ if (item.DOI) {
32274
+ return buildDoiUrl(item.DOI);
32275
+ }
32276
+ if (item.URL) {
32277
+ return item.URL;
32278
+ }
32279
+ if (item.PMID) {
32280
+ return buildPubmedUrl(item.PMID);
32281
+ }
32282
+ if (item.PMCID) {
32283
+ return buildPmcUrl(item.PMCID);
32284
+ }
32285
+ const additionalUrls = item.custom?.additional_urls;
32286
+ if (additionalUrls && additionalUrls.length > 0) {
32287
+ return additionalUrls[0] ?? null;
32288
+ }
32289
+ return null;
32290
+ }
32291
+ function resolveUrlByType(item, type2) {
32292
+ switch (type2) {
32293
+ case "doi":
32294
+ return item.DOI ? buildDoiUrl(item.DOI) : null;
32295
+ case "url":
32296
+ return item.URL ?? null;
32297
+ case "pubmed":
32298
+ return item.PMID ? buildPubmedUrl(item.PMID) : null;
32299
+ case "pmcid":
32300
+ return item.PMCID ? buildPmcUrl(item.PMCID) : null;
32301
+ }
32302
+ }
32303
+ const url = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
32304
+ __proto__: null,
32305
+ resolveAllUrls,
32306
+ resolveDefaultUrl,
32307
+ resolveUrlByType
32308
+ }, Symbol.toStringTag, { value: "Module" }));
32309
+ function getUrlTypeFilter(options) {
32310
+ if (options.doi) return "doi";
32311
+ if (options.pubmed) return "pubmed";
32312
+ if (options.pmcid) return "pmcid";
32313
+ if (options.default) return "default";
32314
+ if (options.open) return "default";
32315
+ return null;
32316
+ }
32317
+ function getFilterLabel(filter) {
32318
+ switch (filter) {
32319
+ case "doi":
32320
+ return "DOI";
32321
+ case "pubmed":
32322
+ return "PubMed";
32323
+ case "pmcid":
32324
+ return "PMC";
32325
+ case "url":
32326
+ return "URL";
32327
+ case "default":
32328
+ return "default";
32329
+ }
32330
+ }
32331
+ function resolveUrlsForItem(item, id2, filter) {
32332
+ if (filter === null) {
32333
+ const urls = resolveAllUrls(item);
32334
+ if (urls.length === 0) {
32335
+ return { id: id2, urls: [], error: `No URLs available for ${id2}` };
32336
+ }
32337
+ return { id: id2, urls };
32338
+ }
32339
+ if (filter === "default") {
32340
+ const url22 = resolveDefaultUrl(item);
32341
+ if (!url22) {
32342
+ return { id: id2, urls: [], error: `No URLs available for ${id2}` };
32343
+ }
32344
+ return { id: id2, urls: [url22] };
32345
+ }
32346
+ const url2 = resolveUrlByType(item, filter);
32347
+ if (!url2) {
32348
+ return { id: id2, urls: [], error: `No ${getFilterLabel(filter)} URL for ${id2}` };
32349
+ }
32350
+ return { id: id2, urls: [url2] };
32351
+ }
32352
+ async function executeUrlCommand(identifiers, options, context) {
32353
+ const filter = getUrlTypeFilter(options);
32354
+ const findOptions = options.uuid ? { idType: "uuid" } : void 0;
32355
+ const results = [];
32356
+ for (const identifier of identifiers) {
32357
+ const item = await context.library.find(identifier, findOptions);
32358
+ if (!item) {
32359
+ results.push({ id: identifier, urls: [], error: `Reference not found: ${identifier}` });
32360
+ continue;
32361
+ }
32362
+ const result = resolveUrlsForItem(item, identifier, filter);
32363
+ results.push(result);
32364
+ }
32365
+ if (options.open) {
32366
+ const firstSuccess = results.find((r) => r.urls.length > 0);
32367
+ if (firstSuccess?.urls[0]) {
32368
+ try {
32369
+ await openWithSystemApp(firstSuccess.urls[0]);
32370
+ } catch (error) {
32371
+ return {
32372
+ results,
32373
+ openError: `Failed to open URL: ${error instanceof Error ? error.message : String(error)}`
32374
+ };
32375
+ }
32376
+ }
32377
+ }
32378
+ return { results };
32379
+ }
32380
+ function hasFilter(options) {
32381
+ return Boolean(options.default || options.doi || options.pubmed || options.pmcid || options.open);
32382
+ }
32383
+ function formatUrlOutput(result, options) {
32384
+ const successResults = result.results.filter((r) => r.urls.length > 0);
32385
+ if (successResults.length === 0) {
32386
+ return "";
32387
+ }
32388
+ const filtered = hasFilter(options);
32389
+ if (filtered || successResults.length === 1) {
32390
+ const lines2 = [];
32391
+ for (const r of successResults) {
32392
+ for (const url2 of r.urls) {
32393
+ lines2.push(url2);
32394
+ }
32395
+ }
32396
+ return lines2.join("\n");
32397
+ }
32398
+ const lines = [];
32399
+ for (const r of successResults) {
32400
+ for (const url2 of r.urls) {
32401
+ lines.push(`${r.id} ${url2}`);
32402
+ }
32403
+ }
32404
+ return lines.join("\n");
32405
+ }
32406
+ function formatUrlErrors(result) {
32407
+ const errorMessages = result.results.filter((r) => r.error).map((r) => `Error: ${r.error}`);
32408
+ if (result.openError) {
32409
+ errorMessages.push(`Error: ${result.openError}`);
32410
+ }
32411
+ return errorMessages.join("\n");
32412
+ }
32413
+ function getUrlExitCode(result) {
32414
+ if (result.openError) return ExitCode.ERROR;
32415
+ const hasSuccess = result.results.some((r) => r.urls.length > 0);
32416
+ const hasError = result.results.some((r) => r.error);
32417
+ if (hasSuccess) return ExitCode.SUCCESS;
32418
+ if (hasError) return ExitCode.ERROR;
32419
+ return ExitCode.SUCCESS;
32420
+ }
32421
+ async function executeInteractiveSelect(context, config2) {
32422
+ const { withAlternateScreen: withAlternateScreen2 } = await Promise.resolve().then(() => alternateScreen);
32423
+ const { selectReferencesOrExit } = await import("./reference-select-DcClzkw2.js");
32424
+ const allReferences = await context.library.getAll();
32425
+ const identifiers = await withAlternateScreen2(
32426
+ () => selectReferencesOrExit(allReferences, { multiSelect: false }, config2.cli.tui)
32427
+ );
32428
+ return identifiers[0] ?? "";
32429
+ }
32430
+ async function handleUrlAction(identifiers, options, globalOpts) {
32431
+ try {
32432
+ const config2 = await loadConfigWithOverrides({ ...globalOpts, ...options });
32433
+ const context = await createExecutionContext(config2, Library.load);
32434
+ let resolvedIdentifiers;
32435
+ let isTuiMode = false;
32436
+ if (identifiers && identifiers.length > 0) {
32437
+ resolvedIdentifiers = identifiers;
32438
+ } else if (isTTY()) {
32439
+ isTuiMode = true;
32440
+ const selected = await executeInteractiveSelect(context, config2);
32441
+ resolvedIdentifiers = [selected];
32442
+ } else {
32443
+ const stdinId = await readIdentifierFromStdin();
32444
+ if (!stdinId) {
32445
+ exitWithError("Identifier is required");
32446
+ return;
32447
+ }
32448
+ resolvedIdentifiers = [stdinId];
32449
+ }
32450
+ const result = await executeUrlCommand(resolvedIdentifiers, options, context);
32451
+ const output = formatUrlOutput(result, options);
32452
+ if (output) {
32453
+ const clipboardEnabled = resolveClipboardEnabled(globalOpts, config2, isTuiMode);
32454
+ await writeOutputWithClipboard(output, clipboardEnabled, config2.logLevel === "silent");
32455
+ }
32456
+ const errors2 = formatUrlErrors(result);
32457
+ if (errors2) {
32458
+ process.stderr.write(`${errors2}
32459
+ `);
32460
+ }
32461
+ setExitCode(getUrlExitCode(result));
32462
+ } catch (error) {
32463
+ process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
32464
+ `);
32465
+ setExitCode(ExitCode.INTERNAL_ERROR);
32466
+ }
32467
+ }
32024
32468
  const SEARCH_SORT_FIELDS = searchSortFieldSchema.options;
32025
32469
  const SORT_ORDERS = sortOrderSchema.options;
32026
32470
  const CITATION_OUTPUT_FORMATS = ["text", "html", "rtf"];
@@ -32305,7 +32749,7 @@ function registerCompletionCommand(program) {
32305
32749
  function createProgram() {
32306
32750
  const program = new Command();
32307
32751
  program.name("reference-manager").version(packageJson.version).description(packageJson.description);
32308
- program.option("--library <path>", "Override library file path").option("--log-level <level>", "Override log level (silent|info|debug)").option("--config <path>", "Use specific config file").option("--quiet", "Suppress all non-error output").option("--verbose", "Enable verbose output").option("--no-backup", "Disable backup creation").option("--backup-dir <path>", "Override backup directory").option("--attachments-dir <path>", "Override attachments directory");
32752
+ program.option("--library <path>", "Override library file path").option("--log-level <level>", "Override log level (silent|info|debug)").option("--config <path>", "Use specific config file").option("--quiet", "Suppress all non-error output").option("--verbose", "Enable verbose output").option("--no-backup", "Disable backup creation").option("--backup-dir <path>", "Override backup directory").option("--attachments-dir <path>", "Override attachments directory").option("--clipboard", "Copy output to system clipboard").option("--no-clipboard", "Disable clipboard copy");
32309
32753
  registerListCommand(program);
32310
32754
  registerSearchCommand(program);
32311
32755
  registerExportCommand(program);
@@ -32318,6 +32762,7 @@ function createProgram() {
32318
32762
  registerFulltextCommand(program);
32319
32763
  registerAttachCommand(program);
32320
32764
  registerMcpCommand(program);
32765
+ registerUrlCommand(program);
32321
32766
  registerConfigCommand(program);
32322
32767
  registerCompletionCommand(program);
32323
32768
  return program;
@@ -32326,12 +32771,12 @@ async function handleListAction(options, program) {
32326
32771
  try {
32327
32772
  const globalOpts = program.opts();
32328
32773
  const config2 = await loadConfigWithOverrides({ ...globalOpts, ...options });
32774
+ const clipboardEnabled = resolveClipboardEnabled(globalOpts, config2, false);
32329
32775
  const context = await createExecutionContext(config2, Library.load);
32330
32776
  const result = await executeList(options, context);
32331
- const output = formatListOutput(result, options);
32777
+ const output = formatListOutput(result, options, config2.citation.defaultKeyFormat);
32332
32778
  if (output) {
32333
- process.stdout.write(`${output}
32334
- `);
32779
+ await writeOutputWithClipboard(output, clipboardEnabled, config2.logLevel === "silent");
32335
32780
  }
32336
32781
  setExitCode(ExitCode.SUCCESS);
32337
32782
  } catch (error) {
@@ -32339,7 +32784,10 @@ async function handleListAction(options, program) {
32339
32784
  }
32340
32785
  }
32341
32786
  function registerListCommand(program) {
32342
- 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) => {
32787
+ program.command("list").description("List all references in the library").option(
32788
+ "-o, --output <format>",
32789
+ "Output format: pretty|json|bibtex|ids|uuid|pandoc-key|latex-key"
32790
+ ).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("-k, --key", "Output citation keys (uses citation.default_key_format config)").option("--pandoc-key", "Alias for --output pandoc-key").option("--latex-key", "Alias for --output latex-key").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) => {
32343
32791
  await handleListAction(options, program);
32344
32792
  });
32345
32793
  }
@@ -32347,12 +32795,12 @@ async function handleExportAction(ids, options, program) {
32347
32795
  try {
32348
32796
  const globalOpts = program.opts();
32349
32797
  const config2 = await loadConfigWithOverrides({ ...globalOpts, ...options });
32798
+ const clipboardEnabled = resolveClipboardEnabled(globalOpts, config2, false);
32350
32799
  const context = await createExecutionContext(config2, Library.load);
32351
32800
  const result = await executeExport({ ...options, ids }, context);
32352
32801
  const output = formatExportOutput(result, { ...options, ids });
32353
32802
  if (output) {
32354
- process.stdout.write(`${output}
32355
- `);
32803
+ await writeOutputWithClipboard(output, clipboardEnabled, config2.logLevel === "silent");
32356
32804
  }
32357
32805
  if (result.notFound.length > 0) {
32358
32806
  for (const id2 of result.notFound) {
@@ -32380,17 +32828,25 @@ async function handleSearchAction(query, options, program) {
32380
32828
  if (options.tui) {
32381
32829
  const result2 = await executeInteractiveSearch({ ...options, query }, context, config2);
32382
32830
  if (result2.output) {
32383
- process.stdout.write(`${result2.output}
32384
- `);
32831
+ const clipboardEnabled = resolveClipboardEnabled(globalOpts, config2, true);
32832
+ await writeOutputWithClipboard(
32833
+ result2.output,
32834
+ clipboardEnabled,
32835
+ config2.logLevel === "silent"
32836
+ );
32385
32837
  }
32386
32838
  setExitCode(ExitCode.SUCCESS);
32387
32839
  return;
32388
32840
  }
32389
32841
  const result = await executeSearch({ ...options, query }, context);
32390
- const output = formatSearchOutput(result, { ...options, query });
32842
+ const output = formatSearchOutput(
32843
+ result,
32844
+ { ...options, query },
32845
+ config2.citation.defaultKeyFormat
32846
+ );
32391
32847
  if (output) {
32392
- process.stdout.write(`${output}
32393
- `);
32848
+ const clipboardEnabled = resolveClipboardEnabled(globalOpts, config2, false);
32849
+ await writeOutputWithClipboard(output, clipboardEnabled, config2.logLevel === "silent");
32394
32850
  }
32395
32851
  setExitCode(ExitCode.SUCCESS);
32396
32852
  } catch (error) {
@@ -32400,7 +32856,10 @@ async function handleSearchAction(query, options, program) {
32400
32856
  }
32401
32857
  }
32402
32858
  function registerSearchCommand(program) {
32403
- 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) => {
32859
+ program.command("search").description("Search references").argument("[query]", "Search query (required unless using --tui)").option("-t, --tui", "Enable TUI (interactive) search mode").option(
32860
+ "-o, --output <format>",
32861
+ "Output format: pretty|json|bibtex|ids|uuid|pandoc-key|latex-key"
32862
+ ).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("-k, --key", "Output citation keys (uses citation.default_key_format config)").option("--pandoc-key", "Alias for --output pandoc-key").option("--latex-key", "Alias for --output latex-key").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) => {
32404
32863
  if (!options.tui && !query) {
32405
32864
  process.stderr.write("Error: Search query is required unless using --tui\n");
32406
32865
  setExitCode(ExitCode.ERROR);
@@ -32616,6 +33075,9 @@ function registerMcpCommand(program) {
32616
33075
  }
32617
33076
  function registerAttachCommand(program) {
32618
33077
  const attachCmd = program.command("attach").description("Manage file attachments for references");
33078
+ attachCmd.argument("[identifier]", "Citation key or UUID (interactive selection if omitted)").option("--uuid", "Interpret identifier as UUID").action(async (identifier, options) => {
33079
+ await handleAttachOpenAction(identifier, void 0, options, program.opts());
33080
+ });
32619
33081
  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) => {
32620
33082
  await handleAttachOpenAction(identifier, filename, options, program.opts());
32621
33083
  });
@@ -32653,6 +33115,12 @@ function registerFulltextCommand(program) {
32653
33115
  await handleFulltextOpenAction(identifier, options, program.opts());
32654
33116
  });
32655
33117
  }
33118
+ function registerUrlCommand(program) {
33119
+ program.command("url").description("Show URLs for references").argument("[identifiers...]", "Reference identifiers").option("--default", "Show only the best URL by priority").option("--doi", "Show only DOI URL").option("--pubmed", "Show only PubMed URL").option("--pmcid", "Show only PMC URL").option("--open", "Open URL in browser").option("--uuid", "Interpret identifiers as UUIDs").action(async (identifiers, options) => {
33120
+ const globalOpts = program.opts();
33121
+ await handleUrlAction(identifiers.length > 0 ? identifiers : void 0, options, globalOpts);
33122
+ });
33123
+ }
32656
33124
  async function main(argv) {
32657
33125
  const program = createProgram();
32658
33126
  if (process.env.COMP_LINE) {
@@ -32670,6 +33138,7 @@ async function main(argv) {
32670
33138
  export {
32671
33139
  Select as S,
32672
33140
  addAttachment as a,
33141
+ stringify as b,
32673
33142
  createProgram as c,
32674
33143
  detachAttachment as d,
32675
33144
  formatBibtex as f,
@@ -32681,4 +33150,4 @@ export {
32681
33150
  restoreStdinAfterInk as r,
32682
33151
  syncAttachments as s
32683
33152
  };
32684
- //# sourceMappingURL=index-wb8zgPJ0.js.map
33153
+ //# sourceMappingURL=index-BPhIwqHO.js.map