@panguard-ai/core 0.1.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 (233) hide show
  1. package/dist/adapters/adapter-registry.d.ts +150 -0
  2. package/dist/adapters/adapter-registry.d.ts.map +1 -0
  3. package/dist/adapters/adapter-registry.js +271 -0
  4. package/dist/adapters/adapter-registry.js.map +1 -0
  5. package/dist/adapters/base-adapter.d.ts +101 -0
  6. package/dist/adapters/base-adapter.d.ts.map +1 -0
  7. package/dist/adapters/base-adapter.js +160 -0
  8. package/dist/adapters/base-adapter.js.map +1 -0
  9. package/dist/adapters/defender-adapter.d.ts +90 -0
  10. package/dist/adapters/defender-adapter.d.ts.map +1 -0
  11. package/dist/adapters/defender-adapter.js +227 -0
  12. package/dist/adapters/defender-adapter.js.map +1 -0
  13. package/dist/adapters/index.d.ts +22 -0
  14. package/dist/adapters/index.d.ts.map +1 -0
  15. package/dist/adapters/index.js +23 -0
  16. package/dist/adapters/index.js.map +1 -0
  17. package/dist/adapters/syslog-adapter.d.ts +207 -0
  18. package/dist/adapters/syslog-adapter.d.ts.map +1 -0
  19. package/dist/adapters/syslog-adapter.js +432 -0
  20. package/dist/adapters/syslog-adapter.js.map +1 -0
  21. package/dist/adapters/types.d.ts +135 -0
  22. package/dist/adapters/types.d.ts.map +1 -0
  23. package/dist/adapters/types.js +13 -0
  24. package/dist/adapters/types.js.map +1 -0
  25. package/dist/adapters/wazuh-adapter.d.ts +120 -0
  26. package/dist/adapters/wazuh-adapter.d.ts.map +1 -0
  27. package/dist/adapters/wazuh-adapter.js +266 -0
  28. package/dist/adapters/wazuh-adapter.js.map +1 -0
  29. package/dist/ai/claude-provider.d.ts +66 -0
  30. package/dist/ai/claude-provider.d.ts.map +1 -0
  31. package/dist/ai/claude-provider.js +166 -0
  32. package/dist/ai/claude-provider.js.map +1 -0
  33. package/dist/ai/funnel-router.d.ts +75 -0
  34. package/dist/ai/funnel-router.d.ts.map +1 -0
  35. package/dist/ai/funnel-router.js +173 -0
  36. package/dist/ai/funnel-router.js.map +1 -0
  37. package/dist/ai/index.d.ts +77 -0
  38. package/dist/ai/index.d.ts.map +1 -0
  39. package/dist/ai/index.js +95 -0
  40. package/dist/ai/index.js.map +1 -0
  41. package/dist/ai/ollama-provider.d.ts +73 -0
  42. package/dist/ai/ollama-provider.d.ts.map +1 -0
  43. package/dist/ai/ollama-provider.js +200 -0
  44. package/dist/ai/ollama-provider.js.map +1 -0
  45. package/dist/ai/openai-provider.d.ts +70 -0
  46. package/dist/ai/openai-provider.d.ts.map +1 -0
  47. package/dist/ai/openai-provider.js +175 -0
  48. package/dist/ai/openai-provider.js.map +1 -0
  49. package/dist/ai/prompts/event-classifier.d.ts +25 -0
  50. package/dist/ai/prompts/event-classifier.d.ts.map +1 -0
  51. package/dist/ai/prompts/event-classifier.js +94 -0
  52. package/dist/ai/prompts/event-classifier.js.map +1 -0
  53. package/dist/ai/prompts/index.d.ts +13 -0
  54. package/dist/ai/prompts/index.d.ts.map +1 -0
  55. package/dist/ai/prompts/index.js +13 -0
  56. package/dist/ai/prompts/index.js.map +1 -0
  57. package/dist/ai/prompts/report-generator.d.ts +25 -0
  58. package/dist/ai/prompts/report-generator.d.ts.map +1 -0
  59. package/dist/ai/prompts/report-generator.js +131 -0
  60. package/dist/ai/prompts/report-generator.js.map +1 -0
  61. package/dist/ai/prompts/threat-analyzer.d.ts +26 -0
  62. package/dist/ai/prompts/threat-analyzer.d.ts.map +1 -0
  63. package/dist/ai/prompts/threat-analyzer.js +75 -0
  64. package/dist/ai/prompts/threat-analyzer.js.map +1 -0
  65. package/dist/ai/provider-base.d.ts +100 -0
  66. package/dist/ai/provider-base.d.ts.map +1 -0
  67. package/dist/ai/provider-base.js +166 -0
  68. package/dist/ai/provider-base.js.map +1 -0
  69. package/dist/ai/response-parser.d.ts +36 -0
  70. package/dist/ai/response-parser.d.ts.map +1 -0
  71. package/dist/ai/response-parser.js +195 -0
  72. package/dist/ai/response-parser.js.map +1 -0
  73. package/dist/ai/token-tracker.d.ts +72 -0
  74. package/dist/ai/token-tracker.d.ts.map +1 -0
  75. package/dist/ai/token-tracker.js +145 -0
  76. package/dist/ai/token-tracker.js.map +1 -0
  77. package/dist/ai/types.d.ts +138 -0
  78. package/dist/ai/types.d.ts.map +1 -0
  79. package/dist/ai/types.js +12 -0
  80. package/dist/ai/types.js.map +1 -0
  81. package/dist/cli/index.d.ts +146 -0
  82. package/dist/cli/index.d.ts.map +1 -0
  83. package/dist/cli/index.js +515 -0
  84. package/dist/cli/index.js.map +1 -0
  85. package/dist/cli/prompts.d.ts +58 -0
  86. package/dist/cli/prompts.d.ts.map +1 -0
  87. package/dist/cli/prompts.js +327 -0
  88. package/dist/cli/prompts.js.map +1 -0
  89. package/dist/cli/wizard.d.ts +58 -0
  90. package/dist/cli/wizard.d.ts.map +1 -0
  91. package/dist/cli/wizard.js +200 -0
  92. package/dist/cli/wizard.js.map +1 -0
  93. package/dist/discovery/firewall-checker.d.ts +28 -0
  94. package/dist/discovery/firewall-checker.d.ts.map +1 -0
  95. package/dist/discovery/firewall-checker.js +379 -0
  96. package/dist/discovery/firewall-checker.js.map +1 -0
  97. package/dist/discovery/index.d.ts +23 -0
  98. package/dist/discovery/index.d.ts.map +1 -0
  99. package/dist/discovery/index.js +29 -0
  100. package/dist/discovery/index.js.map +1 -0
  101. package/dist/discovery/network-scanner.d.ts +60 -0
  102. package/dist/discovery/network-scanner.d.ts.map +1 -0
  103. package/dist/discovery/network-scanner.js +640 -0
  104. package/dist/discovery/network-scanner.js.map +1 -0
  105. package/dist/discovery/os-detector.d.ts +24 -0
  106. package/dist/discovery/os-detector.d.ts.map +1 -0
  107. package/dist/discovery/os-detector.js +253 -0
  108. package/dist/discovery/os-detector.js.map +1 -0
  109. package/dist/discovery/osquery-provider.d.ts +127 -0
  110. package/dist/discovery/osquery-provider.d.ts.map +1 -0
  111. package/dist/discovery/osquery-provider.js +214 -0
  112. package/dist/discovery/osquery-provider.js.map +1 -0
  113. package/dist/discovery/risk-scorer.d.ts +66 -0
  114. package/dist/discovery/risk-scorer.d.ts.map +1 -0
  115. package/dist/discovery/risk-scorer.js +294 -0
  116. package/dist/discovery/risk-scorer.js.map +1 -0
  117. package/dist/discovery/security-tools.d.ts +31 -0
  118. package/dist/discovery/security-tools.d.ts.map +1 -0
  119. package/dist/discovery/security-tools.js +346 -0
  120. package/dist/discovery/security-tools.js.map +1 -0
  121. package/dist/discovery/service-detector.d.ts +28 -0
  122. package/dist/discovery/service-detector.d.ts.map +1 -0
  123. package/dist/discovery/service-detector.js +300 -0
  124. package/dist/discovery/service-detector.js.map +1 -0
  125. package/dist/discovery/types.d.ts +502 -0
  126. package/dist/discovery/types.d.ts.map +1 -0
  127. package/dist/discovery/types.js +12 -0
  128. package/dist/discovery/types.js.map +1 -0
  129. package/dist/discovery/user-auditor.d.ts +28 -0
  130. package/dist/discovery/user-auditor.d.ts.map +1 -0
  131. package/dist/discovery/user-auditor.js +385 -0
  132. package/dist/discovery/user-auditor.js.map +1 -0
  133. package/dist/i18n/config.d.ts +45 -0
  134. package/dist/i18n/config.d.ts.map +1 -0
  135. package/dist/i18n/config.js +135 -0
  136. package/dist/i18n/config.js.map +1 -0
  137. package/dist/i18n/index.d.ts +8 -0
  138. package/dist/i18n/index.d.ts.map +1 -0
  139. package/dist/i18n/index.js +8 -0
  140. package/dist/i18n/index.js.map +1 -0
  141. package/dist/index.d.ts +31 -0
  142. package/dist/index.d.ts.map +1 -0
  143. package/dist/index.js +31 -0
  144. package/dist/index.js.map +1 -0
  145. package/dist/monitor/event-normalizer.d.ts +102 -0
  146. package/dist/monitor/event-normalizer.d.ts.map +1 -0
  147. package/dist/monitor/event-normalizer.js +195 -0
  148. package/dist/monitor/event-normalizer.js.map +1 -0
  149. package/dist/monitor/file-monitor.d.ts +90 -0
  150. package/dist/monitor/file-monitor.d.ts.map +1 -0
  151. package/dist/monitor/file-monitor.js +222 -0
  152. package/dist/monitor/file-monitor.js.map +1 -0
  153. package/dist/monitor/index.d.ts +147 -0
  154. package/dist/monitor/index.d.ts.map +1 -0
  155. package/dist/monitor/index.js +293 -0
  156. package/dist/monitor/index.js.map +1 -0
  157. package/dist/monitor/log-monitor.d.ts +102 -0
  158. package/dist/monitor/log-monitor.d.ts.map +1 -0
  159. package/dist/monitor/log-monitor.js +245 -0
  160. package/dist/monitor/log-monitor.js.map +1 -0
  161. package/dist/monitor/network-monitor.d.ts +103 -0
  162. package/dist/monitor/network-monitor.d.ts.map +1 -0
  163. package/dist/monitor/network-monitor.js +336 -0
  164. package/dist/monitor/network-monitor.js.map +1 -0
  165. package/dist/monitor/process-monitor.d.ts +108 -0
  166. package/dist/monitor/process-monitor.d.ts.map +1 -0
  167. package/dist/monitor/process-monitor.js +245 -0
  168. package/dist/monitor/process-monitor.js.map +1 -0
  169. package/dist/monitor/threat-intel-feeds.d.ts +141 -0
  170. package/dist/monitor/threat-intel-feeds.d.ts.map +1 -0
  171. package/dist/monitor/threat-intel-feeds.js +430 -0
  172. package/dist/monitor/threat-intel-feeds.js.map +1 -0
  173. package/dist/monitor/threat-intel.d.ts +83 -0
  174. package/dist/monitor/threat-intel.d.ts.map +1 -0
  175. package/dist/monitor/threat-intel.js +215 -0
  176. package/dist/monitor/threat-intel.js.map +1 -0
  177. package/dist/monitor/types.d.ts +65 -0
  178. package/dist/monitor/types.d.ts.map +1 -0
  179. package/dist/monitor/types.js +20 -0
  180. package/dist/monitor/types.js.map +1 -0
  181. package/dist/rules/index.d.ts +115 -0
  182. package/dist/rules/index.d.ts.map +1 -0
  183. package/dist/rules/index.js +244 -0
  184. package/dist/rules/index.js.map +1 -0
  185. package/dist/rules/rule-loader.d.ts +54 -0
  186. package/dist/rules/rule-loader.d.ts.map +1 -0
  187. package/dist/rules/rule-loader.js +167 -0
  188. package/dist/rules/rule-loader.js.map +1 -0
  189. package/dist/rules/sigma-matcher.d.ts +40 -0
  190. package/dist/rules/sigma-matcher.d.ts.map +1 -0
  191. package/dist/rules/sigma-matcher.js +447 -0
  192. package/dist/rules/sigma-matcher.js.map +1 -0
  193. package/dist/rules/sigma-parser.d.ts +36 -0
  194. package/dist/rules/sigma-parser.d.ts.map +1 -0
  195. package/dist/rules/sigma-parser.js +180 -0
  196. package/dist/rules/sigma-parser.js.map +1 -0
  197. package/dist/rules/types.d.ts +112 -0
  198. package/dist/rules/types.d.ts.map +1 -0
  199. package/dist/rules/types.js +11 -0
  200. package/dist/rules/types.js.map +1 -0
  201. package/dist/rules/yara-scanner.d.ts +103 -0
  202. package/dist/rules/yara-scanner.d.ts.map +1 -0
  203. package/dist/rules/yara-scanner.js +421 -0
  204. package/dist/rules/yara-scanner.js.map +1 -0
  205. package/dist/scoring/achievements.d.ts +76 -0
  206. package/dist/scoring/achievements.d.ts.map +1 -0
  207. package/dist/scoring/achievements.js +211 -0
  208. package/dist/scoring/achievements.js.map +1 -0
  209. package/dist/scoring/index.d.ts +3 -0
  210. package/dist/scoring/index.d.ts.map +1 -0
  211. package/dist/scoring/index.js +3 -0
  212. package/dist/scoring/index.js.map +1 -0
  213. package/dist/scoring/security-score.d.ts +60 -0
  214. package/dist/scoring/security-score.d.ts.map +1 -0
  215. package/dist/scoring/security-score.js +211 -0
  216. package/dist/scoring/security-score.js.map +1 -0
  217. package/dist/types.d.ts +71 -0
  218. package/dist/types.d.ts.map +1 -0
  219. package/dist/types.js +8 -0
  220. package/dist/types.js.map +1 -0
  221. package/dist/utils/index.d.ts +10 -0
  222. package/dist/utils/index.d.ts.map +1 -0
  223. package/dist/utils/index.js +9 -0
  224. package/dist/utils/index.js.map +1 -0
  225. package/dist/utils/logger.d.ts +38 -0
  226. package/dist/utils/logger.d.ts.map +1 -0
  227. package/dist/utils/logger.js +71 -0
  228. package/dist/utils/logger.js.map +1 -0
  229. package/dist/utils/validation.d.ts +35 -0
  230. package/dist/utils/validation.d.ts.map +1 -0
  231. package/dist/utils/validation.js +56 -0
  232. package/dist/utils/validation.js.map +1 -0
  233. package/package.json +60 -0
@@ -0,0 +1,146 @@
1
+ /**
2
+ * CLI Rendering Utilities - On-brand terminal output for Panguard AI
3
+ * CLI 渲染工具 - Panguard AI 品牌終端輸出
4
+ *
5
+ * Zero external dependencies. Uses 24-bit ANSI colors matching brand tokens.
6
+ * Brand colors: Sage Green #8B9A8E, Cream #F5F1E8, Charcoal #1A1614
7
+ * Status: Safe #2ED573, Caution #FBBF24, Alert #FF6B35, Critical #EF4444
8
+ *
9
+ * @module @panguard-ai/core/cli
10
+ */
11
+ type StyleFn = (text: string) => string;
12
+ /** Brand-matched ANSI color/style helpers */
13
+ export declare const c: {
14
+ sage: StyleFn;
15
+ cream: StyleFn;
16
+ safe: StyleFn;
17
+ caution: StyleFn;
18
+ alert: StyleFn;
19
+ critical: StyleFn;
20
+ dim: StyleFn;
21
+ bold: StyleFn;
22
+ italic: StyleFn;
23
+ underline: StyleFn;
24
+ muted: StyleFn;
25
+ brand: (text: string) => string;
26
+ heading: (text: string) => string;
27
+ success: (text: string) => string;
28
+ error: (text: string) => string;
29
+ warn: (text: string) => string;
30
+ info: (text: string) => string;
31
+ red: StyleFn;
32
+ green: StyleFn;
33
+ yellow: StyleFn;
34
+ blue: StyleFn;
35
+ magenta: StyleFn;
36
+ cyan: StyleFn;
37
+ white: StyleFn;
38
+ gray: StyleFn;
39
+ brightRed: StyleFn;
40
+ brightGreen: StyleFn;
41
+ brightYellow: StyleFn;
42
+ brightBlue: StyleFn;
43
+ brightMagenta: StyleFn;
44
+ brightCyan: StyleFn;
45
+ bgRed: StyleFn;
46
+ bgGreen: StyleFn;
47
+ bgYellow: StyleFn;
48
+ bgBlue: StyleFn;
49
+ };
50
+ /** Brand status indicators (matching CLI mockup v1) */
51
+ export declare const symbols: {
52
+ pass: string;
53
+ fail: string;
54
+ warn: string;
55
+ info: string;
56
+ arrow: string;
57
+ scan: string;
58
+ shield: string;
59
+ bullet: string;
60
+ dot: string;
61
+ };
62
+ /** Map severity to brand status colors */
63
+ export declare function colorSeverity(severity: string): string;
64
+ /** Map score to brand color */
65
+ export declare function colorScore(score: number): string;
66
+ /** Map grade to brand color */
67
+ export declare function colorGrade(grade: string): string;
68
+ /**
69
+ * Panguard AI CLI banner — matching brand mockup v1
70
+ * Logo format: PANGUARD [shield] AI (per brand spec)
71
+ */
72
+ export declare function banner(): string;
73
+ /** Compact header line */
74
+ export declare function header(subtitle?: string): string;
75
+ /** Section divider with optional centered label */
76
+ export declare function divider(label?: string): string;
77
+ export declare class Spinner {
78
+ private frameIndex;
79
+ private timer;
80
+ private message;
81
+ constructor(message: string);
82
+ start(): this;
83
+ update(message: string): void;
84
+ succeed(message?: string): void;
85
+ fail(message?: string): void;
86
+ warn(message?: string): void;
87
+ stop(): void;
88
+ }
89
+ /** Create and start a spinner */
90
+ export declare function spinner(message: string): Spinner;
91
+ export interface ProgressBarOptions {
92
+ total: number;
93
+ width?: number;
94
+ label?: string;
95
+ }
96
+ export declare class ProgressBar {
97
+ private current;
98
+ private total;
99
+ private width;
100
+ private label;
101
+ private startTime;
102
+ constructor(options: ProgressBarOptions);
103
+ update(current: number, label?: string): void;
104
+ increment(label?: string): void;
105
+ complete(label?: string): void;
106
+ private render;
107
+ }
108
+ /** Create a progress bar */
109
+ export declare function progressBar(options: ProgressBarOptions): ProgressBar;
110
+ export interface TableColumn {
111
+ header: string;
112
+ key: string;
113
+ width?: number;
114
+ align?: 'left' | 'right' | 'center';
115
+ color?: (value: string) => string;
116
+ }
117
+ /** Render a formatted table */
118
+ export declare function table(columns: TableColumn[], rows: Record<string, string>[]): string;
119
+ export interface BoxOptions {
120
+ title?: string;
121
+ padding?: number;
122
+ borderColor?: (text: string) => string;
123
+ width?: number;
124
+ }
125
+ /** Draw a bordered box around text */
126
+ export declare function box(content: string, options?: BoxOptions): string;
127
+ /** Render security score display (matching mockup status panel) */
128
+ export declare function scoreDisplay(score: number, grade: string, trend?: string): string;
129
+ export interface StatusItem {
130
+ label: string;
131
+ value: string;
132
+ status?: 'safe' | 'caution' | 'alert' | 'critical';
133
+ }
134
+ /** Render a status panel like the mockup */
135
+ export declare function statusPanel(title: string, items: StatusItem[]): string;
136
+ /** Strip ANSI escape codes from a string */
137
+ export declare function stripAnsi(text: string): string;
138
+ /** Format milliseconds as human-readable duration */
139
+ export declare function formatDuration(ms: number): string;
140
+ /** Format a timestamp as relative time */
141
+ export declare function timeAgo(date: Date | string): string;
142
+ export { visLen, promptSelect, promptText, promptConfirm } from './prompts.js';
143
+ export type { SelectOption, SelectConfig, TextConfig, ConfirmConfig } from './prompts.js';
144
+ export { WizardEngine } from './wizard.js';
145
+ export type { WizardStep, WizardAnswers } from './wizard.js';
146
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,515 @@
1
+ /**
2
+ * CLI Rendering Utilities - On-brand terminal output for Panguard AI
3
+ * CLI 渲染工具 - Panguard AI 品牌終端輸出
4
+ *
5
+ * Zero external dependencies. Uses 24-bit ANSI colors matching brand tokens.
6
+ * Brand colors: Sage Green #8B9A8E, Cream #F5F1E8, Charcoal #1A1614
7
+ * Status: Safe #2ED573, Caution #FBBF24, Alert #FF6B35, Critical #EF4444
8
+ *
9
+ * @module @panguard-ai/core/cli
10
+ */
11
+ // ============================================================
12
+ // Color System — Brand Tokens
13
+ // ============================================================
14
+ const isColorSupported = () => {
15
+ if (process.env['NO_COLOR'] !== undefined)
16
+ return false;
17
+ if (process.env['FORCE_COLOR'] !== undefined)
18
+ return true;
19
+ return process.stdout.isTTY === true;
20
+ };
21
+ const ESC = '\x1b[';
22
+ const RESET = `${ESC}0m`;
23
+ /** 24-bit RGB color */
24
+ function rgb(r, g, b) {
25
+ return `${ESC}38;2;${r};${g};${b}m`;
26
+ }
27
+ /** 24-bit RGB background */
28
+ function bgRgb(r, g, b) {
29
+ return `${ESC}48;2;${r};${g};${b}m`;
30
+ }
31
+ // Brand palette
32
+ const palette = {
33
+ sage: rgb(139, 154, 142), // #8B9A8E — primary brand
34
+ charcoal: rgb(26, 22, 20), // #1A1614 — backgrounds
35
+ cream: rgb(245, 241, 232), // #F5F1E8 — primary text
36
+ safe: rgb(46, 213, 115), // #2ED573 — safe/protected
37
+ caution: rgb(251, 191, 36), // #FBBF24 — warning
38
+ alert: rgb(255, 107, 53), // #FF6B35 — alert
39
+ critical: rgb(239, 68, 68), // #EF4444 — critical/danger
40
+ dim: rgb(120, 120, 120), // muted text
41
+ white: rgb(255, 255, 255), // bright white
42
+ bgSafe: bgRgb(46, 213, 115),
43
+ bgCaution: bgRgb(251, 191, 36),
44
+ bgAlert: bgRgb(255, 107, 53),
45
+ bgCritical: bgRgb(239, 68, 68),
46
+ };
47
+ const BOLD = `${ESC}1m`;
48
+ const DIM = `${ESC}2m`;
49
+ const ITALIC = `${ESC}3m`;
50
+ const UNDERLINE = `${ESC}4m`;
51
+ function wrap(code) {
52
+ return (text) => (isColorSupported() ? `${code}${text}${RESET}` : text);
53
+ }
54
+ /** Brand-matched ANSI color/style helpers */
55
+ export const c = {
56
+ // Brand colors
57
+ sage: wrap(palette.sage),
58
+ cream: wrap(palette.cream),
59
+ safe: wrap(palette.safe),
60
+ caution: wrap(palette.caution),
61
+ alert: wrap(palette.alert),
62
+ critical: wrap(palette.critical),
63
+ dim: wrap(palette.dim),
64
+ // Basic styles
65
+ bold: wrap(BOLD),
66
+ italic: wrap(ITALIC),
67
+ underline: wrap(UNDERLINE),
68
+ muted: wrap(DIM),
69
+ // Semantic styles
70
+ brand: (text) => wrap(BOLD + palette.sage)(text),
71
+ heading: (text) => wrap(BOLD + palette.cream)(text),
72
+ success: (text) => wrap(BOLD + palette.safe)(text),
73
+ error: (text) => wrap(BOLD + palette.critical)(text),
74
+ warn: (text) => wrap(BOLD + palette.caution)(text),
75
+ info: (text) => wrap(BOLD + palette.sage)(text),
76
+ // Fallback for standard terminal colors
77
+ red: wrap(`${ESC}31m`),
78
+ green: wrap(`${ESC}32m`),
79
+ yellow: wrap(`${ESC}33m`),
80
+ blue: wrap(`${ESC}34m`),
81
+ magenta: wrap(`${ESC}35m`),
82
+ cyan: wrap(`${ESC}36m`),
83
+ white: wrap(`${ESC}37m`),
84
+ gray: wrap(`${ESC}90m`),
85
+ brightRed: wrap(palette.critical),
86
+ brightGreen: wrap(palette.safe),
87
+ brightYellow: wrap(palette.caution),
88
+ brightBlue: wrap(`${ESC}94m`),
89
+ brightMagenta: wrap(`${ESC}95m`),
90
+ brightCyan: wrap(palette.sage),
91
+ bgRed: wrap(palette.bgCritical),
92
+ bgGreen: wrap(palette.bgSafe),
93
+ bgYellow: wrap(palette.bgCaution),
94
+ bgBlue: wrap(`${ESC}44m`),
95
+ };
96
+ // ============================================================
97
+ // Status Indicators — matching brand mockups
98
+ // ============================================================
99
+ /** Brand status indicators (matching CLI mockup v1) */
100
+ export const symbols = {
101
+ pass: isColorSupported() ? `${palette.safe}${BOLD}[✓]${RESET}` : '[OK]',
102
+ fail: isColorSupported() ? `${palette.critical}${BOLD}[✗]${RESET}` : '[!!]',
103
+ warn: isColorSupported() ? `${palette.caution}${BOLD}[!]${RESET}` : '[!]',
104
+ info: isColorSupported() ? `${palette.sage}${BOLD}[i]${RESET}` : '[i]',
105
+ arrow: isColorSupported() ? `${palette.sage}[→]${RESET}` : '[->]',
106
+ scan: isColorSupported() ? `${palette.sage}${BOLD}[⚡]${RESET}` : '[~]',
107
+ shield: isColorSupported() ? `${palette.sage}${BOLD}[▣]${RESET}` : '[#]',
108
+ bullet: isColorSupported() ? `${palette.dim} ·${RESET}` : ' -',
109
+ dot: isColorSupported() ? `${palette.dim} ·${RESET}` : ' .',
110
+ };
111
+ // ============================================================
112
+ // Severity & Score Colors
113
+ // ============================================================
114
+ /** Map severity to brand status colors */
115
+ export function colorSeverity(severity) {
116
+ const s = severity.toLowerCase();
117
+ switch (s) {
118
+ case 'critical':
119
+ return wrap(BOLD + palette.bgCritical + rgb(255, 255, 255))(` ${severity.toUpperCase()} `);
120
+ case 'high':
121
+ return c.critical(severity.toUpperCase());
122
+ case 'medium':
123
+ return c.caution(severity.toUpperCase());
124
+ case 'low':
125
+ return c.sage(severity.toUpperCase());
126
+ case 'info':
127
+ return c.dim(severity.toUpperCase());
128
+ default:
129
+ return severity;
130
+ }
131
+ }
132
+ /** Map score to brand color */
133
+ export function colorScore(score) {
134
+ const text = String(score);
135
+ if (score >= 80)
136
+ return c.safe(text);
137
+ if (score >= 60)
138
+ return c.caution(text);
139
+ if (score >= 40)
140
+ return c.alert(text);
141
+ return c.critical(text);
142
+ }
143
+ /** Map grade to brand color */
144
+ export function colorGrade(grade) {
145
+ switch (grade) {
146
+ case 'A':
147
+ return c.success(grade);
148
+ case 'B':
149
+ return c.safe(grade);
150
+ case 'C':
151
+ return c.caution(grade);
152
+ case 'D':
153
+ return c.alert(grade);
154
+ case 'F':
155
+ return c.critical(grade);
156
+ default:
157
+ return grade;
158
+ }
159
+ }
160
+ // ============================================================
161
+ // Banner & Header — Brand Logo
162
+ // ============================================================
163
+ /**
164
+ * Panguard AI CLI banner — matching brand mockup v1
165
+ * Logo format: PANGUARD [shield] AI (per brand spec)
166
+ */
167
+ export function banner() {
168
+ if (!isColorSupported()) {
169
+ return ['', ' PANGUARD [#] AI v0.5.0', ' AI-Powered Security Platform', ''].join('\n');
170
+ }
171
+ const shieldArt = [
172
+ `${palette.sage} _____`,
173
+ ` / \\`,
174
+ ` / ___ \\`,
175
+ ` | / \\ |`,
176
+ ` | | ${palette.safe}${BOLD}[${RESET}${palette.safe}${BOLD}✓${RESET}${palette.sage}| | |`,
177
+ ` | \\___/ |`,
178
+ ` \\ /`,
179
+ ` \\_____/${RESET}`,
180
+ ];
181
+ const titleLines = [
182
+ '',
183
+ ` ${BOLD}${palette.cream}PANGUARD ${palette.sage}[▣]${palette.cream} AI${RESET} ${palette.dim}v0.5.0${RESET}`,
184
+ ` ${palette.dim}AI-Powered Security Platform${RESET}`,
185
+ '',
186
+ ];
187
+ // Combine shield art (right) with title (left)
188
+ const combined = [];
189
+ const maxLines = Math.max(shieldArt.length, titleLines.length);
190
+ for (let i = 0; i < maxLines; i++) {
191
+ const title = titleLines[i] ?? '';
192
+ const shield = shieldArt[i] ?? '';
193
+ if (i < titleLines.length && i < shieldArt.length) {
194
+ const titleWidth = stripAnsi(title).length;
195
+ const pad = Math.max(0, 35 - titleWidth);
196
+ combined.push(title + ' '.repeat(pad) + shield);
197
+ }
198
+ else if (i < titleLines.length) {
199
+ combined.push(title);
200
+ }
201
+ else {
202
+ combined.push(' '.repeat(35) + shield);
203
+ }
204
+ }
205
+ return combined.join('\n');
206
+ }
207
+ /** Compact header line */
208
+ export function header(subtitle = '') {
209
+ const lines = [];
210
+ if (isColorSupported()) {
211
+ lines.push(` ${BOLD}${palette.cream}PANGUARD ${palette.sage}[▣]${palette.cream} AI${RESET}`);
212
+ }
213
+ else {
214
+ lines.push(' PANGUARD [#] AI');
215
+ }
216
+ if (subtitle) {
217
+ lines.push(` ${c.dim(subtitle)}`);
218
+ }
219
+ lines.push('');
220
+ return lines.join('\n');
221
+ }
222
+ // ============================================================
223
+ // Divider — Double-line style (matching mockup)
224
+ // ============================================================
225
+ /** Section divider with optional centered label */
226
+ export function divider(label) {
227
+ const width = Math.min(process.stdout.columns ?? 60, 70);
228
+ if (label) {
229
+ const remaining = width - stripAnsi(label).length - 4;
230
+ const left = Math.max(1, Math.floor(remaining / 2));
231
+ const right = Math.max(1, remaining - left);
232
+ return c.dim('='.repeat(left) + '[ ') + c.bold(label) + c.dim(' ]' + '='.repeat(right));
233
+ }
234
+ return c.dim('='.repeat(width));
235
+ }
236
+ // ============================================================
237
+ // Spinner — matching brand loading animation style
238
+ // ============================================================
239
+ const SPINNER_FRAMES = isColorSupported()
240
+ ? ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
241
+ : ['|', '/', '-', '\\'];
242
+ export class Spinner {
243
+ frameIndex = 0;
244
+ timer = null;
245
+ message;
246
+ constructor(message) {
247
+ this.message = message;
248
+ }
249
+ start() {
250
+ if (!isColorSupported()) {
251
+ process.stdout.write(` ${this.message}...\n`);
252
+ return this;
253
+ }
254
+ process.stdout.write('\x1b[?25l'); // Hide cursor
255
+ this.timer = setInterval(() => {
256
+ const frame = SPINNER_FRAMES[this.frameIndex % SPINNER_FRAMES.length];
257
+ process.stdout.write(`\r ${palette.sage}${frame}${RESET} ${this.message}`);
258
+ this.frameIndex++;
259
+ }, 80);
260
+ this.timer.unref();
261
+ return this;
262
+ }
263
+ update(message) {
264
+ this.message = message;
265
+ if (!isColorSupported()) {
266
+ process.stdout.write(` ${message}\n`);
267
+ }
268
+ }
269
+ succeed(message) {
270
+ this.stop();
271
+ const text = message ?? this.message;
272
+ process.stdout.write(`\r ${symbols.pass} ${text}\n`);
273
+ }
274
+ fail(message) {
275
+ this.stop();
276
+ const text = message ?? this.message;
277
+ process.stdout.write(`\r ${symbols.fail} ${text}\n`);
278
+ }
279
+ warn(message) {
280
+ this.stop();
281
+ const text = message ?? this.message;
282
+ process.stdout.write(`\r ${symbols.warn} ${text}\n`);
283
+ }
284
+ stop() {
285
+ if (this.timer) {
286
+ clearInterval(this.timer);
287
+ this.timer = null;
288
+ }
289
+ if (isColorSupported()) {
290
+ process.stdout.write('\r\x1b[K');
291
+ process.stdout.write('\x1b[?25h'); // Show cursor
292
+ }
293
+ }
294
+ }
295
+ /** Create and start a spinner */
296
+ export function spinner(message) {
297
+ return new Spinner(message).start();
298
+ }
299
+ export class ProgressBar {
300
+ current = 0;
301
+ total;
302
+ width;
303
+ label;
304
+ startTime;
305
+ constructor(options) {
306
+ this.total = options.total;
307
+ this.width = options.width ?? 40;
308
+ this.label = options.label ?? 'Progress';
309
+ this.startTime = Date.now();
310
+ }
311
+ update(current, label) {
312
+ this.current = Math.min(current, this.total);
313
+ if (label !== undefined)
314
+ this.label = label;
315
+ this.render();
316
+ }
317
+ increment(label) {
318
+ this.update(this.current + 1, label);
319
+ }
320
+ complete(label) {
321
+ this.update(this.total, label);
322
+ process.stdout.write('\n');
323
+ }
324
+ render() {
325
+ const ratio = this.total > 0 ? this.current / this.total : 0;
326
+ const percent = Math.round(ratio * 100);
327
+ const filled = Math.round(this.width * ratio);
328
+ const empty = this.width - filled;
329
+ // Mockup style: [=========================> ] 75%
330
+ const barFill = filled > 0 ? c.safe('='.repeat(Math.max(0, filled - 1)) + '>') : '';
331
+ const barEmpty = c.dim(' '.repeat(empty));
332
+ const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1);
333
+ const percentStr = percent === 100 ? c.success(`${percent}%`) : c.cream(`${percent}%`);
334
+ const line = ` ${this.label}: ${c.dim('[')}${barFill}${barEmpty}${c.dim(']')} ${percentStr} ${c.dim(`${elapsed}s`)}`;
335
+ process.stdout.write(`\r\x1b[K${line}`);
336
+ }
337
+ }
338
+ /** Create a progress bar */
339
+ export function progressBar(options) {
340
+ return new ProgressBar(options);
341
+ }
342
+ /** Render a formatted table */
343
+ export function table(columns, rows) {
344
+ const widths = columns.map((col) => {
345
+ const headerLen = stripAnsi(col.header).length;
346
+ const maxDataLen = rows.reduce((max, row) => {
347
+ const val = row[col.key] ?? '';
348
+ return Math.max(max, stripAnsi(val).length);
349
+ }, 0);
350
+ return col.width ?? Math.max(headerLen, maxDataLen) + 2;
351
+ });
352
+ const lines = [];
353
+ // Top border
354
+ const hLine = (ch) => c.dim(' +' + widths.map((w) => ch.repeat(w + 2)).join('+') + '+');
355
+ lines.push(hLine('-'));
356
+ // Header
357
+ const headerCells = columns.map((col, i) => {
358
+ return ' ' + pad(c.brand(col.header), widths[i] ?? 10, 'left') + ' ';
359
+ });
360
+ lines.push(c.dim(' |') + headerCells.join(c.dim('|')) + c.dim('|'));
361
+ // Header separator (double line)
362
+ lines.push(c.dim(' +' + widths.map((w) => '='.repeat(w + 2)).join('+') + '+'));
363
+ // Rows
364
+ for (const row of rows) {
365
+ const cells = columns.map((col, i) => {
366
+ let val = row[col.key] ?? '';
367
+ if (col.color)
368
+ val = col.color(val);
369
+ return ' ' + pad(val, widths[i] ?? 10, col.align ?? 'left') + ' ';
370
+ });
371
+ lines.push(c.dim(' |') + cells.join(c.dim('|')) + c.dim('|'));
372
+ }
373
+ // Bottom border
374
+ lines.push(hLine('-'));
375
+ return lines.join('\n');
376
+ }
377
+ /** Draw a bordered box around text */
378
+ export function box(content, options = {}) {
379
+ const { title, padding = 1, borderColor = c.sage, width: forceWidth } = options;
380
+ const contentLines = content.split('\n');
381
+ const maxContentWidth = Math.max(...contentLines.map((l) => stripAnsi(l).length), title ? stripAnsi(title).length + 4 : 0);
382
+ const innerWidth = forceWidth ?? maxContentWidth + padding * 2;
383
+ const lines = [];
384
+ // Top border
385
+ if (title) {
386
+ const titleStr = ` ${title} `;
387
+ const remaining = innerWidth - stripAnsi(titleStr).length;
388
+ const left = Math.max(1, Math.floor(remaining / 2));
389
+ const right = Math.max(1, remaining - left);
390
+ lines.push(' ' + borderColor('+' + '-'.repeat(left) + titleStr + '-'.repeat(right) + '+'));
391
+ }
392
+ else {
393
+ lines.push(' ' + borderColor('+' + '-'.repeat(innerWidth) + '+'));
394
+ }
395
+ // Padding top
396
+ for (let i = 0; i < Math.max(0, padding - 1); i++) {
397
+ lines.push(' ' + borderColor('|') + ' '.repeat(innerWidth) + borderColor('|'));
398
+ }
399
+ // Content
400
+ for (const line of contentLines) {
401
+ const stripped = stripAnsi(line);
402
+ const padRight = innerWidth - padding - stripped.length;
403
+ lines.push(' ' +
404
+ borderColor('|') +
405
+ ' '.repeat(padding) +
406
+ line +
407
+ ' '.repeat(Math.max(0, padRight)) +
408
+ borderColor('|'));
409
+ }
410
+ // Padding bottom
411
+ for (let i = 0; i < Math.max(0, padding - 1); i++) {
412
+ lines.push(' ' + borderColor('|') + ' '.repeat(innerWidth) + borderColor('|'));
413
+ }
414
+ // Bottom border
415
+ lines.push(' ' + borderColor('+' + '-'.repeat(innerWidth) + '+'));
416
+ return lines.join('\n');
417
+ }
418
+ // ============================================================
419
+ // Score Display — matching brand status panel style
420
+ // ============================================================
421
+ /** Render security score display (matching mockup status panel) */
422
+ export function scoreDisplay(score, grade, trend) {
423
+ const trendIcon = trend === 'improving'
424
+ ? c.safe(' [+] Improving')
425
+ : trend === 'declining'
426
+ ? c.critical(' [-] Declining')
427
+ : c.dim(' [=] Stable');
428
+ const scoreStr = colorScore(score);
429
+ const gradeStr = colorGrade(grade);
430
+ // Score bar matching mockup: [=========================> ]
431
+ const barWidth = 30;
432
+ const filled = Math.round((score / 100) * barWidth);
433
+ const empty = barWidth - filled;
434
+ const barColor = score >= 80 ? c.safe : score >= 60 ? c.caution : score >= 40 ? c.alert : c.critical;
435
+ const bar = c.dim('[') +
436
+ barColor('='.repeat(Math.max(0, filled - 1)) + (filled > 0 ? '>' : '')) +
437
+ c.dim(' '.repeat(empty) + ']');
438
+ return [
439
+ '',
440
+ ` ${c.heading('Security Score:')} ${c.bold(scoreStr)}${c.dim('/100')} ${c.heading('Grade:')} ${gradeStr}${trendIcon}`,
441
+ ` ${bar}`,
442
+ '',
443
+ ].join('\n');
444
+ }
445
+ /** Render a status panel like the mockup */
446
+ export function statusPanel(title, items) {
447
+ const lines = [];
448
+ lines.push('');
449
+ lines.push(divider(title));
450
+ lines.push('');
451
+ for (const item of items) {
452
+ const dot = item.status === 'safe'
453
+ ? c.safe('●')
454
+ : item.status === 'caution'
455
+ ? c.caution('●')
456
+ : item.status === 'alert'
457
+ ? c.alert('●')
458
+ : item.status === 'critical'
459
+ ? c.critical('●')
460
+ : c.dim('●');
461
+ lines.push(` ${dot} ${c.bold(item.label + ':')} ${item.value}`);
462
+ }
463
+ lines.push('');
464
+ return lines.join('\n');
465
+ }
466
+ // ============================================================
467
+ // Utility Helpers
468
+ // ============================================================
469
+ /** Strip ANSI escape codes from a string */
470
+ export function stripAnsi(text) {
471
+ // eslint-disable-next-line no-control-regex
472
+ return text.replace(/\x1b\[[0-9;]*m/g, '');
473
+ }
474
+ /** Pad a string (accounting for ANSI codes) */
475
+ function pad(text, width, align) {
476
+ const visibleLen = stripAnsi(text).length;
477
+ const needed = Math.max(0, width - visibleLen);
478
+ if (align === 'right')
479
+ return ' '.repeat(needed) + text;
480
+ if (align === 'center') {
481
+ const left = Math.floor(needed / 2);
482
+ const right = needed - left;
483
+ return ' '.repeat(left) + text + ' '.repeat(right);
484
+ }
485
+ return text + ' '.repeat(needed);
486
+ }
487
+ /** Format milliseconds as human-readable duration */
488
+ export function formatDuration(ms) {
489
+ if (ms < 1000)
490
+ return `${ms}ms`;
491
+ if (ms < 60000)
492
+ return `${(ms / 1000).toFixed(1)}s`;
493
+ const mins = Math.floor(ms / 60000);
494
+ const secs = Math.round((ms % 60000) / 1000);
495
+ return `${mins}m ${secs}s`;
496
+ }
497
+ /** Format a timestamp as relative time */
498
+ export function timeAgo(date) {
499
+ const now = Date.now();
500
+ const then = typeof date === 'string' ? new Date(date).getTime() : date.getTime();
501
+ const diff = now - then;
502
+ if (diff < 60000)
503
+ return 'just now';
504
+ if (diff < 3600000)
505
+ return `${Math.floor(diff / 60000)}m ago`;
506
+ if (diff < 86400000)
507
+ return `${Math.floor(diff / 3600000)}h ago`;
508
+ return `${Math.floor(diff / 86400000)}d ago`;
509
+ }
510
+ // ============================================================
511
+ // Re-exports from prompt and wizard modules
512
+ // ============================================================
513
+ export { visLen, promptSelect, promptText, promptConfirm } from './prompts.js';
514
+ export { WizardEngine } from './wizard.js';
515
+ //# sourceMappingURL=index.js.map