@vertaaux/cli 0.2.2 → 0.3.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 (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -2
  3. package/dist/auth/device-flow.d.ts.map +1 -1
  4. package/dist/auth/device-flow.js +46 -14
  5. package/dist/commands/audit.d.ts +2 -0
  6. package/dist/commands/audit.d.ts.map +1 -1
  7. package/dist/commands/audit.js +167 -8
  8. package/dist/commands/client.d.ts +14 -0
  9. package/dist/commands/client.d.ts.map +1 -0
  10. package/dist/commands/client.js +362 -0
  11. package/dist/commands/compare.d.ts +20 -0
  12. package/dist/commands/compare.d.ts.map +1 -0
  13. package/dist/commands/compare.js +335 -0
  14. package/dist/commands/doc.d.ts +18 -0
  15. package/dist/commands/doc.d.ts.map +1 -0
  16. package/dist/commands/doc.js +161 -0
  17. package/dist/commands/download.d.ts.map +1 -1
  18. package/dist/commands/download.js +9 -8
  19. package/dist/commands/drift.d.ts +15 -0
  20. package/dist/commands/drift.d.ts.map +1 -0
  21. package/dist/commands/drift.js +309 -0
  22. package/dist/commands/explain.d.ts +14 -33
  23. package/dist/commands/explain.d.ts.map +1 -1
  24. package/dist/commands/explain.js +277 -179
  25. package/dist/commands/fix-plan.d.ts +15 -0
  26. package/dist/commands/fix-plan.d.ts.map +1 -0
  27. package/dist/commands/fix-plan.js +182 -0
  28. package/dist/commands/patch-review.d.ts +14 -0
  29. package/dist/commands/patch-review.d.ts.map +1 -0
  30. package/dist/commands/patch-review.js +200 -0
  31. package/dist/commands/protect.d.ts +16 -0
  32. package/dist/commands/protect.d.ts.map +1 -0
  33. package/dist/commands/protect.js +323 -0
  34. package/dist/commands/release-notes.d.ts +17 -0
  35. package/dist/commands/release-notes.d.ts.map +1 -0
  36. package/dist/commands/release-notes.js +145 -0
  37. package/dist/commands/report.d.ts +15 -0
  38. package/dist/commands/report.d.ts.map +1 -0
  39. package/dist/commands/report.js +214 -0
  40. package/dist/commands/suggest.d.ts +18 -0
  41. package/dist/commands/suggest.d.ts.map +1 -0
  42. package/dist/commands/suggest.js +152 -0
  43. package/dist/commands/triage.d.ts +17 -0
  44. package/dist/commands/triage.d.ts.map +1 -0
  45. package/dist/commands/triage.js +205 -0
  46. package/dist/commands/upload.d.ts.map +1 -1
  47. package/dist/commands/upload.js +8 -7
  48. package/dist/index.js +62 -25
  49. package/dist/output/formats.d.ts.map +1 -1
  50. package/dist/output/formats.js +18 -2
  51. package/dist/output/human.d.ts +1 -10
  52. package/dist/output/human.d.ts.map +1 -1
  53. package/dist/output/human.js +26 -98
  54. package/dist/policy/sync.d.ts +67 -0
  55. package/dist/policy/sync.d.ts.map +1 -0
  56. package/dist/policy/sync.js +147 -0
  57. package/dist/prompts/command-catalog.d.ts +46 -0
  58. package/dist/prompts/command-catalog.d.ts.map +1 -0
  59. package/dist/prompts/command-catalog.js +187 -0
  60. package/dist/ui/spinner.d.ts +10 -35
  61. package/dist/ui/spinner.d.ts.map +1 -1
  62. package/dist/ui/spinner.js +11 -58
  63. package/dist/ui/table.d.ts +1 -18
  64. package/dist/ui/table.d.ts.map +1 -1
  65. package/dist/ui/table.js +56 -163
  66. package/dist/utils/ai-error.d.ts +48 -0
  67. package/dist/utils/ai-error.d.ts.map +1 -0
  68. package/dist/utils/ai-error.js +190 -0
  69. package/dist/utils/detect-env.d.ts +6 -8
  70. package/dist/utils/detect-env.d.ts.map +1 -1
  71. package/dist/utils/detect-env.js +6 -25
  72. package/dist/utils/stdin.d.ts +50 -0
  73. package/dist/utils/stdin.d.ts.map +1 -0
  74. package/dist/utils/stdin.js +93 -0
  75. package/package.json +11 -7
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ import fs from "fs";
10
10
  import path from "path";
11
11
  import { fileURLToPath } from "url";
12
12
  import { Command } from "commander";
13
+ import { setColorEnabled, shouldUseColor } from "@vertaaux/tui";
13
14
  import { showBanner, getVersion } from "./ui/banner.js";
14
15
  import { registerAuditCommand } from "./commands/audit.js";
15
16
  import { registerBaselineCommand } from "./commands/baseline.js";
@@ -22,6 +23,13 @@ import { registerUploadCommand } from "./commands/upload.js";
22
23
  import { registerDownloadCommand } from "./commands/download.js";
23
24
  import { registerPolicyCommand } from "./commands/policy.js";
24
25
  import { registerDoctorCommand } from "./commands/doctor.js";
26
+ import { registerSuggestCommand } from "./commands/suggest.js";
27
+ import { registerTriageCommand } from "./commands/triage.js";
28
+ import { registerFixPlanCommand } from "./commands/fix-plan.js";
29
+ import { registerPatchReviewCommand } from "./commands/patch-review.js";
30
+ import { registerCompareCommand } from "./commands/compare.js";
31
+ import { registerReleaseNotesCommand } from "./commands/release-notes.js";
32
+ import { registerDocCommand } from "./commands/doc.js";
25
33
  import { ExitCode } from "./utils/exit-codes.js";
26
34
  import { formatCommanderError } from "./ui/diagnostics.js";
27
35
  import { parseMode, parseTimeout, parseInterval, parseScore } from "./utils/validators.js";
@@ -625,7 +633,7 @@ async function runCompareCommand(base, urls, flags) {
625
633
  }
626
634
  printOutput(format, compare, formatCompareMarkdown(compare));
627
635
  }
628
- async function runFixCommand(base, jobId, issueId, flags) {
636
+ async function runFixCommand(base, jobId, issueId, flags, globalFlags = {}) {
629
637
  const fileContent = getString(flags, "file-content");
630
638
  const format = resolveFormat(flags);
631
639
  if (!fileContent) {
@@ -649,8 +657,12 @@ async function runFixCommand(base, jobId, issueId, flags) {
649
657
  if (!result.success) {
650
658
  process.exitCode = 1;
651
659
  }
660
+ if (globalFlags.dryRun && isTTYOutput) {
661
+ process.stderr.write("[dry-run] Showing patch preview — no changes applied.\n\n");
662
+ }
652
663
  if (format === "json") {
653
- console.log(JSON.stringify(result, null, 2));
664
+ const output = globalFlags.dryRun ? { ...result, dry_run: true } : result;
665
+ console.log(JSON.stringify(output, null, 2));
654
666
  }
655
667
  else {
656
668
  if (result.success && result.patch) {
@@ -708,7 +720,7 @@ async function runVerifyCommand(base, flags) {
708
720
  }
709
721
  }
710
722
  const BATCH_LIMIT = 10;
711
- async function runFixAllCommand(base, jobId, flags) {
723
+ async function runFixAllCommand(base, jobId, flags, globalFlags = {}) {
712
724
  const fileContent = getString(flags, "file-content");
713
725
  const autoFixOnly = getBool(flags, "auto-fix-only");
714
726
  const format = resolveFormat(flags);
@@ -741,6 +753,21 @@ async function runFixAllCommand(base, jobId, flags) {
741
753
  if (isTTYOutput) {
742
754
  process.stderr.write(" \r");
743
755
  }
756
+ // --dry-run: show what would be processed and exit
757
+ if (globalFlags.dryRun) {
758
+ process.stderr.write(`[dry-run] Would generate patches for ${issuesToProcess.length} issues:\n`);
759
+ for (const issue of issuesToProcess) {
760
+ process.stderr.write(` - ${issue.id || "unknown"}: ${issue.title || issue.description || "(no title)"}\n`);
761
+ }
762
+ if (totalSkipped > 0) {
763
+ process.stderr.write(` (${totalSkipped} additional issues skipped — batch limit ${BATCH_LIMIT})\n`);
764
+ }
765
+ process.stderr.write("\nNo patches generated. Remove --dry-run to execute.\n");
766
+ return;
767
+ }
768
+ // --yes is available for future interactive confirmation steps
769
+ // Currently fix-all runs non-interactively, but --yes suppresses any
770
+ // confirmation prompts that may be added (e.g., "Apply all N patches?")
744
771
  const results = {
745
772
  successes: [],
746
773
  failures: [],
@@ -845,6 +872,13 @@ program
845
872
  .option("-q, --quiet", "Suppress banner and non-essential output")
846
873
  .option("--no-banner", "Hide the V-mark banner")
847
874
  .option("--machine", "Strict machine-readable output (JSON stdout, diagnostics stderr)")
875
+ .option("--color", "Force color output")
876
+ .option("--no-color", "Disable color output")
877
+ .option("--dashboard", "Force live dashboard during audit --wait")
878
+ .option("--no-dashboard", "Disable live dashboard (use spinner instead)")
879
+ .option("--dry-run", "Show what would happen without executing")
880
+ .option("-y, --yes", "Auto-confirm all interactive prompts")
881
+ .option("--verbose", "Expand output with additional details")
848
882
  .configureOutput({
849
883
  outputError: (str, _write) => {
850
884
  const formatted = formatCommanderError(str);
@@ -864,9 +898,20 @@ program
864
898
  .hook("preAction", (thisCommand) => {
865
899
  const opts = thisCommand.optsWithGlobals();
866
900
  const machineMode = opts.machine || false;
901
+ // Apply color settings from flags or environment
902
+ if (opts.color === false || process.env.NO_COLOR !== undefined) {
903
+ setColorEnabled(false);
904
+ }
905
+ else if (opts.color === true || process.env.FORCE_COLOR !== undefined) {
906
+ setColorEnabled(true);
907
+ }
908
+ else {
909
+ setColorEnabled(shouldUseColor());
910
+ }
867
911
  if (machineMode) {
868
912
  opts.quiet = true;
869
913
  opts.banner = false;
914
+ setColorEnabled(false);
870
915
  }
871
916
  showBanner({
872
917
  version,
@@ -886,6 +931,13 @@ registerUploadCommand(program);
886
931
  registerDownloadCommand(program);
887
932
  registerPolicyCommand(program);
888
933
  registerDoctorCommand(program);
934
+ registerSuggestCommand(program);
935
+ registerTriageCommand(program);
936
+ registerFixPlanCommand(program);
937
+ registerPatchReviewCommand(program);
938
+ registerCompareCommand(program);
939
+ registerReleaseNotesCommand(program);
940
+ registerDocCommand(program);
889
941
  // Legacy commands using old argument parsing
890
942
  // These will be migrated in subsequent plans
891
943
  program
@@ -924,24 +976,7 @@ program
924
976
  process.exit(ExitCode.ERROR);
925
977
  }
926
978
  });
927
- program
928
- .command("compare <urlA> <urlB>")
929
- .description("Compare audits of two URLs")
930
- .option("--mode <mode>", "Audit depth: basic|standard|deep", parseMode, "basic")
931
- .option("--wait", "Wait for audits to complete")
932
- .option("--timeout <ms>", "Wait timeout in milliseconds (1-300000)", parseTimeout, 60000)
933
- .option("--interval <ms>", "Poll interval in milliseconds (1-300000)", parseInterval, 5000)
934
- .option("--fail-on-score <n>", "Exit non-zero if score below n (0-100)", parseScore)
935
- .action(async (urlA, urlB, cmdOptions) => {
936
- try {
937
- const base = resolveApiBase(cmdOptions);
938
- await runCompareCommand(base, [urlA, urlB], cmdOptions);
939
- }
940
- catch (error) {
941
- process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}\n`);
942
- process.exit(ExitCode.ERROR);
943
- }
944
- });
979
+ // compare is now registered via registerCompareCommand (Phase 60)
945
980
  program
946
981
  .command("status <jobId>")
947
982
  .description("Get status of an audit job")
@@ -973,13 +1008,14 @@ program
973
1008
  .description("Generate a fix patch for an issue")
974
1009
  .requiredOption("--issue <id>", "Issue ID to fix")
975
1010
  .requiredOption("--file-content <code>", "Source code content")
976
- .action(async (jobId, cmdOptions) => {
1011
+ .action(async (jobId, cmdOptions, command) => {
977
1012
  try {
1013
+ const globalOpts = command.optsWithGlobals();
978
1014
  const base = resolveApiBase(cmdOptions);
979
1015
  const issueId = getString(cmdOptions, "issue");
980
1016
  if (!issueId)
981
1017
  throw new Error("--issue is required");
982
- await runFixCommand(base, jobId, issueId, cmdOptions);
1018
+ await runFixCommand(base, jobId, issueId, cmdOptions, { dryRun: !!globalOpts.dryRun });
983
1019
  }
984
1020
  catch (error) {
985
1021
  process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}\n`);
@@ -991,10 +1027,11 @@ program
991
1027
  .description("Generate fix patches for all issues in an audit")
992
1028
  .requiredOption("--file-content <code>", "Source code content")
993
1029
  .option("--auto-fix-only", "Only process auto-fixable issues")
994
- .action(async (jobId, cmdOptions) => {
1030
+ .action(async (jobId, cmdOptions, command) => {
995
1031
  try {
1032
+ const globalOpts = command.optsWithGlobals();
996
1033
  const base = resolveApiBase(cmdOptions);
997
- await runFixAllCommand(base, jobId, cmdOptions);
1034
+ await runFixAllCommand(base, jobId, cmdOptions, { dryRun: !!globalOpts.dryRun, yes: !!globalOpts.yes });
998
1035
  }
999
1036
  catch (error) {
1000
1037
  process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}\n`);
@@ -1 +1 @@
1
- {"version":3,"file":"formats.d.ts","sourceRoot":"","sources":["../../src/output/formats.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAM7D,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAMzD,CAAC;AAEF,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAclF;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,WAAW,EAAE,OAAO,GACnB,MAAM,CAMR"}
1
+ {"version":3,"file":"formats.d.ts","sourceRoot":"","sources":["../../src/output/formats.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAa7D,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAazD,CAAC;AAEF,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAclF;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,WAAW,EAAE,OAAO,GACnB,MAAM,CASR"}
@@ -10,6 +10,13 @@ export const COMMAND_FORMATS = {
10
10
  explain: ["json", "human"],
11
11
  "policy-show": ["json", "yaml"],
12
12
  diff: ["json", "human"],
13
+ suggest: ["json", "human"],
14
+ triage: ["json", "human"],
15
+ "fix-plan": ["json", "human"],
16
+ "patch-review": ["json", "human"],
17
+ "release-notes": ["json", "human", "markdown"],
18
+ compare: ["json", "human"],
19
+ doc: ["json", "markdown"],
13
20
  };
14
21
  export const COMMAND_DEFAULT_FORMAT = {
15
22
  audit: "human",
@@ -17,6 +24,13 @@ export const COMMAND_DEFAULT_FORMAT = {
17
24
  explain: "human",
18
25
  "policy-show": "yaml",
19
26
  diff: "human",
27
+ suggest: "human",
28
+ triage: "human",
29
+ "fix-plan": "human",
30
+ "patch-review": "human",
31
+ "release-notes": "markdown",
32
+ compare: "human",
33
+ doc: "markdown",
20
34
  };
21
35
  export function validateFormat(command, format) {
22
36
  const allowed = COMMAND_FORMATS[command];
@@ -33,9 +47,11 @@ export function validateFormat(command, format) {
33
47
  return format;
34
48
  }
35
49
  export function resolveCommandFormat(command, explicitFormat, machineMode) {
50
+ // Treat "auto" as unset — let per-command defaults or machine mode decide
51
+ const format = explicitFormat === "auto" ? undefined : explicitFormat;
36
52
  // Machine mode defaults to JSON unless explicitly overridden
37
- if (machineMode && !explicitFormat) {
53
+ if (machineMode && !format) {
38
54
  return "json";
39
55
  }
40
- return validateFormat(command, explicitFormat);
56
+ return validateFormat(command, format);
41
57
  }
@@ -2,7 +2,7 @@
2
2
  * Human-readable output formatter for CLI.
3
3
  *
4
4
  * Outputs audit results as colorful, formatted text for terminal display.
5
- * Uses chalk for colors and cli-table3 for table formatting.
5
+ * Delegates to @vertaaux/tui for consistent rendering.
6
6
  */
7
7
  import { type FormatTableOptions } from "../ui/table.js";
8
8
  export interface AuditResult {
@@ -19,23 +19,14 @@ export interface AuditResult {
19
19
  error?: string;
20
20
  }
21
21
  export interface FormatHumanOptions {
22
- /** How to group issues: severity (default), category, route */
23
22
  groupBy?: "severity" | "category" | "route";
24
- /** Whether to show detailed scores table */
25
23
  showScores?: boolean;
26
- /** Whether to show the summary at the end */
27
24
  showSummary?: boolean;
28
- /** Maximum issues to display per group */
29
25
  maxIssuesPerGroup?: number;
30
- /** Table formatting options */
31
26
  tableOptions?: FormatTableOptions;
32
27
  }
33
28
  /**
34
29
  * Format audit result as human-readable string.
35
- *
36
- * @param result - Audit result object
37
- * @param options - Formatting options
38
- * @returns Formatted string for terminal display
39
30
  */
40
31
  export declare function formatAuditHuman(result: AuditResult, options?: FormatHumanOptions): string;
41
32
  //# sourceMappingURL=human.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"human.d.ts","sourceRoot":"","sources":["../../src/output/human.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAOL,KAAK,kBAAkB,EACxB,MAAM,gBAAgB,CAAC;AAGxB,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,UAAU,GAAG,UAAU,GAAG,OAAO,CAAC;IAC5C,4CAA4C;IAC5C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,0CAA0C;IAC1C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,+BAA+B;IAC/B,YAAY,CAAC,EAAE,kBAAkB,CAAC;CACnC;AAmRD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,WAAW,EACnB,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CAkDR"}
1
+ {"version":3,"file":"human.d.ts","sourceRoot":"","sources":["../../src/output/human.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,EAOL,KAAK,kBAAkB,EACxB,MAAM,gBAAgB,CAAC;AAExB,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,UAAU,GAAG,UAAU,GAAG,OAAO,CAAC;IAC5C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,kBAAkB,CAAC;CACnC;AAuND;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,WAAW,EACnB,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CAuCR"}
@@ -2,11 +2,10 @@
2
2
  * Human-readable output formatter for CLI.
3
3
  *
4
4
  * Outputs audit results as colorful, formatted text for terminal display.
5
- * Uses chalk for colors and cli-table3 for table formatting.
5
+ * Delegates to @vertaaux/tui for consistent rendering.
6
6
  */
7
- import chalk from "chalk";
7
+ import { text, getTerminalWidth, scoreColor, boldColor, } from "@vertaaux/tui";
8
8
  import { formatIssuesTable, formatScoresTable, formatIssueSummary, groupBySeverity, groupByCategory, } from "../ui/table.js";
9
- import { shouldUseColor, getTerminalWidth } from "../utils/detect-env.js";
10
9
  /**
11
10
  * Normalize issues from various API response formats.
12
11
  */
@@ -34,41 +33,22 @@ function getOverallScore(scores) {
34
33
  return null;
35
34
  return Math.round(numeric.reduce((a, b) => a + b, 0) / numeric.length);
36
35
  }
37
- /**
38
- * Format a horizontal rule.
39
- */
40
- function hr() {
41
- const width = Math.min(getTerminalWidth(), 80);
42
- return shouldUseColor()
43
- ? chalk.dim("-".repeat(width))
44
- : "-".repeat(width);
45
- }
46
36
  /**
47
37
  * Format the header section.
48
38
  */
49
39
  function formatHeader(result) {
50
- const useColor = shouldUseColor();
51
40
  const lines = [];
52
- // Title
53
- const title = useColor
54
- ? chalk.bold.cyan("Audit Complete")
55
- : "AUDIT COMPLETE";
56
- lines.push(title);
57
- // URL
41
+ lines.push(text.heading("Audit Complete"));
58
42
  if (result.url) {
59
- const urlLine = useColor
60
- ? chalk.dim("URL: ") + chalk.white(result.url)
61
- : `URL: ${result.url}`;
62
- lines.push(urlLine);
43
+ lines.push(`${text.dim("URL:")} ${text.link(result.url)}`);
63
44
  }
64
- // Mode and Job ID
65
45
  const meta = [];
66
46
  if (result.mode)
67
47
  meta.push(`Mode: ${result.mode}`);
68
48
  if (result.job_id)
69
49
  meta.push(`Job: ${result.job_id}`);
70
50
  if (meta.length) {
71
- lines.push(useColor ? chalk.dim(meta.join(" | ")) : meta.join(" | "));
51
+ lines.push(text.dim(meta.join(" | ")));
72
52
  }
73
53
  return lines.join("\n");
74
54
  }
@@ -76,34 +56,20 @@ function formatHeader(result) {
76
56
  * Format the scores section.
77
57
  */
78
58
  function formatScoresSection(scores) {
79
- const useColor = shouldUseColor();
80
59
  const lines = [];
81
60
  lines.push("");
82
- lines.push(useColor ? chalk.bold("Scores") : "SCORES");
83
- lines.push(hr());
61
+ lines.push(text.bold("Scores"));
62
+ lines.push(text.hr(Math.min(getTerminalWidth(), 80)));
84
63
  if (!scores || Object.keys(scores).length === 0) {
85
- lines.push(useColor ? chalk.dim("No scores available.") : "No scores available.");
64
+ lines.push(text.dim("No scores available."));
86
65
  return lines.join("\n");
87
66
  }
88
- // Overall score with color coding
89
67
  const overall = getOverallScore(scores);
90
68
  if (overall !== null) {
91
- let scoreDisplay = String(overall);
92
- if (useColor) {
93
- if (overall >= 90) {
94
- scoreDisplay = chalk.green.bold(scoreDisplay);
95
- }
96
- else if (overall >= 70) {
97
- scoreDisplay = chalk.yellow.bold(scoreDisplay);
98
- }
99
- else {
100
- scoreDisplay = chalk.red.bold(scoreDisplay);
101
- }
102
- }
103
- lines.push(`Overall: ${scoreDisplay}/100`);
69
+ const scoreStr = boldColor(String(overall), scoreColor(overall));
70
+ lines.push(`Overall: ${scoreStr}/100`);
104
71
  lines.push("");
105
72
  }
106
- // Detailed scores table
107
73
  const numericScores = {};
108
74
  for (const [key, value] of Object.entries(scores)) {
109
75
  if (typeof value === "number" && key !== "overall") {
@@ -119,40 +85,29 @@ function formatScoresSection(scores) {
119
85
  * Format issues section grouped by severity.
120
86
  */
121
87
  function formatIssuesBySeverity(issues, maxPerGroup, tableOptions) {
122
- const useColor = shouldUseColor();
123
88
  const lines = [];
124
89
  lines.push("");
125
- lines.push(useColor ? chalk.bold("Issues") : "ISSUES");
126
- lines.push(hr());
90
+ lines.push(text.bold("Issues"));
91
+ lines.push(text.hr(Math.min(getTerminalWidth(), 80)));
127
92
  if (issues.length === 0) {
128
- lines.push(useColor ? chalk.green("No issues found!") : "No issues found!");
93
+ lines.push(text.success("No issues found!"));
129
94
  return lines.join("\n");
130
95
  }
131
- // Group by severity
132
96
  const groups = groupBySeverity(issues);
133
- const severityOrder = ["critical", "error", "serious", "warning", "minor", "moderate", "info"];
134
- for (const severity of severityOrder) {
135
- const groupIssues = groups.get(severity);
97
+ const order = ["critical", "error", "serious", "warning", "minor", "moderate", "info"];
98
+ for (const sev of order) {
99
+ const groupIssues = groups.get(sev);
136
100
  if (!groupIssues || groupIssues.length === 0)
137
101
  continue;
138
102
  const displayIssues = maxPerGroup
139
103
  ? groupIssues.slice(0, maxPerGroup)
140
104
  : groupIssues;
141
- // Section header
142
- const label = severity.toUpperCase();
143
- const count = groupIssues.length;
144
- const header = useColor
145
- ? chalk.bold(`${label} (${count})`)
146
- : `${label} (${count})`;
147
105
  lines.push("");
148
- lines.push(header);
149
- // Issues table for this severity
106
+ lines.push(text.bold(`${text.severityBadge(sev)} (${groupIssues.length})`));
150
107
  lines.push(formatIssuesTable(displayIssues, tableOptions));
151
- // Show truncation message if needed
152
108
  if (maxPerGroup && groupIssues.length > maxPerGroup) {
153
109
  const remaining = groupIssues.length - maxPerGroup;
154
- const msg = `...and ${remaining} more ${severity} issues`;
155
- lines.push(useColor ? chalk.dim(msg) : msg);
110
+ lines.push(text.dim(`...and ${remaining} more ${sev} issues`));
156
111
  }
157
112
  }
158
113
  return lines.join("\n");
@@ -161,34 +116,25 @@ function formatIssuesBySeverity(issues, maxPerGroup, tableOptions) {
161
116
  * Format issues section grouped by category.
162
117
  */
163
118
  function formatIssuesByCategory(issues, maxPerGroup, tableOptions) {
164
- const useColor = shouldUseColor();
165
119
  const lines = [];
166
120
  lines.push("");
167
- lines.push(useColor ? chalk.bold("Issues by Category") : "ISSUES BY CATEGORY");
168
- lines.push(hr());
121
+ lines.push(text.bold("Issues by Category"));
122
+ lines.push(text.hr(Math.min(getTerminalWidth(), 80)));
169
123
  if (issues.length === 0) {
170
- lines.push(useColor ? chalk.green("No issues found!") : "No issues found!");
124
+ lines.push(text.success("No issues found!"));
171
125
  return lines.join("\n");
172
126
  }
173
- // Group by category
174
127
  const groups = groupByCategory(issues);
175
128
  for (const [category, groupIssues] of groups) {
176
129
  const displayIssues = maxPerGroup
177
130
  ? groupIssues.slice(0, maxPerGroup)
178
131
  : groupIssues;
179
- // Section header
180
- const header = useColor
181
- ? chalk.bold(`${category} (${groupIssues.length})`)
182
- : `${category} (${groupIssues.length})`;
183
132
  lines.push("");
184
- lines.push(header);
185
- // Issues table for this category
133
+ lines.push(text.bold(`${category} (${groupIssues.length})`));
186
134
  lines.push(formatIssuesTable(displayIssues, tableOptions));
187
- // Show truncation message if needed
188
135
  if (maxPerGroup && groupIssues.length > maxPerGroup) {
189
136
  const remaining = groupIssues.length - maxPerGroup;
190
- const msg = `...and ${remaining} more issues`;
191
- lines.push(useColor ? chalk.dim(msg) : msg);
137
+ lines.push(text.dim(`...and ${remaining} more issues`));
192
138
  }
193
139
  }
194
140
  return lines.join("\n");
@@ -197,14 +143,11 @@ function formatIssuesByCategory(issues, maxPerGroup, tableOptions) {
197
143
  * Format the summary section.
198
144
  */
199
145
  function formatSummary(issues, scores, threshold) {
200
- const useColor = shouldUseColor();
201
146
  const lines = [];
202
147
  lines.push("");
203
- lines.push(hr());
204
- // Summary line
148
+ lines.push(text.hr(Math.min(getTerminalWidth(), 80)));
205
149
  const summary = formatIssueSummary(issues);
206
150
  lines.push(summary);
207
- // Pass/fail status
208
151
  const overall = getOverallScore(scores);
209
152
  const hasErrors = issues.some((i) => i.severity?.toLowerCase() === "critical" || i.severity?.toLowerCase() === "error");
210
153
  let status;
@@ -223,50 +166,35 @@ function formatSummary(issues, scores, threshold) {
223
166
  passed = !hasErrors;
224
167
  status = passed ? "PASSED" : "FAILED";
225
168
  }
226
- if (useColor) {
227
- status = passed ? chalk.green.bold(status) : chalk.red.bold(status);
228
- }
169
+ status = passed ? text.success(status) : text.error(status);
229
170
  lines.push(status);
230
171
  return lines.join("\n");
231
172
  }
232
173
  /**
233
174
  * Format audit result as human-readable string.
234
- *
235
- * @param result - Audit result object
236
- * @param options - Formatting options
237
- * @returns Formatted string for terminal display
238
175
  */
239
176
  export function formatAuditHuman(result, options = {}) {
240
177
  const { groupBy = "severity", showScores = true, showSummary = true, maxIssuesPerGroup = 10, tableOptions, } = options;
241
178
  const sections = [];
242
- // Header
243
179
  sections.push(formatHeader(result));
244
- // Handle non-completed audits
245
180
  if (result.status !== "completed") {
246
- const useColor = shouldUseColor();
247
181
  sections.push("");
248
- sections.push(useColor
249
- ? chalk.yellow(`Status: ${result.status || "unknown"}`)
250
- : `Status: ${result.status || "unknown"}`);
182
+ sections.push(text.warning(`Status: ${result.status || "unknown"}`));
251
183
  if (typeof result.progress === "number") {
252
184
  sections.push(`Progress: ${result.progress}%`);
253
185
  }
254
186
  return sections.join("\n");
255
187
  }
256
- // Scores section
257
188
  if (showScores) {
258
189
  sections.push(formatScoresSection(result.scores));
259
190
  }
260
- // Issues section
261
191
  const issues = normalizeIssues(result.issues);
262
192
  if (groupBy === "category") {
263
193
  sections.push(formatIssuesByCategory(issues, maxIssuesPerGroup, tableOptions));
264
194
  }
265
195
  else {
266
- // Default: group by severity
267
196
  sections.push(formatIssuesBySeverity(issues, maxIssuesPerGroup, tableOptions));
268
197
  }
269
- // Summary
270
198
  if (showSummary) {
271
199
  sections.push(formatSummary(issues, result.scores));
272
200
  }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Policy push/pull sync module for VertaaUX CLI.
3
+ *
4
+ * Provides bidirectional sync between local policy files and the server API.
5
+ * Uses the established CLI API patterns (resolveApiBase, getApiKey, apiRequest).
6
+ *
7
+ * Implements POL-10: Policy push/pull sync.
8
+ */
9
+ import type { PolicyFile } from "@vertaaux/quality-control";
10
+ import type { VertaauxConfig } from "../config/schema.js";
11
+ export interface SyncConfig {
12
+ apiBase: string;
13
+ apiKey: string;
14
+ }
15
+ /**
16
+ * Build sync config from CLI config and environment.
17
+ * Uses the established CLI pattern: resolveApiBase + getApiKey.
18
+ */
19
+ export declare function buildSyncConfig(cliConfig?: VertaauxConfig): SyncConfig;
20
+ export interface PushResult {
21
+ success: boolean;
22
+ policyId: string;
23
+ version: number;
24
+ created: boolean;
25
+ error?: string;
26
+ }
27
+ export interface PullResult {
28
+ success: boolean;
29
+ policy?: PolicyFile;
30
+ policyId?: string;
31
+ version?: number;
32
+ name?: string;
33
+ error?: string;
34
+ }
35
+ /**
36
+ * Push a local policy file to the server.
37
+ *
38
+ * If a policy with the given name already exists, updates it (with optional
39
+ * optimistic locking via expectedVersion). Otherwise creates a new policy.
40
+ *
41
+ * @param policy - The policy content to push
42
+ * @param config - API connection config
43
+ * @param options - Push options (name, description, force, etc.)
44
+ * @returns Push result with success status, policy ID, and version
45
+ */
46
+ export declare function pushPolicy(policy: PolicyFile, config: SyncConfig, options: {
47
+ name: string;
48
+ description?: string;
49
+ changeMessage?: string;
50
+ expectedVersion?: number;
51
+ force?: boolean;
52
+ }): Promise<PushResult>;
53
+ /**
54
+ * Pull a policy from the server.
55
+ *
56
+ * Finds the policy by ID, name, or defaults to the default/first policy.
57
+ * Returns the full policy content for writing to a local file.
58
+ *
59
+ * @param config - API connection config
60
+ * @param options - Pull options (policyId or policyName)
61
+ * @returns Pull result with policy content and metadata
62
+ */
63
+ export declare function pullPolicy(config: SyncConfig, options: {
64
+ policyId?: string;
65
+ policyName?: string;
66
+ }): Promise<PullResult>;
67
+ //# sourceMappingURL=sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/policy/sync.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,SAAS,CAAC,EAAE,cAAc,GAAG,UAAU,CAKtE;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA4BD;;;;;;;;;;GAUG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE;IACP,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,GACA,OAAO,CAAC,UAAU,CAAC,CA+ErB;AAED;;;;;;;;;GASG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE;IACP,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GACA,OAAO,CAAC,UAAU,CAAC,CAuDrB"}