@vertaaux/cli 0.3.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/MIGRATION.md +239 -0
  3. package/README.md +34 -16
  4. package/dist/app/interactive-app.d.ts +101 -0
  5. package/dist/app/interactive-app.d.ts.map +1 -0
  6. package/dist/app/interactive-app.js +309 -0
  7. package/dist/app/layout/canvas.d.ts +23 -0
  8. package/dist/app/layout/canvas.d.ts.map +1 -0
  9. package/dist/app/layout/canvas.js +36 -0
  10. package/dist/app/layout/footer.d.ts +31 -0
  11. package/dist/app/layout/footer.d.ts.map +1 -0
  12. package/dist/app/layout/footer.js +41 -0
  13. package/dist/app/layout/header.d.ts +20 -0
  14. package/dist/app/layout/header.d.ts.map +1 -0
  15. package/dist/app/layout/header.js +27 -0
  16. package/dist/app/menu/categories.d.ts +20 -0
  17. package/dist/app/menu/categories.d.ts.map +1 -0
  18. package/dist/app/menu/categories.js +181 -0
  19. package/dist/app/menu/filter.d.ts +17 -0
  20. package/dist/app/menu/filter.d.ts.map +1 -0
  21. package/dist/app/menu/filter.js +33 -0
  22. package/dist/app/menu/menu-view.d.ts +35 -0
  23. package/dist/app/menu/menu-view.d.ts.map +1 -0
  24. package/dist/app/menu/menu-view.js +230 -0
  25. package/dist/app/menu/recent.d.ts +24 -0
  26. package/dist/app/menu/recent.d.ts.map +1 -0
  27. package/dist/app/menu/recent.js +49 -0
  28. package/dist/app/types.d.ts +43 -0
  29. package/dist/app/types.d.ts.map +1 -0
  30. package/dist/app/types.js +7 -0
  31. package/dist/app/views/command-runner.d.ts +36 -0
  32. package/dist/app/views/command-runner.d.ts.map +1 -0
  33. package/dist/app/views/command-runner.js +372 -0
  34. package/dist/app/views/help-overlay.d.ts +21 -0
  35. package/dist/app/views/help-overlay.d.ts.map +1 -0
  36. package/dist/app/views/help-overlay.js +45 -0
  37. package/dist/auth/ci-token.d.ts +14 -2
  38. package/dist/auth/ci-token.d.ts.map +1 -1
  39. package/dist/auth/ci-token.js +15 -30
  40. package/dist/auth/device-flow.d.ts +2 -1
  41. package/dist/auth/device-flow.d.ts.map +1 -1
  42. package/dist/auth/device-flow.js +13 -10
  43. package/dist/auth/token-store.d.ts.map +1 -1
  44. package/dist/auth/token-store.js +12 -2
  45. package/dist/baseline/diff.d.ts +2 -2
  46. package/dist/baseline/diff.d.ts.map +1 -1
  47. package/dist/baseline/diff.js +15 -34
  48. package/dist/commands/a11y.d.ts +9 -0
  49. package/dist/commands/a11y.d.ts.map +1 -0
  50. package/dist/commands/a11y.js +76 -0
  51. package/dist/commands/audit/artifacts.d.ts +27 -0
  52. package/dist/commands/audit/artifacts.d.ts.map +1 -0
  53. package/dist/commands/audit/artifacts.js +158 -0
  54. package/dist/commands/audit/ci-detection.d.ts +18 -0
  55. package/dist/commands/audit/ci-detection.d.ts.map +1 -0
  56. package/dist/commands/audit/ci-detection.js +71 -0
  57. package/dist/commands/audit/explain.d.ts +11 -0
  58. package/dist/commands/audit/explain.d.ts.map +1 -0
  59. package/dist/commands/audit/explain.js +45 -0
  60. package/dist/commands/audit/filters.d.ts +17 -0
  61. package/dist/commands/audit/filters.d.ts.map +1 -0
  62. package/dist/commands/audit/filters.js +40 -0
  63. package/dist/commands/audit/index.d.ts +18 -0
  64. package/dist/commands/audit/index.d.ts.map +1 -0
  65. package/dist/commands/audit/index.js +564 -0
  66. package/dist/commands/audit/output.d.ts +32 -0
  67. package/dist/commands/audit/output.d.ts.map +1 -0
  68. package/dist/commands/audit/output.js +130 -0
  69. package/dist/commands/audit/policy.d.ts +19 -0
  70. package/dist/commands/audit/policy.d.ts.map +1 -0
  71. package/dist/commands/audit/policy.js +102 -0
  72. package/dist/commands/audit/scoring.d.ts +23 -0
  73. package/dist/commands/audit/scoring.d.ts.map +1 -0
  74. package/dist/commands/audit/scoring.js +70 -0
  75. package/dist/commands/audit/types.d.ts +88 -0
  76. package/dist/commands/audit/types.d.ts.map +1 -0
  77. package/dist/commands/audit/types.js +8 -0
  78. package/dist/commands/audit.d.ts +2 -60
  79. package/dist/commands/audit.d.ts.map +1 -1
  80. package/dist/commands/audit.js +2 -1038
  81. package/dist/commands/baseline.d.ts +1 -0
  82. package/dist/commands/baseline.d.ts.map +1 -1
  83. package/dist/commands/baseline.js +205 -121
  84. package/dist/commands/comment.d.ts +22 -0
  85. package/dist/commands/comment.d.ts.map +1 -1
  86. package/dist/commands/comment.js +122 -58
  87. package/dist/commands/compare.d.ts +17 -0
  88. package/dist/commands/compare.d.ts.map +1 -1
  89. package/dist/commands/compare.js +287 -180
  90. package/dist/commands/diff.d.ts +5 -0
  91. package/dist/commands/diff.d.ts.map +1 -1
  92. package/dist/commands/diff.js +168 -141
  93. package/dist/commands/doc.d.ts +10 -0
  94. package/dist/commands/doc.d.ts.map +1 -1
  95. package/dist/commands/doc.js +134 -76
  96. package/dist/commands/doctor.d.ts +2 -0
  97. package/dist/commands/doctor.d.ts.map +1 -1
  98. package/dist/commands/doctor.js +164 -17
  99. package/dist/commands/download.d.ts +10 -0
  100. package/dist/commands/download.d.ts.map +1 -1
  101. package/dist/commands/download.js +169 -112
  102. package/dist/commands/explain.d.ts +5 -0
  103. package/dist/commands/explain.d.ts.map +1 -1
  104. package/dist/commands/explain.js +241 -155
  105. package/dist/commands/fix-all.d.ts +25 -0
  106. package/dist/commands/fix-all.d.ts.map +1 -0
  107. package/dist/commands/fix-all.js +206 -0
  108. package/dist/commands/fix-plan.d.ts +9 -0
  109. package/dist/commands/fix-plan.d.ts.map +1 -1
  110. package/dist/commands/fix-plan.js +152 -89
  111. package/dist/commands/fix.d.ts +17 -0
  112. package/dist/commands/fix.d.ts.map +1 -0
  113. package/dist/commands/fix.js +111 -0
  114. package/dist/commands/init.d.ts +11 -0
  115. package/dist/commands/init.d.ts.map +1 -1
  116. package/dist/commands/init.js +94 -42
  117. package/dist/commands/login.d.ts +18 -0
  118. package/dist/commands/login.d.ts.map +1 -1
  119. package/dist/commands/login.js +268 -95
  120. package/dist/commands/patch-review.d.ts +11 -0
  121. package/dist/commands/patch-review.d.ts.map +1 -1
  122. package/dist/commands/patch-review.js +159 -97
  123. package/dist/commands/policy.d.ts +31 -0
  124. package/dist/commands/policy.d.ts.map +1 -1
  125. package/dist/commands/policy.js +269 -124
  126. package/dist/commands/release-notes.d.ts +10 -0
  127. package/dist/commands/release-notes.d.ts.map +1 -1
  128. package/dist/commands/release-notes.js +127 -73
  129. package/dist/commands/scan.d.ts +13 -0
  130. package/dist/commands/scan.d.ts.map +1 -0
  131. package/dist/commands/scan.js +133 -0
  132. package/dist/commands/status.d.ts +9 -0
  133. package/dist/commands/status.d.ts.map +1 -0
  134. package/dist/commands/status.js +81 -0
  135. package/dist/commands/suggest.d.ts +10 -0
  136. package/dist/commands/suggest.d.ts.map +1 -1
  137. package/dist/commands/suggest.js +153 -82
  138. package/dist/commands/triage.d.ts +35 -0
  139. package/dist/commands/triage.d.ts.map +1 -1
  140. package/dist/commands/triage.js +206 -81
  141. package/dist/commands/upload.d.ts +9 -0
  142. package/dist/commands/upload.d.ts.map +1 -1
  143. package/dist/commands/upload.js +140 -101
  144. package/dist/commands/verify.d.ts +13 -0
  145. package/dist/commands/verify.d.ts.map +1 -0
  146. package/dist/commands/verify.js +118 -0
  147. package/dist/index.d.ts +3 -2
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/index.js +125 -990
  150. package/dist/interactive/fix-wizard.d.ts +3 -0
  151. package/dist/interactive/fix-wizard.d.ts.map +1 -1
  152. package/dist/interactive/fix-wizard.js +130 -112
  153. package/dist/interactive/init-wizard.d.ts +3 -1
  154. package/dist/interactive/init-wizard.d.ts.map +1 -1
  155. package/dist/interactive/init-wizard.js +207 -138
  156. package/dist/interactive/prompts.d.ts +7 -3
  157. package/dist/interactive/prompts.d.ts.map +1 -1
  158. package/dist/interactive/prompts.js +44 -23
  159. package/dist/output/envelope.d.ts +2 -0
  160. package/dist/output/envelope.d.ts.map +1 -1
  161. package/dist/output/envelope.js +18 -2
  162. package/dist/output/factory.d.ts +9 -1
  163. package/dist/output/factory.d.ts.map +1 -1
  164. package/dist/output/html.d.ts +2 -1
  165. package/dist/output/html.d.ts.map +1 -1
  166. package/dist/output/html.js +3 -2
  167. package/dist/output/human.d.ts +9 -1
  168. package/dist/output/human.d.ts.map +1 -1
  169. package/dist/output/human.js +17 -2
  170. package/dist/output/json.d.ts +2 -1
  171. package/dist/output/json.d.ts.map +1 -1
  172. package/dist/output/junit.d.ts +2 -1
  173. package/dist/output/junit.d.ts.map +1 -1
  174. package/dist/output/sarif.d.ts +2 -1
  175. package/dist/output/sarif.d.ts.map +1 -1
  176. package/dist/types.d.ts +74 -0
  177. package/dist/types.d.ts.map +1 -0
  178. package/dist/types.js +5 -0
  179. package/dist/ui/banner.d.ts +34 -0
  180. package/dist/ui/banner.d.ts.map +1 -1
  181. package/dist/ui/banner.js +97 -5
  182. package/dist/ui/diagnostics.d.ts +9 -4
  183. package/dist/ui/diagnostics.d.ts.map +1 -1
  184. package/dist/ui/diagnostics.js +32 -82
  185. package/dist/ui/strings.d.ts +373 -0
  186. package/dist/ui/strings.d.ts.map +1 -0
  187. package/dist/ui/strings.js +499 -0
  188. package/dist/ui/table.d.ts +0 -2
  189. package/dist/ui/table.d.ts.map +1 -1
  190. package/dist/ui/table.js +3 -4
  191. package/dist/utils/api-client.d.ts +46 -0
  192. package/dist/utils/api-client.d.ts.map +1 -0
  193. package/dist/utils/api-client.js +170 -0
  194. package/dist/utils/client.d.ts +29 -18
  195. package/dist/utils/client.d.ts.map +1 -1
  196. package/dist/utils/client.js +102 -12
  197. package/dist/utils/formatters.d.ts +38 -0
  198. package/dist/utils/formatters.d.ts.map +1 -0
  199. package/dist/utils/formatters.js +277 -0
  200. package/dist/utils/local-capture.d.ts +25 -0
  201. package/dist/utils/local-capture.d.ts.map +1 -0
  202. package/dist/utils/local-capture.js +57 -0
  203. package/dist/utils/url-classify.d.ts +18 -0
  204. package/dist/utils/url-classify.d.ts.map +1 -0
  205. package/dist/utils/url-classify.js +106 -0
  206. package/node_modules/@vertaaux/tui/dist/index.cjs +713 -20
  207. package/node_modules/@vertaaux/tui/dist/index.cjs.map +1 -1
  208. package/node_modules/@vertaaux/tui/dist/index.d.cts +361 -4
  209. package/node_modules/@vertaaux/tui/dist/index.d.ts +361 -4
  210. package/node_modules/@vertaaux/tui/dist/index.js +689 -21
  211. package/node_modules/@vertaaux/tui/dist/index.js.map +1 -1
  212. package/package.json +13 -5
  213. package/dist/commands/client.d.ts +0 -14
  214. package/dist/commands/client.d.ts.map +0 -1
  215. package/dist/commands/client.js +0 -362
  216. package/dist/commands/drift.d.ts +0 -15
  217. package/dist/commands/drift.d.ts.map +0 -1
  218. package/dist/commands/drift.js +0 -309
  219. package/dist/commands/protect.d.ts +0 -16
  220. package/dist/commands/protect.d.ts.map +0 -1
  221. package/dist/commands/protect.js +0 -323
  222. package/dist/commands/report.d.ts +0 -15
  223. package/dist/commands/report.d.ts.map +0 -1
  224. package/dist/commands/report.js +0 -214
  225. package/dist/policy/sync.d.ts +0 -67
  226. package/dist/policy/sync.d.ts.map +0 -1
  227. package/dist/policy/sync.js +0 -147
@@ -0,0 +1,309 @@
1
+ /**
2
+ * InteractiveApp — Persistent 3-section interactive CLI shell.
3
+ *
4
+ * Manages a fixed layout:
5
+ * [Header] — sticky banner at top (buildFrame3)
6
+ * [Canvas] — active view content, fixed height
7
+ * [Footer] — status left, keyboard shortcuts right
8
+ *
9
+ * Uses alternate screen buffer (CSI ?1049h/l) so the layout is scroll-proof.
10
+ * Each frame redraw uses cursor.home + screen.clear — no cursor arithmetic needed.
11
+ *
12
+ * All writes go to the injected output stream (defaults to process.stderr).
13
+ * stdout is reserved for --format json output.
14
+ */
15
+ import { cursor, screen, getTerminalWidth, getTerminalHeight, isTTY, FRAME_INTERVAL, } from "@vertaaux/tui";
16
+ import { renderHeader } from "./layout/header.js";
17
+ import { renderFooter } from "./layout/footer.js";
18
+ import { renderCanvas } from "./layout/canvas.js";
19
+ import { getVersion } from "../ui/banner.js";
20
+ // Default keyboard shortcuts shown in the footer when no view is active
21
+ const DEFAULT_SHORTCUTS = ["↑↓ navigate", "↵ select", "F1 help", "^C quit"];
22
+ // Shortcuts shown when a command view is active
23
+ const COMMAND_SHORTCUTS = ["Esc back", "F1 help", "^C quit"];
24
+ /**
25
+ * InteractiveApp provides the persistent 3-section layout for the
26
+ * `vertaa` (no-args) interactive mode.
27
+ *
28
+ * Usage:
29
+ * const app = new InteractiveApp();
30
+ * await app.run(); // blocks until Ctrl+C or app.dispose()
31
+ */
32
+ export class InteractiveApp {
33
+ /** @internal — exposed for testing via private access */
34
+ _state = {
35
+ screen: "menu",
36
+ statusText: "Ready",
37
+ activeView: null,
38
+ };
39
+ /**
40
+ * Whether the app is currently on the alternate screen.
41
+ * @internal — exposed for testing
42
+ */
43
+ entered = false;
44
+ /** @internal — exposed for testing */
45
+ _suspended = false;
46
+ output;
47
+ tickTimer = null;
48
+ resizeTimer = null;
49
+ frameIndex = 0;
50
+ version;
51
+ resolveRun = null;
52
+ constructor(output = process.stderr) {
53
+ this.output = output;
54
+ this.version = getVersion();
55
+ process.stderr.on("resize", this.onResize);
56
+ }
57
+ // ── Public API ─────────────────────────────────────────────────
58
+ /**
59
+ * Start the interactive app.
60
+ *
61
+ * Enters alternate screen buffer, configures stdin for raw keyboard input,
62
+ * renders the initial frame, and returns a Promise that resolves when
63
+ * dispose() is called.
64
+ */
65
+ async run() {
66
+ // Enter alternate screen and hide cursor
67
+ this.output.write(screen.altEnter + cursor.hide);
68
+ this.entered = true;
69
+ if (isTTY()) {
70
+ try {
71
+ process.stdin.setRawMode(true);
72
+ }
73
+ catch {
74
+ // Non-TTY stdin or test environment — ignore
75
+ }
76
+ process.stdin.resume();
77
+ process.stdin.on("data", this.onKeyData);
78
+ }
79
+ this.redraw();
80
+ this.startTick();
81
+ return new Promise((resolve) => {
82
+ this.resolveRun = resolve;
83
+ });
84
+ }
85
+ /**
86
+ * Build the complete frame string for the current state.
87
+ *
88
+ * Frame = header + canvas + footer, sized to terminal dimensions.
89
+ */
90
+ buildFrame() {
91
+ const width = getTerminalWidth();
92
+ const height = getTerminalHeight();
93
+ const header = renderHeader(this.version, width);
94
+ const headerLines = header.split("\n").length;
95
+ // Footer is 2 lines (separator + status) + 1 empty line above it for spacing
96
+ const footerLines = 3;
97
+ const canvasHeight = Math.max(1, height - headerLines - footerLines);
98
+ const viewContent = this._state.activeView
99
+ ? this._state.activeView.render()
100
+ : "";
101
+ const canvas = renderCanvas(viewContent, canvasHeight, width);
102
+ const shortcuts = this._state.screen === "command" ? COMMAND_SHORTCUTS : DEFAULT_SHORTCUTS;
103
+ const footer = renderFooter({
104
+ statusText: this._state.statusText,
105
+ shortcuts,
106
+ width,
107
+ });
108
+ // Assemble: header ends with \n\n, canvas has no trailing \n, empty line before footer
109
+ return header + canvas + "\n\n" + footer;
110
+ }
111
+ /**
112
+ * Redraw the terminal using home+clear on the alternate screen.
113
+ *
114
+ * Guarded by `if (!this.entered) return` — prevents double-redraw race
115
+ * when setView() calls this.redraw() after onMount() completes while
116
+ * the app is suspended (entered === false).
117
+ */
118
+ redraw() {
119
+ if (!this.entered)
120
+ return;
121
+ const frame = this.buildFrame();
122
+ this.output.write(cursor.home + screen.clear + frame);
123
+ }
124
+ /**
125
+ * Set the active view, calling lifecycle hooks.
126
+ *
127
+ * Unmounts any existing view, mounts the new one, switches to command
128
+ * screen, and triggers an immediate redraw.
129
+ *
130
+ * NOTE: When the app is suspended (entered === false), the post-onMount
131
+ * redraw() call is a safe no-op — guarded by the `!this.entered` check.
132
+ */
133
+ async setView(view) {
134
+ if (this._state.activeView?.onUnmount) {
135
+ await this._state.activeView.onUnmount();
136
+ }
137
+ this._state.activeView = view;
138
+ this._state.screen = "command";
139
+ if (view.onMount) {
140
+ await view.onMount();
141
+ }
142
+ this.redraw();
143
+ }
144
+ /**
145
+ * Unmount the active view and return to the menu screen.
146
+ */
147
+ async clearView() {
148
+ if (this._state.activeView?.onUnmount) {
149
+ await this._state.activeView.onUnmount();
150
+ }
151
+ this._state.activeView = null;
152
+ this._state.screen = "menu";
153
+ this._state.statusText = "Ready";
154
+ this.redraw();
155
+ }
156
+ /**
157
+ * Suspend the app — exit alternate screen and stop the tick loop.
158
+ *
159
+ * Call before launching @inquirer/prompts or running a command handler
160
+ * to hand control back to the normal terminal.
161
+ */
162
+ suspend() {
163
+ this._suspended = true;
164
+ this.stopTick();
165
+ // Release stdin so command handlers and @inquirer/prompts can use it
166
+ try {
167
+ process.stdin.removeListener("data", this.onKeyData);
168
+ if (isTTY()) {
169
+ try {
170
+ process.stdin.setRawMode(false);
171
+ }
172
+ catch {
173
+ // Ignore in non-TTY environments
174
+ }
175
+ }
176
+ }
177
+ catch {
178
+ // Ignore listener removal errors
179
+ }
180
+ this.output.write(cursor.show + screen.altExit);
181
+ this.entered = false;
182
+ }
183
+ /**
184
+ * Resume the app — re-enter alternate screen and restart the tick loop.
185
+ *
186
+ * Call after a command handler completes to restore the interactive layout.
187
+ */
188
+ resume() {
189
+ this._suspended = false;
190
+ // Reclaim stdin for interactive app key handling
191
+ if (isTTY()) {
192
+ try {
193
+ process.stdin.setRawMode(true);
194
+ }
195
+ catch {
196
+ // Ignore in non-TTY environments
197
+ }
198
+ process.stdin.resume();
199
+ process.stdin.on("data", this.onKeyData);
200
+ }
201
+ this.output.write(screen.altEnter + cursor.hide);
202
+ this.entered = true;
203
+ this.redraw();
204
+ this.startTick();
205
+ }
206
+ /**
207
+ * Tear down the app — exits alternate screen, stops tick, removes listeners.
208
+ */
209
+ dispose() {
210
+ this.stopTick();
211
+ if (this.resizeTimer !== null) {
212
+ clearTimeout(this.resizeTimer);
213
+ this.resizeTimer = null;
214
+ }
215
+ process.stderr.removeListener("resize", this.onResize);
216
+ try {
217
+ process.stdin.removeListener("data", this.onKeyData);
218
+ if (isTTY()) {
219
+ try {
220
+ process.stdin.setRawMode(false);
221
+ }
222
+ catch {
223
+ // Ignore in non-TTY test environments
224
+ }
225
+ process.stdin.pause();
226
+ }
227
+ }
228
+ catch {
229
+ // Ignore listener removal errors in test environments
230
+ }
231
+ // Exit alternate screen and show cursor
232
+ this.output.write(cursor.show + screen.altExit);
233
+ this.entered = false;
234
+ if (this.resolveRun) {
235
+ this.resolveRun();
236
+ this.resolveRun = null;
237
+ }
238
+ }
239
+ /** Get the current animation frame index (for spinner rendering in views). */
240
+ getFrameIndex() {
241
+ return this.frameIndex;
242
+ }
243
+ // ── Private helpers ─────────────────────────────────────────────
244
+ startTick() {
245
+ if (this.tickTimer !== null || this._suspended)
246
+ return;
247
+ this.tickTimer = setInterval(() => {
248
+ this.frameIndex++;
249
+ if (!this._suspended) {
250
+ this.redraw();
251
+ }
252
+ }, FRAME_INTERVAL);
253
+ }
254
+ stopTick() {
255
+ if (this.tickTimer !== null) {
256
+ clearInterval(this.tickTimer);
257
+ this.tickTimer = null;
258
+ }
259
+ }
260
+ onResize = () => {
261
+ if (this.resizeTimer !== null) {
262
+ clearTimeout(this.resizeTimer);
263
+ }
264
+ this.resizeTimer = setTimeout(() => {
265
+ this.resizeTimer = null;
266
+ // Alternate screen doesn't need cursor math reset — just redraw
267
+ if (!this._suspended) {
268
+ this.redraw();
269
+ }
270
+ }, 100);
271
+ };
272
+ onKeyData = (data) => {
273
+ const key = data.toString();
274
+ const ctrl = key.length === 1 && data[0] < 32 && data[0] !== 27;
275
+ const meta = key.startsWith("\x1b") && key.length > 1;
276
+ // Delegate to active view first
277
+ if (this._state.activeView?.handleKey) {
278
+ const consumed = this._state.activeView.handleKey(key, ctrl, meta);
279
+ if (consumed)
280
+ return;
281
+ }
282
+ // App-level key handling
283
+ if (ctrl && key === "\x03") {
284
+ // Ctrl+C — unmount active view before exiting
285
+ if (this._state.activeView?.onUnmount) {
286
+ void this._state.activeView.onUnmount().finally(() => {
287
+ this.dispose();
288
+ process.exit(0);
289
+ });
290
+ }
291
+ else {
292
+ this.dispose();
293
+ process.exit(0);
294
+ }
295
+ }
296
+ else if (key === "\x1bOP" || key === "\x1b[11~") {
297
+ // F1 — toggle help
298
+ const next = this._state.screen === "help" ? "menu" : "help";
299
+ this._state.screen = next;
300
+ this.redraw();
301
+ }
302
+ else if (key === "\x1b" || key === "\x1b[") {
303
+ // Escape or Escape sequence
304
+ if (this._state.screen === "command" || this._state.screen === "help") {
305
+ void this.clearView();
306
+ }
307
+ }
308
+ };
309
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Canvas section renderer for the InteractiveApp shell.
3
+ *
4
+ * The canvas is the middle section between header and footer. It receives
5
+ * the active view's rendered string and enforces a fixed height so that
6
+ * cursor arithmetic remains stable across frames.
7
+ *
8
+ * Rules:
9
+ * - Returns exactly `availableHeight` lines always
10
+ * - If content is shorter: pad with empty lines
11
+ * - If content is longer: truncate (the view owns internal scrolling)
12
+ * - Each line is truncated to `width` visible characters
13
+ */
14
+ /**
15
+ * Render the canvas section with a fixed height.
16
+ *
17
+ * @param content - Output from the active CommandView's render()
18
+ * @param availableHeight - Exact number of lines to occupy (terminalHeight - headerHeight - footerHeight)
19
+ * @param width - Terminal width in columns for line truncation
20
+ * @returns String of exactly `availableHeight` lines joined by "\n" (no trailing newline)
21
+ */
22
+ export declare function renderCanvas(content: string, availableHeight: number, width: number): string;
23
+ //# sourceMappingURL=canvas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas.d.ts","sourceRoot":"","sources":["../../../src/app/layout/canvas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,eAAe,EAAE,MAAM,EACvB,KAAK,EAAE,MAAM,GACZ,MAAM,CAgBR"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Canvas section renderer for the InteractiveApp shell.
3
+ *
4
+ * The canvas is the middle section between header and footer. It receives
5
+ * the active view's rendered string and enforces a fixed height so that
6
+ * cursor arithmetic remains stable across frames.
7
+ *
8
+ * Rules:
9
+ * - Returns exactly `availableHeight` lines always
10
+ * - If content is shorter: pad with empty lines
11
+ * - If content is longer: truncate (the view owns internal scrolling)
12
+ * - Each line is truncated to `width` visible characters
13
+ */
14
+ import { truncate } from "@vertaaux/tui";
15
+ /**
16
+ * Render the canvas section with a fixed height.
17
+ *
18
+ * @param content - Output from the active CommandView's render()
19
+ * @param availableHeight - Exact number of lines to occupy (terminalHeight - headerHeight - footerHeight)
20
+ * @param width - Terminal width in columns for line truncation
21
+ * @returns String of exactly `availableHeight` lines joined by "\n" (no trailing newline)
22
+ */
23
+ export function renderCanvas(content, availableHeight, width) {
24
+ if (availableHeight <= 0)
25
+ return "";
26
+ const lines = content === "" ? [] : content.split("\n");
27
+ // Truncate each line to terminal width
28
+ const truncated = lines.map((line) => truncate(line, width));
29
+ if (truncated.length >= availableHeight) {
30
+ // Trim to fit — the view owns internal scrolling, we just enforce the boundary
31
+ return truncated.slice(0, availableHeight).join("\n");
32
+ }
33
+ // Pad with empty lines to maintain fixed height for cursor arithmetic
34
+ const padding = Array(availableHeight - truncated.length).fill("");
35
+ return [...truncated, ...padding].join("\n");
36
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Footer section renderer for the InteractiveApp shell.
3
+ *
4
+ * Renders a 2-line sticky footer:
5
+ * Line 1: thin separator (dim horizontal rule)
6
+ * Line 2: status text left, keyboard shortcuts right
7
+ *
8
+ * All ANSI-aware padding uses visibleLength() to avoid width drift from
9
+ * escape codes.
10
+ */
11
+ /** Options for renderFooter */
12
+ export interface FooterOptions {
13
+ /** Status message shown on the left (e.g. "Ready", "Running audit...") */
14
+ statusText: string;
15
+ /** Keyboard shortcuts shown on the right (e.g. ["↑↓ navigate", "↵ select", "H help", "^C quit"]) */
16
+ shortcuts: string[];
17
+ /** Terminal width in columns */
18
+ width: number;
19
+ }
20
+ /**
21
+ * Render the footer section.
22
+ *
23
+ * Returns exactly 2 lines:
24
+ * - A dim separator spanning the full width
25
+ * - Status text aligned left, shortcuts joined right
26
+ *
27
+ * @param opts - Footer options
28
+ * @returns 2-line string (no trailing newline)
29
+ */
30
+ export declare function renderFooter(opts: FooterOptions): string;
31
+ //# sourceMappingURL=footer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"footer.d.ts","sourceRoot":"","sources":["../../../src/app/layout/footer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,+BAA+B;AAC/B,MAAM,WAAW,aAAa;IAC5B,0EAA0E;IAC1E,UAAU,EAAE,MAAM,CAAC;IACnB,oGAAoG;IACpG,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAsBxD"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Footer section renderer for the InteractiveApp shell.
3
+ *
4
+ * Renders a 2-line sticky footer:
5
+ * Line 1: thin separator (dim horizontal rule)
6
+ * Line 2: status text left, keyboard shortcuts right
7
+ *
8
+ * All ANSI-aware padding uses visibleLength() to avoid width drift from
9
+ * escape codes.
10
+ */
11
+ import { dim, bold, visibleLength, padEnd } from "@vertaaux/tui";
12
+ /**
13
+ * Render the footer section.
14
+ *
15
+ * Returns exactly 2 lines:
16
+ * - A dim separator spanning the full width
17
+ * - Status text aligned left, shortcuts joined right
18
+ *
19
+ * @param opts - Footer options
20
+ * @returns 2-line string (no trailing newline)
21
+ */
22
+ export function renderFooter(opts) {
23
+ const { statusText, shortcuts, width } = opts;
24
+ // Line 1: thin separator
25
+ const separator = dim("─".repeat(Math.max(0, width)));
26
+ // Line 2: status left, shortcuts right
27
+ const shortcutStr = shortcuts.map((s) => bold(s)).join(dim(" "));
28
+ const shortcutLen = visibleLength(shortcutStr);
29
+ const statusLen = visibleLength(statusText);
30
+ const gap = width - statusLen - shortcutLen;
31
+ let statusLine;
32
+ if (gap > 0) {
33
+ // Pad status text to push shortcuts to the right
34
+ statusLine = padEnd(statusText, width - shortcutLen) + shortcutStr;
35
+ }
36
+ else {
37
+ // Not enough space — just show status, truncate if needed
38
+ statusLine = statusText.slice(0, Math.max(0, width));
39
+ }
40
+ return separator + "\n" + statusLine;
41
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Header section renderer for the InteractiveApp shell.
3
+ *
4
+ * Renders the static banner (Frame 3 — full lime→cyan gradient) as the
5
+ * persistent top section. No animation in interactive mode since the
6
+ * app is already running.
7
+ */
8
+ /**
9
+ * Render the header section.
10
+ *
11
+ * Returns the full gradient banner (Frame 3) with lines truncated
12
+ * to the terminal width. The banner provides visual identity and
13
+ * occupies the sticky top of the 3-section layout.
14
+ *
15
+ * @param version - CLI version string (e.g. "1.2.3")
16
+ * @param width - Terminal width in columns
17
+ * @returns Multi-line string ready to write to stderr
18
+ */
19
+ export declare function renderHeader(version: string, width: number): string;
20
+ //# sourceMappingURL=header.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header.d.ts","sourceRoot":"","sources":["../../../src/app/layout/header.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAMnE"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Header section renderer for the InteractiveApp shell.
3
+ *
4
+ * Renders the static banner (Frame 3 — full lime→cyan gradient) as the
5
+ * persistent top section. No animation in interactive mode since the
6
+ * app is already running.
7
+ */
8
+ import { truncate } from "@vertaaux/tui";
9
+ import { buildFrame3 } from "../../ui/banner.js";
10
+ /**
11
+ * Render the header section.
12
+ *
13
+ * Returns the full gradient banner (Frame 3) with lines truncated
14
+ * to the terminal width. The banner provides visual identity and
15
+ * occupies the sticky top of the 3-section layout.
16
+ *
17
+ * @param version - CLI version string (e.g. "1.2.3")
18
+ * @param width - Terminal width in columns
19
+ * @returns Multi-line string ready to write to stderr
20
+ */
21
+ export function renderHeader(version, width) {
22
+ const frame = buildFrame3(version, process.cwd());
23
+ // Trim the trailing double newline so the caller controls spacing
24
+ const trimmed = frame.replace(/\n+$/, "");
25
+ const lines = trimmed.split("\n");
26
+ return lines.map((line) => truncate(line, width)).join("\n") + "\n\n";
27
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Command category definitions for the interactive menu.
3
+ *
4
+ * 6 groups of 24 commands matching Commander.js registrations in index.ts.
5
+ * Each MenuItem.value must exactly match the Commander command name.
6
+ */
7
+ import type { CommandCategory, MenuItem } from "../types.js";
8
+ /**
9
+ * All 24 commands organized into 6 semantic groups.
10
+ *
11
+ * Groups follow a natural user workflow:
12
+ * Audit → Results → Fixes → Reports → Project → Account
13
+ */
14
+ export declare const COMMAND_CATEGORIES: CommandCategory[];
15
+ /**
16
+ * Returns a flat list of all MenuItem entries across all categories.
17
+ * Useful for quick lookups by command value.
18
+ */
19
+ export declare function allMenuItems(): MenuItem[];
20
+ //# sourceMappingURL=categories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"categories.d.ts","sourceRoot":"","sources":["../../../src/app/menu/categories.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE7D;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,EAAE,eAAe,EAiK/C,CAAC;AAEF;;;GAGG;AACH,wBAAgB,YAAY,IAAI,QAAQ,EAAE,CAEzC"}