docrev 0.11.0 → 0.11.2

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 (123) hide show
  1. package/.gitattributes +1 -1
  2. package/CHANGELOG.md +202 -184
  3. package/PLAN-tables-and-postprocess.md +850 -850
  4. package/README.md +433 -431
  5. package/bin/rev.js +11 -11
  6. package/bin/rev.ts +145 -145
  7. package/completions/rev.bash +127 -127
  8. package/completions/rev.ps1 +210 -210
  9. package/completions/rev.zsh +207 -207
  10. package/dist/lib/build.d.ts +80 -0
  11. package/dist/lib/build.d.ts.map +1 -1
  12. package/dist/lib/build.js +207 -4
  13. package/dist/lib/build.js.map +1 -1
  14. package/dist/lib/commands/build.d.ts.map +1 -1
  15. package/dist/lib/commands/build.js +34 -12
  16. package/dist/lib/commands/build.js.map +1 -1
  17. package/dist/lib/commands/utilities.js +164 -164
  18. package/dist/lib/commands/word-tools.js +8 -8
  19. package/dist/lib/grammar.js +3 -3
  20. package/dist/lib/macro-filter.lua +201 -201
  21. package/dist/lib/pdf-comments.js +44 -44
  22. package/dist/lib/plugins.js +57 -57
  23. package/dist/lib/pptx-color-filter.lua +37 -37
  24. package/dist/lib/pptx-themes.js +115 -115
  25. package/dist/lib/schema.d.ts.map +1 -1
  26. package/dist/lib/schema.js +8 -2
  27. package/dist/lib/schema.js.map +1 -1
  28. package/dist/lib/spelling.js +2 -2
  29. package/dist/lib/templates.js +387 -387
  30. package/dist/lib/themes.js +51 -51
  31. package/dist/lib/trackchanges.d.ts +41 -25
  32. package/dist/lib/trackchanges.d.ts.map +1 -1
  33. package/dist/lib/trackchanges.js +90 -151
  34. package/dist/lib/trackchanges.js.map +1 -1
  35. package/dist/lib/types.d.ts +0 -7
  36. package/dist/lib/types.d.ts.map +1 -1
  37. package/docs-src/build.py +113 -113
  38. package/docs-src/extra.css +208 -208
  39. package/docs-src/md-to-html.lua +6 -6
  40. package/docs-src/template.html +116 -116
  41. package/eslint.config.js +27 -27
  42. package/lib/anchor-match.ts +307 -307
  43. package/lib/annotations.ts +664 -664
  44. package/lib/build.ts +2047 -1770
  45. package/lib/citations.ts +160 -160
  46. package/lib/commands/build.ts +885 -860
  47. package/lib/commands/citations.ts +515 -515
  48. package/lib/commands/comments.ts +1050 -1050
  49. package/lib/commands/context.ts +176 -176
  50. package/lib/commands/core.ts +309 -309
  51. package/lib/commands/doi.ts +451 -451
  52. package/lib/commands/file-ops.ts +372 -372
  53. package/lib/commands/history.ts +320 -320
  54. package/lib/commands/index.ts +87 -87
  55. package/lib/commands/init.ts +259 -259
  56. package/lib/commands/merge-resolve.ts +378 -378
  57. package/lib/commands/preview.ts +178 -178
  58. package/lib/commands/project-info.ts +244 -244
  59. package/lib/commands/quality.ts +517 -517
  60. package/lib/commands/response.ts +454 -454
  61. package/lib/commands/section-boundaries.ts +82 -82
  62. package/lib/commands/sections.ts +451 -451
  63. package/lib/commands/sync.ts +689 -689
  64. package/lib/commands/text-ops.ts +449 -449
  65. package/lib/commands/utilities.ts +448 -448
  66. package/lib/commands/verify-anchors.ts +272 -272
  67. package/lib/commands/word-tools.ts +340 -340
  68. package/lib/comment-realign.ts +99 -99
  69. package/lib/config.ts +84 -84
  70. package/lib/crossref.ts +781 -781
  71. package/lib/csl.ts +191 -191
  72. package/lib/dependencies.ts +132 -132
  73. package/lib/diff-engine.ts +465 -465
  74. package/lib/doi-cache.ts +115 -115
  75. package/lib/doi.ts +879 -879
  76. package/lib/equations.ts +506 -506
  77. package/lib/errors.ts +350 -350
  78. package/lib/format.ts +541 -541
  79. package/lib/git.ts +326 -326
  80. package/lib/grammar.ts +303 -303
  81. package/lib/image-registry.ts +180 -180
  82. package/lib/import.ts +906 -906
  83. package/lib/journals.ts +543 -543
  84. package/lib/macro-filter.lua +201 -201
  85. package/lib/macros.ts +273 -273
  86. package/lib/merge.ts +633 -633
  87. package/lib/ooxml.ts +768 -768
  88. package/lib/orcid.ts +144 -144
  89. package/lib/pdf-comments.ts +263 -263
  90. package/lib/pdf-import.ts +524 -524
  91. package/lib/plugins.ts +362 -362
  92. package/lib/postprocess.ts +188 -188
  93. package/lib/pptx-color-filter.lua +37 -37
  94. package/lib/pptx-template.ts +469 -469
  95. package/lib/pptx-themes.ts +483 -483
  96. package/lib/protect-restore.ts +520 -520
  97. package/lib/rate-limiter.ts +127 -127
  98. package/lib/response.ts +197 -197
  99. package/lib/restore-references.ts +240 -240
  100. package/lib/review.ts +327 -327
  101. package/lib/schema.ts +494 -488
  102. package/lib/scientific-words.ts +73 -73
  103. package/lib/sections.ts +428 -428
  104. package/lib/slides.ts +756 -756
  105. package/lib/spelling.ts +334 -334
  106. package/lib/templates.ts +526 -526
  107. package/lib/themes.ts +742 -742
  108. package/lib/trackchanges.ts +166 -247
  109. package/lib/tui.ts +450 -450
  110. package/lib/types.ts +546 -558
  111. package/lib/undo.ts +250 -250
  112. package/lib/utils.ts +69 -69
  113. package/lib/variables.ts +179 -179
  114. package/lib/word-extraction.ts +525 -525
  115. package/lib/word.ts +526 -526
  116. package/lib/wordcomments.ts +789 -789
  117. package/mkdocs.yml +64 -64
  118. package/package.json +137 -137
  119. package/scripts/postbuild.js +47 -47
  120. package/skill/REFERENCE.md +550 -539
  121. package/skill/SKILL.md +302 -295
  122. package/tsconfig.json +26 -26
  123. package/types/index.d.ts +531 -525
@@ -1,448 +1,448 @@
1
- /**
2
- * Utility commands: help, completions, open, upgrade, install-cli-skill, uninstall-cli-skill
3
- *
4
- * Core utility commands that don't fit into a specific domain.
5
- */
6
-
7
- import type { Command } from 'commander';
8
- import {
9
- chalk,
10
- fs,
11
- path,
12
- fmt,
13
- } from './context.js';
14
-
15
- // Type definitions for package.json
16
- interface PackageJson {
17
- version?: string;
18
- name?: string;
19
- [key: string]: unknown;
20
- }
21
-
22
- interface OpenOptions {
23
- // No options currently
24
- }
25
-
26
- interface UpgradeOptions {
27
- check?: boolean;
28
- }
29
-
30
- /**
31
- * Register utility commands with the program
32
- */
33
- export function register(program: Command, pkg?: PackageJson): void {
34
- // ==========================================================================
35
- // HELP command - Comprehensive help
36
- // ==========================================================================
37
-
38
- program
39
- .command('help')
40
- .description('Show detailed help and workflow guide')
41
- .argument('[topic]', 'Help topic: workflow, syntax, commands')
42
- .action((topic?: string) => {
43
- if (!topic || topic === 'all') {
44
- showFullHelp(pkg);
45
- } else if (topic === 'workflow') {
46
- showWorkflowHelp();
47
- } else if (topic === 'syntax') {
48
- showSyntaxHelp();
49
- } else if (topic === 'commands') {
50
- showCommandsHelp();
51
- } else {
52
- console.log(chalk.yellow(`Unknown topic: ${topic}`));
53
- console.log(chalk.dim('Available topics: workflow, syntax, commands'));
54
- }
55
- });
56
-
57
- // ==========================================================================
58
- // COMPLETIONS command - Shell completions
59
- // ==========================================================================
60
-
61
- program
62
- .command('completions')
63
- .description('Output shell completions')
64
- .argument('<shell>', 'Shell type: bash, zsh, powershell')
65
- .action((shell: string) => {
66
- const completionsDir = path.join(import.meta.dirname, '..', '..', 'completions');
67
-
68
- if (shell === 'bash') {
69
- const bashFile = path.join(completionsDir, 'rev.bash');
70
- if (fs.existsSync(bashFile)) {
71
- console.log(fs.readFileSync(bashFile, 'utf-8'));
72
- } else {
73
- console.error(chalk.red('Bash completions not found'));
74
- process.exit(1);
75
- }
76
- } else if (shell === 'zsh') {
77
- const zshFile = path.join(completionsDir, 'rev.zsh');
78
- if (fs.existsSync(zshFile)) {
79
- console.log(fs.readFileSync(zshFile, 'utf-8'));
80
- } else {
81
- console.error(chalk.red('Zsh completions not found'));
82
- process.exit(1);
83
- }
84
- } else if (shell === 'powershell' || shell === 'pwsh') {
85
- const psFile = path.join(completionsDir, 'rev.ps1');
86
- if (fs.existsSync(psFile)) {
87
- console.log(fs.readFileSync(psFile, 'utf-8'));
88
- } else {
89
- console.error(chalk.red('PowerShell completions not found'));
90
- process.exit(1);
91
- }
92
- } else {
93
- console.error(chalk.red(`Unknown shell: ${shell}`));
94
- console.log(chalk.dim('Supported shells: bash, zsh, powershell'));
95
- process.exit(1);
96
- }
97
- });
98
-
99
- // ==========================================================================
100
- // OPEN command - Open project folder or file
101
- // ==========================================================================
102
-
103
- program
104
- .command('open')
105
- .description('Open project folder or file in default app')
106
- .argument('[file]', 'File to open (default: project folder)')
107
- .action(async (file?: string, _options?: OpenOptions) => {
108
- const { exec } = await import('child_process');
109
- const target = file || '.';
110
-
111
- if (!fs.existsSync(target)) {
112
- console.error(chalk.red(`File not found: ${target}`));
113
- process.exit(1);
114
- }
115
-
116
- const platform = process.platform;
117
- let command: string;
118
-
119
- if (platform === 'darwin') {
120
- command = `open "${target}"`;
121
- } else if (platform === 'win32') {
122
- command = `start "" "${target}"`;
123
- } else {
124
- command = `xdg-open "${target}"`;
125
- }
126
-
127
- exec(command, (err) => {
128
- if (err) {
129
- console.error(chalk.red(`Failed to open: ${err.message}`));
130
- process.exit(1);
131
- }
132
- console.log(fmt.status('success', `Opened ${target}`));
133
- });
134
- });
135
-
136
- // ==========================================================================
137
- // INSTALL-CLI-SKILL command - Install Claude Code skill
138
- // ==========================================================================
139
-
140
- program
141
- .command('install-cli-skill')
142
- .description('Install docrev skill for Claude Code')
143
- .action(() => {
144
- const homedir = process.env.HOME || process.env.USERPROFILE;
145
- if (!homedir) {
146
- console.error(chalk.red('Could not determine home directory'));
147
- process.exit(1);
148
- }
149
- const skillDir = path.join(homedir, '.claude', 'skills', 'docrev');
150
- const sourceDir = path.join(import.meta.dirname, '..', '..', 'skill');
151
-
152
- // Check if source skill files exist
153
- const skillFile = path.join(sourceDir, 'SKILL.md');
154
- if (!fs.existsSync(skillFile)) {
155
- console.error(chalk.red('Skill files not found in package'));
156
- process.exit(1);
157
- }
158
-
159
- // Create skill directory
160
- fs.mkdirSync(skillDir, { recursive: true });
161
-
162
- // Copy skill files
163
- const files = ['SKILL.md', 'REFERENCE.md'];
164
- for (const file of files) {
165
- const src = path.join(sourceDir, file);
166
- const dest = path.join(skillDir, file);
167
- if (fs.existsSync(src)) {
168
- fs.copyFileSync(src, dest);
169
- }
170
- }
171
-
172
- console.log(fmt.status('success', 'Installed docrev skill for Claude Code'));
173
- console.log(chalk.dim(` Location: ${skillDir}`));
174
- console.log(chalk.dim(' Restart Claude Code to activate'));
175
- });
176
-
177
- program
178
- .command('uninstall-cli-skill')
179
- .description('Remove docrev skill from Claude Code')
180
- .action(() => {
181
- const homedir = process.env.HOME || process.env.USERPROFILE;
182
- if (!homedir) {
183
- console.error(chalk.red('Could not determine home directory'));
184
- process.exit(1);
185
- }
186
- const skillDir = path.join(homedir, '.claude', 'skills', 'docrev');
187
-
188
- if (fs.existsSync(skillDir)) {
189
- fs.rmSync(skillDir, { recursive: true });
190
- console.log(fmt.status('success', 'Removed docrev skill from Claude Code'));
191
- } else {
192
- console.log(chalk.yellow('Skill not installed'));
193
- }
194
- });
195
-
196
- // ==========================================================================
197
- // UPGRADE command - Self-update via npm
198
- // ==========================================================================
199
-
200
- program
201
- .command('upgrade')
202
- .description('Check for updates and upgrade docrev')
203
- .option('--check', 'Only check for updates, do not install')
204
- .action(async (options: UpgradeOptions) => {
205
- const { execSync, spawn } = await import('child_process');
206
-
207
- console.log(chalk.cyan('Checking for updates...'));
208
-
209
- try {
210
- // Get current version
211
- const currentVersion = pkg?.version || 'unknown';
212
-
213
- // Get latest version from npm
214
- let latestVersion: string;
215
- try {
216
- latestVersion = execSync('npm view docrev version', { encoding: 'utf-8' }).trim();
217
- } catch {
218
- console.error(chalk.red('Failed to check npm registry'));
219
- console.error(chalk.dim('Check your internet connection'));
220
- process.exit(1);
221
- }
222
-
223
- if (currentVersion === latestVersion) {
224
- console.log(fmt.status('success', `Already on latest version (${currentVersion})`));
225
- return;
226
- }
227
-
228
- console.log(` Current: ${chalk.yellow(currentVersion)}`);
229
- console.log(` Latest: ${chalk.green(latestVersion)}`);
230
- console.log();
231
-
232
- if (options.check) {
233
- console.log(chalk.cyan('Run "rev upgrade" to install the update'));
234
- return;
235
- }
236
-
237
- console.log(chalk.cyan('Upgrading...'));
238
-
239
- // Run npm update
240
- const child = spawn('npm', ['install', '-g', 'docrev@latest'], {
241
- stdio: 'inherit',
242
- shell: true,
243
- });
244
-
245
- child.on('close', (code) => {
246
- if (code === 0) {
247
- console.log();
248
- console.log(fmt.status('success', `Upgraded to ${latestVersion}`));
249
- } else {
250
- console.error(chalk.red('Upgrade failed'));
251
- console.error(chalk.dim('Try running: npm install -g docrev@latest'));
252
- process.exit(1);
253
- }
254
- });
255
-
256
- child.on('error', (err) => {
257
- console.error(chalk.red(`Upgrade failed: ${err.message}`));
258
- console.error(chalk.dim('Try running: npm install -g docrev@latest'));
259
- process.exit(1);
260
- });
261
- } catch (err) {
262
- console.error(chalk.red(`Error: ${(err as Error).message}`));
263
- process.exit(1);
264
- }
265
- });
266
- }
267
-
268
- // Helper functions for help text
269
-
270
- function showFullHelp(pkg?: PackageJson): void {
271
- console.log(`
272
- ${chalk.bold.cyan('rev')} ${chalk.dim(`v${pkg?.version || 'unknown'}`)} - Revision workflow for Word ↔ Markdown round-trips
273
-
274
- ${chalk.bold('DESCRIPTION')}
275
- Handle reviewer feedback when collaborating on academic papers.
276
- Import changes from Word, review them interactively, and preserve
277
- comments for discussion with Claude.
278
-
279
- ${chalk.bold('GLOBAL OPTIONS')}
280
-
281
- ${chalk.bold('--no-color')} Disable colored output
282
- ${chalk.bold('-q, --quiet')} Suppress non-essential output
283
- ${chalk.bold('--json')} Output in JSON format (for scripting)
284
-
285
- ${chalk.bold('TYPICAL WORKFLOW')}
286
-
287
- ${chalk.dim('1.')} Build and send: ${chalk.green('rev build docx')} ${chalk.dim('(or rev b docx)')}
288
- ${chalk.dim('2.')} Reviewers return ${chalk.yellow('reviewed.docx')} with edits and comments
289
- ${chalk.dim('3.')} Sync their feedback: ${chalk.green('rev sync reviewed.docx')}
290
- ${chalk.dim('4.')} Work through comments: ${chalk.green('rev next')} ${chalk.dim('(n)')} / ${chalk.green('rev todo')} ${chalk.dim('(t)')}
291
- ${chalk.dim('5.')} Accept/reject changes: ${chalk.green('rev accept -a')} ${chalk.dim('(a)')} or ${chalk.green('rev review')}
292
- ${chalk.dim('6.')} Rebuild: ${chalk.green('rev build docx')}
293
- ${chalk.dim('7.')} Archive old files: ${chalk.green('rev archive')}
294
-
295
- ${chalk.bold('MORE HELP')}
296
-
297
- rev help workflow Detailed workflow guide
298
- rev help syntax Annotation syntax reference
299
- rev help commands All commands with options
300
- `);
301
- }
302
-
303
- function showWorkflowHelp(): void {
304
- console.log(`
305
- ${chalk.bold.cyan('rev')} ${chalk.dim('- Workflow Guide')}
306
-
307
- ${chalk.bold('OVERVIEW')}
308
-
309
- The rev workflow solves a common problem: you write in Markdown,
310
- but collaborators review in Word. When they return edited documents,
311
- you need to merge their changes back into your source files.
312
-
313
- ${chalk.bold('STEP 1: BUILD & SEND')}
314
-
315
- ${chalk.green('rev build docx')}
316
- ${chalk.dim('# Send the .docx to reviewers')}
317
-
318
- ${chalk.bold('STEP 2: RECEIVE FEEDBACK')}
319
-
320
- Reviewers edit the document, adding:
321
- ${chalk.dim('•')} Track changes (insertions, deletions)
322
- ${chalk.dim('•')} Comments (questions, suggestions)
323
-
324
- ${chalk.bold('STEP 3: SYNC CHANGES')}
325
-
326
- ${chalk.green('rev sync reviewed.docx')}
327
- ${chalk.dim('# Or just: rev sync (auto-detects most recent .docx)')}
328
-
329
- Your markdown files now contain their feedback as annotations.
330
-
331
- ${chalk.bold('STEP 4: WORK THROUGH COMMENTS')}
332
-
333
- ${chalk.green('rev todo')} ${chalk.dim('# See all pending comments')}
334
- ${chalk.green('rev next')} ${chalk.dim('# Show next pending comment')}
335
- ${chalk.green('rev reply file.md -n 1 -m "Done"')}
336
- ${chalk.green('rev resolve file.md -n 1')}
337
-
338
- ${chalk.bold('STEP 5: ACCEPT/REJECT CHANGES')}
339
-
340
- ${chalk.green('rev accept file.md -a')} ${chalk.dim('# Accept all changes')}
341
- ${chalk.green('rev reject file.md -n 2')} ${chalk.dim('# Reject specific change')}
342
- ${chalk.dim('# Or use interactive mode:')}
343
- ${chalk.green('rev review file.md')}
344
-
345
- ${chalk.bold('STEP 6: REBUILD')}
346
-
347
- ${chalk.green('rev build docx')}
348
- ${chalk.green('rev build docx --dual')} ${chalk.dim('# Clean + comments version')}
349
-
350
- ${chalk.bold('STEP 7: ARCHIVE & REPEAT')}
351
-
352
- ${chalk.green('rev archive')} ${chalk.dim('# Move reviewer files to archive/')}
353
- ${chalk.dim('# Send new .docx, repeat cycle')}
354
- `);
355
- }
356
-
357
- function showSyntaxHelp(): void {
358
- console.log(`
359
- ${chalk.bold.cyan('rev')} ${chalk.dim('- Annotation Syntax (CriticMarkup)')}
360
-
361
- ${chalk.bold('INSERTIONS')}
362
-
363
- Syntax: ${chalk.green('{++inserted text++}')}
364
- Meaning: This text was added by the reviewer
365
-
366
- Example:
367
- We ${chalk.green('{++specifically++}')} focused on neophytes.
368
- → Reviewer added the word "specifically"
369
-
370
- ${chalk.bold('DELETIONS')}
371
-
372
- Syntax: ${chalk.red('{--deleted text--}')}
373
- Meaning: This text was removed by the reviewer
374
-
375
- Example:
376
- We focused on ${chalk.red('{--recent--}')} neophytes.
377
- → Reviewer removed the word "recent"
378
-
379
- ${chalk.bold('SUBSTITUTIONS')}
380
-
381
- Syntax: ${chalk.yellow('{~~old text~>new text~~}')}
382
- Meaning: Text was changed from old to new
383
-
384
- Example:
385
- The effect was ${chalk.yellow('{~~significant~>substantial~~}')}.
386
- → Reviewer changed "significant" to "substantial"
387
-
388
- ${chalk.bold('COMMENTS')}
389
-
390
- Syntax: ${chalk.blue('{>>Author: comment text<<}')}
391
- Meaning: Reviewer left a comment at this location
392
-
393
- Example:
394
- The results were significant. ${chalk.blue('{>>Dr. Smith: Add p-value<<}')}
395
- → Dr. Smith commented asking for a p-value
396
-
397
- Comments are placed ${chalk.bold('after')} the text they reference.
398
- `);
399
- }
400
-
401
- function showCommandsHelp(): void {
402
- console.log(`
403
- ${chalk.bold.cyan('rev')} ${chalk.dim('- Command Reference')}
404
-
405
- ${chalk.bold('rev import')} <docx> <original-md>
406
-
407
- Import changes from a Word document by comparing against your
408
- original Markdown source.
409
-
410
- ${chalk.bold('Arguments:')}
411
- docx Word document from reviewer
412
- original-md Your original Markdown file
413
-
414
- ${chalk.bold('Options:')}
415
- -o, --output <file> Write to different file (default: overwrites original)
416
- -a, --author <name> Author name for changes (default: "Reviewer")
417
- --dry-run Preview changes without saving
418
-
419
- ${chalk.bold('rev review')} <file>
420
-
421
- Interactively review and accept/reject track changes.
422
- Comments are preserved; only track changes are processed.
423
-
424
- ${chalk.bold('Keys:')}
425
- a Accept this change
426
- r Reject this change
427
- s Skip (decide later)
428
- A Accept all remaining changes
429
- L Reject all remaining changes
430
- q Quit without saving
431
-
432
- ${chalk.bold('rev strip')} <file>
433
-
434
- Remove annotations, outputting clean Markdown.
435
- Track changes are applied (insertions kept, deletions removed).
436
-
437
- ${chalk.bold('Options:')}
438
- -o, --output <file> Write to file (default: stdout)
439
- -c, --keep-comments Keep comment annotations
440
-
441
- ${chalk.bold('rev help')} [topic]
442
-
443
- Show help. Optional topics:
444
- workflow Step-by-step workflow guide
445
- syntax Annotation syntax reference
446
- commands This command reference
447
- `);
448
- }
1
+ /**
2
+ * Utility commands: help, completions, open, upgrade, install-cli-skill, uninstall-cli-skill
3
+ *
4
+ * Core utility commands that don't fit into a specific domain.
5
+ */
6
+
7
+ import type { Command } from 'commander';
8
+ import {
9
+ chalk,
10
+ fs,
11
+ path,
12
+ fmt,
13
+ } from './context.js';
14
+
15
+ // Type definitions for package.json
16
+ interface PackageJson {
17
+ version?: string;
18
+ name?: string;
19
+ [key: string]: unknown;
20
+ }
21
+
22
+ interface OpenOptions {
23
+ // No options currently
24
+ }
25
+
26
+ interface UpgradeOptions {
27
+ check?: boolean;
28
+ }
29
+
30
+ /**
31
+ * Register utility commands with the program
32
+ */
33
+ export function register(program: Command, pkg?: PackageJson): void {
34
+ // ==========================================================================
35
+ // HELP command - Comprehensive help
36
+ // ==========================================================================
37
+
38
+ program
39
+ .command('help')
40
+ .description('Show detailed help and workflow guide')
41
+ .argument('[topic]', 'Help topic: workflow, syntax, commands')
42
+ .action((topic?: string) => {
43
+ if (!topic || topic === 'all') {
44
+ showFullHelp(pkg);
45
+ } else if (topic === 'workflow') {
46
+ showWorkflowHelp();
47
+ } else if (topic === 'syntax') {
48
+ showSyntaxHelp();
49
+ } else if (topic === 'commands') {
50
+ showCommandsHelp();
51
+ } else {
52
+ console.log(chalk.yellow(`Unknown topic: ${topic}`));
53
+ console.log(chalk.dim('Available topics: workflow, syntax, commands'));
54
+ }
55
+ });
56
+
57
+ // ==========================================================================
58
+ // COMPLETIONS command - Shell completions
59
+ // ==========================================================================
60
+
61
+ program
62
+ .command('completions')
63
+ .description('Output shell completions')
64
+ .argument('<shell>', 'Shell type: bash, zsh, powershell')
65
+ .action((shell: string) => {
66
+ const completionsDir = path.join(import.meta.dirname, '..', '..', 'completions');
67
+
68
+ if (shell === 'bash') {
69
+ const bashFile = path.join(completionsDir, 'rev.bash');
70
+ if (fs.existsSync(bashFile)) {
71
+ console.log(fs.readFileSync(bashFile, 'utf-8'));
72
+ } else {
73
+ console.error(chalk.red('Bash completions not found'));
74
+ process.exit(1);
75
+ }
76
+ } else if (shell === 'zsh') {
77
+ const zshFile = path.join(completionsDir, 'rev.zsh');
78
+ if (fs.existsSync(zshFile)) {
79
+ console.log(fs.readFileSync(zshFile, 'utf-8'));
80
+ } else {
81
+ console.error(chalk.red('Zsh completions not found'));
82
+ process.exit(1);
83
+ }
84
+ } else if (shell === 'powershell' || shell === 'pwsh') {
85
+ const psFile = path.join(completionsDir, 'rev.ps1');
86
+ if (fs.existsSync(psFile)) {
87
+ console.log(fs.readFileSync(psFile, 'utf-8'));
88
+ } else {
89
+ console.error(chalk.red('PowerShell completions not found'));
90
+ process.exit(1);
91
+ }
92
+ } else {
93
+ console.error(chalk.red(`Unknown shell: ${shell}`));
94
+ console.log(chalk.dim('Supported shells: bash, zsh, powershell'));
95
+ process.exit(1);
96
+ }
97
+ });
98
+
99
+ // ==========================================================================
100
+ // OPEN command - Open project folder or file
101
+ // ==========================================================================
102
+
103
+ program
104
+ .command('open')
105
+ .description('Open project folder or file in default app')
106
+ .argument('[file]', 'File to open (default: project folder)')
107
+ .action(async (file?: string, _options?: OpenOptions) => {
108
+ const { exec } = await import('child_process');
109
+ const target = file || '.';
110
+
111
+ if (!fs.existsSync(target)) {
112
+ console.error(chalk.red(`File not found: ${target}`));
113
+ process.exit(1);
114
+ }
115
+
116
+ const platform = process.platform;
117
+ let command: string;
118
+
119
+ if (platform === 'darwin') {
120
+ command = `open "${target}"`;
121
+ } else if (platform === 'win32') {
122
+ command = `start "" "${target}"`;
123
+ } else {
124
+ command = `xdg-open "${target}"`;
125
+ }
126
+
127
+ exec(command, (err) => {
128
+ if (err) {
129
+ console.error(chalk.red(`Failed to open: ${err.message}`));
130
+ process.exit(1);
131
+ }
132
+ console.log(fmt.status('success', `Opened ${target}`));
133
+ });
134
+ });
135
+
136
+ // ==========================================================================
137
+ // INSTALL-CLI-SKILL command - Install Claude Code skill
138
+ // ==========================================================================
139
+
140
+ program
141
+ .command('install-cli-skill')
142
+ .description('Install docrev skill for Claude Code')
143
+ .action(() => {
144
+ const homedir = process.env.HOME || process.env.USERPROFILE;
145
+ if (!homedir) {
146
+ console.error(chalk.red('Could not determine home directory'));
147
+ process.exit(1);
148
+ }
149
+ const skillDir = path.join(homedir, '.claude', 'skills', 'docrev');
150
+ const sourceDir = path.join(import.meta.dirname, '..', '..', 'skill');
151
+
152
+ // Check if source skill files exist
153
+ const skillFile = path.join(sourceDir, 'SKILL.md');
154
+ if (!fs.existsSync(skillFile)) {
155
+ console.error(chalk.red('Skill files not found in package'));
156
+ process.exit(1);
157
+ }
158
+
159
+ // Create skill directory
160
+ fs.mkdirSync(skillDir, { recursive: true });
161
+
162
+ // Copy skill files
163
+ const files = ['SKILL.md', 'REFERENCE.md'];
164
+ for (const file of files) {
165
+ const src = path.join(sourceDir, file);
166
+ const dest = path.join(skillDir, file);
167
+ if (fs.existsSync(src)) {
168
+ fs.copyFileSync(src, dest);
169
+ }
170
+ }
171
+
172
+ console.log(fmt.status('success', 'Installed docrev skill for Claude Code'));
173
+ console.log(chalk.dim(` Location: ${skillDir}`));
174
+ console.log(chalk.dim(' Restart Claude Code to activate'));
175
+ });
176
+
177
+ program
178
+ .command('uninstall-cli-skill')
179
+ .description('Remove docrev skill from Claude Code')
180
+ .action(() => {
181
+ const homedir = process.env.HOME || process.env.USERPROFILE;
182
+ if (!homedir) {
183
+ console.error(chalk.red('Could not determine home directory'));
184
+ process.exit(1);
185
+ }
186
+ const skillDir = path.join(homedir, '.claude', 'skills', 'docrev');
187
+
188
+ if (fs.existsSync(skillDir)) {
189
+ fs.rmSync(skillDir, { recursive: true });
190
+ console.log(fmt.status('success', 'Removed docrev skill from Claude Code'));
191
+ } else {
192
+ console.log(chalk.yellow('Skill not installed'));
193
+ }
194
+ });
195
+
196
+ // ==========================================================================
197
+ // UPGRADE command - Self-update via npm
198
+ // ==========================================================================
199
+
200
+ program
201
+ .command('upgrade')
202
+ .description('Check for updates and upgrade docrev')
203
+ .option('--check', 'Only check for updates, do not install')
204
+ .action(async (options: UpgradeOptions) => {
205
+ const { execSync, spawn } = await import('child_process');
206
+
207
+ console.log(chalk.cyan('Checking for updates...'));
208
+
209
+ try {
210
+ // Get current version
211
+ const currentVersion = pkg?.version || 'unknown';
212
+
213
+ // Get latest version from npm
214
+ let latestVersion: string;
215
+ try {
216
+ latestVersion = execSync('npm view docrev version', { encoding: 'utf-8' }).trim();
217
+ } catch {
218
+ console.error(chalk.red('Failed to check npm registry'));
219
+ console.error(chalk.dim('Check your internet connection'));
220
+ process.exit(1);
221
+ }
222
+
223
+ if (currentVersion === latestVersion) {
224
+ console.log(fmt.status('success', `Already on latest version (${currentVersion})`));
225
+ return;
226
+ }
227
+
228
+ console.log(` Current: ${chalk.yellow(currentVersion)}`);
229
+ console.log(` Latest: ${chalk.green(latestVersion)}`);
230
+ console.log();
231
+
232
+ if (options.check) {
233
+ console.log(chalk.cyan('Run "rev upgrade" to install the update'));
234
+ return;
235
+ }
236
+
237
+ console.log(chalk.cyan('Upgrading...'));
238
+
239
+ // Run npm update
240
+ const child = spawn('npm', ['install', '-g', 'docrev@latest'], {
241
+ stdio: 'inherit',
242
+ shell: true,
243
+ });
244
+
245
+ child.on('close', (code) => {
246
+ if (code === 0) {
247
+ console.log();
248
+ console.log(fmt.status('success', `Upgraded to ${latestVersion}`));
249
+ } else {
250
+ console.error(chalk.red('Upgrade failed'));
251
+ console.error(chalk.dim('Try running: npm install -g docrev@latest'));
252
+ process.exit(1);
253
+ }
254
+ });
255
+
256
+ child.on('error', (err) => {
257
+ console.error(chalk.red(`Upgrade failed: ${err.message}`));
258
+ console.error(chalk.dim('Try running: npm install -g docrev@latest'));
259
+ process.exit(1);
260
+ });
261
+ } catch (err) {
262
+ console.error(chalk.red(`Error: ${(err as Error).message}`));
263
+ process.exit(1);
264
+ }
265
+ });
266
+ }
267
+
268
+ // Helper functions for help text
269
+
270
+ function showFullHelp(pkg?: PackageJson): void {
271
+ console.log(`
272
+ ${chalk.bold.cyan('rev')} ${chalk.dim(`v${pkg?.version || 'unknown'}`)} - Revision workflow for Word ↔ Markdown round-trips
273
+
274
+ ${chalk.bold('DESCRIPTION')}
275
+ Handle reviewer feedback when collaborating on academic papers.
276
+ Import changes from Word, review them interactively, and preserve
277
+ comments for discussion with Claude.
278
+
279
+ ${chalk.bold('GLOBAL OPTIONS')}
280
+
281
+ ${chalk.bold('--no-color')} Disable colored output
282
+ ${chalk.bold('-q, --quiet')} Suppress non-essential output
283
+ ${chalk.bold('--json')} Output in JSON format (for scripting)
284
+
285
+ ${chalk.bold('TYPICAL WORKFLOW')}
286
+
287
+ ${chalk.dim('1.')} Build and send: ${chalk.green('rev build docx')} ${chalk.dim('(or rev b docx)')}
288
+ ${chalk.dim('2.')} Reviewers return ${chalk.yellow('reviewed.docx')} with edits and comments
289
+ ${chalk.dim('3.')} Sync their feedback: ${chalk.green('rev sync reviewed.docx')}
290
+ ${chalk.dim('4.')} Work through comments: ${chalk.green('rev next')} ${chalk.dim('(n)')} / ${chalk.green('rev todo')} ${chalk.dim('(t)')}
291
+ ${chalk.dim('5.')} Accept/reject changes: ${chalk.green('rev accept -a')} ${chalk.dim('(a)')} or ${chalk.green('rev review')}
292
+ ${chalk.dim('6.')} Rebuild: ${chalk.green('rev build docx')}
293
+ ${chalk.dim('7.')} Archive old files: ${chalk.green('rev archive')}
294
+
295
+ ${chalk.bold('MORE HELP')}
296
+
297
+ rev help workflow Detailed workflow guide
298
+ rev help syntax Annotation syntax reference
299
+ rev help commands All commands with options
300
+ `);
301
+ }
302
+
303
+ function showWorkflowHelp(): void {
304
+ console.log(`
305
+ ${chalk.bold.cyan('rev')} ${chalk.dim('- Workflow Guide')}
306
+
307
+ ${chalk.bold('OVERVIEW')}
308
+
309
+ The rev workflow solves a common problem: you write in Markdown,
310
+ but collaborators review in Word. When they return edited documents,
311
+ you need to merge their changes back into your source files.
312
+
313
+ ${chalk.bold('STEP 1: BUILD & SEND')}
314
+
315
+ ${chalk.green('rev build docx')}
316
+ ${chalk.dim('# Send the .docx to reviewers')}
317
+
318
+ ${chalk.bold('STEP 2: RECEIVE FEEDBACK')}
319
+
320
+ Reviewers edit the document, adding:
321
+ ${chalk.dim('•')} Track changes (insertions, deletions)
322
+ ${chalk.dim('•')} Comments (questions, suggestions)
323
+
324
+ ${chalk.bold('STEP 3: SYNC CHANGES')}
325
+
326
+ ${chalk.green('rev sync reviewed.docx')}
327
+ ${chalk.dim('# Or just: rev sync (auto-detects most recent .docx)')}
328
+
329
+ Your markdown files now contain their feedback as annotations.
330
+
331
+ ${chalk.bold('STEP 4: WORK THROUGH COMMENTS')}
332
+
333
+ ${chalk.green('rev todo')} ${chalk.dim('# See all pending comments')}
334
+ ${chalk.green('rev next')} ${chalk.dim('# Show next pending comment')}
335
+ ${chalk.green('rev reply file.md -n 1 -m "Done"')}
336
+ ${chalk.green('rev resolve file.md -n 1')}
337
+
338
+ ${chalk.bold('STEP 5: ACCEPT/REJECT CHANGES')}
339
+
340
+ ${chalk.green('rev accept file.md -a')} ${chalk.dim('# Accept all changes')}
341
+ ${chalk.green('rev reject file.md -n 2')} ${chalk.dim('# Reject specific change')}
342
+ ${chalk.dim('# Or use interactive mode:')}
343
+ ${chalk.green('rev review file.md')}
344
+
345
+ ${chalk.bold('STEP 6: REBUILD')}
346
+
347
+ ${chalk.green('rev build docx')}
348
+ ${chalk.green('rev build docx --dual')} ${chalk.dim('# Clean + comments version')}
349
+
350
+ ${chalk.bold('STEP 7: ARCHIVE & REPEAT')}
351
+
352
+ ${chalk.green('rev archive')} ${chalk.dim('# Move reviewer files to archive/')}
353
+ ${chalk.dim('# Send new .docx, repeat cycle')}
354
+ `);
355
+ }
356
+
357
+ function showSyntaxHelp(): void {
358
+ console.log(`
359
+ ${chalk.bold.cyan('rev')} ${chalk.dim('- Annotation Syntax (CriticMarkup)')}
360
+
361
+ ${chalk.bold('INSERTIONS')}
362
+
363
+ Syntax: ${chalk.green('{++inserted text++}')}
364
+ Meaning: This text was added by the reviewer
365
+
366
+ Example:
367
+ We ${chalk.green('{++specifically++}')} focused on neophytes.
368
+ → Reviewer added the word "specifically"
369
+
370
+ ${chalk.bold('DELETIONS')}
371
+
372
+ Syntax: ${chalk.red('{--deleted text--}')}
373
+ Meaning: This text was removed by the reviewer
374
+
375
+ Example:
376
+ We focused on ${chalk.red('{--recent--}')} neophytes.
377
+ → Reviewer removed the word "recent"
378
+
379
+ ${chalk.bold('SUBSTITUTIONS')}
380
+
381
+ Syntax: ${chalk.yellow('{~~old text~>new text~~}')}
382
+ Meaning: Text was changed from old to new
383
+
384
+ Example:
385
+ The effect was ${chalk.yellow('{~~significant~>substantial~~}')}.
386
+ → Reviewer changed "significant" to "substantial"
387
+
388
+ ${chalk.bold('COMMENTS')}
389
+
390
+ Syntax: ${chalk.blue('{>>Author: comment text<<}')}
391
+ Meaning: Reviewer left a comment at this location
392
+
393
+ Example:
394
+ The results were significant. ${chalk.blue('{>>Dr. Smith: Add p-value<<}')}
395
+ → Dr. Smith commented asking for a p-value
396
+
397
+ Comments are placed ${chalk.bold('after')} the text they reference.
398
+ `);
399
+ }
400
+
401
+ function showCommandsHelp(): void {
402
+ console.log(`
403
+ ${chalk.bold.cyan('rev')} ${chalk.dim('- Command Reference')}
404
+
405
+ ${chalk.bold('rev import')} <docx> <original-md>
406
+
407
+ Import changes from a Word document by comparing against your
408
+ original Markdown source.
409
+
410
+ ${chalk.bold('Arguments:')}
411
+ docx Word document from reviewer
412
+ original-md Your original Markdown file
413
+
414
+ ${chalk.bold('Options:')}
415
+ -o, --output <file> Write to different file (default: overwrites original)
416
+ -a, --author <name> Author name for changes (default: "Reviewer")
417
+ --dry-run Preview changes without saving
418
+
419
+ ${chalk.bold('rev review')} <file>
420
+
421
+ Interactively review and accept/reject track changes.
422
+ Comments are preserved; only track changes are processed.
423
+
424
+ ${chalk.bold('Keys:')}
425
+ a Accept this change
426
+ r Reject this change
427
+ s Skip (decide later)
428
+ A Accept all remaining changes
429
+ L Reject all remaining changes
430
+ q Quit without saving
431
+
432
+ ${chalk.bold('rev strip')} <file>
433
+
434
+ Remove annotations, outputting clean Markdown.
435
+ Track changes are applied (insertions kept, deletions removed).
436
+
437
+ ${chalk.bold('Options:')}
438
+ -o, --output <file> Write to file (default: stdout)
439
+ -c, --keep-comments Keep comment annotations
440
+
441
+ ${chalk.bold('rev help')} [topic]
442
+
443
+ Show help. Optional topics:
444
+ workflow Step-by-step workflow guide
445
+ syntax Annotation syntax reference
446
+ commands This command reference
447
+ `);
448
+ }