@vertaaux/cli 0.4.0 → 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.
- package/CHANGELOG.md +97 -0
- package/MIGRATION.md +239 -0
- package/README.md +34 -16
- package/dist/app/interactive-app.d.ts +101 -0
- package/dist/app/interactive-app.d.ts.map +1 -0
- package/dist/app/interactive-app.js +309 -0
- package/dist/app/layout/canvas.d.ts +23 -0
- package/dist/app/layout/canvas.d.ts.map +1 -0
- package/dist/app/layout/canvas.js +36 -0
- package/dist/app/layout/footer.d.ts +31 -0
- package/dist/app/layout/footer.d.ts.map +1 -0
- package/dist/app/layout/footer.js +41 -0
- package/dist/app/layout/header.d.ts +20 -0
- package/dist/app/layout/header.d.ts.map +1 -0
- package/dist/app/layout/header.js +27 -0
- package/dist/app/menu/categories.d.ts +20 -0
- package/dist/app/menu/categories.d.ts.map +1 -0
- package/dist/app/menu/categories.js +181 -0
- package/dist/app/menu/filter.d.ts +17 -0
- package/dist/app/menu/filter.d.ts.map +1 -0
- package/dist/app/menu/filter.js +33 -0
- package/dist/app/menu/menu-view.d.ts +35 -0
- package/dist/app/menu/menu-view.d.ts.map +1 -0
- package/dist/app/menu/menu-view.js +230 -0
- package/dist/app/menu/recent.d.ts +24 -0
- package/dist/app/menu/recent.d.ts.map +1 -0
- package/dist/app/menu/recent.js +49 -0
- package/dist/app/types.d.ts +43 -0
- package/dist/app/types.d.ts.map +1 -0
- package/dist/app/types.js +7 -0
- package/dist/app/views/command-runner.d.ts +36 -0
- package/dist/app/views/command-runner.d.ts.map +1 -0
- package/dist/app/views/command-runner.js +372 -0
- package/dist/app/views/help-overlay.d.ts +21 -0
- package/dist/app/views/help-overlay.d.ts.map +1 -0
- package/dist/app/views/help-overlay.js +45 -0
- package/dist/auth/ci-token.d.ts +8 -2
- package/dist/auth/ci-token.d.ts.map +1 -1
- package/dist/auth/ci-token.js +15 -30
- package/dist/auth/device-flow.d.ts +2 -1
- package/dist/auth/device-flow.d.ts.map +1 -1
- package/dist/auth/device-flow.js +13 -10
- package/dist/auth/token-store.d.ts.map +1 -1
- package/dist/auth/token-store.js +12 -2
- package/dist/baseline/diff.d.ts +2 -2
- package/dist/baseline/diff.d.ts.map +1 -1
- package/dist/baseline/diff.js +15 -34
- package/dist/commands/a11y.d.ts +9 -0
- package/dist/commands/a11y.d.ts.map +1 -0
- package/dist/commands/a11y.js +76 -0
- package/dist/commands/audit/artifacts.d.ts +27 -0
- package/dist/commands/audit/artifacts.d.ts.map +1 -0
- package/dist/commands/audit/artifacts.js +158 -0
- package/dist/commands/audit/ci-detection.d.ts +18 -0
- package/dist/commands/audit/ci-detection.d.ts.map +1 -0
- package/dist/commands/audit/ci-detection.js +71 -0
- package/dist/commands/audit/explain.d.ts +11 -0
- package/dist/commands/audit/explain.d.ts.map +1 -0
- package/dist/commands/audit/explain.js +45 -0
- package/dist/commands/audit/filters.d.ts +17 -0
- package/dist/commands/audit/filters.d.ts.map +1 -0
- package/dist/commands/audit/filters.js +40 -0
- package/dist/commands/audit/index.d.ts +18 -0
- package/dist/commands/audit/index.d.ts.map +1 -0
- package/dist/commands/audit/index.js +564 -0
- package/dist/commands/audit/output.d.ts +32 -0
- package/dist/commands/audit/output.d.ts.map +1 -0
- package/dist/commands/audit/output.js +130 -0
- package/dist/commands/audit/policy.d.ts +19 -0
- package/dist/commands/audit/policy.d.ts.map +1 -0
- package/dist/commands/audit/policy.js +102 -0
- package/dist/commands/audit/scoring.d.ts +23 -0
- package/dist/commands/audit/scoring.d.ts.map +1 -0
- package/dist/commands/audit/scoring.js +70 -0
- package/dist/commands/audit/types.d.ts +88 -0
- package/dist/commands/audit/types.d.ts.map +1 -0
- package/dist/commands/audit/types.js +8 -0
- package/dist/commands/audit.d.ts +2 -60
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +2 -1097
- package/dist/commands/baseline.d.ts +1 -0
- package/dist/commands/baseline.d.ts.map +1 -1
- package/dist/commands/baseline.js +205 -121
- package/dist/commands/comment.d.ts +22 -0
- package/dist/commands/comment.d.ts.map +1 -1
- package/dist/commands/comment.js +122 -58
- package/dist/commands/compare.d.ts +17 -0
- package/dist/commands/compare.d.ts.map +1 -1
- package/dist/commands/compare.js +287 -180
- package/dist/commands/diff.d.ts +5 -0
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +168 -141
- package/dist/commands/doc.d.ts +10 -0
- package/dist/commands/doc.d.ts.map +1 -1
- package/dist/commands/doc.js +134 -76
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +164 -17
- package/dist/commands/download.d.ts +10 -0
- package/dist/commands/download.d.ts.map +1 -1
- package/dist/commands/download.js +169 -112
- package/dist/commands/explain.d.ts +5 -0
- package/dist/commands/explain.d.ts.map +1 -1
- package/dist/commands/explain.js +241 -155
- package/dist/commands/fix-all.d.ts +25 -0
- package/dist/commands/fix-all.d.ts.map +1 -0
- package/dist/commands/fix-all.js +206 -0
- package/dist/commands/fix-plan.d.ts +9 -0
- package/dist/commands/fix-plan.d.ts.map +1 -1
- package/dist/commands/fix-plan.js +152 -89
- package/dist/commands/fix.d.ts +17 -0
- package/dist/commands/fix.d.ts.map +1 -0
- package/dist/commands/fix.js +111 -0
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +94 -42
- package/dist/commands/login.d.ts +18 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +263 -92
- package/dist/commands/patch-review.d.ts +11 -0
- package/dist/commands/patch-review.d.ts.map +1 -1
- package/dist/commands/patch-review.js +159 -97
- package/dist/commands/policy.d.ts +31 -0
- package/dist/commands/policy.d.ts.map +1 -1
- package/dist/commands/policy.js +269 -124
- package/dist/commands/release-notes.d.ts +10 -0
- package/dist/commands/release-notes.d.ts.map +1 -1
- package/dist/commands/release-notes.js +127 -73
- package/dist/commands/scan.d.ts +13 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +133 -0
- package/dist/commands/status.d.ts +9 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +81 -0
- package/dist/commands/suggest.d.ts +10 -0
- package/dist/commands/suggest.d.ts.map +1 -1
- package/dist/commands/suggest.js +153 -82
- package/dist/commands/triage.d.ts +35 -0
- package/dist/commands/triage.d.ts.map +1 -1
- package/dist/commands/triage.js +206 -81
- package/dist/commands/upload.d.ts +9 -0
- package/dist/commands/upload.d.ts.map +1 -1
- package/dist/commands/upload.js +140 -101
- package/dist/commands/verify.d.ts +13 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +118 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +125 -990
- package/dist/interactive/fix-wizard.d.ts +3 -0
- package/dist/interactive/fix-wizard.d.ts.map +1 -1
- package/dist/interactive/fix-wizard.js +130 -112
- package/dist/interactive/init-wizard.d.ts +3 -1
- package/dist/interactive/init-wizard.d.ts.map +1 -1
- package/dist/interactive/init-wizard.js +207 -138
- package/dist/interactive/prompts.d.ts +7 -3
- package/dist/interactive/prompts.d.ts.map +1 -1
- package/dist/interactive/prompts.js +44 -23
- package/dist/output/envelope.d.ts +2 -0
- package/dist/output/envelope.d.ts.map +1 -1
- package/dist/output/envelope.js +18 -2
- package/dist/output/factory.d.ts +2 -1
- package/dist/output/factory.d.ts.map +1 -1
- package/dist/output/html.d.ts +2 -1
- package/dist/output/html.d.ts.map +1 -1
- package/dist/output/html.js +3 -2
- package/dist/output/human.d.ts +2 -1
- package/dist/output/human.d.ts.map +1 -1
- package/dist/output/human.js +3 -2
- package/dist/output/json.d.ts +2 -1
- package/dist/output/json.d.ts.map +1 -1
- package/dist/output/junit.d.ts +2 -1
- package/dist/output/junit.d.ts.map +1 -1
- package/dist/output/sarif.d.ts +2 -1
- package/dist/output/sarif.d.ts.map +1 -1
- package/dist/types.d.ts +74 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/ui/banner.d.ts +34 -0
- package/dist/ui/banner.d.ts.map +1 -1
- package/dist/ui/banner.js +97 -5
- package/dist/ui/diagnostics.d.ts +9 -4
- package/dist/ui/diagnostics.d.ts.map +1 -1
- package/dist/ui/diagnostics.js +32 -82
- package/dist/ui/strings.d.ts +373 -0
- package/dist/ui/strings.d.ts.map +1 -0
- package/dist/ui/strings.js +499 -0
- package/dist/ui/table.d.ts +0 -2
- package/dist/ui/table.d.ts.map +1 -1
- package/dist/ui/table.js +3 -4
- package/dist/utils/api-client.d.ts +46 -0
- package/dist/utils/api-client.d.ts.map +1 -0
- package/dist/utils/api-client.js +170 -0
- package/dist/utils/client.d.ts +29 -18
- package/dist/utils/client.d.ts.map +1 -1
- package/dist/utils/client.js +102 -12
- package/dist/utils/formatters.d.ts +38 -0
- package/dist/utils/formatters.d.ts.map +1 -0
- package/dist/utils/formatters.js +277 -0
- package/dist/utils/url-classify.d.ts.map +1 -1
- package/dist/utils/url-classify.js +24 -3
- package/node_modules/@vertaaux/tui/dist/index.cjs +713 -20
- package/node_modules/@vertaaux/tui/dist/index.cjs.map +1 -1
- package/node_modules/@vertaaux/tui/dist/index.d.cts +361 -4
- package/node_modules/@vertaaux/tui/dist/index.d.ts +361 -4
- package/node_modules/@vertaaux/tui/dist/index.js +689 -21
- package/node_modules/@vertaaux/tui/dist/index.js.map +1 -1
- package/package.json +13 -5
- package/dist/commands/client.d.ts +0 -14
- package/dist/commands/client.d.ts.map +0 -1
- package/dist/commands/client.js +0 -362
- package/dist/commands/drift.d.ts +0 -15
- package/dist/commands/drift.d.ts.map +0 -1
- package/dist/commands/drift.js +0 -309
- package/dist/commands/protect.d.ts +0 -16
- package/dist/commands/protect.d.ts.map +0 -1
- package/dist/commands/protect.js +0 -323
- package/dist/commands/report.d.ts +0 -15
- package/dist/commands/report.d.ts.map +0 -1
- package/dist/commands/report.js +0 -214
- package/dist/policy/sync.d.ts +0 -67
- package/dist/policy/sync.d.ts.map +0 -1
- 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"}
|