@greenarmor/ges 1.5.2 → 1.5.3
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/assign.js +6 -6
- package/dist/commands/governance.js +118 -33
- package/dist/commands/hooks.js +44 -0
- package/dist/commands/mcp.js +29 -0
- package/dist/commands/policy.js +83 -2
- package/package.json +14 -14
package/dist/commands/assign.js
CHANGED
|
@@ -151,15 +151,15 @@ async function assignFinding(root, options) {
|
|
|
151
151
|
errorOut("Governance record not found.");
|
|
152
152
|
return;
|
|
153
153
|
}
|
|
154
|
-
const assignee = options.assignee || await input({ message: "Assignee name:" });
|
|
154
|
+
const assignee = options.assignee || await input({ message: "Assignee name (who will fix this):" });
|
|
155
155
|
if (!assignee.trim()) {
|
|
156
|
-
errorOut("Assignee name is required.");
|
|
156
|
+
errorOut("Assignee name is required.", "Please provide the name of the person responsible for this fix.");
|
|
157
157
|
return;
|
|
158
158
|
}
|
|
159
|
-
const assigneeRole = options.assigneeRole || await input({ message: "Assignee role (optional):" });
|
|
160
|
-
const notes = options.notes || await input({ message: "Notes (optional):" });
|
|
161
|
-
const actorName = options.actor || await input({ message: "Your name (for audit trail):" });
|
|
162
|
-
const actorRole = options.actorRole || await input({ message: "Your role (optional):" });
|
|
159
|
+
const assigneeRole = options.assigneeRole || await input({ message: "Assignee role (optional, e.g., 'Security Engineer'):", default: "" });
|
|
160
|
+
const notes = options.notes || await input({ message: "Notes (optional, e.g., 'Urgent — fix before release'):", default: "" });
|
|
161
|
+
const actorName = options.actor || await input({ message: "Your name (for audit trail):", default: "cli-user" });
|
|
162
|
+
const actorRole = options.actorRole || await input({ message: "Your role (optional):", default: "" });
|
|
163
163
|
const assignment = createFixAssignment({
|
|
164
164
|
finding_key: fkey,
|
|
165
165
|
finding_rule_id: selectedFinding.ruleId,
|
|
@@ -1,7 +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
|
+
import { banner, divider, blank, success, error, warn, info, kv, statusBadge, severityBadge, BOLD, DIM, GREEN, RED, YELLOW, GRAY, } from "../utils/ui.js";
|
|
5
5
|
import { loadGovernanceRecords, createGovernanceRecord, addGovernanceRecord, findGovernanceRecord, setGovernanceApproval, addGovernanceEvidence, createEvidenceRef, verifyGovernanceRecord, deleteGovernanceRecord, setGovernanceRiskAssessment, setGovernancePolicyBasis, setGovernanceReviewCycle, setGovernanceDataInventory, setGovernanceComplianceLinks, setGovernanceCommittee, } from "@greenarmor/ges-core";
|
|
6
6
|
import { recordActivity } from "@greenarmor/ges-core";
|
|
7
7
|
const STATUS_BADGE = {
|
|
@@ -34,6 +34,91 @@ function printRecordSummary(record) {
|
|
|
34
34
|
}
|
|
35
35
|
export const governanceCommand = new Command("governance")
|
|
36
36
|
.description("Manage governance approval provenance chains")
|
|
37
|
+
.action(async () => {
|
|
38
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
39
|
+
governanceCommand.outputHelp();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
banner("GESF Governance", "Provenance Chain Management");
|
|
43
|
+
let root;
|
|
44
|
+
try {
|
|
45
|
+
root = ensureGESInitialized();
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
error("GESF is not initialized.", "Run `ges init` first.");
|
|
49
|
+
blank();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const records = loadGovernanceRecords(root);
|
|
53
|
+
if (records.length > 0) {
|
|
54
|
+
console.log(` ${BOLD("Existing Records")} ${GRAY(`(${records.length})`)}`);
|
|
55
|
+
records.forEach(r => printRecordSummary(r));
|
|
56
|
+
console.log("");
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
warn("No governance records yet.", "Create one to start building a provenance chain.");
|
|
60
|
+
blank();
|
|
61
|
+
}
|
|
62
|
+
const action = await select({
|
|
63
|
+
message: "What would you like to do?",
|
|
64
|
+
choices: [
|
|
65
|
+
{ name: `Create a new governance record ${DIM("— start a new approval chain")}`, value: "add" },
|
|
66
|
+
...(records.length > 0 ? [
|
|
67
|
+
{ name: `List all records ${DIM(`(${records.length} existing)`)}`, value: "list" },
|
|
68
|
+
{ name: `Show a record's full provenance chain ${DIM("— all dimensions in detail")}`, value: "show" },
|
|
69
|
+
{ name: `Verify a record's completeness ${DIM("— check all 8 dimensions")}`, value: "verify" },
|
|
70
|
+
{ name: `Record an approval decision ${DIM("— who approved, under what authority")}`, value: "approve" },
|
|
71
|
+
{ name: `Add an evidence reference ${DIM("— link to Jira, Confluence, etc.")}`, value: "evidence" },
|
|
72
|
+
{ name: `Link a risk assessment ${DIM("— assessor, methodology, score")}`, value: "risk-assessment" },
|
|
73
|
+
{ name: `Document the policy basis ${DIM("— which policy/standard applies")}`, value: "policy-basis" },
|
|
74
|
+
{ name: `Set up a review cycle ${DIM("— when to re-review")}`, value: "review-cycle" },
|
|
75
|
+
{ name: `Document data inventory ${DIM("— what personal data is processed")}`, value: "data-inventory" },
|
|
76
|
+
{ name: `Record committee approval ${DIM("— formal committee sign-off")}`, value: "committee" },
|
|
77
|
+
{ name: `Map compliance frameworks ${DIM("— GDPR, OWASP, etc.")}`, value: "compliance-links" },
|
|
78
|
+
{ name: `Delete a record ${DIM("— permanently remove")}`, value: "delete" },
|
|
79
|
+
] : []),
|
|
80
|
+
{ name: `${YELLOW("Exit")} ${DIM("— return to terminal")}`, value: "exit" },
|
|
81
|
+
],
|
|
82
|
+
});
|
|
83
|
+
if (action === "exit") {
|
|
84
|
+
blank();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
let cmd = `ges governance ${action}`;
|
|
88
|
+
if (["show", "verify", "approve", "evidence", "risk-assessment", "policy-basis", "review-cycle", "data-inventory", "committee", "compliance-links", "delete"].includes(action)) {
|
|
89
|
+
if (records.length === 0) {
|
|
90
|
+
error("No records to work with.", "Create one first with: ges governance add");
|
|
91
|
+
blank();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (records.length === 1) {
|
|
95
|
+
cmd += ` ${records[0].id}`;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
const recordChoice = await select({
|
|
99
|
+
message: "Select a record:",
|
|
100
|
+
choices: [
|
|
101
|
+
...records.map(r => ({
|
|
102
|
+
name: `${r.system_name} ${GRAY(`(${r.status}, ${r.risk_level})`)}`,
|
|
103
|
+
value: r.id,
|
|
104
|
+
})),
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
cmd += ` ${recordChoice}`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
blank();
|
|
111
|
+
info("Running", GREEN(cmd));
|
|
112
|
+
divider();
|
|
113
|
+
blank();
|
|
114
|
+
const { execSync } = await import("node:child_process");
|
|
115
|
+
try {
|
|
116
|
+
execSync(cmd, { stdio: "inherit" });
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
})
|
|
37
122
|
.addCommand(new Command("add")
|
|
38
123
|
.description("Create a new governance record")
|
|
39
124
|
.option("-n, --name <name>", "System name")
|
|
@@ -119,10 +204,10 @@ export const governanceCommand = new Command("governance")
|
|
|
119
204
|
console.error(` Error: Governance record "${id}" not found.`);
|
|
120
205
|
process.exit(1);
|
|
121
206
|
}
|
|
122
|
-
const approverName = options.approver || await input({ message: "Approver name:", default: "" });
|
|
123
|
-
const approverRole = options.role || await input({ message: "Approver role:", default: "" });
|
|
124
|
-
const approverEmail = options.email || await input({ message: "Approver email:", default: "" });
|
|
125
|
-
const authority = options.authority || await input({ message: "Approval authority:", default: "" });
|
|
207
|
+
const approverName = options.approver || await input({ message: "Approver full name:", default: "" });
|
|
208
|
+
const approverRole = options.role || await input({ message: "Approver role/title (e.g., 'CISO', 'CTO'):", default: "" });
|
|
209
|
+
const approverEmail = options.email || await input({ message: "Approver email (optional):", default: "" });
|
|
210
|
+
const authority = options.authority || await input({ message: "Approval authority (e.g., 'AI Ethics Board', 'Security Committee'):", default: "" });
|
|
126
211
|
const decision = (options.decision || await select({
|
|
127
212
|
message: "Decision:",
|
|
128
213
|
choices: [
|
|
@@ -132,9 +217,9 @@ export const governanceCommand = new Command("governance")
|
|
|
132
217
|
],
|
|
133
218
|
}));
|
|
134
219
|
const validFrom = options.validFrom || new Date().toISOString().split("T")[0];
|
|
135
|
-
const validUntil = options.validUntil || await input({ message: "Valid until
|
|
136
|
-
const conditionsStr = options.conditions || await input({ message: "Conditions (comma-separated):", default: "" });
|
|
137
|
-
const rationale = options.rationale || await input({ message: "Rationale:", default: "" });
|
|
220
|
+
const validUntil = options.validUntil || await input({ message: "Valid until YYYY-MM-DD (or press Enter for indefinite):", default: "" });
|
|
221
|
+
const conditionsStr = options.conditions || await input({ message: "Conditions (comma-separated, or press Enter to skip):", default: "" });
|
|
222
|
+
const rationale = options.rationale || await input({ message: "Rationale (why was this decision made?):", default: "" });
|
|
138
223
|
const updated = setGovernanceApproval(root, record.id, {
|
|
139
224
|
approver_name: approverName,
|
|
140
225
|
approver_role: approverRole,
|
|
@@ -182,7 +267,7 @@ export const governanceCommand = new Command("governance")
|
|
|
182
267
|
console.error(` Error: Governance record "${id}" not found.`);
|
|
183
268
|
process.exit(1);
|
|
184
269
|
}
|
|
185
|
-
const title = options.title || await input({ message: "Evidence title:", default: "" });
|
|
270
|
+
const title = options.title || await input({ message: "Evidence title (e.g., 'DPIA Report Q4 2026'):", default: "" });
|
|
186
271
|
const sourceSystem = (options.source || await select({
|
|
187
272
|
message: "Source system:",
|
|
188
273
|
choices: [
|
|
@@ -198,7 +283,7 @@ export const governanceCommand = new Command("governance")
|
|
|
198
283
|
{ name: "Other", value: "other" },
|
|
199
284
|
],
|
|
200
285
|
}));
|
|
201
|
-
const reference = options.reference || await input({ message: "Reference (ticket ID, URL,
|
|
286
|
+
const reference = options.reference || await input({ message: "Reference (ticket ID, URL, or document path):", default: "" });
|
|
202
287
|
const evidenceType = await select({
|
|
203
288
|
message: "Evidence type:",
|
|
204
289
|
choices: [
|
|
@@ -214,7 +299,7 @@ export const governanceCommand = new Command("governance")
|
|
|
214
299
|
{ name: "Other", value: "other" },
|
|
215
300
|
],
|
|
216
301
|
});
|
|
217
|
-
const locationDesc = await input({ message: "Location description:", default: "" });
|
|
302
|
+
const locationDesc = await input({ message: "Location description (where to find it, optional):", default: "" });
|
|
218
303
|
const evidence = createEvidenceRef({
|
|
219
304
|
type: evidenceType,
|
|
220
305
|
title,
|
|
@@ -507,12 +592,12 @@ export const governanceCommand = new Command("governance")
|
|
|
507
592
|
console.error(` Error: Governance record "${id}" not found.`);
|
|
508
593
|
process.exit(1);
|
|
509
594
|
}
|
|
510
|
-
const assessor = options.assessor || await input({ message: "
|
|
511
|
-
const methodology = options.methodology || await input({ message: "Methodology:", default: "" });
|
|
512
|
-
const score = options.score || await input({ message: "Risk score:", default: "" });
|
|
513
|
-
const residual = options.residual || await input({ message: "Residual risk level:", default: "" });
|
|
514
|
-
const risksStr = await input({ message: "Identified risks (comma-separated):", default: "" });
|
|
515
|
-
const mitigationsStr = await input({ message: "Mitigation measures (comma-separated):", default: "" });
|
|
595
|
+
const assessor = options.assessor || await input({ message: "Risk assessor name:", default: "" });
|
|
596
|
+
const methodology = options.methodology || await input({ message: "Methodology (e.g., 'NIST RMF', 'ISO 27005'):", default: "" });
|
|
597
|
+
const score = options.score || await input({ message: "Risk score (e.g., '7.5/10', 'High'):", default: "" });
|
|
598
|
+
const residual = options.residual || await input({ message: "Residual risk level (low/medium/high):", default: "" });
|
|
599
|
+
const risksStr = await input({ message: "Identified risks (comma-separated, or Enter to skip):", default: "" });
|
|
600
|
+
const mitigationsStr = await input({ message: "Mitigation measures (comma-separated, or Enter to skip):", default: "" });
|
|
516
601
|
const updated = setGovernanceRiskAssessment(root, record.id, {
|
|
517
602
|
id: `risk-${Date.now()}`,
|
|
518
603
|
assessor,
|
|
@@ -552,11 +637,11 @@ export const governanceCommand = new Command("governance")
|
|
|
552
637
|
console.error(` Error: Governance record "${id}" not found.`);
|
|
553
638
|
process.exit(1);
|
|
554
639
|
}
|
|
555
|
-
const policyId = options.policyId || await input({ message: "Policy ID:", default: "" });
|
|
556
|
-
const policyName = options.policyName || await input({ message: "Policy name:", default: "" });
|
|
640
|
+
const policyId = options.policyId || await input({ message: "Policy ID (e.g., 'POL-SEC-001'):", default: "" });
|
|
641
|
+
const policyName = options.policyName || await input({ message: "Policy name (e.g., 'Information Security Policy'):", default: "" });
|
|
557
642
|
const version = options.pv || await input({ message: "Policy version:", default: "1.0" });
|
|
558
|
-
const standard = options.standard || await input({ message: "Standard:", default: "" });
|
|
559
|
-
const clausesStr = await input({ message: "Applicable clauses (comma-separated):", default: "" });
|
|
643
|
+
const standard = options.standard || await input({ message: "Standard (e.g., 'GDPR', 'ISO 27001'):", default: "" });
|
|
644
|
+
const clausesStr = await input({ message: "Applicable clauses (comma-separated, or Enter to skip):", default: "" });
|
|
560
645
|
const updated = setGovernancePolicyBasis(root, record.id, {
|
|
561
646
|
policy_id: policyId,
|
|
562
647
|
policy_name: policyName,
|
|
@@ -633,11 +718,11 @@ export const governanceCommand = new Command("governance")
|
|
|
633
718
|
console.error(` Error: Governance record "${id}" not found.`);
|
|
634
719
|
process.exit(1);
|
|
635
720
|
}
|
|
636
|
-
const categoriesStr = options.categories || await input({ message: "Personal data categories (
|
|
637
|
-
const purposesStr = options.purposes || await input({ message: "Processing purposes (
|
|
638
|
-
const subjectsStr = await input({ message: "Data subjects (
|
|
639
|
-
const transfersStr = await input({ message: "Cross-border transfers (
|
|
640
|
-
const retention = options.retention || await input({ message: "Retention period:", default: "" });
|
|
721
|
+
const categoriesStr = options.categories || await input({ message: "Personal data categories (e.g., 'names,emails,IP addresses'):", default: "" });
|
|
722
|
+
const purposesStr = options.purposes || await input({ message: "Processing purposes (e.g., 'user auth,analytics'):", default: "" });
|
|
723
|
+
const subjectsStr = await input({ message: "Data subjects (e.g., 'customers,employees', or Enter to skip):", default: "" });
|
|
724
|
+
const transfersStr = await input({ message: "Cross-border transfers (e.g., 'US,EU', or Enter to skip):", default: "" });
|
|
725
|
+
const retention = options.retention || await input({ message: "Retention period (e.g., '2 years', '90 days'):", default: "" });
|
|
641
726
|
const updated = setGovernanceDataInventory(root, record.id, {
|
|
642
727
|
personal_data_categories: categoriesStr ? categoriesStr.split(",").map((s) => s.trim()).filter(Boolean) : [],
|
|
643
728
|
processing_purposes: purposesStr ? purposesStr.split(",").map((s) => s.trim()).filter(Boolean) : [],
|
|
@@ -669,11 +754,11 @@ export const governanceCommand = new Command("governance")
|
|
|
669
754
|
console.error(` Error: Governance record "${id}" not found.`);
|
|
670
755
|
process.exit(1);
|
|
671
756
|
}
|
|
672
|
-
const committeeName = options.committee || await input({ message: "Committee name:", default: "" });
|
|
673
|
-
const meetingRef = options.meetingRef || await input({ message: "Meeting reference:", default: "" });
|
|
757
|
+
const committeeName = options.committee || await input({ message: "Committee name (e.g., 'AI Ethics Board'):", default: "" });
|
|
758
|
+
const meetingRef = options.meetingRef || await input({ message: "Meeting reference (e.g., 'MIN-2026-001'):", default: "" });
|
|
674
759
|
const meetingDate = options.meetingDate || await input({ message: "Meeting date (YYYY-MM-DD):", default: "" });
|
|
675
|
-
const attendeesStr = await input({ message: "Attendees (comma-separated):", default: "" });
|
|
676
|
-
const summary = await input({ message: "Decision summary:", default: "" });
|
|
760
|
+
const attendeesStr = await input({ message: "Attendees (comma-separated names, or Enter to skip):", default: "" });
|
|
761
|
+
const summary = await input({ message: "Decision summary (what was decided):", default: "" });
|
|
677
762
|
const updated = setGovernanceCommittee(root, record.id, {
|
|
678
763
|
committee_name: committeeName,
|
|
679
764
|
meeting_date: meetingDate,
|
|
@@ -707,9 +792,9 @@ export const governanceCommand = new Command("governance")
|
|
|
707
792
|
console.error(` Error: Governance record "${id}" not found.`);
|
|
708
793
|
process.exit(1);
|
|
709
794
|
}
|
|
710
|
-
const frameworksStr = options.frameworks || await input({ message: "Frameworks (
|
|
711
|
-
const controlsStr = options.controls || await input({ message: "Controls satisfied (
|
|
712
|
-
const packsStr = await input({ message: "Control pack IDs (comma-separated):", default: "" });
|
|
795
|
+
const frameworksStr = options.frameworks || await input({ message: "Frameworks (e.g., 'GDPR,OWASP'):", default: "" });
|
|
796
|
+
const controlsStr = options.controls || await input({ message: "Controls satisfied (e.g., 'GDPR-ART32-002', or Enter to skip):", default: "" });
|
|
797
|
+
const packsStr = await input({ message: "Control pack IDs (comma-separated, or Enter to skip):", default: "" });
|
|
713
798
|
const updated = setGovernanceComplianceLinks(root, record.id, {
|
|
714
799
|
frameworks: frameworksStr ? frameworksStr.split(",").map((s) => s.trim()).filter(Boolean) : [],
|
|
715
800
|
controls_satisfied: controlsStr ? controlsStr.split(",").map((s) => s.trim()).filter(Boolean) : [],
|
package/dist/commands/hooks.js
CHANGED
|
@@ -2,8 +2,52 @@ import { Command } from "commander";
|
|
|
2
2
|
import { ensureGESInitialized } from "../utils/project.js";
|
|
3
3
|
import { installHooks, uninstallHooks } from "@greenarmor/ges-git-hooks";
|
|
4
4
|
import { recordActivity } from "@greenarmor/ges-core";
|
|
5
|
+
import { banner, blank, DIM, YELLOW } from "../utils/ui.js";
|
|
6
|
+
import { select } from "../utils/prompts.js";
|
|
7
|
+
import * as fs from "node:fs";
|
|
8
|
+
import * as path from "node:path";
|
|
5
9
|
export const hooksCommand = new Command("hooks")
|
|
6
10
|
.description("Manage GESF git hooks (pre-commit compliance enforcement)")
|
|
11
|
+
.action(async () => {
|
|
12
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
13
|
+
hooksCommand.outputHelp();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
banner("Git Hooks", "Pre-commit compliance enforcement");
|
|
17
|
+
let root;
|
|
18
|
+
try {
|
|
19
|
+
root = ensureGESInitialized();
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
const { error } = await import("../utils/ui.js");
|
|
23
|
+
error("GESF is not initialized.", "Run `ges init` first.");
|
|
24
|
+
blank();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const hookPath = path.join(root, ".git", "hooks", "pre-commit");
|
|
28
|
+
const isInstalled = fs.existsSync(hookPath);
|
|
29
|
+
const action = await select({
|
|
30
|
+
message: "What would you like to do?",
|
|
31
|
+
choices: [
|
|
32
|
+
...(isInstalled
|
|
33
|
+
? [{ name: `Uninstall pre-commit hook ${DIM("— remove compliance gate")}`, value: "uninstall" }]
|
|
34
|
+
: [{ name: `Install pre-commit hook ${DIM("— blocks commits with critical findings")}`, value: "install" }]),
|
|
35
|
+
{ name: `${YELLOW("Exit")} ${DIM("— return to terminal")}`, value: "exit" },
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
if (action === "exit") {
|
|
39
|
+
blank();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
blank();
|
|
43
|
+
const { execSync } = await import("node:child_process");
|
|
44
|
+
try {
|
|
45
|
+
execSync(`ges hooks ${action}`, { stdio: "inherit" });
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
})
|
|
7
51
|
.addCommand(new Command("install")
|
|
8
52
|
.description("Install the pre-commit hook that runs ges audit before each commit")
|
|
9
53
|
.action(async () => {
|
package/dist/commands/mcp.js
CHANGED
|
@@ -2,8 +2,37 @@ import { Command } from "commander";
|
|
|
2
2
|
import * as readline from "node:readline";
|
|
3
3
|
import { handleRequest } from "@greenarmor/ges-mcp-server";
|
|
4
4
|
import { mcpSetupCommand } from "./mcp-setup.js";
|
|
5
|
+
import { banner, blank, DIM, YELLOW } from "../utils/ui.js";
|
|
6
|
+
import { select } from "../utils/prompts.js";
|
|
5
7
|
export const mcpCommand = new Command("mcp")
|
|
6
8
|
.description("MCP AI Compliance Assistant")
|
|
9
|
+
.action(async () => {
|
|
10
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
11
|
+
mcpCommand.outputHelp();
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
banner("GESF MCP Server", "AI Compliance Assistant Integration");
|
|
15
|
+
const action = await select({
|
|
16
|
+
message: "What would you like to do?",
|
|
17
|
+
choices: [
|
|
18
|
+
{ name: `Setup MCP clients ${DIM("— configure Claude, Cursor, VS Code, etc.")}`, value: "setup" },
|
|
19
|
+
{ name: `Start MCP server ${DIM("— JSON-RPC over stdio (for advanced use)")}`, value: "start" },
|
|
20
|
+
{ name: `${YELLOW("Exit")} ${DIM("— return to terminal")}`, value: "exit" },
|
|
21
|
+
],
|
|
22
|
+
});
|
|
23
|
+
if (action === "exit") {
|
|
24
|
+
blank();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
blank();
|
|
28
|
+
const { execSync } = await import("node:child_process");
|
|
29
|
+
try {
|
|
30
|
+
execSync(`ges mcp ${action}`, { stdio: "inherit" });
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
})
|
|
7
36
|
.addCommand(new Command("start")
|
|
8
37
|
.description("Start the GESF MCP server (JSON-RPC over stdio)")
|
|
9
38
|
.action(() => {
|
package/dist/commands/policy.js
CHANGED
|
@@ -3,11 +3,92 @@ 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
|
+
import { banner, blank, success, error, BOLD, CYAN, DIM, GRAY, YELLOW } from "../utils/ui.js";
|
|
7
|
+
import { select } from "../utils/prompts.js";
|
|
7
8
|
import * as fs from "node:fs";
|
|
8
9
|
import * as path from "node:path";
|
|
9
10
|
const policyCmd = new Command("policy")
|
|
10
|
-
.description("Manage policy packs")
|
|
11
|
+
.description("Manage policy packs")
|
|
12
|
+
.action(async () => {
|
|
13
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
14
|
+
policyCmd.outputHelp();
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
banner("Policy Packs", "Compliance control management");
|
|
18
|
+
let root;
|
|
19
|
+
try {
|
|
20
|
+
root = ensureGESInitialized();
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
error("GESF is not initialized.", "Run `ges init` first.");
|
|
24
|
+
blank();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const installedPacks = fs.existsSync(path.join(root, "controls"))
|
|
28
|
+
? fs.readdirSync(path.join(root, "controls")).filter(d => {
|
|
29
|
+
try {
|
|
30
|
+
return fs.statSync(path.join(root, "controls", d)).isDirectory();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
: [];
|
|
37
|
+
if (installedPacks.length > 0) {
|
|
38
|
+
console.log(` ${BOLD("Installed Packs")} ${GRAY(`(${installedPacks.length})`)}`);
|
|
39
|
+
installedPacks.forEach(p => console.log(` ${GRAY("•")} ${p}`));
|
|
40
|
+
console.log("");
|
|
41
|
+
}
|
|
42
|
+
const action = await select({
|
|
43
|
+
message: "What would you like to do?",
|
|
44
|
+
choices: [
|
|
45
|
+
{ name: `List all available packs ${DIM("— see what can be installed")}`, value: "list" },
|
|
46
|
+
{ name: `Install a pack ${DIM("— add compliance controls")}`, value: "install" },
|
|
47
|
+
...(installedPacks.length > 0 ? [
|
|
48
|
+
{ name: `Remove a pack ${DIM(`(${installedPacks.length} installed)`)}`, value: "remove" },
|
|
49
|
+
] : []),
|
|
50
|
+
{ name: `${YELLOW("Exit")} ${DIM("— return to terminal")}`, value: "exit" },
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
if (action === "exit") {
|
|
54
|
+
blank();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
let cmd = "ges policy";
|
|
58
|
+
if (action === "list") {
|
|
59
|
+
cmd += " list";
|
|
60
|
+
}
|
|
61
|
+
else if (action === "install") {
|
|
62
|
+
const packs = getAllPacks();
|
|
63
|
+
const packChoice = await select({
|
|
64
|
+
message: "Select a pack to install:",
|
|
65
|
+
choices: [
|
|
66
|
+
...packs.map(p => ({
|
|
67
|
+
name: `${p.id.padEnd(16)} ${DIM(p.name)} ${GRAY(`(${p.controls.length} controls)`)}`,
|
|
68
|
+
value: p.id,
|
|
69
|
+
})),
|
|
70
|
+
],
|
|
71
|
+
});
|
|
72
|
+
cmd += ` install ${packChoice}`;
|
|
73
|
+
}
|
|
74
|
+
else if (action === "remove") {
|
|
75
|
+
const packChoice = await select({
|
|
76
|
+
message: "Select a pack to remove:",
|
|
77
|
+
choices: [
|
|
78
|
+
...installedPacks.map(p => ({ name: p, value: p })),
|
|
79
|
+
],
|
|
80
|
+
});
|
|
81
|
+
cmd += ` remove ${packChoice}`;
|
|
82
|
+
}
|
|
83
|
+
blank();
|
|
84
|
+
const { execSync } = await import("node:child_process");
|
|
85
|
+
try {
|
|
86
|
+
execSync(cmd, { stdio: "inherit" });
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
11
92
|
policyCmd
|
|
12
93
|
.command("list")
|
|
13
94
|
.description("List available policy packs")
|
package/package.json
CHANGED
|
@@ -3,19 +3,19 @@
|
|
|
3
3
|
"ges": "./dist/cli.js"
|
|
4
4
|
},
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@greenarmor/ges-audit-engine": "1.5.
|
|
7
|
-
"@greenarmor/ges-cicd-generator": "1.5.
|
|
8
|
-
"@greenarmor/ges-compliance-engine": "1.5.
|
|
9
|
-
"@greenarmor/ges-core": "1.5.
|
|
10
|
-
"@greenarmor/ges-doc-generator": "1.5.
|
|
11
|
-
"@greenarmor/ges-git-hooks": "1.5.
|
|
12
|
-
"@greenarmor/ges-mcp-server": "1.5.
|
|
13
|
-
"@greenarmor/ges-policy-engine": "1.5.
|
|
14
|
-
"@greenarmor/ges-report-generator": "1.5.
|
|
15
|
-
"@greenarmor/ges-rules-engine": "1.5.
|
|
16
|
-
"@greenarmor/ges-scanner-integration": "1.5.
|
|
17
|
-
"@greenarmor/ges-scoring-engine": "1.5.
|
|
18
|
-
"@greenarmor/ges-web-dashboard": "1.5.
|
|
6
|
+
"@greenarmor/ges-audit-engine": "1.5.3",
|
|
7
|
+
"@greenarmor/ges-cicd-generator": "1.5.3",
|
|
8
|
+
"@greenarmor/ges-compliance-engine": "1.5.3",
|
|
9
|
+
"@greenarmor/ges-core": "1.5.3",
|
|
10
|
+
"@greenarmor/ges-doc-generator": "1.5.3",
|
|
11
|
+
"@greenarmor/ges-git-hooks": "1.5.3",
|
|
12
|
+
"@greenarmor/ges-mcp-server": "1.5.3",
|
|
13
|
+
"@greenarmor/ges-policy-engine": "1.5.3",
|
|
14
|
+
"@greenarmor/ges-report-generator": "1.5.3",
|
|
15
|
+
"@greenarmor/ges-rules-engine": "1.5.3",
|
|
16
|
+
"@greenarmor/ges-scanner-integration": "1.5.3",
|
|
17
|
+
"@greenarmor/ges-scoring-engine": "1.5.3",
|
|
18
|
+
"@greenarmor/ges-web-dashboard": "1.5.3",
|
|
19
19
|
"chalk": "^5.6.2",
|
|
20
20
|
"commander": "^13.0.0"
|
|
21
21
|
},
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
},
|
|
61
61
|
"type": "module",
|
|
62
62
|
"types": "./dist/index.d.ts",
|
|
63
|
-
"version": "1.5.
|
|
63
|
+
"version": "1.5.3",
|
|
64
64
|
"scripts": {
|
|
65
65
|
"build": "tsc",
|
|
66
66
|
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|