reslop 0.1.9 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/README.md +3 -1
  3. package/bin/reslop.js +2 -2
  4. package/lib/ansi.js +148 -5
  5. package/lib/api.js +4 -2
  6. package/lib/capabilities.js +13 -14
  7. package/lib/cli.js +38 -49
  8. package/lib/clipboard.js +1 -1
  9. package/lib/{deps/changes.js → deps-diff.js} +309 -19
  10. package/lib/deps.js +498 -29
  11. package/lib/detect.js +1 -7
  12. package/lib/diff/align.js +7 -10
  13. package/lib/diff/diff.js +53 -0
  14. package/lib/diff/display.js +5 -4
  15. package/lib/diff/inline.js +26 -21
  16. package/lib/diff/items.js +6 -4
  17. package/lib/diff/patch.js +1 -3
  18. package/lib/diff/unit.js +392 -0
  19. package/lib/files.js +5 -8
  20. package/lib/{git/branches.js → git-branches.js} +191 -11
  21. package/lib/{git/dependencies.js → git-deps.js} +111 -38
  22. package/lib/git-reword-todo.js +20 -0
  23. package/lib/git.js +606 -68
  24. package/lib/github.js +28 -197
  25. package/lib/gitlab.js +24 -195
  26. package/lib/highlight/bash.js +1 -3
  27. package/lib/highlight/css.js +1 -3
  28. package/lib/highlight/js.js +11 -14
  29. package/lib/highlight.js +6 -9
  30. package/lib/keys.js +14 -8
  31. package/lib/manifest.js +330 -0
  32. package/lib/remote.js +374 -0
  33. package/lib/render/branches.js +18 -100
  34. package/lib/render/chrome.js +71 -14
  35. package/lib/render/commits.js +69 -60
  36. package/lib/render/diff.js +20 -20
  37. package/lib/render/files.js +6 -34
  38. package/lib/render/frame.js +36 -13
  39. package/lib/render/notes.js +26 -8
  40. package/lib/render/primitives.js +99 -11
  41. package/lib/{render.js → render/render.js} +8 -8
  42. package/lib/render/unit.js +41 -0
  43. package/lib/review-merge.js +270 -0
  44. package/lib/review.js +782 -14
  45. package/lib/select.js +5 -10
  46. package/lib/session/accessors.js +9 -1
  47. package/lib/session/actions.js +61 -18
  48. package/lib/session/branches.js +116 -128
  49. package/lib/session/change-actions.js +202 -123
  50. package/lib/session/commit.js +201 -115
  51. package/lib/session/compose.js +181 -15
  52. package/lib/session/dispatch.js +110 -60
  53. package/lib/session/files-pane.js +156 -148
  54. package/lib/session/items.js +22 -30
  55. package/lib/session/lifecycle.js +151 -54
  56. package/lib/session/load.js +19 -33
  57. package/lib/session/navigation.js +12 -1
  58. package/lib/session/ops.js +28 -41
  59. package/lib/session/pointer.js +57 -57
  60. package/lib/session/review.js +23 -37
  61. package/lib/session/terminal.js +6 -12
  62. package/lib/session/todos.js +37 -13
  63. package/lib/session/unit.js +208 -0
  64. package/lib/session/update.js +33 -46
  65. package/lib/session/view.js +78 -9
  66. package/lib/session/watch.js +128 -24
  67. package/lib/session.js +22 -246
  68. package/lib/source.js +3 -3
  69. package/lib/update.js +40 -75
  70. package/lib/{git/proc.js → utilities.js} +75 -11
  71. package/lib/wrap.js +1 -3
  72. package/package.json +2 -2
  73. package/lib/deps/items.js +0 -134
  74. package/lib/deps/manifest.js +0 -152
  75. package/lib/deps/merge.js +0 -120
  76. package/lib/deps/proposals.js +0 -164
  77. package/lib/deps/reports.js +0 -180
  78. package/lib/deps/rows.js +0 -176
  79. package/lib/deps/scan.js +0 -138
  80. package/lib/deps/usage.js +0 -68
  81. package/lib/deps/versions.js +0 -119
  82. package/lib/diff.js +0 -33
  83. package/lib/git/changes.js +0 -164
  84. package/lib/git/read.js +0 -253
  85. package/lib/git/run.js +0 -100
  86. package/lib/npm/commands.js +0 -100
  87. package/lib/remote/changes.js +0 -37
  88. package/lib/remote/headers.js +0 -36
  89. package/lib/remote/http.js +0 -122
  90. package/lib/remote/notes.js +0 -94
  91. package/lib/review/format.js +0 -434
  92. package/lib/review/storage.js +0 -150
  93. package/lib/review/store.js +0 -178
  94. package/lib/review/templates.js +0 -49
  95. package/lib/session/origins.js +0 -7
  96. package/lib/sys.js +0 -84
package/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  ## [Unreleased][unreleased]
4
4
 
5
+ ## [0.2.1][] - 2026-09-23
6
+
7
+ - Type a new commit message at the top of the list
8
+ - Improve `fixup!` functions: create and apply
9
+ - Reword the selected commit message with `r`
10
+ - Add light theme: `-light` option and `l` hotkey to switch
11
+ - Support checkbox state in TODOs, UI for state change
12
+ - Reload the file list when leaving todos, branches, or commits
13
+ - Let agents run `editing` reviews and leave `done` files unchanged
14
+ - Skip npm audit and outdated unless package files changed
15
+ - Sync and merge TODOs with file instead of overwriting it
16
+
17
+ ## [0.2.0][] - 2026-09-22
18
+
19
+ - Add a full-size file editor
20
+ - Optimize codebase and reorganize code structure
21
+ - Add pull and push on the commits list screen
22
+ - Fix disk watcher startup on macOS
23
+ - Fix Windows temp-path comparison and same-size file reload polling
24
+
25
+ ## [0.1.9][] - 2026-09-19
26
+
5
27
  - Remove `r` reload hotkey and hint; auto-reload covers disk changes
6
28
  - Improve branch list TUI/TUX
7
29
  - Right-align the time column in the branch list
@@ -95,7 +117,9 @@
95
117
  - Feedback and todos that produce a repair plan for an agent
96
118
  - Intra-line highlighting in unified, mixed, and side-by-side layouts
97
119
 
98
- [unreleased]: https://github.com/tshemsedinov/reslop/compare/v0.1.9...HEAD
120
+ [unreleased]: https://github.com/tshemsedinov/reslop/compare/v0.2.1...HEAD
121
+ [0.2.1]: https://github.com/tshemsedinov/reslop/compare/v0.2.0...v0.2.1
122
+ [0.2.0]: https://github.com/tshemsedinov/reslop/compare/v0.1.9...v0.2.0
99
123
  [0.1.9]: https://github.com/tshemsedinov/reslop/compare/v0.1.8...v0.1.9
100
124
  [0.1.8]: https://github.com/tshemsedinov/reslop/compare/v0.1.7...v0.1.8
101
125
  [0.1.7]: https://github.com/tshemsedinov/reslop/compare/v0.1.6...v0.1.7
package/README.md CHANGED
@@ -20,10 +20,11 @@ Review → Plan → Repair → Verify
20
20
 
21
21
  - Leave feedback, issues/todos, and in-place code proposals as a repair plan
22
22
  - Review uncommitted git diffs, a given commit, a GitHub PR, or a GitLab MR
23
+ - File-scope review mode: not just diffs, with editor mode
23
24
  - Stage, unstage, or revert each contiguous block of diff lines
24
25
  - Auto-reload local diffs when files change, keeping the current screen
25
26
  - Import GitHub PR and GitLab MR review comments into the local plan for AI
26
- - Commits list: commit, amend, fixup, and drop
27
+ - Commits list: commit, amend, fixup, reword, drop, pull, and push
27
28
  - Branches: checkout, create, rebase, drop, pull, and push
28
29
  - Review each npm dependency once across `package.json` and the lockfile
29
30
  - Propose unused removals, npm audit fixes, and outdated updates as diffs
@@ -47,6 +48,7 @@ Works on Linux, macOS, and Windows. On Windows use Windows Terminal.
47
48
  - `reslop https://github.com/metarhia/metacom/pull/555` GitHub/GitLab PR/MR
48
49
  - `reslop -n` start a new review even if the latest is still editing
49
50
  - `reslop -r` read-only mode
51
+ - `reslop -light` light color theme (default dark)
50
52
 
51
53
  Reviews go in `.review/YYYY-MM-DD-NN.md` with frontmatter `status`:
52
54
 
package/bin/reslop.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- const { run, failLine } = require('../lib/cli.js');
4
+ const { run, errorMessage } = require('../lib/cli.js');
5
5
  const { LEAVE_TERM } = require('../lib/session.js');
6
6
 
7
7
  const fail = (reason) => {
@@ -10,7 +10,7 @@ const fail = (reason) => {
10
10
  } catch {
11
11
  // ignore
12
12
  }
13
- process.stderr.write(failLine(reason));
13
+ process.stderr.write(`reslop: ${errorMessage(reason)}\n`);
14
14
  process.exit(1);
15
15
  };
16
16
 
package/lib/ansi.js CHANGED
@@ -40,7 +40,7 @@ const WIDE_CP = [
40
40
  0x1fae9, 0x1faf0, 0x1faf8, 0x20000, 0x2fffd, 0x30000, 0x3fffd,
41
41
  ];
42
42
 
43
- const THEME = {
43
+ const DARK_UI = {
44
44
  delLineFg: [0xcc, 0x7a, 0x7a],
45
45
  delLineBg: [0x3a, 0x1a, 0x1a],
46
46
  delCharFg: [0xff, 0xff, 0xff],
@@ -87,7 +87,7 @@ const THEME = {
87
87
  stagedAddCharBg: [0x4e, 0x4e, 0x58],
88
88
  };
89
89
 
90
- const CODE_FG = {
90
+ const DARK_CODE = {
91
91
  keyword: [197, 134, 192],
92
92
  control: [197, 134, 192],
93
93
  storage: [86, 156, 214],
@@ -122,7 +122,7 @@ const CODE_FG = {
122
122
  logDebug: [140, 160, 180],
123
123
  };
124
124
 
125
- const LOG_COL_FG = [
125
+ const DARK_LOG_COLS = [
126
126
  [210, 210, 220],
127
127
  [120, 180, 220],
128
128
  [180, 160, 220],
@@ -133,6 +133,143 @@ const LOG_COL_FG = [
133
133
  [200, 170, 140],
134
134
  ];
135
135
 
136
+ const LIGHT_UI = {
137
+ delLineFg: [0xb3, 0x1d, 0x28],
138
+ delLineBg: [0xff, 0xeb, 0xe9],
139
+ delCharFg: [0x1f, 0x23, 0x28],
140
+ delCharBg: [0xff, 0xc1, 0xc0],
141
+ addLineFg: [0x1a, 0x7f, 0x37],
142
+ addLineBg: [0xe6, 0xff, 0xec],
143
+ addCharFg: [0x1f, 0x23, 0x28],
144
+ addCharBg: [0xab, 0xf2, 0xbc],
145
+ ctxFg: [0x57, 0x60, 0x6a],
146
+ ctxBg: [0xff, 0xff, 0xff],
147
+ splitGutterFg: [0xea, 0xee, 0xf2],
148
+ splitGutterBg: [0xea, 0xee, 0xf2],
149
+ headerFg: [0x24, 0x29, 0x2f],
150
+ headerBg: [0xd8, 0xde, 0xe4],
151
+ headerChromeFg: [0x42, 0x4a, 0x53],
152
+ headerRepoFg: [0x24, 0x29, 0x2f],
153
+ headerDirFg: [0x57, 0x60, 0x6a],
154
+ headerSlashFg: [0x8c, 0x95, 0x9f],
155
+ headerFileFg: [0x1f, 0x23, 0x28],
156
+ headerMetaFg: [0x42, 0x4a, 0x53],
157
+ chromeFg: [0x24, 0x29, 0x2f],
158
+ chromeBg: [0xf0, 0xf2, 0xf5],
159
+ noteBg: [0xe8, 0xf0, 0xfe],
160
+ buttonFg: [0x57, 0x60, 0x6a],
161
+ buttonBg: [0xe1, 0xe5, 0xea],
162
+ buttonHotFg: [0x0f, 0x14, 0x19],
163
+ currentBg: [0xac, 0xe1, 0xc2],
164
+ checkFg: [0x1f, 0x23, 0x28],
165
+ checkBg: [0xc8, 0xcf, 0xd8],
166
+ checkDoneFg: [0x0f, 0x3d, 0x1f],
167
+ checkDoneBg: [0xab, 0xf2, 0xbc],
168
+ errorFg: [0xcf, 0x22, 0x2e],
169
+ warnFg: [0x9a, 0x67, 0x00],
170
+ mutedFg: [0x6e, 0x77, 0x81],
171
+ shaFg: [0x05, 0x50, 0xae],
172
+ shaDarkFg: [0x0b, 0x3d, 0x70],
173
+ stagedDelLineFg: [0x57, 0x60, 0x6a],
174
+ stagedDelLineBg: [0xf3, 0xf4, 0xf6],
175
+ stagedDelCharFg: [0x1f, 0x23, 0x28],
176
+ stagedDelCharBg: [0xd8, 0xdc, 0xe1],
177
+ stagedAddLineFg: [0x42, 0x4a, 0x53],
178
+ stagedAddLineBg: [0xea, 0xec, 0xef],
179
+ stagedAddCharFg: [0x0f, 0x14, 0x19],
180
+ stagedAddCharBg: [0xcc, 0xd1, 0xd7],
181
+ };
182
+
183
+ const LIGHT_CODE = {
184
+ keyword: [175, 0, 219],
185
+ control: [175, 0, 219],
186
+ storage: [0, 0, 255],
187
+ literal: [0, 0, 255],
188
+ string: [163, 21, 21],
189
+ template: [163, 21, 21],
190
+ escape: [238, 0, 0],
191
+ interpolation: [0, 16, 128],
192
+ number: [9, 134, 88],
193
+ comment: [0, 128, 0],
194
+ function: [121, 94, 38],
195
+ method: [121, 94, 38],
196
+ className: [38, 127, 153],
197
+ type: [38, 127, 153],
198
+ interface: [38, 127, 153],
199
+ property: [0, 16, 128],
200
+ variable: [0, 16, 128],
201
+ parameter: [0, 16, 128],
202
+ constant: [0, 112, 193],
203
+ operator: [60, 60, 60],
204
+ punct: [60, 60, 60],
205
+ regex: [129, 31, 63],
206
+ decorator: [121, 94, 38],
207
+ tag: [128, 0, 0],
208
+ attr: [229, 0, 0],
209
+ plain: [31, 31, 31],
210
+ punctDim: [140, 140, 140],
211
+ logDate: [60, 60, 70],
212
+ logError: [205, 49, 49],
213
+ logWarn: [160, 110, 0],
214
+ logInfo: [0, 90, 180],
215
+ logDebug: [100, 110, 125],
216
+ };
217
+
218
+ const LIGHT_LOG_COLS = [
219
+ [60, 60, 70],
220
+ [0, 95, 150],
221
+ [110, 70, 170],
222
+ [30, 120, 60],
223
+ [150, 95, 20],
224
+ [170, 50, 90],
225
+ [20, 120, 120],
226
+ [130, 90, 50],
227
+ ];
228
+
229
+ const PALETTES = {
230
+ dark: { ui: DARK_UI, code: DARK_CODE, logCols: DARK_LOG_COLS },
231
+ light: { ui: LIGHT_UI, code: LIGHT_CODE, logCols: LIGHT_LOG_COLS },
232
+ };
233
+ const THEME_NAMES = Object.keys(PALETTES);
234
+
235
+ const applyColors = (into, from) => {
236
+ for (const key of Object.keys(from)) {
237
+ const src = from[key];
238
+ let dst = into[key];
239
+ if (!dst) {
240
+ dst = [0, 0, 0];
241
+ into[key] = dst;
242
+ }
243
+ dst[0] = src[0];
244
+ dst[1] = src[1];
245
+ dst[2] = src[2];
246
+ }
247
+ return into;
248
+ };
249
+
250
+ const cloneColors = (colors) => {
251
+ const into = Array.isArray(colors) ? [] : {};
252
+ return applyColors(into, colors);
253
+ };
254
+
255
+ const THEME = cloneColors(DARK_UI);
256
+ const CODE_FG = cloneColors(DARK_CODE);
257
+ const LOG_COL_FG = cloneColors(DARK_LOG_COLS);
258
+ let currentTheme = 'dark';
259
+
260
+ const setTheme = (name) => {
261
+ if (!Object.hasOwn(PALETTES, name)) {
262
+ throw new Error(`unknown theme ${name}`);
263
+ }
264
+ const palette = PALETTES[name];
265
+ applyColors(THEME, palette.ui);
266
+ applyColors(CODE_FG, palette.code);
267
+ applyColors(LOG_COL_FG, palette.logCols);
268
+ currentTheme = name;
269
+ };
270
+
271
+ const themeName = () => currentTheme;
272
+
136
273
  const codeFg = (style) => {
137
274
  if (typeof style !== 'string') return CODE_FG.plain;
138
275
  if (Object.hasOwn(CODE_FG, style)) return CODE_FG[style];
@@ -158,8 +295,10 @@ const paint = (text, fgRgb, bgRgb, color, bold) => {
158
295
  const stripAnsi = (text) => text.replace(ANSI_RE, '');
159
296
 
160
297
  const graphemes = (text) => {
161
- const parts = [];
162
- for (const { segment } of SEGMENTER.segment(text)) parts.push(segment);
298
+ const parts = new Array(text.length);
299
+ let n = 0;
300
+ for (const { segment } of SEGMENTER.segment(text)) parts[n++] = segment;
301
+ parts.length = n;
163
302
  return parts;
164
303
  };
165
304
 
@@ -220,6 +359,10 @@ module.exports = {
220
359
  EL,
221
360
  THEME,
222
361
  CODE_FG,
362
+ PALETTES,
363
+ THEME_NAMES,
364
+ setTheme,
365
+ themeName,
223
366
  codeFg,
224
367
  fg,
225
368
  bg,
package/lib/api.js CHANGED
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  const ansi = require('./ansi.js');
4
- const diff = require('./diff.js');
4
+ const diff = require('./diff/diff.js');
5
5
  const git = require('./git.js');
6
6
  const keys = require('./keys.js');
7
- const render = require('./render.js');
7
+ const render = require('./render/render.js');
8
8
  const highlight = require('./highlight.js');
9
9
  const detect = require('./detect.js');
10
10
  const github = require('./github.js');
@@ -16,6 +16,8 @@ const cli = require('./cli.js');
16
16
  module.exports = {
17
17
  THEME: ansi.THEME,
18
18
  CODE_FG: ansi.CODE_FG,
19
+ THEME_NAMES: ansi.THEME_NAMES,
20
+ setTheme: ansi.setTheme,
19
21
  ACTIONS: keys.ACTIONS,
20
22
  diffChars: diff.diffChars,
21
23
  parseDiff: diff.parseDiff,
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- const fs = require('node:fs');
4
-
5
3
  const READ_METHODS = [
6
4
  'load',
7
5
  'loadAsync',
8
6
  'loadExtras',
9
7
  'toplevel',
10
8
  'resolveRev',
9
+ 'listFiles',
10
+ 'fileText',
11
11
  ];
12
12
 
13
13
  const CHANGE_METHODS = [
@@ -17,6 +17,8 @@ const CHANGE_METHODS = [
17
17
  'revert',
18
18
  'revertFile',
19
19
  'edit',
20
+ 'writeFile',
21
+ 'stagePath',
20
22
  ];
21
23
 
22
24
  const BRANCH_METHODS = [
@@ -24,6 +26,7 @@ const BRANCH_METHODS = [
24
26
  'commitAsync',
25
27
  'hasStaged',
26
28
  'lastMessage',
29
+ 'commitMessage',
27
30
  'listBranches',
28
31
  'listCommits',
29
32
  'currentBranch',
@@ -37,6 +40,10 @@ const BRANCH_METHODS = [
37
40
  'dropAsync',
38
41
  'dropCommit',
39
42
  'dropCommitAsync',
43
+ 'applyFixup',
44
+ 'applyFixupAsync',
45
+ 'reword',
46
+ 'rewordAsync',
40
47
  'pull',
41
48
  'pullAsync',
42
49
  'push',
@@ -46,9 +53,9 @@ const BRANCH_METHODS = [
46
53
  const SOURCE_CAPS = {
47
54
  local: { read: true, changes: true, branches: true, review: true },
48
55
  commit: { read: true, changes: false, branches: false, review: true },
49
- 'github-pr': { read: true, changes: false, branches: false, review: true },
50
- 'gitlab-mr': { read: true, changes: false, branches: false, review: true },
51
- 'read-only': { read: true, changes: false, branches: false, review: true },
56
+ pr: { read: true, changes: false, branches: false, review: true },
57
+ mr: { read: true, changes: false, branches: false, review: true },
58
+ readonly: { read: true, changes: false, branches: false, review: true },
52
59
  };
53
60
 
54
61
  const withReadOnly = (caps, readOnly) => {
@@ -86,16 +93,9 @@ const sessionCapabilities = (options) => {
86
93
  }
87
94
  const repoCaps = options.repo && options.repo.capabilities;
88
95
  if (repoCaps) return withReadOnly(repoCaps, readOnly);
89
- return capabilitiesFor(readOnly ? 'read-only' : 'local');
96
+ return capabilitiesFor(readOnly ? 'readonly' : 'local');
90
97
  };
91
98
 
92
- const createReviewStorage = (io = {}) => ({
93
- readdirSync: io.readdirSync ?? fs.readdirSync,
94
- readFileSync: io.readFileSync ?? fs.readFileSync,
95
- writeFileSync: io.writeFileSync ?? fs.writeFileSync,
96
- mkdirSync: io.mkdirSync ?? fs.mkdirSync,
97
- });
98
-
99
99
  module.exports = {
100
100
  READ_METHODS,
101
101
  CHANGE_METHODS,
@@ -105,5 +105,4 @@ module.exports = {
105
105
  withReadOnly,
106
106
  attachCapabilities,
107
107
  sessionCapabilities,
108
- createReviewStorage,
109
108
  };
package/lib/cli.js CHANGED
@@ -1,29 +1,32 @@
1
1
  'use strict';
2
2
 
3
- const fs = require('node:fs');
4
3
  const path = require('node:path');
5
4
 
5
+ const { exists } = require('metautil');
6
+
6
7
  const { createGitRepo } = require('./git.js');
7
8
  const { githubToken, loadPullRequest } = require('./github.js');
8
9
  const { gitlabToken, loadMergeRequest } = require('./gitlab.js');
9
10
  const { selectChangeSource, createLoadedSource } = require('./source.js');
10
11
  const { Session } = require('./session.js');
11
12
  const { capabilitiesFor, attachCapabilities } = require('./capabilities.js');
13
+ const { setTheme } = require('./ansi.js');
12
14
 
13
15
  const HEX_REV = /^[0-9a-f]{4,40}$/i;
14
16
  const HEAD_REV = /^HEAD([~^].*)?$/i;
15
17
  const RANGE_REV = /[~^]/;
16
18
 
17
- const USAGE = 'Usage: reslop [-n] [-r] [path | commit | pr-url | mr-url]';
19
+ const USAGE =
20
+ 'Usage: reslop [-n] [-r] [-light] [path | commit | pr-url | mr-url]';
18
21
 
19
22
  const REMOTE_CHANGE = {
20
- 'github-pr': {
23
+ pr: {
21
24
  load: 'loadPullRequest',
22
25
  fallback: loadPullRequest,
23
26
  token: githubToken,
24
27
  target: (selected) => selected.pr,
25
28
  },
26
- 'gitlab-mr': {
29
+ mr: {
27
30
  load: 'loadMergeRequest',
28
31
  fallback: loadMergeRequest,
29
32
  token: gitlabToken,
@@ -38,16 +41,8 @@ const errorMessage = (reason) => {
38
41
  return `${reason}`;
39
42
  };
40
43
 
41
- const failLine = (reason) => `reslop: ${errorMessage(reason)}\n`;
42
-
43
44
  const fail = (stderr, reason) => {
44
- stderr.write(failLine(reason));
45
- return 1;
46
- };
47
-
48
- const failUsage = (stderr, reason) => {
49
- fail(stderr, reason);
50
- stderr.write(`${USAGE}\n`);
45
+ stderr.write(`reslop: ${errorMessage(reason)}\n`);
51
46
  return 1;
52
47
  };
53
48
 
@@ -55,6 +50,7 @@ const parseArgv = (argv) => {
55
50
  const paths = [];
56
51
  let newReview = false;
57
52
  let readOnly = false;
53
+ let theme = 'dark';
58
54
  for (const arg of argv) {
59
55
  if (arg === '-n') {
60
56
  newReview = true;
@@ -64,12 +60,16 @@ const parseArgv = (argv) => {
64
60
  readOnly = true;
65
61
  continue;
66
62
  }
63
+ if (arg === '-light') {
64
+ theme = 'light';
65
+ continue;
66
+ }
67
67
  if (arg.startsWith('-')) {
68
68
  throw new Error(`unknown option ${arg}`);
69
69
  }
70
70
  paths.push(arg);
71
71
  }
72
- return { paths, newReview, readOnly };
72
+ return { paths, newReview, readOnly, theme };
73
73
  };
74
74
 
75
75
  const looksLikeRev = (spec) => {
@@ -79,19 +79,10 @@ const looksLikeRev = (spec) => {
79
79
  return RANGE_REV.test(spec);
80
80
  };
81
81
 
82
- const pathExists = (cwd, rel, statSync) => {
83
- try {
84
- statSync(path.resolve(cwd, rel));
85
- return true;
86
- } catch {
87
- return false;
88
- }
89
- };
90
-
91
- const resolveScope = (cwd, paths, repo, statSync = fs.statSync) => {
82
+ const resolveScope = async (cwd, paths, repo) => {
92
83
  if (!paths.length) return { rev: null, paths };
93
84
  const first = paths[0];
94
- if (pathExists(cwd, first, statSync)) {
85
+ if (await exists(path.resolve(cwd, first))) {
95
86
  return { rev: null, paths };
96
87
  }
97
88
  const resolved = repo.resolveRev(cwd, first);
@@ -107,14 +98,11 @@ const resolveScope = (cwd, paths, repo, statSync = fs.statSync) => {
107
98
  return { rev: null, paths };
108
99
  };
109
100
 
110
- const isColorEnabled = (stdout, env) => stdout.isTTY === true && !env.NO_COLOR;
111
-
112
101
  const sessionOptions = (proc, deps, args, env, extra) => ({
113
102
  cwd: deps.cwd ?? proc.cwd(),
114
103
  stdin: proc.stdin,
115
104
  stdout: proc.stdout,
116
- color: isColorEnabled(proc.stdout, env),
117
- getSize: deps.getSize,
105
+ color: proc.stdout.isTTY === true && !env.NO_COLOR,
118
106
  startPane: 'files',
119
107
  newReview: args.newReview,
120
108
  readOnly: args.readOnly,
@@ -122,22 +110,21 @@ const sessionOptions = (proc, deps, args, env, extra) => ({
122
110
  ...extra,
123
111
  });
124
112
 
125
- const openLocalSession = (proc, deps, args, selected, env) => {
113
+ const openLocalSession = async (proc, deps, args, selected, env) => {
126
114
  const cwd = deps.cwd ?? proc.cwd();
127
115
  const rawRepo = deps.repo ?? createGitRepo();
128
- const statSync = deps.statSync ?? fs.statSync;
129
- const scope = resolveScope(cwd, selected.paths, rawRepo, statSync);
116
+ const scope = await resolveScope(cwd, selected.paths, rawRepo);
130
117
  const kind = scope.rev ? 'commit' : 'local';
131
118
  const capabilities = capabilitiesFor(kind, { readOnly: args.readOnly });
132
119
  const repo = attachCapabilities(rawRepo, capabilities);
133
- const session = new Session(
134
- sessionOptions(proc, deps, args, env, {
135
- repo,
136
- paths: scope.paths,
137
- rev: scope.rev,
138
- capabilities,
139
- }),
140
- );
120
+ const extra = {
121
+ repo,
122
+ paths: scope.paths,
123
+ rev: scope.rev,
124
+ capabilities,
125
+ };
126
+ const options = sessionOptions(proc, deps, args, env, extra);
127
+ const session = new Session(options);
141
128
  session.ensureRepo();
142
129
  return session;
143
130
  };
@@ -168,13 +155,12 @@ const openRemoteSession = (proc, deps, args, selected, env) => {
168
155
  const capabilities = capabilitiesFor(selected.kind, {
169
156
  readOnly: args.readOnly,
170
157
  });
171
- return new Session(
172
- sessionOptions(proc, deps, args, env, {
173
- repo: remoteRepo(deps, selected, env, cwd),
174
- paths: selected.paths,
175
- capabilities,
176
- }),
177
- );
158
+ const repo = remoteRepo(deps, selected, env, cwd);
159
+ const extra = { repo, paths: selected.paths, capabilities };
160
+ const options = sessionOptions(proc, deps, args, env, extra);
161
+ const session = new Session(options);
162
+ session.ensureRepo();
163
+ return session;
178
164
  };
179
165
 
180
166
  const loadSession = async (proc, deps, args) => {
@@ -200,8 +186,11 @@ const run = async (proc, deps = {}) => {
200
186
  try {
201
187
  args = parseArgv(proc.argv.slice(2));
202
188
  } catch (error) {
203
- return failUsage(stderr, error);
189
+ fail(stderr, error);
190
+ stderr.write(`${USAGE}\n`);
191
+ return 1;
204
192
  }
193
+ setTheme(args.theme);
205
194
  let session;
206
195
  try {
207
196
  session = await loadSession(proc, deps, args);
@@ -224,7 +213,7 @@ const run = async (proc, deps = {}) => {
224
213
  };
225
214
 
226
215
  module.exports = {
227
- failLine,
216
+ errorMessage,
228
217
  parseArgv,
229
218
  resolveScope,
230
219
  run,
package/lib/clipboard.js CHANGED
@@ -3,7 +3,7 @@
3
3
  const { spawnSync } = require('node:child_process');
4
4
 
5
5
  const { ESC } = require('./ansi.js');
6
- const { clipTools, spawnBase } = require('./sys.js');
6
+ const { clipTools, spawnBase } = require('./utilities.js');
7
7
 
8
8
  const CLIP_TIMEOUT_MS = 1000;
9
9