@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
@@ -15,6 +15,7 @@ export interface BaselineCommandOptions {
15
15
  list?: boolean;
16
16
  base?: string;
17
17
  }
18
+ export declare function handleBaseline(jobIdArg: string | undefined, cmdOptions: BaselineCommandOptions): Promise<void>;
18
19
  /**
19
20
  * Register the baseline command with the Commander program.
20
21
  */
@@ -1 +1 @@
1
- {"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../../src/commands/baseline.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoIzC,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA8H9D"}
1
+ {"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../../src/commands/baseline.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsFzC,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,UAAU,EAAE,sBAAsB,GACjC,OAAO,CAAC,IAAI,CAAC,CAkOf;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA4B9D"}
@@ -8,51 +8,18 @@
8
8
  */
9
9
  import { readFile } from "fs/promises";
10
10
  import { existsSync } from "fs";
11
+ import { renderError, renderWarning, runSteps, createRenderer } from "@vertaaux/tui";
11
12
  import { ExitCode } from "../utils/exit-codes.js";
13
+ import { createClient } from "../utils/client.js";
12
14
  import { loadBaseline, saveBaseline, createBaseline, addToBaseline, DEFAULT_BASELINE_PATH, } from "../baseline/manager.js";
13
15
  import { generateFingerprint } from "../baseline/hash.js";
14
- const DEFAULT_API_BASE = "https://vertaaux.ai/v1";
16
+ import { writeOutput } from "../output/envelope.js";
17
+ import { strings } from "../ui/strings.js";
15
18
  /**
16
- * Get API base URL from environment.
17
- */
18
- function getApiBase() {
19
- return (process.env.VERTAAUX_API_BASE || DEFAULT_API_BASE).replace(/\/$/, "");
20
- }
21
- /**
22
- * Get API key from environment.
23
- */
24
- function getApiKey() {
25
- const key = process.env.VERTAAUX_API_KEY;
26
- if (!key) {
27
- throw new Error("VERTAAUX_API_KEY is required");
28
- }
29
- return key;
30
- }
31
- /**
32
- * Fetch audit results from API.
19
+ * Fetch audit results via SDK client.
33
20
  */
34
21
  async function fetchAudit(jobId) {
35
- const base = getApiBase();
36
- const url = `${base}/audit/${jobId}`;
37
- const res = await fetch(url, {
38
- method: "GET",
39
- headers: {
40
- "Content-Type": "application/json",
41
- "X-API-Key": getApiKey(),
42
- },
43
- });
44
- if (!res.ok) {
45
- let detail = res.statusText;
46
- try {
47
- const data = await res.json();
48
- detail = data.error || data.message || detail;
49
- }
50
- catch {
51
- // ignore
52
- }
53
- throw new Error(`HTTP ${res.status}: ${detail}`);
54
- }
55
- return (await res.json());
22
+ return createClient().audits.retrieve(jobId);
56
23
  }
57
24
  /**
58
25
  * Normalize issues from various API response formats.
@@ -106,6 +73,199 @@ function formatBaselineSummary(baseline) {
106
73
  }
107
74
  return lines.join("\n");
108
75
  }
76
+ export async function handleBaseline(jobIdArg, cmdOptions) {
77
+ const baselinePath = cmdOptions.path || DEFAULT_BASELINE_PATH;
78
+ const renderer = createRenderer("auto");
79
+ const baseState = {
80
+ phase: "baseline",
81
+ phaseIndex: 1,
82
+ phaseTotal: 1,
83
+ url: "",
84
+ mode: "baseline",
85
+ progress: {},
86
+ totals: {},
87
+ issueCount: 0,
88
+ scorePreview: null,
89
+ verbose: false,
90
+ elapsed: 0,
91
+ };
92
+ const startTime = Date.now();
93
+ // Handle --list: show baseline summary
94
+ if (cmdOptions.list) {
95
+ const steps = [
96
+ {
97
+ id: "fetch",
98
+ actionText: "Fetching baseline...",
99
+ summaryText: "Baseline loaded",
100
+ run: async () => {
101
+ const baseline = await loadBaseline(baselinePath);
102
+ if (!baseline) {
103
+ process.stderr.write(renderWarning({
104
+ message: "No baseline found.",
105
+ suggestion: `vertaa baseline <job-id> --path ${baselinePath}`,
106
+ }) + "\n");
107
+ return;
108
+ }
109
+ writeOutput(formatBaselineSummary(baseline));
110
+ },
111
+ },
112
+ ];
113
+ const { success, states } = await runSteps(steps, {
114
+ failFast: true,
115
+ onStateChange: (stepStates) => {
116
+ renderer.update({ ...baseState, stepStates, elapsed: Date.now() - startTime });
117
+ },
118
+ });
119
+ renderer.finish({ url: "", mode: "baseline", overallScore: 0, scores: {}, issueCount: 0, passed: success, elapsed: Date.now() - startTime });
120
+ if (!success) {
121
+ const failed = states.find(s => s.status === "failed");
122
+ process.stderr.write(renderError({
123
+ message: failed?.failReason || "Command failed",
124
+ suggestion: "vertaa doctor",
125
+ }) + "\n");
126
+ process.exitCode = ExitCode.ERROR;
127
+ }
128
+ return;
129
+ }
130
+ // Handle --ignore: add single issue to baseline
131
+ if (cmdOptions.ignore) {
132
+ const issueId = cmdOptions.ignore;
133
+ const steps = [
134
+ {
135
+ id: "update",
136
+ actionText: "Updating baseline...",
137
+ summaryText: "Baseline updated",
138
+ run: async () => {
139
+ let baseline = await loadBaseline(baselinePath);
140
+ if (!baseline) {
141
+ throw new Error("No baseline found. Create one first with: vertaa baseline <job-id>");
142
+ }
143
+ // Need to find the issue to add it properly
144
+ let issue;
145
+ if (jobIdArg) {
146
+ const audit = await fetchAudit(jobIdArg);
147
+ const issues = normalizeIssues(audit.issues);
148
+ const found = findIssueById(issues, issueId);
149
+ if (!found) {
150
+ throw new Error(`Issue "${issueId}" not found in job ${jobIdArg}`);
151
+ }
152
+ issue = found;
153
+ }
154
+ else {
155
+ // Create minimal issue from ID only
156
+ issue = { id: issueId, description: `Ignored: ${issueId}` };
157
+ }
158
+ baseline = addToBaseline(baseline, issue, cmdOptions.reason);
159
+ await saveBaseline(baseline, baselinePath);
160
+ },
161
+ },
162
+ ];
163
+ const { success: ignoreSuccess, states: ignoreStates } = await runSteps(steps, {
164
+ failFast: true,
165
+ onStateChange: (stepStates) => {
166
+ renderer.update({ ...baseState, stepStates, elapsed: Date.now() - startTime });
167
+ },
168
+ });
169
+ renderer.finish({ url: "", mode: "baseline", overallScore: 0, scores: {}, issueCount: 0, passed: ignoreSuccess, elapsed: Date.now() - startTime });
170
+ if (!ignoreSuccess) {
171
+ const failed = ignoreStates.find(s => s.status === "failed");
172
+ process.stderr.write(renderError({
173
+ message: failed?.failReason || "Command failed",
174
+ suggestion: "vertaa doctor",
175
+ }) + "\n");
176
+ process.exitCode = ExitCode.ERROR;
177
+ }
178
+ writeOutput(`Added issue ${issueId} to baseline${cmdOptions.reason ? `\nReason: ${cmdOptions.reason}` : ""}`);
179
+ return;
180
+ }
181
+ // Handle --from-file: create baseline from JSON file
182
+ if (cmdOptions.fromFile) {
183
+ const filePath = cmdOptions.fromFile;
184
+ if (!existsSync(filePath)) {
185
+ process.stderr.write(renderError({
186
+ message: `File not found: ${filePath}`,
187
+ suggestion: "vertaa audit <url>",
188
+ exitCode: ExitCode.ERROR,
189
+ }) + "\n");
190
+ process.exit(ExitCode.ERROR);
191
+ }
192
+ const steps = [
193
+ {
194
+ id: "save",
195
+ actionText: "Saving baseline...",
196
+ summaryText: "Baseline saved",
197
+ run: async () => {
198
+ const content = await readFile(filePath, "utf-8");
199
+ const data = JSON.parse(content);
200
+ const issues = normalizeIssues(data.issues);
201
+ const url = data.url || "unknown";
202
+ const baseline = createBaseline(issues, url);
203
+ await saveBaseline(baseline, baselinePath);
204
+ writeOutput(`Baseline created with ${issues.length} issues\nSaved to: ${baselinePath}`);
205
+ },
206
+ },
207
+ ];
208
+ const { success: fileSuccess, states: fileStates } = await runSteps(steps, {
209
+ failFast: true,
210
+ onStateChange: (stepStates) => {
211
+ renderer.update({ ...baseState, stepStates, elapsed: Date.now() - startTime });
212
+ },
213
+ });
214
+ renderer.finish({ url: "", mode: "baseline", overallScore: 0, scores: {}, issueCount: 0, passed: fileSuccess, elapsed: Date.now() - startTime });
215
+ if (!fileSuccess) {
216
+ const failed = fileStates.find(s => s.status === "failed");
217
+ process.stderr.write(renderError({
218
+ message: failed?.failReason || "Command failed",
219
+ suggestion: "vertaa doctor",
220
+ }) + "\n");
221
+ process.exitCode = ExitCode.ERROR;
222
+ }
223
+ return;
224
+ }
225
+ // Handle job-id: create baseline from API audit
226
+ if (!jobIdArg) {
227
+ process.stderr.write(renderError({
228
+ message: "Provide job ID or use --from-file or --list",
229
+ context: "Usage: vertaa baseline <job-id>\n vertaa baseline --from-file <audit.json>\n vertaa baseline --list",
230
+ suggestion: "vertaa baseline --list",
231
+ exitCode: ExitCode.ERROR,
232
+ }) + "\n");
233
+ process.exit(ExitCode.ERROR);
234
+ }
235
+ const steps = [
236
+ {
237
+ id: "fetch",
238
+ actionText: "Fetching audit results...",
239
+ summaryText: "Audit results loaded",
240
+ run: async () => {
241
+ const audit = await fetchAudit(jobIdArg);
242
+ if (audit.status !== "completed") {
243
+ throw new Error(strings.fixAll.auditIncomplete(jobIdArg, audit.status || "unknown"));
244
+ }
245
+ const issues = normalizeIssues(audit.issues);
246
+ const url = audit.url || "unknown";
247
+ const baseline = createBaseline(issues, url);
248
+ await saveBaseline(baseline, baselinePath);
249
+ writeOutput(`Baseline created with ${issues.length} issues\nSaved to: ${baselinePath}`);
250
+ },
251
+ },
252
+ ];
253
+ const { success: jobSuccess, states: jobStates } = await runSteps(steps, {
254
+ failFast: true,
255
+ onStateChange: (stepStates) => {
256
+ renderer.update({ ...baseState, stepStates, elapsed: Date.now() - startTime });
257
+ },
258
+ });
259
+ renderer.finish({ url: "", mode: "baseline", overallScore: 0, scores: {}, issueCount: 0, passed: jobSuccess, elapsed: Date.now() - startTime });
260
+ if (!jobSuccess) {
261
+ const failed = jobStates.find(s => s.status === "failed");
262
+ process.stderr.write(renderError({
263
+ message: failed?.failReason || "Command failed",
264
+ suggestion: "vertaa doctor",
265
+ }) + "\n");
266
+ process.exitCode = ExitCode.ERROR;
267
+ }
268
+ }
109
269
  /**
110
270
  * Register the baseline command with the Commander program.
111
271
  */
@@ -120,90 +280,14 @@ export function registerBaselineCommand(program) {
120
280
  .option("--list", "Show baselined issues count and last update")
121
281
  .action(async (jobIdArg, cmdOptions) => {
122
282
  try {
123
- const baselinePath = cmdOptions.path || DEFAULT_BASELINE_PATH;
124
- // Handle --list: show baseline summary
125
- if (cmdOptions.list) {
126
- const baseline = await loadBaseline(baselinePath);
127
- if (!baseline) {
128
- console.error("No baseline found.");
129
- console.error(`Create one with: vertaa baseline <job-id> --path ${baselinePath}`);
130
- return;
131
- }
132
- console.error(formatBaselineSummary(baseline));
133
- return;
134
- }
135
- // Handle --ignore: add single issue to baseline
136
- if (cmdOptions.ignore) {
137
- const issueId = cmdOptions.ignore;
138
- let baseline = await loadBaseline(baselinePath);
139
- if (!baseline) {
140
- console.error("No baseline found. Create one first with: vertaa baseline <job-id>");
141
- process.exit(ExitCode.ERROR);
142
- }
143
- // Need to find the issue to add it properly
144
- // If we have a job ID, fetch the audit; otherwise use a minimal issue
145
- let issue;
146
- if (jobIdArg) {
147
- const audit = await fetchAudit(jobIdArg);
148
- const issues = normalizeIssues(audit.issues);
149
- const found = findIssueById(issues, issueId);
150
- if (!found) {
151
- console.error(`Issue "${issueId}" not found in job ${jobIdArg}`);
152
- process.exit(ExitCode.ERROR);
153
- }
154
- issue = found;
155
- }
156
- else {
157
- // Create minimal issue from ID only
158
- issue = { id: issueId, description: `Ignored: ${issueId}` };
159
- }
160
- baseline = addToBaseline(baseline, issue, cmdOptions.reason);
161
- await saveBaseline(baseline, baselinePath);
162
- console.error(`Added issue ${issueId} to baseline`);
163
- if (cmdOptions.reason) {
164
- console.error(`Reason: ${cmdOptions.reason}`);
165
- }
166
- return;
167
- }
168
- // Handle --from-file: create baseline from JSON file
169
- if (cmdOptions.fromFile) {
170
- const filePath = cmdOptions.fromFile;
171
- if (!existsSync(filePath)) {
172
- console.error(`File not found: ${filePath}`);
173
- process.exit(ExitCode.ERROR);
174
- }
175
- const content = await readFile(filePath, "utf-8");
176
- const data = JSON.parse(content);
177
- const issues = normalizeIssues(data.issues);
178
- const url = data.url || "unknown";
179
- const baseline = createBaseline(issues, url);
180
- await saveBaseline(baseline, baselinePath);
181
- console.error(`Baseline created with ${issues.length} issues`);
182
- console.error(`Saved to: ${baselinePath}`);
183
- return;
184
- }
185
- // Handle job-id: create baseline from API audit
186
- if (!jobIdArg) {
187
- console.error("Provide job ID or use --from-file or --list");
188
- console.error("Usage: vertaa baseline <job-id>");
189
- console.error(" vertaa baseline --from-file <audit.json>");
190
- console.error(" vertaa baseline --list");
191
- process.exit(ExitCode.ERROR);
192
- }
193
- const audit = await fetchAudit(jobIdArg);
194
- if (audit.status !== "completed") {
195
- console.error(`Audit ${jobIdArg} is not completed (status: ${audit.status})`);
196
- process.exit(ExitCode.ERROR);
197
- }
198
- const issues = normalizeIssues(audit.issues);
199
- const url = audit.url || "unknown";
200
- const baseline = createBaseline(issues, url);
201
- await saveBaseline(baseline, baselinePath);
202
- console.error(`Baseline created with ${issues.length} issues`);
203
- console.error(`Saved to: ${baselinePath}`);
283
+ await handleBaseline(jobIdArg, cmdOptions);
204
284
  }
205
285
  catch (error) {
206
- console.error("Error:", error instanceof Error ? error.message : String(error));
286
+ process.stderr.write(renderError({
287
+ message: error instanceof Error ? error.message : String(error),
288
+ suggestion: "vertaa audit <url>",
289
+ exitCode: ExitCode.ERROR,
290
+ }) + "\n");
207
291
  process.exit(ExitCode.ERROR);
208
292
  }
209
293
  });
@@ -6,6 +6,28 @@
6
6
  * Uses sticky comment semantics to update existing comments.
7
7
  */
8
8
  import { Command } from "commander";
9
+ import { type MarkdownOptions } from "../output/markdown.js";
10
+ /**
11
+ * Options for comment generation.
12
+ */
13
+ interface CommentOptions {
14
+ input?: string;
15
+ baseline?: string;
16
+ format?: "markdown" | "json";
17
+ output?: string;
18
+ collapse?: boolean;
19
+ groupBy?: MarkdownOptions["groupBy"];
20
+ post?: boolean;
21
+ githubToken?: string;
22
+ gitlabToken?: string;
23
+ pr?: number;
24
+ repo?: string;
25
+ noPostIfClean?: boolean;
26
+ }
27
+ /**
28
+ * Handle the comment command.
29
+ */
30
+ export declare function handleComment(options: CommentOptions): Promise<void>;
9
31
  /**
10
32
  * Register the comment command with the Commander program.
11
33
  */
@@ -1 +1 @@
1
- {"version":3,"file":"comment.d.ts","sourceRoot":"","sources":["../../src/commands/comment.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgZpC;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA6F7D;AAGD,OAAO,EAAE,qBAAqB,IAAI,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"comment.d.ts","sourceRoot":"","sources":["../../src/commands/comment.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,OAAO,EAGL,KAAK,eAAe,EAErB,MAAM,uBAAuB,CAAC;AA0B/B;;GAEG;AACH,UAAU,cAAc;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC;IAErC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAqJD;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAyQ1E;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAgG7D;AAGD,OAAO,EAAE,qBAAqB,IAAI,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import fs from "fs";
9
9
  import path from "path";
10
+ import { renderError, runSteps, createRenderer } from "@vertaaux/tui";
10
11
  import { loadBaseline } from "../baseline/manager.js";
11
12
  import { ExitCode } from "../utils/exit-codes.js";
12
13
  import { formatMarkdownComment, categorizeIssuesForComment, } from "../output/markdown.js";
@@ -14,6 +15,7 @@ import { createEnvelope, writeJsonOutput, writeOutput } from "../output/envelope
14
15
  import { resolveCommandFormat } from "../output/formats.js";
15
16
  import { postOrUpdateGitHubComment, parseRepository, extractPRNumber, } from "../ci/github-api.js";
16
17
  import { postOrUpdateGitLabNote, getGitLabConfig, extractMRIid, } from "../ci/gitlab-api.js";
18
+ import { strings } from "../ui/strings.js";
17
19
  /**
18
20
  * Default path for latest audit results.
19
21
  */
@@ -126,14 +128,33 @@ async function postComment(context, markdown) {
126
128
  /**
127
129
  * Handle the comment command.
128
130
  */
129
- async function handleComment(options) {
131
+ export async function handleComment(options) {
132
+ const renderer = createRenderer("auto");
133
+ const baseState = {
134
+ phase: "comment",
135
+ phaseIndex: 1,
136
+ phaseTotal: 1,
137
+ url: "",
138
+ mode: "comment",
139
+ progress: {},
140
+ totals: {},
141
+ issueCount: 0,
142
+ scorePreview: null,
143
+ verbose: false,
144
+ elapsed: 0,
145
+ };
146
+ const startTime = Date.now();
130
147
  let auditResult;
131
- // Load audit results
148
+ // Load audit results (pre-step, may need async iteration for stdin)
132
149
  if (options.input) {
133
150
  // From file
134
151
  const inputPath = path.resolve(process.cwd(), options.input);
135
152
  if (!fs.existsSync(inputPath)) {
136
- console.error(`Error: Input file not found: ${options.input}`);
153
+ process.stderr.write(renderError({
154
+ message: `Input file not found: ${options.input}`,
155
+ suggestion: "vertaa audit <url>",
156
+ exitCode: ExitCode.ERROR,
157
+ }) + "\n");
137
158
  process.exit(ExitCode.ERROR);
138
159
  }
139
160
  const content = fs.readFileSync(inputPath, "utf-8");
@@ -156,7 +177,11 @@ async function handleComment(options) {
156
177
  auditResult = JSON.parse(content);
157
178
  }
158
179
  else {
159
- console.error("Error: No audit results found. Provide --input or pipe results.");
180
+ process.stderr.write(renderError({
181
+ message: strings.comment.errors.noInput,
182
+ suggestion: "vertaa audit <url>",
183
+ exitCode: ExitCode.ERROR,
184
+ }) + "\n");
160
185
  process.exit(ExitCode.ERROR);
161
186
  }
162
187
  }
@@ -169,58 +194,85 @@ async function handleComment(options) {
169
194
  // Try default baseline
170
195
  baseline = await loadBaseline();
171
196
  }
172
- // Categorize issues
173
- const issues = normalizeIssues(auditResult.issues);
174
- const { newIssues, fixedIssues, existingIssues } = categorizeIssuesForComment(issues, baseline);
175
- // Build comment data
176
- const commentData = {
177
- auditId: auditResult.job_id,
178
- url: auditResult.url,
179
- newIssues,
180
- fixedIssues,
181
- existingIssues,
182
- scores: auditResult.scores,
183
- };
184
- // Generate output
197
+ // State to capture within steps
198
+ let outputContent = "";
199
+ let jsonData = {};
185
200
  const isJson = options.format === "json";
186
- const jsonData = {
187
- auditId: auditResult.job_id,
188
- url: auditResult.url,
189
- newIssues: newIssues.length,
190
- existingIssues: existingIssues.length,
191
- fixedIssues: fixedIssues.length,
192
- issues: {
193
- new: newIssues,
194
- existing: existingIssues,
195
- fixed: fixedIssues,
201
+ const steps = [
202
+ {
203
+ id: "generate",
204
+ actionText: "Generating review comment...",
205
+ summaryText: "Comment generated",
206
+ run: async () => {
207
+ // Categorize issues
208
+ const issues = normalizeIssues(auditResult.issues);
209
+ const { newIssues, fixedIssues, existingIssues } = categorizeIssuesForComment(issues, baseline);
210
+ // Build comment data
211
+ const commentData = {
212
+ auditId: auditResult.job_id,
213
+ url: auditResult.url,
214
+ newIssues,
215
+ fixedIssues,
216
+ existingIssues,
217
+ scores: auditResult.scores,
218
+ };
219
+ jsonData = {
220
+ auditId: auditResult.job_id,
221
+ url: auditResult.url,
222
+ newIssues: newIssues.length,
223
+ existingIssues: existingIssues.length,
224
+ fixedIssues: fixedIssues.length,
225
+ issues: {
226
+ new: newIssues,
227
+ existing: existingIssues,
228
+ fixed: fixedIssues,
229
+ },
230
+ };
231
+ if (isJson) {
232
+ outputContent = JSON.stringify(jsonData, null, 2);
233
+ }
234
+ else {
235
+ outputContent = formatMarkdownComment(commentData, {
236
+ groupBy: options.groupBy || "severity",
237
+ collapse: options.collapse ?? newIssues.length > 3,
238
+ collapseThreshold: 3,
239
+ includeEvidence: true,
240
+ includeFixes: true,
241
+ baseUrl: "https://vertaaux.ai",
242
+ });
243
+ }
244
+ },
196
245
  },
197
- };
198
- let output;
199
- if (isJson) {
200
- // JSON format - will be wrapped in envelope at output time
201
- output = JSON.stringify(jsonData, null, 2);
246
+ ];
247
+ // Check if posting and we should skip
248
+ if (options.post && options.noPostIfClean) {
249
+ // We need to generate first to check, so we still run the step
202
250
  }
203
- else {
204
- // Markdown format (default)
205
- output = formatMarkdownComment(commentData, {
206
- groupBy: options.groupBy || "severity",
207
- collapse: options.collapse ?? newIssues.length > 3,
208
- collapseThreshold: 3,
209
- includeEvidence: true,
210
- includeFixes: true,
211
- baseUrl: "https://vertaaux.ai",
212
- });
251
+ const { success, states } = await runSteps(steps, {
252
+ failFast: true,
253
+ onStateChange: (stepStates) => {
254
+ renderer.update({ ...baseState, stepStates, elapsed: Date.now() - startTime });
255
+ },
256
+ });
257
+ renderer.finish({ url: "", mode: "comment", overallScore: 0, scores: {}, issueCount: 0, passed: success, elapsed: Date.now() - startTime });
258
+ if (!success) {
259
+ const failed = states.find(s => s.status === "failed");
260
+ process.stderr.write(renderError({
261
+ message: failed?.failReason || "Command failed",
262
+ suggestion: "vertaa doctor",
263
+ }) + "\n");
264
+ process.exitCode = ExitCode.ERROR;
213
265
  }
214
266
  // Handle posting
215
267
  if (options.post) {
268
+ const newIssuesCount = jsonData.newIssues ?? 0;
216
269
  // Check if we should skip posting
217
- if (options.noPostIfClean && newIssues.length === 0) {
218
- console.error("No new issues found. Skipping comment post (--no-post-if-clean).");
270
+ if (options.noPostIfClean && newIssuesCount === 0) {
219
271
  // Still write output if requested
220
272
  if (options.output) {
221
273
  const outputPath = path.resolve(process.cwd(), options.output);
222
- fs.writeFileSync(outputPath, output, "utf-8");
223
- console.error(`Comment written to: ${outputPath}`);
274
+ fs.writeFileSync(outputPath, outputContent, "utf-8");
275
+ writeOutput(`Comment written to: ${outputPath}`);
224
276
  }
225
277
  return;
226
278
  }
@@ -264,30 +316,38 @@ async function handleComment(options) {
264
316
  context = detectCIContext();
265
317
  }
266
318
  if (!context) {
267
- console.error("Error: Cannot post comment - not in a recognized CI environment.");
268
- console.error("Provide --github-token with --repo and --pr, or run in GitHub Actions/GitLab CI.");
319
+ process.stderr.write(renderError({
320
+ message: "Cannot post comment not in a recognized CI environment.",
321
+ context: "Provide --github-token with --repo and --pr, or run in GitHub Actions/GitLab CI.",
322
+ suggestion: "vertaa comment --github-token <token> --repo owner/repo --pr <number>",
323
+ exitCode: ExitCode.ERROR,
324
+ }) + "\n");
269
325
  // Output for debugging via stdout
270
326
  if (isJson) {
271
327
  writeJsonOutput(jsonData, "comment");
272
328
  }
273
329
  else {
274
- writeOutput(output);
330
+ writeOutput(outputContent);
275
331
  }
276
332
  process.exit(ExitCode.ERROR);
277
333
  }
278
334
  try {
279
- const result = await postComment(context, output);
280
- console.error(`Comment posted: ${result.url}`);
335
+ const result = await postComment(context, outputContent);
336
+ writeOutput(`Comment posted: ${result.url}`);
281
337
  }
282
338
  catch (error) {
283
- console.error(`Error posting comment: ${error instanceof Error ? error.message : String(error)}`);
339
+ process.stderr.write(renderError({
340
+ message: `Error posting comment: ${error instanceof Error ? error.message : String(error)}`,
341
+ context: "Generated output written for debugging.",
342
+ suggestion: "vertaa comment --github-token <token> --repo owner/repo --pr <number>",
343
+ exitCode: ExitCode.ERROR,
344
+ }) + "\n");
284
345
  // Output for debugging even on failure
285
- console.error("\nGenerated output (for debugging):");
286
346
  if (isJson) {
287
347
  writeJsonOutput(jsonData, "comment");
288
348
  }
289
349
  else {
290
- writeOutput(output);
350
+ writeOutput(outputContent);
291
351
  }
292
352
  process.exit(ExitCode.ERROR);
293
353
  }
@@ -297,9 +357,9 @@ async function handleComment(options) {
297
357
  const outputPath = path.resolve(process.cwd(), options.output);
298
358
  const fileContent = isJson
299
359
  ? JSON.stringify(createEnvelope(jsonData, "comment"), null, 2)
300
- : output;
360
+ : outputContent;
301
361
  fs.writeFileSync(outputPath, fileContent, "utf-8");
302
- console.error(`Comment written to: ${outputPath}`);
362
+ writeOutput(`Comment written to: ${outputPath}`);
303
363
  }
304
364
  else if (!options.post) {
305
365
  // Only print to stdout if not posting (posting already logs URL)
@@ -307,7 +367,7 @@ async function handleComment(options) {
307
367
  writeJsonOutput(jsonData, "comment");
308
368
  }
309
369
  else {
310
- writeOutput(output);
370
+ writeOutput(outputContent);
311
371
  }
312
372
  }
313
373
  }
@@ -354,7 +414,11 @@ export function registerCommentCommand(program) {
354
414
  });
355
415
  }
356
416
  catch (error) {
357
- console.error("Error:", error instanceof Error ? error.message : String(error));
417
+ process.stderr.write(renderError({
418
+ message: error instanceof Error ? error.message : String(error),
419
+ suggestion: "vertaa audit <url>",
420
+ exitCode: ExitCode.ERROR,
421
+ }) + "\n");
358
422
  process.exit(ExitCode.ERROR);
359
423
  }
360
424
  });