@greenarmor/ges 1.4.0 → 1.4.2
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/commands/audit.js +28 -19
- package/dist/commands/doctor.js +18 -6
- package/dist/commands/governance.js +97 -59
- package/dist/commands/init.js +35 -27
- package/dist/commands/policy.js +14 -10
- package/dist/commands/score.js +6 -2
- package/dist/utils/next-steps.js +11 -6
- package/dist/utils/prompts.d.ts +8 -0
- package/dist/utils/prompts.js +62 -11
- package/dist/utils/ui.d.ts +37 -0
- package/dist/utils/ui.js +115 -0
- package/package.json +18 -14
package/dist/commands/audit.js
CHANGED
|
@@ -5,6 +5,7 @@ import { getPacksForProjectType, getAllPacks } from "@greenarmor/ges-policy-engi
|
|
|
5
5
|
import { generateScoreFile, formatScoreOutput } from "@greenarmor/ges-scoring-engine";
|
|
6
6
|
import { runAudit, runAuditIncremental, deduplicateFindings } from "@greenarmor/ges-audit-engine";
|
|
7
7
|
import { showNextStepsMenu } from "../utils/next-steps.js";
|
|
8
|
+
import { banner, divider, blank, success, warn, info, severityBadge, BOLD, DIM, CYAN, RED, YELLOW, GRAY } from "../utils/ui.js";
|
|
8
9
|
import * as fs from "node:fs";
|
|
9
10
|
import * as path from "node:path";
|
|
10
11
|
export const auditCommand = new Command("audit")
|
|
@@ -15,9 +16,10 @@ export const auditCommand = new Command("audit")
|
|
|
15
16
|
.action(async (options) => {
|
|
16
17
|
const root = ensureGESInitialized();
|
|
17
18
|
const config = readJsonFile(path.join(root, ".ges", "config.json"));
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
if (!options.json) {
|
|
20
|
+
banner("GESF Compliance Audit", options.incremental ? "Incremental scan" : "Full project scan");
|
|
21
|
+
info("Scanning project files...");
|
|
22
|
+
}
|
|
21
23
|
let rawFindings;
|
|
22
24
|
let scannedFiles;
|
|
23
25
|
if (options.incremental) {
|
|
@@ -27,16 +29,19 @@ export const auditCommand = new Command("audit")
|
|
|
27
29
|
writeJsonFile(cachePath, result.newCache);
|
|
28
30
|
rawFindings = result.findings;
|
|
29
31
|
scannedFiles = result.scannedFiles;
|
|
30
|
-
|
|
32
|
+
if (!options.json)
|
|
33
|
+
success("Scan complete", `${scannedFiles} files (${result.changedFiles} changed)`);
|
|
31
34
|
}
|
|
32
35
|
else {
|
|
33
36
|
const result = runAudit(root);
|
|
34
37
|
rawFindings = result.findings;
|
|
35
38
|
scannedFiles = result.scannedFiles;
|
|
36
|
-
|
|
39
|
+
if (!options.json)
|
|
40
|
+
success("Scan complete", `${scannedFiles} files`);
|
|
37
41
|
}
|
|
38
42
|
const findings = deduplicateFindings(rawFindings);
|
|
39
|
-
|
|
43
|
+
if (!options.json)
|
|
44
|
+
blank();
|
|
40
45
|
const configFrameworks = (config?.frameworks || ["GDPR", "OWASP"]);
|
|
41
46
|
const projectPacks = getPacksForProjectType(config?.project_type || "generic-web-application");
|
|
42
47
|
const packIds = new Set(projectPacks.map(p => p.id));
|
|
@@ -76,7 +81,8 @@ export const auditCommand = new Command("audit")
|
|
|
76
81
|
if (overrides.length > 0) {
|
|
77
82
|
const naCount = overrides.filter(o => o.status === "not-applicable").length;
|
|
78
83
|
const passCount = overrides.filter(o => o.status === "pass").length;
|
|
79
|
-
|
|
84
|
+
if (!options.json)
|
|
85
|
+
info("Control overrides", `${naCount} not-applicable, ${passCount} pre-verified`);
|
|
80
86
|
}
|
|
81
87
|
if (options.json) {
|
|
82
88
|
console.log(JSON.stringify({ findings, score: scoreData }, null, 2));
|
|
@@ -84,34 +90,37 @@ export const auditCommand = new Command("audit")
|
|
|
84
90
|
process.exit(1);
|
|
85
91
|
return;
|
|
86
92
|
}
|
|
87
|
-
console.log("
|
|
88
|
-
|
|
89
|
-
console.log(`
|
|
93
|
+
console.log(` ${BOLD("Findings")}`);
|
|
94
|
+
divider(40);
|
|
95
|
+
console.log(` ${DIM("Total")} ${findings.length}`);
|
|
96
|
+
console.log(` ${RED(`Critical ${critical.length}`)} ${RED(`High ${high.length}`)} ${YELLOW(`Medium ${medium.length}`)} ${CYAN(`Low ${low.length}`)}\n`);
|
|
90
97
|
if (findings.length > 0) {
|
|
91
98
|
const grouped = groupByCategory(findings);
|
|
92
99
|
for (const [category, categoryFindings] of Object.entries(grouped)) {
|
|
93
|
-
console.log(`
|
|
100
|
+
console.log(` ${BOLD(category.toUpperCase())}`);
|
|
94
101
|
for (const f of categoryFindings.slice(0, 10)) {
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
console.log(` [${sev}] ${f.title}${loc}`);
|
|
102
|
+
const loc = f.file !== "project" ? ` ${DIM(`(${f.file}${f.line ? ":" + f.line : ""})`)}` : "";
|
|
103
|
+
console.log(` ${severityBadge(f.severity).padEnd(10)} ${f.title}${loc}`);
|
|
98
104
|
if (f.evidence && f.file !== "project") {
|
|
99
|
-
console.log(` ${f.evidence.slice(0, 100)}`);
|
|
105
|
+
console.log(` ${GRAY(f.evidence.slice(0, 100))}`);
|
|
100
106
|
}
|
|
101
107
|
}
|
|
102
108
|
if (categoryFindings.length > 10) {
|
|
103
|
-
console.log(`
|
|
109
|
+
console.log(` ${DIM(`... and ${categoryFindings.length - 10} more`)}`);
|
|
104
110
|
}
|
|
105
111
|
console.log("");
|
|
106
112
|
}
|
|
107
113
|
}
|
|
108
114
|
else {
|
|
109
|
-
|
|
115
|
+
success("No security or compliance issues found in source code.");
|
|
116
|
+
blank();
|
|
110
117
|
}
|
|
111
|
-
console.log("
|
|
118
|
+
console.log(` ${BOLD("Compliance Score")}`);
|
|
119
|
+
divider(40);
|
|
112
120
|
console.log(formatScoreOutput(scoreData));
|
|
113
121
|
if (critical.length > 0) {
|
|
114
|
-
|
|
122
|
+
warn("Critical issues must be resolved before deployment.");
|
|
123
|
+
blank();
|
|
115
124
|
}
|
|
116
125
|
recordActivity(root, {
|
|
117
126
|
source: "cli",
|
package/dist/commands/doctor.js
CHANGED
|
@@ -3,13 +3,13 @@ import { findProjectRoot, readJsonFile } from "../utils/project.js";
|
|
|
3
3
|
import { CLI_VERSION } from "../utils/version.js";
|
|
4
4
|
import { GES_DIR, loadGovernanceRecords, verifyGovernanceRecord } from "@greenarmor/ges-core";
|
|
5
5
|
import { showNextStepsMenu } from "../utils/next-steps.js";
|
|
6
|
+
import { banner, success, warn, error, blank, progressBar, BOLD, DIM, GREEN, RED, YELLOW } from "../utils/ui.js";
|
|
6
7
|
import * as fs from "node:fs";
|
|
7
8
|
import * as path from "node:path";
|
|
8
9
|
export const doctorCommand = new Command("doctor")
|
|
9
10
|
.description("Diagnose GESF configuration and health")
|
|
10
11
|
.action(async () => {
|
|
11
|
-
|
|
12
|
-
console.log(" ─────────────────────────────\n");
|
|
12
|
+
banner("GESF Doctor", "Diagnostic health check");
|
|
13
13
|
const checks = [];
|
|
14
14
|
const root = findProjectRoot();
|
|
15
15
|
if (root) {
|
|
@@ -82,11 +82,23 @@ export const doctorCommand = new Command("doctor")
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
checks.push({ name: "GESF Version", status: "OK", detail: CLI_VERSION });
|
|
85
|
+
const okCount = checks.filter(c => c.status === "OK").length;
|
|
86
|
+
const warnCount = checks.filter(c => c.status === "WARN" || c.status === "MISSING").length;
|
|
87
|
+
const failCount = checks.filter(c => c.status === "FAIL").length;
|
|
88
|
+
console.log(` ${BOLD("Health Score")} ${progressBar(okCount, checks.length, 24)}`);
|
|
89
|
+
console.log(` ${DIM("Checks")} ${GREEN(`${okCount} ok`)} ${YELLOW(`${warnCount} warn`)} ${RED(`${failCount} fail`)}`);
|
|
90
|
+
blank();
|
|
85
91
|
for (const check of checks) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
if (check.status === "OK") {
|
|
93
|
+
success(check.name, check.detail);
|
|
94
|
+
}
|
|
95
|
+
else if (check.status === "WARN" || check.status === "MISSING") {
|
|
96
|
+
warn(check.name, check.detail);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
error(check.name, check.detail);
|
|
100
|
+
}
|
|
89
101
|
}
|
|
90
|
-
|
|
102
|
+
blank();
|
|
91
103
|
await showNextStepsMenu("doctor");
|
|
92
104
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { ensureGESInitialized } from "../utils/project.js";
|
|
3
3
|
import { input, select } from "../utils/prompts.js";
|
|
4
|
+
import { banner, divider, blank, success, info, kv, statusBadge, severityBadge, BOLD, DIM, GREEN, RED, YELLOW, GRAY, } from "../utils/ui.js";
|
|
4
5
|
import { loadGovernanceRecords, createGovernanceRecord, addGovernanceRecord, findGovernanceRecord, setGovernanceApproval, addGovernanceEvidence, createEvidenceRef, verifyGovernanceRecord, deleteGovernanceRecord, setGovernanceRiskAssessment, setGovernancePolicyBasis, setGovernanceReviewCycle, setGovernanceDataInventory, setGovernanceComplianceLinks, setGovernanceCommittee, } from "@greenarmor/ges-core";
|
|
5
6
|
import { recordActivity } from "@greenarmor/ges-core";
|
|
6
7
|
const STATUS_BADGE = {
|
|
@@ -19,17 +20,17 @@ const RISK_COLOR = {
|
|
|
19
20
|
critical: "CRITICAL",
|
|
20
21
|
};
|
|
21
22
|
function printRecordSummary(record) {
|
|
22
|
-
|
|
23
|
-
console.log(`
|
|
24
|
-
console.log(` Type
|
|
23
|
+
console.log(` ${statusBadge(record.status)} ${BOLD(record.system_name)}`);
|
|
24
|
+
console.log(` ${DIM("ID")} ${record.id}`);
|
|
25
|
+
console.log(` ${DIM("Type")} ${record.system_type} ${GRAY("|")} ${DIM("Risk")} ${severityBadge(record.risk_level)}`);
|
|
25
26
|
if (record.approval) {
|
|
26
|
-
console.log(`
|
|
27
|
-
console.log(` Valid
|
|
27
|
+
console.log(` ${DIM("By")} ${record.approval.approver_name} (${record.approval.approver_role})`);
|
|
28
|
+
console.log(` ${DIM("Valid")} ${record.approval.valid_from} ${GRAY("→")} ${record.approval.valid_until || "indefinite"}`);
|
|
28
29
|
}
|
|
29
30
|
else {
|
|
30
|
-
console.log(`
|
|
31
|
+
console.log(` ${DIM("By")} ${GRAY("NOT RECORDED")}`);
|
|
31
32
|
}
|
|
32
|
-
console.log(`
|
|
33
|
+
console.log(` ${DIM("Ev")} ${record.evidence.length} reference(s)`);
|
|
33
34
|
}
|
|
34
35
|
export const governanceCommand = new Command("governance")
|
|
35
36
|
.description("Manage governance approval provenance chains")
|
|
@@ -78,13 +79,15 @@ export const governanceCommand = new Command("governance")
|
|
|
78
79
|
created_by: "cli-user",
|
|
79
80
|
});
|
|
80
81
|
addGovernanceRecord(root, record);
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
blank();
|
|
83
|
+
success("Governance record created");
|
|
84
|
+
kv("ID", record.id, 6);
|
|
85
|
+
console.log();
|
|
83
86
|
printRecordSummary(record);
|
|
84
|
-
console.log(`\n Next steps
|
|
85
|
-
console.log(` ges governance approve ${record.id}
|
|
86
|
-
console.log(` ges governance evidence ${record.id}
|
|
87
|
-
console.log(` ges governance verify ${record.id}
|
|
87
|
+
console.log(`\n ${DIM("Next steps:")}`);
|
|
88
|
+
console.log(` ${GRAY("–")} ${GREEN("ges governance approve")} ${record.id} ${DIM("Record approval decision")}`);
|
|
89
|
+
console.log(` ${GRAY("–")} ${GREEN("ges governance evidence")} ${record.id} ${DIM("Add evidence reference")}`);
|
|
90
|
+
console.log(` ${GRAY("–")} ${GREEN("ges governance verify")} ${record.id} ${DIM("Verify provenance chain")}\n`);
|
|
88
91
|
recordActivity(root, {
|
|
89
92
|
source: "cli",
|
|
90
93
|
action: "control_override",
|
|
@@ -148,10 +151,12 @@ export const governanceCommand = new Command("governance")
|
|
|
148
151
|
console.error(` Error: Failed to update record.`);
|
|
149
152
|
process.exit(1);
|
|
150
153
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
blank();
|
|
155
|
+
success("Approval recorded", `for ${updated.system_name}`);
|
|
156
|
+
kv("Decision", decision.toUpperCase(), 6);
|
|
157
|
+
kv("Approver", `${approverName} (${approverRole})`, 6);
|
|
158
|
+
kv("Valid", `${validFrom} → ${validUntil || "indefinite"}`, 6);
|
|
159
|
+
console.log();
|
|
155
160
|
recordActivity(root, {
|
|
156
161
|
source: "cli",
|
|
157
162
|
action: "control_override",
|
|
@@ -223,10 +228,13 @@ export const governanceCommand = new Command("governance")
|
|
|
223
228
|
console.error(` Error: Failed to add evidence.`);
|
|
224
229
|
process.exit(1);
|
|
225
230
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
console.log(`
|
|
229
|
-
|
|
231
|
+
blank();
|
|
232
|
+
success("Evidence added", `to ${updated.system_name}`);
|
|
233
|
+
console.log(` ${BOLD(evidence.title)}`);
|
|
234
|
+
kv("Source", evidence.source_system, 6);
|
|
235
|
+
kv("Ref", evidence.reference, 6);
|
|
236
|
+
kv("Total", `${updated.evidence.length} reference(s)`, 6);
|
|
237
|
+
console.log();
|
|
230
238
|
recordActivity(root, {
|
|
231
239
|
source: "cli",
|
|
232
240
|
action: "control_override",
|
|
@@ -243,14 +251,16 @@ export const governanceCommand = new Command("governance")
|
|
|
243
251
|
const root = ensureGESInitialized();
|
|
244
252
|
const records = loadGovernanceRecords(root);
|
|
245
253
|
if (records.length === 0) {
|
|
246
|
-
|
|
247
|
-
console.log(` Create one with: ges governance add\n`);
|
|
254
|
+
info("No governance records found.");
|
|
255
|
+
console.log(` ${DIM("Create one with:")} ${GREEN("ges governance add")}\n`);
|
|
248
256
|
return;
|
|
249
257
|
}
|
|
250
|
-
|
|
258
|
+
blank();
|
|
259
|
+
console.log(` ${BOLD("Governance Records")} ${GRAY(`(${records.length})`)}`);
|
|
260
|
+
console.log();
|
|
251
261
|
records.forEach(r => {
|
|
252
262
|
printRecordSummary(r);
|
|
253
|
-
console.log(
|
|
263
|
+
console.log();
|
|
254
264
|
});
|
|
255
265
|
}))
|
|
256
266
|
.addCommand(new Command("show")
|
|
@@ -411,34 +421,43 @@ export const governanceCommand = new Command("governance")
|
|
|
411
421
|
process.exit(1);
|
|
412
422
|
}
|
|
413
423
|
const result = verifyGovernanceRecord(record);
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
console.log(`
|
|
417
|
-
console.log(`
|
|
418
|
-
console.log(` Approval Status: ${result.approval_status.toUpperCase()}`);
|
|
424
|
+
banner("VERIFICATION", record.system_name);
|
|
425
|
+
const overallText = result.valid ? GREEN(BOLD("✓ VALID")) : RED(BOLD("✕ ISSUES FOUND"));
|
|
426
|
+
console.log(` ${DIM("Overall:")} ${overallText}`);
|
|
427
|
+
console.log(` ${DIM("Approval:")} ${statusBadge(result.approval_status)}`);
|
|
419
428
|
if (result.days_until_expiry !== null) {
|
|
420
|
-
const dayLabel = result.days_until_expiry < 0
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
console.log(`
|
|
428
|
-
console.log(
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
429
|
+
const dayLabel = result.days_until_expiry < 0
|
|
430
|
+
? RED(`${Math.abs(result.days_until_expiry)} days AGO`)
|
|
431
|
+
: result.days_until_expiry <= 30
|
|
432
|
+
? YELLOW(`${result.days_until_expiry} days remaining`)
|
|
433
|
+
: GREEN(`${result.days_until_expiry} days remaining`);
|
|
434
|
+
console.log(` ${DIM("Expiry:")} ${dayLabel}`);
|
|
435
|
+
}
|
|
436
|
+
console.log(` ${DIM("Evidence:")} ${result.completeness.evidence_count} reference(s)`);
|
|
437
|
+
console.log(`\n ${BOLD("Completeness Checklist")}`);
|
|
438
|
+
divider(40);
|
|
439
|
+
const check = (ok, label, isWarning = false) => {
|
|
440
|
+
const icon = ok ? GREEN("✓") : isWarning ? YELLOW("△") : RED("✕");
|
|
441
|
+
const text = ok ? label : isWarning ? YELLOW(label) : RED(label);
|
|
442
|
+
console.log(` ${icon} ${text}`);
|
|
443
|
+
};
|
|
444
|
+
check(result.completeness.has_approval, "Approval Decision");
|
|
445
|
+
check(result.completeness.has_risk_assessment, "Risk Assessment");
|
|
446
|
+
check(result.completeness.has_policy_basis, "Policy Basis");
|
|
447
|
+
check(result.completeness.has_evidence, "Evidence Chain");
|
|
448
|
+
check(result.completeness.has_review_cycle, "Review Cycle", true);
|
|
449
|
+
check(result.completeness.has_data_inventory, "Data Inventory", true);
|
|
450
|
+
check(result.completeness.has_compliance_links, "Compliance Links", true);
|
|
451
|
+
check(result.completeness.is_current, "Currently Valid");
|
|
433
452
|
if (result.issues.length > 0) {
|
|
434
|
-
console.log(`\n BLOCKING ISSUES
|
|
435
|
-
result.issues.forEach(i => console.log(` ✕ ${i}`));
|
|
453
|
+
console.log(`\n ${RED(BOLD("BLOCKING ISSUES"))}`);
|
|
454
|
+
result.issues.forEach(i => console.log(` ${RED("✕")} ${i}`));
|
|
436
455
|
}
|
|
437
456
|
if (result.warnings.length > 0) {
|
|
438
|
-
console.log(`\n WARNINGS
|
|
439
|
-
result.warnings.forEach(w => console.log(` △ ${w}`));
|
|
457
|
+
console.log(`\n ${YELLOW(BOLD("WARNINGS"))}`);
|
|
458
|
+
result.warnings.forEach(w => console.log(` ${YELLOW("△")} ${w}`));
|
|
440
459
|
}
|
|
441
|
-
console.log(
|
|
460
|
+
console.log();
|
|
442
461
|
}))
|
|
443
462
|
.addCommand(new Command("delete")
|
|
444
463
|
.description("Delete a governance record")
|
|
@@ -454,7 +473,9 @@ export const governanceCommand = new Command("governance")
|
|
|
454
473
|
}
|
|
455
474
|
const deleted = deleteGovernanceRecord(root, record.id);
|
|
456
475
|
if (deleted) {
|
|
457
|
-
|
|
476
|
+
blank();
|
|
477
|
+
success("Deleted governance record", `${record.system_name} (${record.id})`);
|
|
478
|
+
console.log();
|
|
458
479
|
recordActivity(root, {
|
|
459
480
|
source: "cli",
|
|
460
481
|
action: "control_override",
|
|
@@ -507,8 +528,12 @@ export const governanceCommand = new Command("governance")
|
|
|
507
528
|
console.error(` Error: Failed to update.`);
|
|
508
529
|
process.exit(1);
|
|
509
530
|
}
|
|
510
|
-
|
|
511
|
-
|
|
531
|
+
blank();
|
|
532
|
+
success("Risk assessment linked", `to ${updated.system_name}`);
|
|
533
|
+
kv("Assessor", assessor, 6);
|
|
534
|
+
kv("Score", score, 6);
|
|
535
|
+
kv("Residual", residual, 6);
|
|
536
|
+
console.log();
|
|
512
537
|
recordActivity(root, { source: "cli", action: "control_override", title: `Risk assessment added: ${updated.system_name}`, description: `Risk assessment by ${assessor} linked to ${updated.system_name}. Score: ${score}, Residual: ${residual}.`, details: { governance_record_id: updated.id }, actor_name: options.actor, actor_role: options.actorRole });
|
|
513
538
|
}))
|
|
514
539
|
.addCommand(new Command("policy-basis")
|
|
@@ -544,8 +569,11 @@ export const governanceCommand = new Command("governance")
|
|
|
544
569
|
console.error(` Error: Failed to update.`);
|
|
545
570
|
process.exit(1);
|
|
546
571
|
}
|
|
547
|
-
|
|
548
|
-
|
|
572
|
+
blank();
|
|
573
|
+
success("Policy basis documented", `for ${updated.system_name}`);
|
|
574
|
+
kv("Policy", `${policyName} (${policyId} v${version})`, 6);
|
|
575
|
+
kv("Standard", standard, 6);
|
|
576
|
+
console.log();
|
|
549
577
|
recordActivity(root, { source: "cli", action: "control_override", title: `Policy basis added: ${updated.system_name}`, description: `Policy ${policyName} (${policyId} v${version}) documented for ${updated.system_name}.`, details: { governance_record_id: updated.id }, actor_name: options.actor, actor_role: options.actorRole });
|
|
550
578
|
}))
|
|
551
579
|
.addCommand(new Command("review-cycle")
|
|
@@ -583,8 +611,11 @@ export const governanceCommand = new Command("governance")
|
|
|
583
611
|
console.error(` Error: Failed to update.`);
|
|
584
612
|
process.exit(1);
|
|
585
613
|
}
|
|
586
|
-
|
|
587
|
-
|
|
614
|
+
blank();
|
|
615
|
+
success("Review cycle set", `for ${updated.system_name}`);
|
|
616
|
+
kv("Frequency", frequency, 6);
|
|
617
|
+
kv("Next review", nextReview, 6);
|
|
618
|
+
console.log();
|
|
588
619
|
recordActivity(root, { source: "cli", action: "control_override", title: `Review cycle set: ${updated.system_name}`, description: `Review cycle (${frequency}) set for ${updated.system_name}. Next review: ${nextReview}.`, details: { governance_record_id: updated.id }, actor_name: options.actor, actor_role: options.actorRole });
|
|
589
620
|
}))
|
|
590
621
|
.addCommand(new Command("data-inventory")
|
|
@@ -618,7 +649,9 @@ export const governanceCommand = new Command("governance")
|
|
|
618
649
|
console.error(` Error: Failed to update.`);
|
|
619
650
|
process.exit(1);
|
|
620
651
|
}
|
|
621
|
-
|
|
652
|
+
blank();
|
|
653
|
+
success("Data inventory documented", `for ${updated.system_name}`);
|
|
654
|
+
console.log();
|
|
622
655
|
recordActivity(root, { source: "cli", action: "control_override", title: `Data inventory added: ${updated.system_name}`, description: `Data inventory documented for ${updated.system_name}.`, details: { governance_record_id: updated.id }, actor_name: options.actor, actor_role: options.actorRole });
|
|
623
656
|
}))
|
|
624
657
|
.addCommand(new Command("committee")
|
|
@@ -653,8 +686,11 @@ export const governanceCommand = new Command("governance")
|
|
|
653
686
|
console.error(` Error: Failed to update.`);
|
|
654
687
|
process.exit(1);
|
|
655
688
|
}
|
|
656
|
-
|
|
657
|
-
|
|
689
|
+
blank();
|
|
690
|
+
success("Committee approval recorded", `for ${updated.system_name}`);
|
|
691
|
+
kv("Committee", committeeName, 6);
|
|
692
|
+
kv("Meeting", `${meetingDate} (${meetingRef})`, 6);
|
|
693
|
+
console.log();
|
|
658
694
|
recordActivity(root, { source: "cli", action: "control_override", title: `Committee approval added: ${updated.system_name}`, description: `Committee ${committeeName} (${meetingRef}) recorded for ${updated.system_name}.`, details: { governance_record_id: updated.id }, actor_name: options.actor, actor_role: options.actorRole });
|
|
659
695
|
}))
|
|
660
696
|
.addCommand(new Command("compliance-links")
|
|
@@ -683,6 +719,8 @@ export const governanceCommand = new Command("governance")
|
|
|
683
719
|
console.error(` Error: Failed to update.`);
|
|
684
720
|
process.exit(1);
|
|
685
721
|
}
|
|
686
|
-
|
|
722
|
+
blank();
|
|
723
|
+
success("Compliance links mapped", `for ${updated.system_name}`);
|
|
724
|
+
console.log();
|
|
687
725
|
recordActivity(root, { source: "cli", action: "control_override", title: `Compliance links added: ${updated.system_name}`, description: `Compliance frameworks mapped for ${updated.system_name}.`, details: { governance_record_id: updated.id }, actor_name: options.actor, actor_role: options.actorRole });
|
|
688
726
|
}));
|
package/dist/commands/init.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { input, select, checkbox } from "../utils/prompts.js";
|
|
3
|
+
import { banner, divider, blank, success, error, warn, info, step, kv, label, BOLD, CYAN, GREEN, GRAY, } from "../utils/ui.js";
|
|
3
4
|
import { PROJECT_TYPES, FRAMEWORKS, DEFAULT_FRAMEWORKS, GES_DIR, COMPLIANCE_DIR, SECURITY_DIR, CONTROLS_DIR, POLICIES_DIR, CHECKLISTS_DIR, DOCS_DIR, REPORTS_DIR, } from "@greenarmor/ges-core";
|
|
4
5
|
import { CLI_VERSION } from "../utils/version.js";
|
|
5
6
|
import { recordActivity } from "@greenarmor/ges-core";
|
|
@@ -18,16 +19,15 @@ export const initCommand = new Command("init")
|
|
|
18
19
|
.option("-c, --country <country>", "Country of origin (e.g., BR, CA, US-CA, GB, SG)")
|
|
19
20
|
.option("--force", "Re-initialize even if GESF is already set up")
|
|
20
21
|
.action(async (options) => {
|
|
21
|
-
|
|
22
|
-
console.log(" ─────────────────────────────────────────────\n");
|
|
22
|
+
banner(`Green Engineering Standard Framework`, `v${CLI_VERSION}`);
|
|
23
23
|
const gesDir = path.join(process.cwd(), GES_DIR);
|
|
24
24
|
if (fs.existsSync(gesDir)) {
|
|
25
25
|
if (!options.force) {
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
error("GESF is already initialized in this project.");
|
|
27
|
+
info("Use", "ges init --force to re-initialize.\n");
|
|
28
28
|
process.exit(1);
|
|
29
29
|
}
|
|
30
|
-
|
|
30
|
+
warn("Re-initializing GESF", "existing files will be overwritten\n");
|
|
31
31
|
fs.rmSync(gesDir, { recursive: true, force: true });
|
|
32
32
|
}
|
|
33
33
|
const projectName = options.name || await input({ message: "Project name:", default: path.basename(process.cwd()) });
|
|
@@ -48,7 +48,7 @@ export const initCommand = new Command("init")
|
|
|
48
48
|
})),
|
|
49
49
|
});
|
|
50
50
|
if (selectedFrameworks.length === 0) {
|
|
51
|
-
|
|
51
|
+
error("At least one framework must be selected.");
|
|
52
52
|
process.exit(1);
|
|
53
53
|
}
|
|
54
54
|
// --- Mandatory: Country of Origin ---
|
|
@@ -80,8 +80,8 @@ export const initCommand = new Command("init")
|
|
|
80
80
|
countryCode = countryCode.toUpperCase();
|
|
81
81
|
const countryInfo = getCountryByCode(countryCode);
|
|
82
82
|
if (options.country && !countryInfo && countryCode !== "EU") {
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
warn(`Country code '${options.country}' not recognized.`, "No privacy pack will be auto-installed.");
|
|
84
|
+
info("Available codes:", `${PRIVACY_COUNTRIES.map(c => c.code).join(", ")}, EU`);
|
|
85
85
|
}
|
|
86
86
|
// --- Optional: Additional privacy packs ---
|
|
87
87
|
const additionalPacks = await checkbox({
|
|
@@ -193,31 +193,39 @@ export const initCommand = new Command("init")
|
|
|
193
193
|
for (const wf of workflows) {
|
|
194
194
|
writeFileSync(path.join(process.cwd(), wf.filePath), wf.content);
|
|
195
195
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
196
|
+
blank();
|
|
197
|
+
step(1, 4, "Creating project structure");
|
|
198
|
+
success("Project structure created");
|
|
199
|
+
success("Configuration files generated");
|
|
200
|
+
success("Compliance documents created");
|
|
201
|
+
success("Security documents created");
|
|
200
202
|
if (countryInfo) {
|
|
201
|
-
|
|
203
|
+
success("Country privacy pack auto-installed", `${countryInfo.packId} (${countryInfo.name})`);
|
|
202
204
|
}
|
|
203
205
|
else if (countryCode === "EU") {
|
|
204
|
-
|
|
206
|
+
success("EU GDPR privacy pack auto-installed");
|
|
205
207
|
}
|
|
206
208
|
if (additionalPacks.length > 0) {
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
209
|
+
success("Additional privacy packs installed", additionalPacks.join(", "));
|
|
210
|
+
}
|
|
211
|
+
success("Control packs installed", packs.map(p => p.id).join(", "));
|
|
212
|
+
success("GitHub Actions workflows generated");
|
|
213
|
+
success("Developer logs directory created", ".dev-logs/");
|
|
214
|
+
blank();
|
|
215
|
+
step(2, 4, "Project summary");
|
|
216
|
+
blank();
|
|
217
|
+
console.log(` ${CYAN(BOLD("GESF initialized"))} for "${projectName}" (${projectType})`);
|
|
213
218
|
if (countryInfo) {
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
console.log("
|
|
219
|
+
kv("Country", `${countryInfo.name} — ${countryInfo.lawName}`);
|
|
220
|
+
}
|
|
221
|
+
divider(40);
|
|
222
|
+
blank();
|
|
223
|
+
step(3, 4, "Next steps");
|
|
224
|
+
label("Quick start:");
|
|
225
|
+
console.log(` ${GRAY("1.")} Review generated compliance documents`);
|
|
226
|
+
console.log(` ${GRAY("2.")} Run ${GREEN("ges audit")} to evaluate your project`);
|
|
227
|
+
console.log(` ${GRAY("3.")} Run ${GREEN("ges score")} to see your compliance score`);
|
|
228
|
+
console.log(` ${GRAY("4.")} Add more packs with ${GREEN("ges policy install <pack-id>")}`);
|
|
221
229
|
recordActivity(process.cwd(), {
|
|
222
230
|
source: "cli",
|
|
223
231
|
action: "init",
|
package/dist/commands/policy.js
CHANGED
|
@@ -3,6 +3,7 @@ import { getAllPacks, listPackIds } from "@greenarmor/ges-policy-engine";
|
|
|
3
3
|
import { ensureGESInitialized, writeFileSync } from "../utils/project.js";
|
|
4
4
|
import { addFrameworkToConfig, removeFrameworkFromConfig, recordActivity } from "@greenarmor/ges-core";
|
|
5
5
|
import { showNextStepsMenu } from "../utils/next-steps.js";
|
|
6
|
+
import { banner, blank, success, error, BOLD, CYAN, DIM, GRAY } from "../utils/ui.js";
|
|
6
7
|
import * as fs from "node:fs";
|
|
7
8
|
import * as path from "node:path";
|
|
8
9
|
const policyCmd = new Command("policy")
|
|
@@ -11,12 +12,11 @@ policyCmd
|
|
|
11
12
|
.command("list")
|
|
12
13
|
.description("List available policy packs")
|
|
13
14
|
.action(async () => {
|
|
14
|
-
|
|
15
|
+
banner("Policy Packs", "Available compliance control packs");
|
|
15
16
|
const packs = getAllPacks();
|
|
16
17
|
for (const pack of packs) {
|
|
17
|
-
console.log(` ${pack.id.padEnd(
|
|
18
|
-
|
|
19
|
-
console.log(` ${indent} ${pack.controls.length} controls | ${pack.project_types.join(", ")}`);
|
|
18
|
+
console.log(` ${CYAN(BOLD(pack.id.padEnd(18)))} ${pack.name}`);
|
|
19
|
+
console.log(` ${DIM(`${pack.controls.length} controls`)} ${GRAY("|")} ${DIM(pack.project_types.join(", "))}`);
|
|
20
20
|
console.log("");
|
|
21
21
|
}
|
|
22
22
|
await showNextStepsMenu("policy-list");
|
|
@@ -29,7 +29,7 @@ policyCmd
|
|
|
29
29
|
const packs = getAllPacks();
|
|
30
30
|
const pack = packs.find(p => p.id === packId);
|
|
31
31
|
if (!pack) {
|
|
32
|
-
|
|
32
|
+
error(`Pack '${packId}' not found.`, `Available: ${listPackIds().join(", ")}`);
|
|
33
33
|
process.exit(1);
|
|
34
34
|
}
|
|
35
35
|
const packDir = path.join(root, "controls", pack.id);
|
|
@@ -41,11 +41,13 @@ policyCmd
|
|
|
41
41
|
frameworksAdded.push(fw);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
blank();
|
|
45
|
+
success("Installed policy pack", `${pack.id} (${pack.controls.length} controls)`);
|
|
45
46
|
if (frameworksAdded.length > 0) {
|
|
46
|
-
|
|
47
|
+
success("Updated project frameworks", frameworksAdded.join(", "));
|
|
47
48
|
}
|
|
48
|
-
|
|
49
|
+
success("Dashboard will reflect this pack's controls");
|
|
50
|
+
blank();
|
|
49
51
|
recordActivity(root, {
|
|
50
52
|
source: "cli",
|
|
51
53
|
action: "policy_install",
|
|
@@ -62,7 +64,7 @@ policyCmd
|
|
|
62
64
|
const root = ensureGESInitialized();
|
|
63
65
|
const packDir = path.join(root, "controls", packId);
|
|
64
66
|
if (!fs.existsSync(packDir)) {
|
|
65
|
-
|
|
67
|
+
error(`Pack '${packId}' is not installed.`);
|
|
66
68
|
process.exit(1);
|
|
67
69
|
}
|
|
68
70
|
fs.rmSync(packDir, { recursive: true, force: true });
|
|
@@ -76,7 +78,9 @@ policyCmd
|
|
|
76
78
|
else {
|
|
77
79
|
removeFrameworkFromConfig(root, packId.toUpperCase());
|
|
78
80
|
}
|
|
79
|
-
|
|
81
|
+
blank();
|
|
82
|
+
success("Removed policy pack", packId);
|
|
83
|
+
blank();
|
|
80
84
|
recordActivity(root, {
|
|
81
85
|
source: "cli",
|
|
82
86
|
action: "policy_remove",
|
package/dist/commands/score.js
CHANGED
|
@@ -3,6 +3,7 @@ import { ensureGESInitialized, readJsonFile } from "../utils/project.js";
|
|
|
3
3
|
import { recordActivity } from "@greenarmor/ges-core";
|
|
4
4
|
import { formatScoreOutput } from "@greenarmor/ges-scoring-engine";
|
|
5
5
|
import { showNextStepsMenu } from "../utils/next-steps.js";
|
|
6
|
+
import { warn, info, blank, DIM } from "../utils/ui.js";
|
|
6
7
|
import * as path from "node:path";
|
|
7
8
|
export const scoreCommand = new Command("score")
|
|
8
9
|
.description("Calculate and display compliance score")
|
|
@@ -12,7 +13,10 @@ export const scoreCommand = new Command("score")
|
|
|
12
13
|
const scorePath = path.join(root, ".ges", "score.json");
|
|
13
14
|
const score = readJsonFile(scorePath);
|
|
14
15
|
if (!score || !score.frameworks || Object.keys(score.frameworks).length === 0) {
|
|
15
|
-
|
|
16
|
+
blank();
|
|
17
|
+
warn("No compliance score available.");
|
|
18
|
+
info("Run", "ges audit first.");
|
|
19
|
+
blank();
|
|
16
20
|
await showNextStepsMenu("score");
|
|
17
21
|
return;
|
|
18
22
|
}
|
|
@@ -21,7 +25,7 @@ export const scoreCommand = new Command("score")
|
|
|
21
25
|
}
|
|
22
26
|
else {
|
|
23
27
|
console.log(formatScoreOutput(score));
|
|
24
|
-
console.log(` Last evaluated: ${score.evaluated_at}\n`);
|
|
28
|
+
console.log(` ${DIM("Last evaluated:")} ${score.evaluated_at}\n`);
|
|
25
29
|
}
|
|
26
30
|
recordActivity(root, {
|
|
27
31
|
source: "cli",
|
package/dist/utils/next-steps.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { select } from "./prompts.js";
|
|
2
|
-
|
|
2
|
+
import { divider, blank, label, info, GREEN } from "./ui.js";
|
|
3
3
|
function isInteractive() {
|
|
4
4
|
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
5
5
|
}
|
|
@@ -15,6 +15,7 @@ const ALL_COMMANDS = {
|
|
|
15
15
|
generate: { label: "Regenerate docs", value: "ges generate --all", description: "Update all documentation and workflows" },
|
|
16
16
|
policy: { label: "Manage policies", value: "ges policy list", description: "List, install, or remove policy packs" },
|
|
17
17
|
update: { label: "Check updates", value: "ges update", description: "Check for GESF updates" },
|
|
18
|
+
governance: { label: "Governance", value: "ges governance list", description: "View approval provenance chains" },
|
|
18
19
|
};
|
|
19
20
|
function buildSteps(exclude) {
|
|
20
21
|
const steps = [];
|
|
@@ -55,6 +56,8 @@ export function getNextStepsForCommand(command, context) {
|
|
|
55
56
|
return buildSteps(["policy"]);
|
|
56
57
|
case "update":
|
|
57
58
|
return buildSteps(["update"]);
|
|
59
|
+
case "governance":
|
|
60
|
+
return buildSteps(["governance"]);
|
|
58
61
|
case "mcp-setup":
|
|
59
62
|
return buildSteps(["mcp-setup"]);
|
|
60
63
|
default:
|
|
@@ -65,10 +68,10 @@ export async function showNextStepsMenu(command, context) {
|
|
|
65
68
|
if (!isInteractive())
|
|
66
69
|
return;
|
|
67
70
|
const steps = getNextStepsForCommand(command, context);
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
divider();
|
|
72
|
+
label("What would you like to do next?");
|
|
70
73
|
const answer = await select({
|
|
71
|
-
message: "
|
|
74
|
+
message: "Choose your next action:",
|
|
72
75
|
choices: steps.map(step => ({
|
|
73
76
|
name: step.description ? `${step.label} — ${step.description}` : step.label,
|
|
74
77
|
value: step.value,
|
|
@@ -78,8 +81,10 @@ export async function showNextStepsMenu(command, context) {
|
|
|
78
81
|
console.log("");
|
|
79
82
|
return;
|
|
80
83
|
}
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
blank();
|
|
85
|
+
info("Running", GREEN(answer));
|
|
86
|
+
divider();
|
|
87
|
+
blank();
|
|
83
88
|
const { execSync } = await import("node:child_process");
|
|
84
89
|
try {
|
|
85
90
|
execSync(answer, { stdio: "inherit" });
|
package/dist/utils/prompts.d.ts
CHANGED
|
@@ -7,7 +7,9 @@ export declare function select<T = string>(options: {
|
|
|
7
7
|
choices: {
|
|
8
8
|
name: string;
|
|
9
9
|
value: T;
|
|
10
|
+
description?: string;
|
|
10
11
|
}[];
|
|
12
|
+
pageSize?: number;
|
|
11
13
|
}): Promise<T>;
|
|
12
14
|
export declare function checkbox<T = string>(options: {
|
|
13
15
|
message: string;
|
|
@@ -15,5 +17,11 @@ export declare function checkbox<T = string>(options: {
|
|
|
15
17
|
name: string;
|
|
16
18
|
value: T;
|
|
17
19
|
checked?: boolean;
|
|
20
|
+
description?: string;
|
|
18
21
|
}[];
|
|
22
|
+
pageSize?: number;
|
|
19
23
|
}): Promise<T[]>;
|
|
24
|
+
export declare function confirm(options: {
|
|
25
|
+
message: string;
|
|
26
|
+
default?: boolean;
|
|
27
|
+
}): Promise<boolean>;
|
package/dist/utils/prompts.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as readline from "node:readline";
|
|
2
|
+
import { chalk, DIM, GREEN, CYAN, GRAY } from "./ui.js";
|
|
2
3
|
function isInteractive() {
|
|
3
4
|
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
4
5
|
}
|
|
@@ -18,6 +19,17 @@ async function getInquirer() {
|
|
|
18
19
|
}
|
|
19
20
|
return cachedInquirer;
|
|
20
21
|
}
|
|
22
|
+
const selectTheme = {
|
|
23
|
+
prefix: { idle: chalk.gray("?"), done: chalk.green("✓") },
|
|
24
|
+
helpMode: "always",
|
|
25
|
+
};
|
|
26
|
+
const checkboxTheme = {
|
|
27
|
+
prefix: { idle: chalk.gray("?"), done: chalk.green("✓") },
|
|
28
|
+
helpMode: "always",
|
|
29
|
+
};
|
|
30
|
+
const inputTheme = {
|
|
31
|
+
prefix: { idle: chalk.gray("?"), done: chalk.green("✓") },
|
|
32
|
+
};
|
|
21
33
|
export async function input(options) {
|
|
22
34
|
if (!isInteractive()) {
|
|
23
35
|
return options.default ?? "";
|
|
@@ -27,9 +39,9 @@ export async function input(options) {
|
|
|
27
39
|
return inquirer.input({ message: options.message, default: options.default });
|
|
28
40
|
}
|
|
29
41
|
const rl = createRL();
|
|
30
|
-
const suffix = options.default ? ` (${options.default})` : "";
|
|
42
|
+
const suffix = options.default ? DIM(` (${options.default})`) : "";
|
|
31
43
|
return new Promise((resolve) => {
|
|
32
|
-
rl.question(` ${options.message}${suffix}: `, (answer) => {
|
|
44
|
+
rl.question(` ${GRAY("?")} ${options.message}${suffix}${GRAY(":")} `, (answer) => {
|
|
33
45
|
rl.close();
|
|
34
46
|
resolve(answer.trim() || options.default || "");
|
|
35
47
|
});
|
|
@@ -41,15 +53,21 @@ export async function select(options) {
|
|
|
41
53
|
}
|
|
42
54
|
const inquirer = await getInquirer();
|
|
43
55
|
if (inquirer) {
|
|
44
|
-
return inquirer.select({
|
|
56
|
+
return inquirer.select({
|
|
57
|
+
message: options.message,
|
|
58
|
+
choices: options.choices,
|
|
59
|
+
theme: selectTheme,
|
|
60
|
+
pageSize: options.pageSize ?? 10,
|
|
61
|
+
});
|
|
45
62
|
}
|
|
46
|
-
console.log(`\n ${options.message}:\n`);
|
|
63
|
+
console.log(`\n ${CYAN(options.message)}:\n`);
|
|
47
64
|
options.choices.forEach((c, i) => {
|
|
48
|
-
|
|
65
|
+
const num = GRAY(`${String(i + 1).padStart(2)}.`);
|
|
66
|
+
console.log(` ${num} ${c.name}`);
|
|
49
67
|
});
|
|
50
68
|
const rl = createRL();
|
|
51
69
|
return new Promise((resolve) => {
|
|
52
|
-
rl.question(`\n Enter choice [1-${options.choices.length}]: `, (answer) => {
|
|
70
|
+
rl.question(`\n ${GRAY("Enter choice")} [1-${options.choices.length}]: `, (answer) => {
|
|
53
71
|
rl.close();
|
|
54
72
|
const num = parseInt(answer.trim(), 10);
|
|
55
73
|
if (num >= 1 && num <= options.choices.length) {
|
|
@@ -67,16 +85,22 @@ export async function checkbox(options) {
|
|
|
67
85
|
}
|
|
68
86
|
const inquirer = await getInquirer();
|
|
69
87
|
if (inquirer) {
|
|
70
|
-
return inquirer.checkbox({
|
|
88
|
+
return inquirer.checkbox({
|
|
89
|
+
message: options.message,
|
|
90
|
+
choices: options.choices,
|
|
91
|
+
theme: checkboxTheme,
|
|
92
|
+
pageSize: options.pageSize ?? 10,
|
|
93
|
+
});
|
|
71
94
|
}
|
|
72
|
-
console.log(`\n ${options.message} (comma-separated numbers)
|
|
95
|
+
console.log(`\n ${CYAN(options.message)} ${GRAY("(comma-separated numbers)")}\n`);
|
|
73
96
|
options.choices.forEach((c, i) => {
|
|
74
|
-
const marker = c.checked ? "[x]" : "[ ]";
|
|
75
|
-
|
|
97
|
+
const marker = c.checked ? GREEN("[x]") : GRAY("[ ]");
|
|
98
|
+
const num = `${String(i + 1).padStart(2)}.`;
|
|
99
|
+
console.log(` ${marker} ${GRAY(num)} ${c.name}`);
|
|
76
100
|
});
|
|
77
101
|
const rl = createRL();
|
|
78
102
|
return new Promise((resolve) => {
|
|
79
|
-
rl.question(`\n Enter choices [1-${options.choices.length}]: `, (answer) => {
|
|
103
|
+
rl.question(`\n ${GRAY("Enter choices")} [1-${options.choices.length}]: `, (answer) => {
|
|
80
104
|
rl.close();
|
|
81
105
|
const trimmed = answer.trim();
|
|
82
106
|
if (!trimmed) {
|
|
@@ -92,3 +116,30 @@ export async function checkbox(options) {
|
|
|
92
116
|
});
|
|
93
117
|
});
|
|
94
118
|
}
|
|
119
|
+
export async function confirm(options) {
|
|
120
|
+
if (!isInteractive()) {
|
|
121
|
+
return options.default ?? false;
|
|
122
|
+
}
|
|
123
|
+
const inquirer = await getInquirer();
|
|
124
|
+
if (inquirer) {
|
|
125
|
+
return inquirer.confirm({
|
|
126
|
+
message: options.message,
|
|
127
|
+
default: options.default,
|
|
128
|
+
theme: inputTheme,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
const suffix = options.default !== undefined ? ` (${options.default ? "Y/n" : "y/N"})` : " (y/n)";
|
|
132
|
+
const rl = createRL();
|
|
133
|
+
return new Promise((resolve) => {
|
|
134
|
+
rl.question(` ${GRAY("?")} ${options.message}${suffix}: `, (answer) => {
|
|
135
|
+
rl.close();
|
|
136
|
+
const trimmed = answer.trim().toLowerCase();
|
|
137
|
+
if (!trimmed) {
|
|
138
|
+
resolve(options.default ?? false);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
resolve(trimmed === "y" || trimmed === "yes");
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
declare const DIM: import("chalk").ChalkInstance;
|
|
3
|
+
declare const BOLD: import("chalk").ChalkInstance;
|
|
4
|
+
declare const GREEN: import("chalk").ChalkInstance;
|
|
5
|
+
declare const RED: import("chalk").ChalkInstance;
|
|
6
|
+
declare const YELLOW: import("chalk").ChalkInstance;
|
|
7
|
+
declare const CYAN: import("chalk").ChalkInstance;
|
|
8
|
+
declare const MAGENTA: import("chalk").ChalkInstance;
|
|
9
|
+
declare const GRAY: import("chalk").ChalkInstance;
|
|
10
|
+
declare const icons: {
|
|
11
|
+
success: string;
|
|
12
|
+
error: string;
|
|
13
|
+
warn: string;
|
|
14
|
+
info: string;
|
|
15
|
+
arrow: string;
|
|
16
|
+
bullet: string;
|
|
17
|
+
check: string;
|
|
18
|
+
cross: string;
|
|
19
|
+
dash: string;
|
|
20
|
+
};
|
|
21
|
+
export declare function banner(title: string, subtitle?: string): void;
|
|
22
|
+
export declare function divider(width?: number): void;
|
|
23
|
+
export declare function blank(): void;
|
|
24
|
+
export declare function success(message: string, detail?: string): void;
|
|
25
|
+
export declare function error(message: string, detail?: string): void;
|
|
26
|
+
export declare function warn(message: string, detail?: string): void;
|
|
27
|
+
export declare function info(message: string, detail?: string): void;
|
|
28
|
+
export declare function step(n: number, total: number, message: string): void;
|
|
29
|
+
export declare function kv(key: string, value: string, indent?: number): void;
|
|
30
|
+
export declare function label(text: string, color?: typeof GREEN): void;
|
|
31
|
+
export declare function item(text: string, value?: string): void;
|
|
32
|
+
export declare function group(title: string, lines: string[]): void;
|
|
33
|
+
export declare function progressBar(current: number, total: number, width?: number): string;
|
|
34
|
+
export declare function statusBadge(status: string): string;
|
|
35
|
+
export declare function severityBadge(severity: string): string;
|
|
36
|
+
export declare function gradeColor(grade: string): string;
|
|
37
|
+
export { chalk, icons, DIM, BOLD, GREEN, RED, YELLOW, CYAN, MAGENTA, GRAY };
|
package/dist/utils/ui.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
const DIM = chalk.dim;
|
|
3
|
+
const BOLD = chalk.bold;
|
|
4
|
+
const GREEN = chalk.green;
|
|
5
|
+
const RED = chalk.red;
|
|
6
|
+
const YELLOW = chalk.yellow;
|
|
7
|
+
const CYAN = chalk.cyan;
|
|
8
|
+
const MAGENTA = chalk.magenta;
|
|
9
|
+
const GRAY = chalk.gray;
|
|
10
|
+
const icons = {
|
|
11
|
+
success: GREEN("✓"),
|
|
12
|
+
error: RED("✕"),
|
|
13
|
+
warn: YELLOW("!"),
|
|
14
|
+
info: CYAN("○"),
|
|
15
|
+
arrow: GRAY("→"),
|
|
16
|
+
bullet: GRAY("•"),
|
|
17
|
+
check: GREEN("✓"),
|
|
18
|
+
cross: RED("✕"),
|
|
19
|
+
dash: GRAY("—"),
|
|
20
|
+
};
|
|
21
|
+
export function banner(title, subtitle) {
|
|
22
|
+
const line = "═".repeat(52);
|
|
23
|
+
console.log();
|
|
24
|
+
console.log(CYAN(BOLD(` ${title}`)));
|
|
25
|
+
if (subtitle) {
|
|
26
|
+
console.log(DIM(` ${subtitle}`));
|
|
27
|
+
}
|
|
28
|
+
console.log(GRAY(` ${line}`));
|
|
29
|
+
console.log();
|
|
30
|
+
}
|
|
31
|
+
export function divider(width = 52) {
|
|
32
|
+
console.log(GRAY(` ${"─".repeat(width)}`));
|
|
33
|
+
}
|
|
34
|
+
export function blank() {
|
|
35
|
+
console.log();
|
|
36
|
+
}
|
|
37
|
+
export function success(message, detail) {
|
|
38
|
+
console.log(` ${icons.success} ${GREEN(message)}${detail ? DIM(` ${detail}`) : ""}`);
|
|
39
|
+
}
|
|
40
|
+
export function error(message, detail) {
|
|
41
|
+
console.error(` ${icons.error} ${RED(message)}${detail ? DIM(` ${detail}`) : ""}`);
|
|
42
|
+
}
|
|
43
|
+
export function warn(message, detail) {
|
|
44
|
+
console.log(` ${icons.warn} ${YELLOW(message)}${detail ? DIM(` ${detail}`) : ""}`);
|
|
45
|
+
}
|
|
46
|
+
export function info(message, detail) {
|
|
47
|
+
console.log(` ${icons.info} ${CYAN(message)}${detail ? DIM(` ${detail}`) : ""}`);
|
|
48
|
+
}
|
|
49
|
+
export function step(n, total, message) {
|
|
50
|
+
const counter = DIM(`[${n}/${total}]`);
|
|
51
|
+
console.log(`\n ${counter} ${BOLD(message)}`);
|
|
52
|
+
console.log(GRAY(` ${"─".repeat(40)}`));
|
|
53
|
+
}
|
|
54
|
+
export function kv(key, value, indent = 4) {
|
|
55
|
+
const pad = Math.max(key.length, 16);
|
|
56
|
+
console.log(`${" ".repeat(indent)}${DIM(key.padEnd(pad))} ${value}`);
|
|
57
|
+
}
|
|
58
|
+
export function label(text, color = CYAN) {
|
|
59
|
+
console.log(`\n ${color(BOLD(text))}`);
|
|
60
|
+
}
|
|
61
|
+
export function item(text, value) {
|
|
62
|
+
const v = value ? DIM(GRAY(` ${value}`)) : "";
|
|
63
|
+
console.log(` ${icons.bullet} ${text}${v}`);
|
|
64
|
+
}
|
|
65
|
+
export function group(title, lines) {
|
|
66
|
+
console.log(`\n ${BOLD(title)}`);
|
|
67
|
+
for (const line of lines) {
|
|
68
|
+
console.log(` ${icons.bullet} ${line}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export function progressBar(current, total, width = 30) {
|
|
72
|
+
const pct = Math.round((current / total) * 100);
|
|
73
|
+
const filled = Math.round((current / total) * width);
|
|
74
|
+
const empty = width - filled;
|
|
75
|
+
const bar = GREEN("█".repeat(filled)) + GRAY("░".repeat(empty));
|
|
76
|
+
return `${bar} ${pct}%`;
|
|
77
|
+
}
|
|
78
|
+
export function statusBadge(status) {
|
|
79
|
+
const badges = {
|
|
80
|
+
pass: GREEN("● PASS"),
|
|
81
|
+
fail: RED("● FAIL"),
|
|
82
|
+
warning: YELLOW("● WARN"),
|
|
83
|
+
"not-implemented": GRAY("○ N/I"),
|
|
84
|
+
"not-applicable": CYAN("◐ N/A"),
|
|
85
|
+
approved: GREEN("● APPROVED"),
|
|
86
|
+
rejected: RED("✕ REJECTED"),
|
|
87
|
+
conditional: YELLOW("◔ CONDITIONAL"),
|
|
88
|
+
"pending-review": YELLOW("◐ PENDING"),
|
|
89
|
+
draft: GRAY("○ DRAFT"),
|
|
90
|
+
expired: RED("⚠ EXPIRED"),
|
|
91
|
+
valid: GREEN("✓ VALID"),
|
|
92
|
+
};
|
|
93
|
+
return badges[status] || GRAY(`○ ${status.toUpperCase()}`);
|
|
94
|
+
}
|
|
95
|
+
export function severityBadge(severity) {
|
|
96
|
+
const badges = {
|
|
97
|
+
critical: RED(BOLD("CRITICAL")),
|
|
98
|
+
high: RED("HIGH"),
|
|
99
|
+
medium: YELLOW("MEDIUM"),
|
|
100
|
+
low: CYAN("LOW"),
|
|
101
|
+
};
|
|
102
|
+
return badges[severity] || GRAY(severity.toUpperCase());
|
|
103
|
+
}
|
|
104
|
+
export function gradeColor(grade) {
|
|
105
|
+
if (grade === "A")
|
|
106
|
+
return GREEN(BOLD(grade));
|
|
107
|
+
if (grade === "B")
|
|
108
|
+
return CYAN(BOLD(grade));
|
|
109
|
+
if (grade === "C")
|
|
110
|
+
return YELLOW(BOLD(grade));
|
|
111
|
+
if (grade === "D")
|
|
112
|
+
return MAGENTA(BOLD(grade));
|
|
113
|
+
return RED(BOLD(grade));
|
|
114
|
+
}
|
|
115
|
+
export { chalk, icons, DIM, BOLD, GREEN, RED, YELLOW, CYAN, MAGENTA, GRAY };
|
package/package.json
CHANGED
|
@@ -3,19 +3,20 @@
|
|
|
3
3
|
"ges": "./dist/cli.js"
|
|
4
4
|
},
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@greenarmor/ges-audit-engine": "1.4.
|
|
7
|
-
"@greenarmor/ges-cicd-generator": "1.4.
|
|
8
|
-
"@greenarmor/ges-compliance-engine": "1.4.
|
|
9
|
-
"@greenarmor/ges-core": "1.4.
|
|
10
|
-
"@greenarmor/ges-doc-generator": "1.4.
|
|
11
|
-
"@greenarmor/ges-git-hooks": "1.4.
|
|
12
|
-
"@greenarmor/ges-mcp-server": "1.4.
|
|
13
|
-
"@greenarmor/ges-policy-engine": "1.4.
|
|
14
|
-
"@greenarmor/ges-report-generator": "1.4.
|
|
15
|
-
"@greenarmor/ges-rules-engine": "1.4.
|
|
16
|
-
"@greenarmor/ges-scanner-integration": "1.4.
|
|
17
|
-
"@greenarmor/ges-scoring-engine": "1.4.
|
|
18
|
-
"@greenarmor/ges-web-dashboard": "1.4.
|
|
6
|
+
"@greenarmor/ges-audit-engine": "1.4.2",
|
|
7
|
+
"@greenarmor/ges-cicd-generator": "1.4.2",
|
|
8
|
+
"@greenarmor/ges-compliance-engine": "1.4.2",
|
|
9
|
+
"@greenarmor/ges-core": "1.4.2",
|
|
10
|
+
"@greenarmor/ges-doc-generator": "1.4.2",
|
|
11
|
+
"@greenarmor/ges-git-hooks": "1.4.2",
|
|
12
|
+
"@greenarmor/ges-mcp-server": "1.4.2",
|
|
13
|
+
"@greenarmor/ges-policy-engine": "1.4.2",
|
|
14
|
+
"@greenarmor/ges-report-generator": "1.4.2",
|
|
15
|
+
"@greenarmor/ges-rules-engine": "1.4.2",
|
|
16
|
+
"@greenarmor/ges-scanner-integration": "1.4.2",
|
|
17
|
+
"@greenarmor/ges-scoring-engine": "1.4.2",
|
|
18
|
+
"@greenarmor/ges-web-dashboard": "1.4.2",
|
|
19
|
+
"chalk": "^5.6.2",
|
|
19
20
|
"commander": "^13.0.0"
|
|
20
21
|
},
|
|
21
22
|
"description": "Green Engineering Standard Framework - Compliance-as-Code CLI",
|
|
@@ -24,6 +25,9 @@
|
|
|
24
25
|
"typescript": "^6.0.0",
|
|
25
26
|
"vitest": "^4.1.8"
|
|
26
27
|
},
|
|
28
|
+
"optionalDependencies": {
|
|
29
|
+
"@inquirer/prompts": "^7.10.1"
|
|
30
|
+
},
|
|
27
31
|
"engines": {
|
|
28
32
|
"node": ">=20.0.0"
|
|
29
33
|
},
|
|
@@ -53,7 +57,7 @@
|
|
|
53
57
|
},
|
|
54
58
|
"type": "module",
|
|
55
59
|
"types": "./dist/index.d.ts",
|
|
56
|
-
"version": "1.4.
|
|
60
|
+
"version": "1.4.2",
|
|
57
61
|
"scripts": {
|
|
58
62
|
"build": "tsc",
|
|
59
63
|
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|