@vertaaux/cli 0.4.0 → 0.5.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 (248) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/MIGRATION.md +239 -0
  3. package/README.md +62 -17
  4. package/dist/app/interactive-app.d.ts +103 -0
  5. package/dist/app/interactive-app.d.ts.map +1 -0
  6. package/dist/app/interactive-app.js +328 -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 +166 -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 +415 -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 +46 -0
  37. package/dist/auth/ci-token.d.ts +8 -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 +11 -0
  49. package/dist/commands/a11y.d.ts.map +1 -0
  50. package/dist/commands/a11y.js +149 -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 +589 -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 +129 -0
  69. package/dist/commands/audit/policy.d.ts +27 -0
  70. package/dist/commands/audit/policy.d.ts.map +1 -0
  71. package/dist/commands/audit/policy.js +147 -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 +89 -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 -1097
  81. package/dist/commands/baseline.d.ts +2 -0
  82. package/dist/commands/baseline.d.ts.map +1 -1
  83. package/dist/commands/baseline.js +221 -123
  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 +127 -62
  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 +288 -181
  90. package/dist/commands/diff.d.ts +7 -0
  91. package/dist/commands/diff.d.ts.map +1 -1
  92. package/dist/commands/diff.js +181 -143
  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 +135 -77
  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 +166 -19
  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 +242 -156
  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 +154 -90
  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 +263 -92
  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 +160 -98
  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 +270 -125
  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 +128 -74
  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 +180 -83
  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 +207 -82
  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/config/schema.d.ts +4 -0
  148. package/dist/config/schema.d.ts.map +1 -1
  149. package/dist/index.d.ts +3 -2
  150. package/dist/index.d.ts.map +1 -1
  151. package/dist/index.js +127 -991
  152. package/dist/interactive/fix-wizard.d.ts +3 -0
  153. package/dist/interactive/fix-wizard.d.ts.map +1 -1
  154. package/dist/interactive/fix-wizard.js +130 -112
  155. package/dist/interactive/init-wizard.d.ts +3 -1
  156. package/dist/interactive/init-wizard.d.ts.map +1 -1
  157. package/dist/interactive/init-wizard.js +207 -138
  158. package/dist/interactive/prompts.d.ts +7 -3
  159. package/dist/interactive/prompts.d.ts.map +1 -1
  160. package/dist/interactive/prompts.js +44 -23
  161. package/dist/output/envelope.d.ts +9 -0
  162. package/dist/output/envelope.d.ts.map +1 -1
  163. package/dist/output/envelope.js +37 -3
  164. package/dist/output/factory.d.ts +2 -1
  165. package/dist/output/factory.d.ts.map +1 -1
  166. package/dist/output/html.d.ts +2 -1
  167. package/dist/output/html.d.ts.map +1 -1
  168. package/dist/output/html.js +3 -2
  169. package/dist/output/human.d.ts +2 -1
  170. package/dist/output/human.d.ts.map +1 -1
  171. package/dist/output/human.js +3 -2
  172. package/dist/output/json.d.ts +2 -1
  173. package/dist/output/json.d.ts.map +1 -1
  174. package/dist/output/junit.d.ts +2 -1
  175. package/dist/output/junit.d.ts.map +1 -1
  176. package/dist/output/sarif.d.ts +2 -1
  177. package/dist/output/sarif.d.ts.map +1 -1
  178. package/dist/policy/schema.d.ts +137 -0
  179. package/dist/policy/schema.d.ts.map +1 -1
  180. package/dist/policy/schema.js +107 -0
  181. package/dist/prompts/command-catalog.js +9 -9
  182. package/dist/types.d.ts +74 -0
  183. package/dist/types.d.ts.map +1 -0
  184. package/dist/types.js +5 -0
  185. package/dist/ui/banner.d.ts +34 -0
  186. package/dist/ui/banner.d.ts.map +1 -1
  187. package/dist/ui/banner.js +97 -5
  188. package/dist/ui/diagnostics.d.ts +9 -4
  189. package/dist/ui/diagnostics.d.ts.map +1 -1
  190. package/dist/ui/diagnostics.js +32 -82
  191. package/dist/ui/strings.d.ts +373 -0
  192. package/dist/ui/strings.d.ts.map +1 -0
  193. package/dist/ui/strings.js +499 -0
  194. package/dist/ui/table.d.ts +0 -2
  195. package/dist/ui/table.d.ts.map +1 -1
  196. package/dist/ui/table.js +3 -4
  197. package/dist/utils/api-client.d.ts +46 -0
  198. package/dist/utils/api-client.d.ts.map +1 -0
  199. package/dist/utils/api-client.js +170 -0
  200. package/dist/utils/client.d.ts +29 -18
  201. package/dist/utils/client.d.ts.map +1 -1
  202. package/dist/utils/client.js +104 -12
  203. package/dist/utils/formatters.d.ts +38 -0
  204. package/dist/utils/formatters.d.ts.map +1 -0
  205. package/dist/utils/formatters.js +277 -0
  206. package/dist/utils/root-args.d.ts +12 -0
  207. package/dist/utils/root-args.d.ts.map +1 -0
  208. package/dist/utils/root-args.js +44 -0
  209. package/dist/utils/stdin.d.ts +7 -0
  210. package/dist/utils/stdin.d.ts.map +1 -1
  211. package/dist/utils/stdin.js +32 -2
  212. package/dist/utils/url-classify.d.ts.map +1 -1
  213. package/dist/utils/url-classify.js +24 -3
  214. package/node_modules/@vertaaux/tui/dist/index.cjs +1216 -27
  215. package/node_modules/@vertaaux/tui/dist/index.cjs.map +1 -1
  216. package/node_modules/@vertaaux/tui/dist/index.d.cts +361 -4
  217. package/node_modules/@vertaaux/tui/dist/index.d.ts +361 -4
  218. package/node_modules/@vertaaux/tui/dist/index.js +1189 -27
  219. package/node_modules/@vertaaux/tui/dist/index.js.map +1 -1
  220. package/node_modules/@vertaaux/tui/package.json +2 -3
  221. package/node_modules/chalk/license +9 -0
  222. package/node_modules/chalk/package.json +83 -0
  223. package/node_modules/chalk/readme.md +297 -0
  224. package/node_modules/chalk/source/index.d.ts +325 -0
  225. package/node_modules/chalk/source/index.js +225 -0
  226. package/node_modules/chalk/source/utilities.js +33 -0
  227. package/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
  228. package/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
  229. package/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
  230. package/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
  231. package/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
  232. package/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
  233. package/package.json +20 -5
  234. package/dist/commands/client.d.ts +0 -14
  235. package/dist/commands/client.d.ts.map +0 -1
  236. package/dist/commands/client.js +0 -362
  237. package/dist/commands/drift.d.ts +0 -15
  238. package/dist/commands/drift.d.ts.map +0 -1
  239. package/dist/commands/drift.js +0 -309
  240. package/dist/commands/protect.d.ts +0 -16
  241. package/dist/commands/protect.d.ts.map +0 -1
  242. package/dist/commands/protect.js +0 -323
  243. package/dist/commands/report.d.ts +0 -15
  244. package/dist/commands/report.d.ts.map +0 -1
  245. package/dist/commands/report.js +0 -214
  246. package/dist/policy/sync.d.ts +0 -67
  247. package/dist/policy/sync.d.ts.map +0 -1
  248. package/dist/policy/sync.js +0 -147
@@ -0,0 +1,328 @@
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
+ import { HelpOverlayView } from "./views/help-overlay.js";
21
+ // Default keyboard shortcuts shown in the footer when no view is active
22
+ const DEFAULT_SHORTCUTS = ["↑↓ navigate", "↵ select", "F1 help", "^C quit"];
23
+ // Shortcuts shown when a command view is active
24
+ const COMMAND_SHORTCUTS = ["Esc back", "F1 help", "^C quit"];
25
+ /**
26
+ * InteractiveApp provides the persistent 3-section layout for the
27
+ * `vertaa` (no-args) interactive mode.
28
+ *
29
+ * Usage:
30
+ * const app = new InteractiveApp();
31
+ * await app.run(); // blocks until Ctrl+C or app.dispose()
32
+ */
33
+ export class InteractiveApp {
34
+ /** @internal — exposed for testing via private access */
35
+ _state = {
36
+ screen: "menu",
37
+ statusText: "Ready",
38
+ activeView: null,
39
+ };
40
+ /**
41
+ * Whether the app is currently on the alternate screen.
42
+ * @internal — exposed for testing
43
+ */
44
+ entered = false;
45
+ /** @internal — exposed for testing */
46
+ _suspended = false;
47
+ output;
48
+ tickTimer = null;
49
+ resizeTimer = null;
50
+ frameIndex = 0;
51
+ version;
52
+ resolveRun = null;
53
+ /** Saved menu view so we can restore it after help overlay */
54
+ savedMenuView = null;
55
+ constructor(output = process.stderr) {
56
+ this.output = output;
57
+ this.version = getVersion();
58
+ process.stderr.on("resize", this.onResize);
59
+ }
60
+ // ── Public API ─────────────────────────────────────────────────
61
+ /**
62
+ * Start the interactive app.
63
+ *
64
+ * Enters alternate screen buffer, configures stdin for raw keyboard input,
65
+ * renders the initial frame, and returns a Promise that resolves when
66
+ * dispose() is called.
67
+ */
68
+ async run() {
69
+ // Enter alternate screen and hide cursor
70
+ this.output.write(screen.altEnter + cursor.hide);
71
+ this.entered = true;
72
+ if (isTTY()) {
73
+ try {
74
+ process.stdin.setRawMode(true);
75
+ }
76
+ catch {
77
+ // Non-TTY stdin or test environment — ignore
78
+ }
79
+ process.stdin.resume();
80
+ process.stdin.on("data", this.onKeyData);
81
+ }
82
+ this.redraw();
83
+ this.startTick();
84
+ return new Promise((resolve) => {
85
+ this.resolveRun = resolve;
86
+ });
87
+ }
88
+ /**
89
+ * Build the complete frame string for the current state.
90
+ *
91
+ * Frame = header + canvas + footer, sized to terminal dimensions.
92
+ */
93
+ buildFrame() {
94
+ const width = getTerminalWidth();
95
+ const height = getTerminalHeight();
96
+ const header = renderHeader(this.version, width);
97
+ const headerLines = header.split("\n").length;
98
+ // Footer is 2 lines (separator + status) + 1 empty line above it for spacing
99
+ const footerLines = 3;
100
+ const canvasHeight = Math.max(1, height - headerLines - footerLines);
101
+ const viewContent = this._state.activeView
102
+ ? this._state.activeView.render()
103
+ : "";
104
+ const canvas = renderCanvas(viewContent, canvasHeight, width);
105
+ const shortcuts = this._state.screen === "command" ? COMMAND_SHORTCUTS : DEFAULT_SHORTCUTS;
106
+ const footer = renderFooter({
107
+ statusText: this._state.statusText,
108
+ shortcuts,
109
+ width,
110
+ });
111
+ // Assemble: header ends with \n\n, canvas has no trailing \n, empty line before footer
112
+ return header + canvas + "\n\n" + footer;
113
+ }
114
+ /**
115
+ * Redraw the terminal using home+clear on the alternate screen.
116
+ *
117
+ * Guarded by `if (!this.entered) return` — prevents double-redraw race
118
+ * when setView() calls this.redraw() after onMount() completes while
119
+ * the app is suspended (entered === false).
120
+ */
121
+ redraw() {
122
+ if (!this.entered)
123
+ return;
124
+ const frame = this.buildFrame();
125
+ this.output.write(cursor.home + screen.clear + frame);
126
+ }
127
+ /**
128
+ * Set the active view, calling lifecycle hooks.
129
+ *
130
+ * Unmounts any existing view, mounts the new one, switches to command
131
+ * screen, and triggers an immediate redraw.
132
+ *
133
+ * NOTE: When the app is suspended (entered === false), the post-onMount
134
+ * redraw() call is a safe no-op — guarded by the `!this.entered` check.
135
+ */
136
+ async setView(view) {
137
+ if (this._state.activeView?.onUnmount) {
138
+ await this._state.activeView.onUnmount();
139
+ }
140
+ this._state.activeView = view;
141
+ this._state.screen = "command";
142
+ if (view.onMount) {
143
+ await view.onMount();
144
+ }
145
+ this.redraw();
146
+ }
147
+ /**
148
+ * Unmount the active view and return to the menu screen.
149
+ */
150
+ async clearView() {
151
+ if (this._state.activeView?.onUnmount) {
152
+ await this._state.activeView.onUnmount();
153
+ }
154
+ this._state.activeView = null;
155
+ this._state.screen = "menu";
156
+ this._state.statusText = "Ready";
157
+ this.redraw();
158
+ }
159
+ /**
160
+ * Suspend the app — exit alternate screen and stop the tick loop.
161
+ *
162
+ * Call before launching @inquirer/prompts or running a command handler
163
+ * to hand control back to the normal terminal.
164
+ */
165
+ suspend() {
166
+ this._suspended = true;
167
+ this.stopTick();
168
+ // Release stdin so command handlers and @inquirer/prompts can use it
169
+ try {
170
+ process.stdin.removeListener("data", this.onKeyData);
171
+ if (isTTY()) {
172
+ try {
173
+ process.stdin.setRawMode(false);
174
+ }
175
+ catch {
176
+ // Ignore in non-TTY environments
177
+ }
178
+ }
179
+ }
180
+ catch {
181
+ // Ignore listener removal errors
182
+ }
183
+ this.output.write(cursor.show + screen.altExit);
184
+ this.entered = false;
185
+ }
186
+ /**
187
+ * Resume the app — re-enter alternate screen and restart the tick loop.
188
+ *
189
+ * Call after a command handler completes to restore the interactive layout.
190
+ */
191
+ resume() {
192
+ this._suspended = false;
193
+ // Reclaim stdin for interactive app key handling
194
+ if (isTTY()) {
195
+ try {
196
+ process.stdin.setRawMode(true);
197
+ }
198
+ catch {
199
+ // Ignore in non-TTY environments
200
+ }
201
+ process.stdin.resume();
202
+ process.stdin.on("data", this.onKeyData);
203
+ }
204
+ this.output.write(screen.altEnter + cursor.hide);
205
+ this.entered = true;
206
+ this.redraw();
207
+ this.startTick();
208
+ }
209
+ /**
210
+ * Tear down the app — exits alternate screen, stops tick, removes listeners.
211
+ */
212
+ dispose() {
213
+ this.stopTick();
214
+ if (this.resizeTimer !== null) {
215
+ clearTimeout(this.resizeTimer);
216
+ this.resizeTimer = null;
217
+ }
218
+ process.stderr.removeListener("resize", this.onResize);
219
+ try {
220
+ process.stdin.removeListener("data", this.onKeyData);
221
+ if (isTTY()) {
222
+ try {
223
+ process.stdin.setRawMode(false);
224
+ }
225
+ catch {
226
+ // Ignore in non-TTY test environments
227
+ }
228
+ process.stdin.pause();
229
+ }
230
+ }
231
+ catch {
232
+ // Ignore listener removal errors in test environments
233
+ }
234
+ // Exit alternate screen and show cursor
235
+ this.output.write(cursor.show + screen.altExit);
236
+ this.entered = false;
237
+ if (this.resolveRun) {
238
+ this.resolveRun();
239
+ this.resolveRun = null;
240
+ }
241
+ }
242
+ /** Get the current animation frame index (for spinner rendering in views). */
243
+ getFrameIndex() {
244
+ return this.frameIndex;
245
+ }
246
+ // ── Private helpers ─────────────────────────────────────────────
247
+ startTick() {
248
+ if (this.tickTimer !== null || this._suspended)
249
+ return;
250
+ this.tickTimer = setInterval(() => {
251
+ this.frameIndex++;
252
+ if (!this._suspended) {
253
+ this.redraw();
254
+ }
255
+ }, FRAME_INTERVAL);
256
+ }
257
+ stopTick() {
258
+ if (this.tickTimer !== null) {
259
+ clearInterval(this.tickTimer);
260
+ this.tickTimer = null;
261
+ }
262
+ }
263
+ onResize = () => {
264
+ if (this.resizeTimer !== null) {
265
+ clearTimeout(this.resizeTimer);
266
+ }
267
+ this.resizeTimer = setTimeout(() => {
268
+ this.resizeTimer = null;
269
+ // Alternate screen doesn't need cursor math reset — just redraw
270
+ if (!this._suspended) {
271
+ this.redraw();
272
+ }
273
+ }, 100);
274
+ };
275
+ onKeyData = (data) => {
276
+ const key = data.toString();
277
+ const ctrl = key.length === 1 && data[0] < 32 && data[0] !== 27;
278
+ const meta = key.startsWith("\x1b") && key.length > 1;
279
+ // F1 is always handled at app level (before view delegation)
280
+ if (key === "\x1bOP" || key === "\x1b[11~") {
281
+ if (this._state.screen === "help") {
282
+ // Restore saved view
283
+ this._state.activeView = this.savedMenuView;
284
+ this.savedMenuView = null;
285
+ this._state.screen = "menu";
286
+ }
287
+ else {
288
+ // Save current view and show help overlay
289
+ this.savedMenuView = this._state.activeView;
290
+ this._state.activeView = new HelpOverlayView(() => {
291
+ this._state.activeView = this.savedMenuView;
292
+ this.savedMenuView = null;
293
+ this._state.screen = "menu";
294
+ this.redraw();
295
+ });
296
+ this._state.screen = "help";
297
+ }
298
+ this.redraw();
299
+ return;
300
+ }
301
+ // Delegate to active view
302
+ if (this._state.activeView?.handleKey) {
303
+ const consumed = this._state.activeView.handleKey(key, ctrl, meta);
304
+ if (consumed)
305
+ return;
306
+ }
307
+ // App-level key handling
308
+ if (ctrl && key === "\x03") {
309
+ // Ctrl+C — unmount active view before exiting
310
+ if (this._state.activeView?.onUnmount) {
311
+ void this._state.activeView.onUnmount().finally(() => {
312
+ this.dispose();
313
+ process.exit(0);
314
+ });
315
+ }
316
+ else {
317
+ this.dispose();
318
+ process.exit(0);
319
+ }
320
+ }
321
+ else if (key === "\x1b" || key === "\x1b[") {
322
+ // Escape or Escape sequence
323
+ if (this._state.screen === "command" || this._state.screen === "help") {
324
+ void this.clearView();
325
+ }
326
+ }
327
+ };
328
+ }
@@ -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 23 interactive-safe commands matching the TUI runner.
5
+ * Each MenuItem.value must exactly match the Commander command name.
6
+ */
7
+ import type { CommandCategory, MenuItem } from "../types.js";
8
+ /**
9
+ * All interactive-safe 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,EAkJ/C,CAAC;AAEF;;;GAGG;AACH,wBAAgB,YAAY,IAAI,QAAQ,EAAE,CAEzC"}