@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.
- package/dist/adapters/adapter-registry.d.ts +150 -0
- package/dist/adapters/adapter-registry.d.ts.map +1 -0
- package/dist/adapters/adapter-registry.js +271 -0
- package/dist/adapters/adapter-registry.js.map +1 -0
- package/dist/adapters/base-adapter.d.ts +101 -0
- package/dist/adapters/base-adapter.d.ts.map +1 -0
- package/dist/adapters/base-adapter.js +160 -0
- package/dist/adapters/base-adapter.js.map +1 -0
- package/dist/adapters/defender-adapter.d.ts +90 -0
- package/dist/adapters/defender-adapter.d.ts.map +1 -0
- package/dist/adapters/defender-adapter.js +227 -0
- package/dist/adapters/defender-adapter.js.map +1 -0
- package/dist/adapters/index.d.ts +22 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +23 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/syslog-adapter.d.ts +207 -0
- package/dist/adapters/syslog-adapter.d.ts.map +1 -0
- package/dist/adapters/syslog-adapter.js +432 -0
- package/dist/adapters/syslog-adapter.js.map +1 -0
- package/dist/adapters/types.d.ts +135 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +13 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/adapters/wazuh-adapter.d.ts +120 -0
- package/dist/adapters/wazuh-adapter.d.ts.map +1 -0
- package/dist/adapters/wazuh-adapter.js +266 -0
- package/dist/adapters/wazuh-adapter.js.map +1 -0
- package/dist/ai/claude-provider.d.ts +66 -0
- package/dist/ai/claude-provider.d.ts.map +1 -0
- package/dist/ai/claude-provider.js +166 -0
- package/dist/ai/claude-provider.js.map +1 -0
- package/dist/ai/funnel-router.d.ts +75 -0
- package/dist/ai/funnel-router.d.ts.map +1 -0
- package/dist/ai/funnel-router.js +173 -0
- package/dist/ai/funnel-router.js.map +1 -0
- package/dist/ai/index.d.ts +77 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +95 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/ollama-provider.d.ts +73 -0
- package/dist/ai/ollama-provider.d.ts.map +1 -0
- package/dist/ai/ollama-provider.js +200 -0
- package/dist/ai/ollama-provider.js.map +1 -0
- package/dist/ai/openai-provider.d.ts +70 -0
- package/dist/ai/openai-provider.d.ts.map +1 -0
- package/dist/ai/openai-provider.js +175 -0
- package/dist/ai/openai-provider.js.map +1 -0
- package/dist/ai/prompts/event-classifier.d.ts +25 -0
- package/dist/ai/prompts/event-classifier.d.ts.map +1 -0
- package/dist/ai/prompts/event-classifier.js +94 -0
- package/dist/ai/prompts/event-classifier.js.map +1 -0
- package/dist/ai/prompts/index.d.ts +13 -0
- package/dist/ai/prompts/index.d.ts.map +1 -0
- package/dist/ai/prompts/index.js +13 -0
- package/dist/ai/prompts/index.js.map +1 -0
- package/dist/ai/prompts/report-generator.d.ts +25 -0
- package/dist/ai/prompts/report-generator.d.ts.map +1 -0
- package/dist/ai/prompts/report-generator.js +131 -0
- package/dist/ai/prompts/report-generator.js.map +1 -0
- package/dist/ai/prompts/threat-analyzer.d.ts +26 -0
- package/dist/ai/prompts/threat-analyzer.d.ts.map +1 -0
- package/dist/ai/prompts/threat-analyzer.js +75 -0
- package/dist/ai/prompts/threat-analyzer.js.map +1 -0
- package/dist/ai/provider-base.d.ts +100 -0
- package/dist/ai/provider-base.d.ts.map +1 -0
- package/dist/ai/provider-base.js +166 -0
- package/dist/ai/provider-base.js.map +1 -0
- package/dist/ai/response-parser.d.ts +36 -0
- package/dist/ai/response-parser.d.ts.map +1 -0
- package/dist/ai/response-parser.js +195 -0
- package/dist/ai/response-parser.js.map +1 -0
- package/dist/ai/token-tracker.d.ts +72 -0
- package/dist/ai/token-tracker.d.ts.map +1 -0
- package/dist/ai/token-tracker.js +145 -0
- package/dist/ai/token-tracker.js.map +1 -0
- package/dist/ai/types.d.ts +138 -0
- package/dist/ai/types.d.ts.map +1 -0
- package/dist/ai/types.js +12 -0
- package/dist/ai/types.js.map +1 -0
- package/dist/cli/index.d.ts +146 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +515 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/prompts.d.ts +58 -0
- package/dist/cli/prompts.d.ts.map +1 -0
- package/dist/cli/prompts.js +327 -0
- package/dist/cli/prompts.js.map +1 -0
- package/dist/cli/wizard.d.ts +58 -0
- package/dist/cli/wizard.d.ts.map +1 -0
- package/dist/cli/wizard.js +200 -0
- package/dist/cli/wizard.js.map +1 -0
- package/dist/discovery/firewall-checker.d.ts +28 -0
- package/dist/discovery/firewall-checker.d.ts.map +1 -0
- package/dist/discovery/firewall-checker.js +379 -0
- package/dist/discovery/firewall-checker.js.map +1 -0
- package/dist/discovery/index.d.ts +23 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +29 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/discovery/network-scanner.d.ts +60 -0
- package/dist/discovery/network-scanner.d.ts.map +1 -0
- package/dist/discovery/network-scanner.js +640 -0
- package/dist/discovery/network-scanner.js.map +1 -0
- package/dist/discovery/os-detector.d.ts +24 -0
- package/dist/discovery/os-detector.d.ts.map +1 -0
- package/dist/discovery/os-detector.js +253 -0
- package/dist/discovery/os-detector.js.map +1 -0
- package/dist/discovery/osquery-provider.d.ts +127 -0
- package/dist/discovery/osquery-provider.d.ts.map +1 -0
- package/dist/discovery/osquery-provider.js +214 -0
- package/dist/discovery/osquery-provider.js.map +1 -0
- package/dist/discovery/risk-scorer.d.ts +66 -0
- package/dist/discovery/risk-scorer.d.ts.map +1 -0
- package/dist/discovery/risk-scorer.js +294 -0
- package/dist/discovery/risk-scorer.js.map +1 -0
- package/dist/discovery/security-tools.d.ts +31 -0
- package/dist/discovery/security-tools.d.ts.map +1 -0
- package/dist/discovery/security-tools.js +346 -0
- package/dist/discovery/security-tools.js.map +1 -0
- package/dist/discovery/service-detector.d.ts +28 -0
- package/dist/discovery/service-detector.d.ts.map +1 -0
- package/dist/discovery/service-detector.js +300 -0
- package/dist/discovery/service-detector.js.map +1 -0
- package/dist/discovery/types.d.ts +502 -0
- package/dist/discovery/types.d.ts.map +1 -0
- package/dist/discovery/types.js +12 -0
- package/dist/discovery/types.js.map +1 -0
- package/dist/discovery/user-auditor.d.ts +28 -0
- package/dist/discovery/user-auditor.d.ts.map +1 -0
- package/dist/discovery/user-auditor.js +385 -0
- package/dist/discovery/user-auditor.js.map +1 -0
- package/dist/i18n/config.d.ts +45 -0
- package/dist/i18n/config.d.ts.map +1 -0
- package/dist/i18n/config.js +135 -0
- package/dist/i18n/config.js.map +1 -0
- package/dist/i18n/index.d.ts +8 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +8 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/monitor/event-normalizer.d.ts +102 -0
- package/dist/monitor/event-normalizer.d.ts.map +1 -0
- package/dist/monitor/event-normalizer.js +195 -0
- package/dist/monitor/event-normalizer.js.map +1 -0
- package/dist/monitor/file-monitor.d.ts +90 -0
- package/dist/monitor/file-monitor.d.ts.map +1 -0
- package/dist/monitor/file-monitor.js +222 -0
- package/dist/monitor/file-monitor.js.map +1 -0
- package/dist/monitor/index.d.ts +147 -0
- package/dist/monitor/index.d.ts.map +1 -0
- package/dist/monitor/index.js +293 -0
- package/dist/monitor/index.js.map +1 -0
- package/dist/monitor/log-monitor.d.ts +102 -0
- package/dist/monitor/log-monitor.d.ts.map +1 -0
- package/dist/monitor/log-monitor.js +245 -0
- package/dist/monitor/log-monitor.js.map +1 -0
- package/dist/monitor/network-monitor.d.ts +103 -0
- package/dist/monitor/network-monitor.d.ts.map +1 -0
- package/dist/monitor/network-monitor.js +336 -0
- package/dist/monitor/network-monitor.js.map +1 -0
- package/dist/monitor/process-monitor.d.ts +108 -0
- package/dist/monitor/process-monitor.d.ts.map +1 -0
- package/dist/monitor/process-monitor.js +245 -0
- package/dist/monitor/process-monitor.js.map +1 -0
- package/dist/monitor/threat-intel-feeds.d.ts +141 -0
- package/dist/monitor/threat-intel-feeds.d.ts.map +1 -0
- package/dist/monitor/threat-intel-feeds.js +430 -0
- package/dist/monitor/threat-intel-feeds.js.map +1 -0
- package/dist/monitor/threat-intel.d.ts +83 -0
- package/dist/monitor/threat-intel.d.ts.map +1 -0
- package/dist/monitor/threat-intel.js +215 -0
- package/dist/monitor/threat-intel.js.map +1 -0
- package/dist/monitor/types.d.ts +65 -0
- package/dist/monitor/types.d.ts.map +1 -0
- package/dist/monitor/types.js +20 -0
- package/dist/monitor/types.js.map +1 -0
- package/dist/rules/index.d.ts +115 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +244 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/rule-loader.d.ts +54 -0
- package/dist/rules/rule-loader.d.ts.map +1 -0
- package/dist/rules/rule-loader.js +167 -0
- package/dist/rules/rule-loader.js.map +1 -0
- package/dist/rules/sigma-matcher.d.ts +40 -0
- package/dist/rules/sigma-matcher.d.ts.map +1 -0
- package/dist/rules/sigma-matcher.js +447 -0
- package/dist/rules/sigma-matcher.js.map +1 -0
- package/dist/rules/sigma-parser.d.ts +36 -0
- package/dist/rules/sigma-parser.d.ts.map +1 -0
- package/dist/rules/sigma-parser.js +180 -0
- package/dist/rules/sigma-parser.js.map +1 -0
- package/dist/rules/types.d.ts +112 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +11 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/rules/yara-scanner.d.ts +103 -0
- package/dist/rules/yara-scanner.d.ts.map +1 -0
- package/dist/rules/yara-scanner.js +421 -0
- package/dist/rules/yara-scanner.js.map +1 -0
- package/dist/scoring/achievements.d.ts +76 -0
- package/dist/scoring/achievements.d.ts.map +1 -0
- package/dist/scoring/achievements.js +211 -0
- package/dist/scoring/achievements.js.map +1 -0
- package/dist/scoring/index.d.ts +3 -0
- package/dist/scoring/index.d.ts.map +1 -0
- package/dist/scoring/index.js +3 -0
- package/dist/scoring/index.js.map +1 -0
- package/dist/scoring/security-score.d.ts +60 -0
- package/dist/scoring/security-score.d.ts.map +1 -0
- package/dist/scoring/security-score.js +211 -0
- package/dist/scoring/security-score.js.map +1 -0
- package/dist/types.d.ts +71 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/index.d.ts +10 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +38 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +71 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/validation.d.ts +35 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +56 -0
- package/dist/utils/validation.js.map +1 -0
- 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
|