docrev 0.8.1 → 0.8.5

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 (306) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/PLAN-tables-and-postprocess.md +850 -0
  3. package/README.md +33 -0
  4. package/bin/rev.js +12 -131
  5. package/bin/rev.ts +145 -0
  6. package/dist/bin/rev.d.ts +9 -0
  7. package/dist/bin/rev.d.ts.map +1 -0
  8. package/dist/bin/rev.js +118 -0
  9. package/dist/bin/rev.js.map +1 -0
  10. package/dist/lib/annotations.d.ts +91 -0
  11. package/dist/lib/annotations.d.ts.map +1 -0
  12. package/dist/lib/annotations.js +554 -0
  13. package/dist/lib/annotations.js.map +1 -0
  14. package/dist/lib/build.d.ts +171 -0
  15. package/dist/lib/build.d.ts.map +1 -0
  16. package/dist/lib/build.js +755 -0
  17. package/dist/lib/build.js.map +1 -0
  18. package/dist/lib/citations.d.ts +34 -0
  19. package/dist/lib/citations.d.ts.map +1 -0
  20. package/dist/lib/citations.js +140 -0
  21. package/dist/lib/citations.js.map +1 -0
  22. package/dist/lib/commands/build.d.ts +13 -0
  23. package/dist/lib/commands/build.d.ts.map +1 -0
  24. package/dist/lib/commands/build.js +678 -0
  25. package/dist/lib/commands/build.js.map +1 -0
  26. package/dist/lib/commands/citations.d.ts +11 -0
  27. package/dist/lib/commands/citations.d.ts.map +1 -0
  28. package/dist/lib/commands/citations.js +428 -0
  29. package/dist/lib/commands/citations.js.map +1 -0
  30. package/dist/lib/commands/comments.d.ts +11 -0
  31. package/dist/lib/commands/comments.d.ts.map +1 -0
  32. package/dist/lib/commands/comments.js +883 -0
  33. package/dist/lib/commands/comments.js.map +1 -0
  34. package/dist/lib/commands/context.d.ts +35 -0
  35. package/dist/lib/commands/context.d.ts.map +1 -0
  36. package/dist/lib/commands/context.js +59 -0
  37. package/dist/lib/commands/context.js.map +1 -0
  38. package/dist/lib/commands/core.d.ts +11 -0
  39. package/dist/lib/commands/core.d.ts.map +1 -0
  40. package/dist/lib/commands/core.js +246 -0
  41. package/dist/lib/commands/core.js.map +1 -0
  42. package/dist/lib/commands/doi.d.ts +11 -0
  43. package/dist/lib/commands/doi.d.ts.map +1 -0
  44. package/dist/lib/commands/doi.js +373 -0
  45. package/dist/lib/commands/doi.js.map +1 -0
  46. package/dist/lib/commands/history.d.ts +11 -0
  47. package/dist/lib/commands/history.d.ts.map +1 -0
  48. package/dist/lib/commands/history.js +245 -0
  49. package/dist/lib/commands/history.js.map +1 -0
  50. package/dist/lib/commands/index.d.ts +28 -0
  51. package/dist/lib/commands/index.d.ts.map +1 -0
  52. package/dist/lib/commands/index.js +35 -0
  53. package/dist/lib/commands/index.js.map +1 -0
  54. package/dist/lib/commands/init.d.ts +11 -0
  55. package/dist/lib/commands/init.d.ts.map +1 -0
  56. package/dist/lib/commands/init.js +209 -0
  57. package/dist/lib/commands/init.js.map +1 -0
  58. package/dist/lib/commands/response.d.ts +11 -0
  59. package/dist/lib/commands/response.d.ts.map +1 -0
  60. package/dist/lib/commands/response.js +317 -0
  61. package/dist/lib/commands/response.js.map +1 -0
  62. package/dist/lib/commands/sections.d.ts +11 -0
  63. package/dist/lib/commands/sections.d.ts.map +1 -0
  64. package/dist/lib/commands/sections.js +1071 -0
  65. package/dist/lib/commands/sections.js.map +1 -0
  66. package/dist/lib/commands/utilities.d.ts +19 -0
  67. package/dist/lib/commands/utilities.d.ts.map +1 -0
  68. package/dist/lib/commands/utilities.js +2009 -0
  69. package/dist/lib/commands/utilities.js.map +1 -0
  70. package/dist/lib/comment-realign.d.ts +50 -0
  71. package/dist/lib/comment-realign.d.ts.map +1 -0
  72. package/dist/lib/comment-realign.js +372 -0
  73. package/dist/lib/comment-realign.js.map +1 -0
  74. package/dist/lib/config.d.ts +41 -0
  75. package/dist/lib/config.d.ts.map +1 -0
  76. package/dist/lib/config.js +76 -0
  77. package/dist/lib/config.js.map +1 -0
  78. package/dist/lib/crossref.d.ts +108 -0
  79. package/dist/lib/crossref.d.ts.map +1 -0
  80. package/dist/lib/crossref.js +597 -0
  81. package/dist/lib/crossref.js.map +1 -0
  82. package/dist/lib/dependencies.d.ts +30 -0
  83. package/dist/lib/dependencies.d.ts.map +1 -0
  84. package/dist/lib/dependencies.js +95 -0
  85. package/dist/lib/dependencies.js.map +1 -0
  86. package/dist/lib/doi-cache.d.ts +29 -0
  87. package/dist/lib/doi-cache.d.ts.map +1 -0
  88. package/dist/lib/doi-cache.js +104 -0
  89. package/dist/lib/doi-cache.js.map +1 -0
  90. package/dist/lib/doi.d.ts +65 -0
  91. package/dist/lib/doi.d.ts.map +1 -0
  92. package/dist/lib/doi.js +710 -0
  93. package/dist/lib/doi.js.map +1 -0
  94. package/dist/lib/equations.d.ts +61 -0
  95. package/dist/lib/equations.d.ts.map +1 -0
  96. package/dist/lib/equations.js +445 -0
  97. package/dist/lib/equations.js.map +1 -0
  98. package/dist/lib/errors.d.ts +60 -0
  99. package/dist/lib/errors.d.ts.map +1 -0
  100. package/dist/lib/errors.js +303 -0
  101. package/dist/lib/errors.js.map +1 -0
  102. package/dist/lib/format.d.ts +104 -0
  103. package/dist/lib/format.d.ts.map +1 -0
  104. package/dist/lib/format.js +416 -0
  105. package/dist/lib/format.js.map +1 -0
  106. package/dist/lib/git.d.ts +88 -0
  107. package/dist/lib/git.d.ts.map +1 -0
  108. package/dist/lib/git.js +304 -0
  109. package/dist/lib/git.js.map +1 -0
  110. package/dist/lib/grammar.d.ts +62 -0
  111. package/dist/lib/grammar.d.ts.map +1 -0
  112. package/dist/lib/grammar.js +244 -0
  113. package/dist/lib/grammar.js.map +1 -0
  114. package/dist/lib/image-registry.d.ts +68 -0
  115. package/dist/lib/image-registry.d.ts.map +1 -0
  116. package/dist/lib/image-registry.js +112 -0
  117. package/dist/lib/image-registry.js.map +1 -0
  118. package/dist/lib/import.d.ts +184 -0
  119. package/dist/lib/import.d.ts.map +1 -0
  120. package/dist/lib/import.js +1581 -0
  121. package/dist/lib/import.js.map +1 -0
  122. package/dist/lib/journals.d.ts +55 -0
  123. package/dist/lib/journals.d.ts.map +1 -0
  124. package/dist/lib/journals.js +417 -0
  125. package/dist/lib/journals.js.map +1 -0
  126. package/dist/lib/merge.d.ts +138 -0
  127. package/dist/lib/merge.d.ts.map +1 -0
  128. package/dist/lib/merge.js +603 -0
  129. package/dist/lib/merge.js.map +1 -0
  130. package/dist/lib/orcid.d.ts +36 -0
  131. package/dist/lib/orcid.d.ts.map +1 -0
  132. package/dist/lib/orcid.js +117 -0
  133. package/dist/lib/orcid.js.map +1 -0
  134. package/dist/lib/pdf-comments.d.ts +95 -0
  135. package/dist/lib/pdf-comments.d.ts.map +1 -0
  136. package/dist/lib/pdf-comments.js +192 -0
  137. package/dist/lib/pdf-comments.js.map +1 -0
  138. package/dist/lib/pdf-import.d.ts +118 -0
  139. package/dist/lib/pdf-import.d.ts.map +1 -0
  140. package/dist/lib/pdf-import.js +397 -0
  141. package/dist/lib/pdf-import.js.map +1 -0
  142. package/dist/lib/plugins.d.ts +76 -0
  143. package/dist/lib/plugins.d.ts.map +1 -0
  144. package/dist/lib/plugins.js +235 -0
  145. package/dist/lib/plugins.js.map +1 -0
  146. package/dist/lib/postprocess.d.ts +42 -0
  147. package/dist/lib/postprocess.d.ts.map +1 -0
  148. package/dist/lib/postprocess.js +138 -0
  149. package/dist/lib/postprocess.js.map +1 -0
  150. package/dist/lib/pptx-template.d.ts +59 -0
  151. package/dist/lib/pptx-template.d.ts.map +1 -0
  152. package/dist/lib/pptx-template.js +613 -0
  153. package/dist/lib/pptx-template.js.map +1 -0
  154. package/dist/lib/pptx-themes.d.ts +80 -0
  155. package/dist/lib/pptx-themes.d.ts.map +1 -0
  156. package/dist/lib/pptx-themes.js +818 -0
  157. package/dist/lib/pptx-themes.js.map +1 -0
  158. package/dist/lib/protect-restore.d.ts +137 -0
  159. package/dist/lib/protect-restore.d.ts.map +1 -0
  160. package/dist/lib/protect-restore.js +394 -0
  161. package/dist/lib/protect-restore.js.map +1 -0
  162. package/dist/lib/rate-limiter.d.ts +27 -0
  163. package/dist/lib/rate-limiter.d.ts.map +1 -0
  164. package/dist/lib/rate-limiter.js +79 -0
  165. package/dist/lib/rate-limiter.js.map +1 -0
  166. package/dist/lib/response.d.ts +41 -0
  167. package/dist/lib/response.d.ts.map +1 -0
  168. package/dist/lib/response.js +150 -0
  169. package/dist/lib/response.js.map +1 -0
  170. package/dist/lib/review.d.ts +35 -0
  171. package/dist/lib/review.d.ts.map +1 -0
  172. package/dist/lib/review.js +263 -0
  173. package/dist/lib/review.js.map +1 -0
  174. package/dist/lib/schema.d.ts +66 -0
  175. package/dist/lib/schema.d.ts.map +1 -0
  176. package/dist/lib/schema.js +339 -0
  177. package/dist/lib/schema.js.map +1 -0
  178. package/dist/lib/scientific-words.d.ts +6 -0
  179. package/dist/lib/scientific-words.d.ts.map +1 -0
  180. package/dist/lib/scientific-words.js +66 -0
  181. package/dist/lib/scientific-words.js.map +1 -0
  182. package/dist/lib/sections.d.ts +40 -0
  183. package/dist/lib/sections.d.ts.map +1 -0
  184. package/dist/lib/sections.js +288 -0
  185. package/dist/lib/sections.js.map +1 -0
  186. package/dist/lib/slides.d.ts +86 -0
  187. package/dist/lib/slides.d.ts.map +1 -0
  188. package/dist/lib/slides.js +676 -0
  189. package/dist/lib/slides.js.map +1 -0
  190. package/dist/lib/spelling.d.ts +76 -0
  191. package/dist/lib/spelling.d.ts.map +1 -0
  192. package/dist/lib/spelling.js +272 -0
  193. package/dist/lib/spelling.js.map +1 -0
  194. package/dist/lib/templates.d.ts +30 -0
  195. package/dist/lib/templates.d.ts.map +1 -0
  196. package/dist/lib/templates.js +504 -0
  197. package/dist/lib/templates.js.map +1 -0
  198. package/dist/lib/themes.d.ts +85 -0
  199. package/dist/lib/themes.d.ts.map +1 -0
  200. package/dist/lib/themes.js +652 -0
  201. package/dist/lib/themes.js.map +1 -0
  202. package/dist/lib/trackchanges.d.ts +51 -0
  203. package/dist/lib/trackchanges.d.ts.map +1 -0
  204. package/dist/lib/trackchanges.js +202 -0
  205. package/dist/lib/trackchanges.js.map +1 -0
  206. package/dist/lib/tui.d.ts +76 -0
  207. package/dist/lib/tui.d.ts.map +1 -0
  208. package/dist/lib/tui.js +377 -0
  209. package/dist/lib/tui.js.map +1 -0
  210. package/dist/lib/types.d.ts +447 -0
  211. package/dist/lib/types.d.ts.map +1 -0
  212. package/dist/lib/types.js +6 -0
  213. package/dist/lib/types.js.map +1 -0
  214. package/dist/lib/undo.d.ts +57 -0
  215. package/dist/lib/undo.d.ts.map +1 -0
  216. package/dist/lib/undo.js +185 -0
  217. package/dist/lib/undo.js.map +1 -0
  218. package/dist/lib/utils.d.ts +16 -0
  219. package/dist/lib/utils.d.ts.map +1 -0
  220. package/dist/lib/utils.js +40 -0
  221. package/dist/lib/utils.js.map +1 -0
  222. package/dist/lib/variables.d.ts +42 -0
  223. package/dist/lib/variables.d.ts.map +1 -0
  224. package/dist/lib/variables.js +141 -0
  225. package/dist/lib/variables.js.map +1 -0
  226. package/dist/lib/word.d.ts +80 -0
  227. package/dist/lib/word.d.ts.map +1 -0
  228. package/dist/lib/word.js +360 -0
  229. package/dist/lib/word.js.map +1 -0
  230. package/dist/lib/wordcomments.d.ts +51 -0
  231. package/dist/lib/wordcomments.d.ts.map +1 -0
  232. package/dist/lib/wordcomments.js +587 -0
  233. package/dist/lib/wordcomments.js.map +1 -0
  234. package/eslint.config.js +27 -0
  235. package/lib/annotations.ts +622 -0
  236. package/lib/apply-buildup-colors.py +88 -0
  237. package/lib/build.ts +1013 -0
  238. package/lib/{citations.js → citations.ts} +38 -27
  239. package/lib/commands/{build.js → build.ts} +80 -27
  240. package/lib/commands/{citations.js → citations.ts} +36 -18
  241. package/lib/commands/{comments.js → comments.ts} +187 -54
  242. package/lib/commands/{context.js → context.ts} +18 -8
  243. package/lib/commands/{core.js → core.ts} +34 -20
  244. package/lib/commands/{doi.js → doi.ts} +32 -16
  245. package/lib/commands/{history.js → history.ts} +25 -12
  246. package/lib/commands/{index.js → index.ts} +9 -5
  247. package/lib/commands/{init.js → init.ts} +20 -8
  248. package/lib/commands/{response.js → response.ts} +47 -20
  249. package/lib/commands/{sections.js → sections.ts} +273 -68
  250. package/lib/commands/{utilities.js → utilities.ts} +338 -158
  251. package/lib/{comment-realign.js → comment-realign.ts} +117 -45
  252. package/lib/config.ts +84 -0
  253. package/lib/{crossref.js → crossref.ts} +213 -138
  254. package/lib/dependencies.ts +106 -0
  255. package/lib/doi-cache.ts +115 -0
  256. package/lib/{doi.js → doi.ts} +115 -281
  257. package/lib/{equations.js → equations.ts} +60 -64
  258. package/lib/{errors.js → errors.ts} +56 -48
  259. package/lib/{format.js → format.ts} +137 -63
  260. package/lib/{git.js → git.ts} +66 -63
  261. package/lib/{grammar.js → grammar.ts} +45 -32
  262. package/lib/image-registry.ts +180 -0
  263. package/lib/import.ts +2060 -0
  264. package/lib/journals.ts +505 -0
  265. package/lib/{merge.js → merge.ts} +185 -135
  266. package/lib/{orcid.js → orcid.ts} +17 -22
  267. package/lib/{pdf-comments.js → pdf-comments.ts} +76 -18
  268. package/lib/{pdf-import.js → pdf-import.ts} +148 -70
  269. package/lib/{plugins.js → plugins.ts} +82 -39
  270. package/lib/postprocess.ts +188 -0
  271. package/lib/pptx-color-filter.lua +37 -0
  272. package/lib/pptx-template.ts +625 -0
  273. package/lib/pptx-themes/academic.pptx +0 -0
  274. package/lib/pptx-themes/corporate.pptx +0 -0
  275. package/lib/pptx-themes/dark.pptx +0 -0
  276. package/lib/pptx-themes/default.pptx +0 -0
  277. package/lib/pptx-themes/minimal.pptx +0 -0
  278. package/lib/pptx-themes/plant.pptx +0 -0
  279. package/lib/pptx-themes.ts +896 -0
  280. package/lib/protect-restore.ts +516 -0
  281. package/lib/rate-limiter.ts +94 -0
  282. package/lib/{response.js → response.ts} +36 -21
  283. package/lib/{review.js → review.ts} +53 -43
  284. package/lib/{schema.js → schema.ts} +70 -25
  285. package/lib/{sections.js → sections.ts} +71 -76
  286. package/lib/slides.ts +793 -0
  287. package/lib/{spelling.js → spelling.ts} +43 -59
  288. package/lib/{templates.js → templates.ts} +20 -17
  289. package/lib/themes.ts +742 -0
  290. package/lib/{trackchanges.js → trackchanges.ts} +52 -23
  291. package/lib/types.ts +509 -0
  292. package/lib/{undo.js → undo.ts} +75 -52
  293. package/lib/utils.ts +41 -0
  294. package/lib/{variables.js → variables.ts} +60 -54
  295. package/lib/word.ts +428 -0
  296. package/lib/{wordcomments.js → wordcomments.ts} +94 -40
  297. package/package.json +15 -5
  298. package/skill/REFERENCE.md +67 -0
  299. package/tsconfig.json +26 -0
  300. package/lib/annotations.js +0 -414
  301. package/lib/build.js +0 -639
  302. package/lib/config.js +0 -79
  303. package/lib/import.js +0 -1145
  304. package/lib/journals.js +0 -629
  305. package/lib/word.js +0 -225
  306. /package/lib/{scientific-words.js → scientific-words.ts} +0 -0
@@ -5,15 +5,17 @@
5
5
 
6
6
  import * as fs from 'fs';
7
7
  import * as path from 'path';
8
+ import type { Citation, CitationValidation, CitationStats } from './types.js';
8
9
 
9
10
  /**
10
11
  * Extract all citation keys from markdown text
11
12
  * Handles: [@Key], [@Key1; @Key2], @Key (inline)
12
- * @param {string} text
13
- * @returns {Array<{key: string, line: number, file: string}>}
13
+ * @param text - Markdown text to parse
14
+ * @param file - Optional filename for context
15
+ * @returns Array of citation objects
14
16
  */
15
- export function extractCitations(text, file = '') {
16
- const citations = [];
17
+ export function extractCitations(text: string, file: string = ''): Citation[] {
18
+ const citations: Citation[] = [];
17
19
  const lines = text.split('\n');
18
20
 
19
21
  // Pattern for bracketed citations: [@Key] or [@Key1; @Key2]
@@ -23,15 +25,18 @@ export function extractCitations(text, file = '') {
23
25
 
24
26
  for (let lineNum = 0; lineNum < lines.length; lineNum++) {
25
27
  const line = lines[lineNum];
28
+ if (!line) continue;
26
29
 
27
30
  // Skip code blocks and comments
28
31
  if (line.trim().startsWith('```') || line.trim().startsWith('<!--')) continue;
29
32
 
30
33
  // Bracketed citations
31
- let match;
34
+ let match: RegExpExecArray | null;
32
35
  while ((match = bracketPattern.exec(line)) !== null) {
33
36
  // Split by ; for multiple citations
34
- const keys = match[1].split(';').map(k => k.trim().replace(/^@/, ''));
37
+ const citationGroup = match[1];
38
+ if (!citationGroup) continue;
39
+ const keys = citationGroup.split(';').map(k => k.trim().replace(/^@/, ''));
35
40
  for (const key of keys) {
36
41
  if (key) {
37
42
  citations.push({ key, line: lineNum + 1, file });
@@ -42,7 +47,10 @@ export function extractCitations(text, file = '') {
42
47
  // Inline citations (reset lastIndex)
43
48
  inlinePattern.lastIndex = 0;
44
49
  while ((match = inlinePattern.exec(line)) !== null) {
45
- citations.push({ key: match[1], line: lineNum + 1, file });
50
+ const citationKey = match[1];
51
+ if (citationKey) {
52
+ citations.push({ key: citationKey, line: lineNum + 1, file });
53
+ }
46
54
  }
47
55
  }
48
56
 
@@ -51,11 +59,11 @@ export function extractCitations(text, file = '') {
51
59
 
52
60
  /**
53
61
  * Parse .bib file and extract all entry keys
54
- * @param {string} bibPath
55
- * @returns {Set<string>}
62
+ * @param bibPath - Path to bibliography file
63
+ * @returns Set of citation keys found in the bib file
56
64
  */
57
- export function parseBibFile(bibPath) {
58
- const keys = new Set();
65
+ export function parseBibFile(bibPath: string): Set<string> {
66
+ const keys = new Set<string>();
59
67
 
60
68
  if (!fs.existsSync(bibPath)) {
61
69
  return keys;
@@ -66,9 +74,12 @@ export function parseBibFile(bibPath) {
66
74
  // Pattern for bib entries: @type{key,
67
75
  const entryPattern = /@\w+\s*\{\s*([^,\s]+)\s*,/g;
68
76
 
69
- let match;
77
+ let match: RegExpExecArray | null;
70
78
  while ((match = entryPattern.exec(content)) !== null) {
71
- keys.add(match[1]);
79
+ const key = match[1];
80
+ if (key) {
81
+ keys.add(key);
82
+ }
72
83
  }
73
84
 
74
85
  return keys;
@@ -76,13 +87,13 @@ export function parseBibFile(bibPath) {
76
87
 
77
88
  /**
78
89
  * Validate citations against bib file
79
- * @param {string[]} mdFiles - Markdown files to check
80
- * @param {string} bibPath - Path to .bib file
81
- * @returns {{valid: Array, missing: Array, unused: Array, duplicates: Array}}
90
+ * @param mdFiles - Markdown files to check
91
+ * @param bibPath - Path to .bib file
92
+ * @returns Validation result with valid, missing, unused, and duplicate citations
82
93
  */
83
- export function validateCitations(mdFiles, bibPath) {
94
+ export function validateCitations(mdFiles: string[], bibPath: string): CitationValidation {
84
95
  // Collect all citations from markdown
85
- const allCitations = [];
96
+ const allCitations: Citation[] = [];
86
97
  for (const file of mdFiles) {
87
98
  if (!fs.existsSync(file)) continue;
88
99
  const text = fs.readFileSync(file, 'utf-8');
@@ -94,10 +105,10 @@ export function validateCitations(mdFiles, bibPath) {
94
105
  const bibKeys = parseBibFile(bibPath);
95
106
 
96
107
  // Categorize
97
- const valid = [];
98
- const missing = [];
99
- const citedKeys = new Set();
100
- const keyOccurrences = new Map();
108
+ const valid: Citation[] = [];
109
+ const missing: Citation[] = [];
110
+ const citedKeys = new Set<string>();
111
+ const keyOccurrences = new Map<string, Citation[]>();
101
112
 
102
113
  for (const citation of allCitations) {
103
114
  citedKeys.add(citation.key);
@@ -106,7 +117,7 @@ export function validateCitations(mdFiles, bibPath) {
106
117
  if (!keyOccurrences.has(citation.key)) {
107
118
  keyOccurrences.set(citation.key, []);
108
119
  }
109
- keyOccurrences.get(citation.key).push(citation);
120
+ keyOccurrences.get(citation.key)!.push(citation);
110
121
 
111
122
  if (bibKeys.has(citation.key)) {
112
123
  valid.push(citation);
@@ -128,11 +139,11 @@ export function validateCitations(mdFiles, bibPath) {
128
139
 
129
140
  /**
130
141
  * Get citation statistics
131
- * @param {string[]} mdFiles
132
- * @param {string} bibPath
133
- * @returns {object}
142
+ * @param mdFiles - Markdown files to analyze
143
+ * @param bibPath - Path to bibliography file
144
+ * @returns Statistics object
134
145
  */
135
- export function getCitationStats(mdFiles, bibPath) {
146
+ export function getCitationStats(mdFiles: string[], bibPath: string): CitationStats {
136
147
  const result = validateCitations(mdFiles, bibPath);
137
148
  const bibKeys = parseBibFile(bibPath);
138
149
 
@@ -23,13 +23,40 @@ import {
23
23
  formatBuildResults,
24
24
  getUserName,
25
25
  } from './context.js';
26
+ import type { Command } from 'commander';
27
+ import * as readline from 'readline';
28
+
29
+ interface RefsOptions {
30
+ dir: string;
31
+ }
32
+
33
+ interface MigrateOptions {
34
+ dir: string;
35
+ auto?: boolean;
36
+ dryRun?: boolean;
37
+ }
38
+
39
+ interface InstallOptions {
40
+ check?: boolean;
41
+ }
42
+
43
+ interface BuildOptions {
44
+ dir: string;
45
+ crossref?: boolean;
46
+ toc?: boolean;
47
+ showChanges?: boolean;
48
+ dual?: boolean;
49
+ reference?: string;
50
+ theme?: string;
51
+ colortheme?: string;
52
+ aspectratio?: string;
53
+ verbose?: boolean;
54
+ }
26
55
 
27
56
  /**
28
57
  * Register build commands with the program
29
- * @param {import('commander').Command} program
30
- * @param {object} pkg - Package.json object for version info
31
58
  */
32
- export function register(program, pkg) {
59
+ export function register(program: Command, pkg?: { version?: string }): void {
33
60
  // ==========================================================================
34
61
  // REFS command - Show figure/table reference status
35
62
  // ==========================================================================
@@ -39,7 +66,7 @@ export function register(program, pkg) {
39
66
  .description('Show figure/table reference registry and status')
40
67
  .argument('[file]', 'Optional file to analyze for references')
41
68
  .option('-d, --dir <directory>', 'Directory to scan for anchors', '.')
42
- .action((file, options) => {
69
+ .action((file: string | undefined, options: RefsOptions) => {
43
70
  const dir = path.resolve(options.dir);
44
71
 
45
72
  if (!fs.existsSync(dir)) {
@@ -103,7 +130,7 @@ export function register(program, pkg) {
103
130
  .option('-d, --dir <directory>', 'Directory for registry', '.')
104
131
  .option('--auto', 'Auto-convert without prompting')
105
132
  .option('--dry-run', 'Preview without saving')
106
- .action(async (file, options) => {
133
+ .action(async (file: string, options: MigrateOptions) => {
107
134
  if (!fs.existsSync(file)) {
108
135
  console.error(chalk.red(`File not found: ${file}`));
109
136
  process.exit(1);
@@ -141,8 +168,7 @@ export function register(program, pkg) {
141
168
  console.log(chalk.green(`\nConverted ${conversions.length} reference(s) in ${file}`));
142
169
  }
143
170
  } else {
144
- const rl = await import('readline');
145
- const readline = rl.createInterface({
171
+ const rl = readline.createInterface({
146
172
  input: process.stdin,
147
173
  output: process.stdout,
148
174
  });
@@ -151,8 +177,8 @@ export function register(program, pkg) {
151
177
  let converted = 0;
152
178
  let skipped = 0;
153
179
 
154
- const askQuestion = (prompt) =>
155
- new Promise((resolve) => readline.question(prompt, resolve));
180
+ const askQuestion = (prompt: string): Promise<string> =>
181
+ new Promise((resolve) => rl.question(prompt, resolve));
156
182
 
157
183
  const sortedRefs = [...refs].sort((a, b) => b.position - a.position);
158
184
 
@@ -198,7 +224,7 @@ export function register(program, pkg) {
198
224
  }
199
225
  }
200
226
 
201
- readline.close();
227
+ rl.close();
202
228
 
203
229
  console.log(chalk.cyan(`\nConverted: ${converted}, Skipped: ${skipped}`));
204
230
 
@@ -219,7 +245,7 @@ export function register(program, pkg) {
219
245
  .command('install')
220
246
  .description('Check and install dependencies (pandoc-crossref)')
221
247
  .option('--check', 'Only check, don\'t install')
222
- .action(async (options) => {
248
+ .action(async (options: InstallOptions) => {
223
249
  const os = await import('os');
224
250
  const { execSync } = await import('child_process');
225
251
  const platform = os.platform();
@@ -303,7 +329,7 @@ export function register(program, pkg) {
303
329
  execSync('conda --version', { encoding: 'utf-8', stdio: 'pipe' });
304
330
  console.log(chalk.cyan('Conda detected. Install missing dependencies? [y/N] '));
305
331
 
306
- const rl = (await import('readline')).createInterface({
332
+ const rl = readline.createInterface({
307
333
  input: process.stdin,
308
334
  output: process.stdout,
309
335
  });
@@ -323,7 +349,8 @@ export function register(program, pkg) {
323
349
  }
324
350
  console.log(chalk.green('\nDone! Run "rev install --check" to verify.'));
325
351
  } catch (err) {
326
- console.log(chalk.red(`\nInstallation failed: ${err.message}`));
352
+ const error = err as Error;
353
+ console.log(chalk.red(`\nInstallation failed: ${error.message}`));
327
354
  console.log(chalk.dim('Try installing manually with the commands above.'));
328
355
  }
329
356
  }
@@ -421,7 +448,8 @@ export function register(program, pkg) {
421
448
  }
422
449
  }
423
450
  } catch (e) {
424
- console.log(chalk.red(' ✗') + ` rev.yaml parse error: ${e.message}`);
451
+ const error = e as Error;
452
+ console.log(chalk.red(' ✗') + ` rev.yaml parse error: ${error.message}`);
425
453
  issues++;
426
454
  }
427
455
  } else {
@@ -453,15 +481,19 @@ export function register(program, pkg) {
453
481
  program
454
482
  .command('build')
455
483
  .alias('b')
456
- .description('Build PDF/DOCX/TEX from sections')
457
- .argument('[formats...]', 'Output formats: pdf, docx, tex, all', ['pdf', 'docx'])
484
+ .description('Build PDF/DOCX/TEX/PPTX/Beamer from sections')
485
+ .argument('[formats...]', 'Output formats: pdf, docx, tex, beamer, pptx, all', ['pdf', 'docx'])
458
486
  .option('-d, --dir <directory>', 'Project directory', '.')
459
487
  .option('--no-crossref', 'Skip pandoc-crossref filter')
460
488
  .option('--toc', 'Include table of contents')
461
489
  .option('--show-changes', 'Export DOCX with visible track changes (audit mode)')
462
490
  .option('--dual', 'Output both clean version and annotated version (with comments)')
463
491
  .option('--reference <docx>', 'Reference DOCX for comment position alignment (use with --dual)')
464
- .action(async (formats, options) => {
492
+ .option('--theme <name>', 'Beamer theme (default, metropolis, etc.)')
493
+ .option('--colortheme <name>', 'Beamer color theme')
494
+ .option('--aspectratio <ratio>', 'Beamer aspect ratio (169, 43)')
495
+ .option('--verbose', 'Show detailed output including postprocess scripts')
496
+ .action(async (formats: string[], options: BuildOptions) => {
465
497
  const dir = path.resolve(options.dir);
466
498
 
467
499
  if (!fs.existsSync(dir)) {
@@ -496,14 +528,31 @@ export function register(program, pkg) {
496
528
  console.log('');
497
529
 
498
530
  if (options.toc) {
531
+ config.pdf = config.pdf || {};
499
532
  config.pdf.toc = true;
533
+ config.docx = config.docx || {};
500
534
  config.docx.toc = true;
501
535
  }
502
536
 
503
537
  if (options.dual) {
538
+ config.docx = config.docx || {};
504
539
  config.docx.keepComments = false;
505
540
  }
506
541
 
542
+ // Apply beamer CLI options
543
+ if (options.theme) {
544
+ config.beamer = config.beamer || {};
545
+ config.beamer.theme = options.theme;
546
+ }
547
+ if (options.colortheme) {
548
+ config.beamer = config.beamer || {};
549
+ config.beamer.colortheme = options.colortheme;
550
+ }
551
+ if (options.aspectratio) {
552
+ config.beamer = config.beamer || {};
553
+ config.beamer.aspectratio = options.aspectratio;
554
+ }
555
+
507
556
  if (options.showChanges) {
508
557
  if (!targetFormats.includes('docx') && !targetFormats.includes('all')) {
509
558
  console.error(fmt.status('error', '--show-changes only applies to DOCX output'));
@@ -545,8 +594,9 @@ export function register(program, pkg) {
545
594
  }
546
595
  } catch (err) {
547
596
  spin.stop();
548
- console.error(fmt.status('error', err.message));
549
- if (process.env.DEBUG) console.error(err.stack);
597
+ const error = err as Error;
598
+ console.error(fmt.status('error', error.message));
599
+ if (process.env.DEBUG) console.error(error.stack);
550
600
  process.exit(1);
551
601
  }
552
602
  return;
@@ -558,6 +608,7 @@ export function register(program, pkg) {
558
608
  const { results, paperPath, forwardRefsResolved, refsAutoInjected } = await build(dir, targetFormats, {
559
609
  crossref: options.crossref,
560
610
  config,
611
+ verbose: options.verbose,
561
612
  });
562
613
 
563
614
  spin.stop();
@@ -632,7 +683,7 @@ export function register(program, pkg) {
632
683
  if (!pandocResult.success) {
633
684
  console.error(chalk.yellow(`\nWarning: Could not build marked DOCX: ${pandocResult.error}`));
634
685
  } else {
635
- const commentsDocxPath = docxResult.outputPath.replace(/\.docx$/, '_comments.docx');
686
+ const commentsDocxPath = docxResult.outputPath!.replace(/\.docx$/, '_comments.docx');
636
687
  const spinInject = fmt.spinner('Injecting comments at markers...').start();
637
688
  const commentResult = await injectCommentsAtMarkers(markedDocxPath, comments, commentsDocxPath);
638
689
  spinInject.stop();
@@ -646,7 +697,7 @@ export function register(program, pkg) {
646
697
 
647
698
  if (commentResult.success) {
648
699
  console.log(chalk.cyan('\nDual output:'));
649
- console.log(` Clean: ${path.basename(docxResult.outputPath)}`);
700
+ console.log(` Clean: ${path.basename(docxResult.outputPath!)}`);
650
701
  console.log(` Comments: ${path.basename(commentsDocxPath)} (${commentResult.commentCount} comments)`);
651
702
  if (commentResult.skippedComments > 0) {
652
703
  console.log(chalk.yellow(` Warning: ${commentResult.skippedComments} comments could not be anchored (markers not found)`));
@@ -684,7 +735,7 @@ export function register(program, pkg) {
684
735
  annotatedConfig.pdf['header-includes'] = (annotatedConfig.pdf['header-includes'] || '') + preamble;
685
736
  annotatedConfig.pdf.geometry = 'left=2.5cm,right=4.5cm,top=2.5cm,bottom=2.5cm,marginparwidth=3.5cm';
686
737
 
687
- const annotatedPdfPath = pdfResult.outputPath.replace(/\.pdf$/, '_comments.pdf');
738
+ const annotatedPdfPath = pdfResult.outputPath!.replace(/\.pdf$/, '_comments.pdf');
688
739
  spinPdf.text = 'Building annotated PDF...';
689
740
  const pandocResult = await runPandoc(annotatedPath, 'pdf', annotatedConfig, { ...options, outputPath: annotatedPdfPath });
690
741
  spinPdf.stop();
@@ -695,7 +746,7 @@ export function register(program, pkg) {
695
746
 
696
747
  if (pandocResult.success) {
697
748
  console.log(chalk.cyan('\nPDF dual output:'));
698
- console.log(` Clean: ${path.basename(pdfResult.outputPath)}`);
749
+ console.log(` Clean: ${path.basename(pdfResult.outputPath!)}`);
699
750
  console.log(` Comments: ${path.basename(annotatedPdfPath)} (${commentCount} margin notes)`);
700
751
  } else {
701
752
  console.error(chalk.yellow(`\nWarning: Could not create annotated PDF: ${pandocResult.error}`));
@@ -709,12 +760,13 @@ export function register(program, pkg) {
709
760
  if (docxResult && !options.dual) {
710
761
  try {
711
762
  const { storeBaseDocument } = await import('../merge.js');
712
- storeBaseDocument(dir, docxResult.outputPath);
763
+ storeBaseDocument(dir, docxResult.outputPath!);
713
764
  console.log(chalk.dim(`\n Saved as .rev/base.docx for merge`));
714
765
  } catch (err) {
715
766
  // Non-fatal - just log if DEBUG
716
767
  if (process.env.DEBUG) {
717
- console.log(chalk.dim(`\n Could not store base document: ${err.message}`));
768
+ const error = err as Error;
769
+ console.log(chalk.dim(`\n Could not store base document: ${error.message}`));
718
770
  }
719
771
  }
720
772
  }
@@ -722,8 +774,9 @@ export function register(program, pkg) {
722
774
  console.log(chalk.green('\nBuild complete!'));
723
775
  } catch (err) {
724
776
  spin.stop();
725
- console.error(fmt.status('error', err.message));
726
- if (process.env.DEBUG) console.error(err.stack);
777
+ const error = err as Error;
778
+ console.error(fmt.status('error', error.message));
779
+ if (process.env.DEBUG) console.error(error.stack);
727
780
  process.exit(1);
728
781
  }
729
782
  });
@@ -10,12 +10,28 @@ import {
10
10
  path,
11
11
  fmt,
12
12
  } from './context.js';
13
+ import type { Command } from 'commander';
14
+
15
+ interface CitationsOptions {
16
+ bib: string;
17
+ }
18
+
19
+ interface EquationsOptions {
20
+ output?: string;
21
+ }
22
+
23
+ interface PdfCommentsOptions {
24
+ append?: string;
25
+ json?: boolean;
26
+ byPage?: boolean;
27
+ byAuthor?: boolean;
28
+ withText?: boolean;
29
+ }
13
30
 
14
31
  /**
15
32
  * Register citation commands with the program
16
- * @param {import('commander').Command} program
17
33
  */
18
- export function register(program) {
34
+ export function register(program: Command): void {
19
35
  // ==========================================================================
20
36
  // CITATIONS command - Validate citations against .bib file
21
37
  // ==========================================================================
@@ -26,7 +42,7 @@ export function register(program) {
26
42
  .description('Validate citations against bibliography')
27
43
  .argument('[files...]', 'Markdown files to check (default: all section files)')
28
44
  .option('-b, --bib <file>', 'Bibliography file', 'references.bib')
29
- .action(async (files, options) => {
45
+ .action(async (files: string[], options: CitationsOptions) => {
30
46
  const { getCitationStats } = await import('../citations.js');
31
47
 
32
48
  // If no files specified, find all .md files
@@ -48,7 +64,7 @@ export function register(program) {
48
64
  console.log();
49
65
 
50
66
  // Summary table
51
- const rows = [
67
+ const rows: string[][] = [
52
68
  ['Total citations', stats.totalCitations.toString()],
53
69
  ['Unique keys cited', stats.uniqueCited.toString()],
54
70
  ['Bib entries', stats.bibEntries.toString()],
@@ -97,7 +113,7 @@ export function register(program) {
97
113
  .alias('figs')
98
114
  .description('List all figures and tables with reference counts')
99
115
  .argument('[files...]', 'Markdown files to scan')
100
- .action(async (files) => {
116
+ .action(async (files: string[]) => {
101
117
  const { buildRegistry } = await import('../crossref.js');
102
118
 
103
119
  // If no files specified, find all .md files
@@ -112,7 +128,7 @@ export function register(program) {
112
128
  const registry = buildRegistry('.');
113
129
 
114
130
  // Count references in files
115
- const refCounts = new Map();
131
+ const refCounts = new Map<string, number>();
116
132
  for (const file of mdFiles) {
117
133
  if (!fs.existsSync(file)) continue;
118
134
  const text = fs.readFileSync(file, 'utf-8');
@@ -174,7 +190,7 @@ export function register(program) {
174
190
  }
175
191
 
176
192
  // Warn about unreferenced
177
- const unreferenced = [];
193
+ const unreferenced: string[] = [];
178
194
  for (const [label] of registry.figures) {
179
195
  if (!refCounts.get(`fig:${label}`)) unreferenced.push(`@fig:${label}`);
180
196
  }
@@ -198,7 +214,7 @@ export function register(program) {
198
214
  .argument('<action>', 'Action: list, extract, convert, from-word')
199
215
  .argument('[input]', 'Input file (.md for extract/convert, .docx for from-word)')
200
216
  .option('-o, --output <file>', 'Output file')
201
- .action(async (action, input, options) => {
217
+ .action(async (action: string, input: string | undefined, options: EquationsOptions) => {
202
218
  const { extractEquations, getEquationStats, createEquationsDoc, extractEquationsFromWord, getWordEquationStats } = await import('../equations.js');
203
219
 
204
220
  if (action === 'from-word') {
@@ -218,7 +234,7 @@ export function register(program) {
218
234
  const result = await extractEquationsFromWord(input);
219
235
 
220
236
  if (!result.success) {
221
- spin.error(result.error);
237
+ spin.error(result.error!);
222
238
  process.exit(1);
223
239
  }
224
240
 
@@ -243,10 +259,10 @@ export function register(program) {
243
259
  const typeLabel = eq.type === 'display' ? chalk.cyan('[display]') : chalk.yellow('[inline]');
244
260
 
245
261
  if (eq.latex) {
246
- console.log(`${chalk.bold(i + 1)}. ${typeLabel}`);
262
+ console.log(`${chalk.bold((i + 1).toString())}. ${typeLabel}`);
247
263
  console.log(chalk.dim(' LaTeX:'), eq.latex.length > 80 ? eq.latex.substring(0, 77) + '...' : eq.latex);
248
264
  } else {
249
- console.log(`${chalk.bold(i + 1)}. ${typeLabel} ${chalk.red('[conversion failed]')}`);
265
+ console.log(`${chalk.bold((i + 1).toString())}. ${typeLabel} ${chalk.red('[conversion failed]')}`);
250
266
  }
251
267
  }
252
268
 
@@ -302,7 +318,7 @@ export function register(program) {
302
318
 
303
319
  if (result.success) {
304
320
  console.log(fmt.status('success', result.message));
305
- console.log(chalk.dim(` ${result.stats.display} display, ${result.stats.inline} inline equations`));
321
+ console.log(chalk.dim(` ${result.stats!.display} display, ${result.stats!.inline} inline equations`));
306
322
  } else {
307
323
  console.error(fmt.status('error', result.message));
308
324
  process.exit(1);
@@ -326,7 +342,8 @@ export function register(program) {
326
342
  await execAsync(`pandoc "${input}" -o "${output}" --mathml`);
327
343
  spin.success(`Created ${output}`);
328
344
  } catch (err) {
329
- spin.error(err.message);
345
+ const error = err as Error;
346
+ spin.error(error.message);
330
347
  process.exit(1);
331
348
  }
332
349
  } else {
@@ -350,7 +367,7 @@ export function register(program) {
350
367
  .option('--by-page', 'Group comments by page')
351
368
  .option('--by-author', 'Group comments by author')
352
369
  .option('--with-text', 'Extract highlighted text (slower but shows what was highlighted)')
353
- .action(async (pdf, options) => {
370
+ .action(async (pdf: string, options: PdfCommentsOptions) => {
354
371
  if (!fs.existsSync(pdf)) {
355
372
  console.error(fmt.status('error', `File not found: ${pdf}`));
356
373
  process.exit(1);
@@ -373,7 +390,7 @@ export function register(program) {
373
390
  const spin = fmt.spinner(`Extracting comments from ${path.basename(pdf)}...`).start();
374
391
 
375
392
  try {
376
- let comments;
393
+ let comments: any[];
377
394
 
378
395
  if (options.withText) {
379
396
  // Use the new text extraction feature
@@ -435,7 +452,7 @@ export function register(program) {
435
452
 
436
453
  if (options.byAuthor) {
437
454
  // Group by author
438
- const byAuthor = {};
455
+ const byAuthor: Record<string, any[]> = {};
439
456
  for (const c of comments) {
440
457
  const author = c.author || 'Unknown';
441
458
  if (!byAuthor[author]) byAuthor[author] = [];
@@ -489,8 +506,9 @@ export function register(program) {
489
506
 
490
507
  } catch (err) {
491
508
  spin.stop();
492
- console.error(fmt.status('error', `Failed to extract PDF comments: ${err.message}`));
493
- if (process.env.DEBUG) console.error(err.stack);
509
+ const error = err as Error;
510
+ console.error(fmt.status('error', `Failed to extract PDF comments: ${error.message}`));
511
+ if (process.env.DEBUG) console.error(error.stack);
494
512
  process.exit(1);
495
513
  }
496
514
  });