@greenarmor/ges 1.5.2 → 1.5.4

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.
@@ -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, label, 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 = {
@@ -32,8 +32,157 @@ function printRecordSummary(record) {
32
32
  }
33
33
  console.log(` ${DIM("Ev")} ${record.evidence.length} reference(s)`);
34
34
  }
35
+ async function showGovernanceNextAction(root, records, lastShownId) {
36
+ if (process.stdin.isTTY !== true || process.stdout.isTTY !== true)
37
+ return;
38
+ const choices = [];
39
+ if (lastShownId) {
40
+ choices.push({ name: `Verify this record ${DIM("— check provenance completeness")}`, value: `ges governance verify ${lastShownId}` });
41
+ if (records.length > 1) {
42
+ choices.push({ name: `Show another record ${DIM("— pick from list")}`, value: `__pick_show__` });
43
+ }
44
+ choices.push({ name: `Record an approval ${DIM("— add approval decision")}`, value: `ges governance approve ${lastShownId}` });
45
+ choices.push({ name: `Add evidence reference ${DIM("— link to Jira/Confluence/etc")}`, value: `ges governance evidence ${lastShownId}` });
46
+ choices.push({ name: `Link risk assessment ${DIM("— assessor, methodology, score")}`, value: `ges governance risk-assessment ${lastShownId}` });
47
+ choices.push({ name: `Document policy basis ${DIM("— which policy applies")}`, value: `ges governance policy-basis ${lastShownId}` });
48
+ }
49
+ else if (records.length > 0) {
50
+ choices.push({ name: `Show a record's full provenance chain ${DIM("— all dimensions in detail")}`, value: `__pick_show__` });
51
+ choices.push({ name: `Verify a record's completeness ${DIM("— check all 8 dimensions")}`, value: `__pick_verify__` });
52
+ }
53
+ choices.push({ name: `Create a new governance record ${DIM("— start a new approval chain")}`, value: `ges governance add` });
54
+ if (records.length === 0) {
55
+ choices.length = 0;
56
+ choices.push({ name: `Create a new governance record ${DIM("— start a new approval chain")}`, value: `ges governance add` });
57
+ }
58
+ choices.push({ name: `${YELLOW("Exit")} ${DIM("— return to terminal")}`, value: `exit` });
59
+ divider();
60
+ label("What would you like to do next?");
61
+ const answer = await select({
62
+ message: "Choose your next action:",
63
+ choices,
64
+ });
65
+ if (answer === "exit") {
66
+ blank();
67
+ return;
68
+ }
69
+ let cmd = answer;
70
+ if (answer === "__pick_show__" || answer === "__pick_verify__") {
71
+ let recordChoice;
72
+ if (records.length === 1) {
73
+ recordChoice = records[0].id;
74
+ }
75
+ else {
76
+ recordChoice = await select({
77
+ message: "Select a record:",
78
+ choices: records.map(r => ({
79
+ name: `${r.system_name} ${GRAY(`(${r.status}, ${r.risk_level})`)}`,
80
+ value: r.id,
81
+ })),
82
+ });
83
+ }
84
+ const sub = answer === "__pick_show__" ? "show" : "verify";
85
+ cmd = `ges governance ${sub} ${recordChoice}`;
86
+ }
87
+ blank();
88
+ info("Running", GREEN(cmd));
89
+ divider();
90
+ blank();
91
+ const { execSync } = await import("node:child_process");
92
+ try {
93
+ execSync(cmd, { stdio: "inherit" });
94
+ }
95
+ catch {
96
+ process.exit(1);
97
+ }
98
+ }
35
99
  export const governanceCommand = new Command("governance")
36
100
  .description("Manage governance approval provenance chains")
101
+ .action(async () => {
102
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
103
+ governanceCommand.outputHelp();
104
+ return;
105
+ }
106
+ banner("GESF Governance", "Provenance Chain Management");
107
+ let root;
108
+ try {
109
+ root = ensureGESInitialized();
110
+ }
111
+ catch {
112
+ error("GESF is not initialized.", "Run `ges init` first.");
113
+ blank();
114
+ return;
115
+ }
116
+ const records = loadGovernanceRecords(root);
117
+ if (records.length > 0) {
118
+ console.log(` ${BOLD("Existing Records")} ${GRAY(`(${records.length})`)}`);
119
+ records.forEach(r => printRecordSummary(r));
120
+ console.log("");
121
+ }
122
+ else {
123
+ warn("No governance records yet.", "Create one to start building a provenance chain.");
124
+ blank();
125
+ }
126
+ const action = await select({
127
+ message: "What would you like to do?",
128
+ choices: [
129
+ { name: `Create a new governance record ${DIM("— start a new approval chain")}`, value: "add" },
130
+ ...(records.length > 0 ? [
131
+ { name: `List all records ${DIM(`(${records.length} existing)`)}`, value: "list" },
132
+ { name: `Show a record's full provenance chain ${DIM("— all dimensions in detail")}`, value: "show" },
133
+ { name: `Verify a record's completeness ${DIM("— check all 8 dimensions")}`, value: "verify" },
134
+ { name: `Record an approval decision ${DIM("— who approved, under what authority")}`, value: "approve" },
135
+ { name: `Add an evidence reference ${DIM("— link to Jira, Confluence, etc.")}`, value: "evidence" },
136
+ { name: `Link a risk assessment ${DIM("— assessor, methodology, score")}`, value: "risk-assessment" },
137
+ { name: `Document the policy basis ${DIM("— which policy/standard applies")}`, value: "policy-basis" },
138
+ { name: `Set up a review cycle ${DIM("— when to re-review")}`, value: "review-cycle" },
139
+ { name: `Document data inventory ${DIM("— what personal data is processed")}`, value: "data-inventory" },
140
+ { name: `Record committee approval ${DIM("— formal committee sign-off")}`, value: "committee" },
141
+ { name: `Map compliance frameworks ${DIM("— GDPR, OWASP, etc.")}`, value: "compliance-links" },
142
+ { name: `Delete a record ${DIM("— permanently remove")}`, value: "delete" },
143
+ ] : []),
144
+ { name: `${YELLOW("Exit")} ${DIM("— return to terminal")}`, value: "exit" },
145
+ ],
146
+ });
147
+ if (action === "exit") {
148
+ blank();
149
+ return;
150
+ }
151
+ let cmd = `ges governance ${action}`;
152
+ if (["show", "verify", "approve", "evidence", "risk-assessment", "policy-basis", "review-cycle", "data-inventory", "committee", "compliance-links", "delete"].includes(action)) {
153
+ if (records.length === 0) {
154
+ error("No records to work with.", "Create one first with: ges governance add");
155
+ blank();
156
+ return;
157
+ }
158
+ if (records.length === 1) {
159
+ cmd += ` ${records[0].id}`;
160
+ }
161
+ else {
162
+ const recordChoice = await select({
163
+ message: "Select a record:",
164
+ choices: [
165
+ ...records.map(r => ({
166
+ name: `${r.system_name} ${GRAY(`(${r.status}, ${r.risk_level})`)}`,
167
+ value: r.id,
168
+ })),
169
+ ],
170
+ });
171
+ cmd += ` ${recordChoice}`;
172
+ }
173
+ }
174
+ blank();
175
+ info("Running", GREEN(cmd));
176
+ divider();
177
+ blank();
178
+ const { execSync } = await import("node:child_process");
179
+ try {
180
+ execSync(cmd, { stdio: "inherit" });
181
+ }
182
+ catch {
183
+ process.exit(1);
184
+ }
185
+ })
37
186
  .addCommand(new Command("add")
38
187
  .description("Create a new governance record")
39
188
  .option("-n, --name <name>", "System name")
@@ -119,10 +268,10 @@ export const governanceCommand = new Command("governance")
119
268
  console.error(` Error: Governance record "${id}" not found.`);
120
269
  process.exit(1);
121
270
  }
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: "" });
271
+ const approverName = options.approver || await input({ message: "Approver full name:", default: "" });
272
+ const approverRole = options.role || await input({ message: "Approver role/title (e.g., 'CISO', 'CTO'):", default: "" });
273
+ const approverEmail = options.email || await input({ message: "Approver email (optional):", default: "" });
274
+ const authority = options.authority || await input({ message: "Approval authority (e.g., 'AI Ethics Board', 'Security Committee'):", default: "" });
126
275
  const decision = (options.decision || await select({
127
276
  message: "Decision:",
128
277
  choices: [
@@ -132,9 +281,9 @@ export const governanceCommand = new Command("governance")
132
281
  ],
133
282
  }));
134
283
  const validFrom = options.validFrom || new Date().toISOString().split("T")[0];
135
- const validUntil = options.validUntil || await input({ message: "Valid until (YYYY-MM-DD, or blank for indefinite):", default: "" });
136
- const conditionsStr = options.conditions || await input({ message: "Conditions (comma-separated):", default: "" });
137
- const rationale = options.rationale || await input({ message: "Rationale:", default: "" });
284
+ const validUntil = options.validUntil || await input({ message: "Valid until YYYY-MM-DD (or press Enter for indefinite):", default: "" });
285
+ const conditionsStr = options.conditions || await input({ message: "Conditions (comma-separated, or press Enter to skip):", default: "" });
286
+ const rationale = options.rationale || await input({ message: "Rationale (why was this decision made?):", default: "" });
138
287
  const updated = setGovernanceApproval(root, record.id, {
139
288
  approver_name: approverName,
140
289
  approver_role: approverRole,
@@ -182,7 +331,7 @@ export const governanceCommand = new Command("governance")
182
331
  console.error(` Error: Governance record "${id}" not found.`);
183
332
  process.exit(1);
184
333
  }
185
- const title = options.title || await input({ message: "Evidence title:", default: "" });
334
+ const title = options.title || await input({ message: "Evidence title (e.g., 'DPIA Report Q4 2026'):", default: "" });
186
335
  const sourceSystem = (options.source || await select({
187
336
  message: "Source system:",
188
337
  choices: [
@@ -198,7 +347,7 @@ export const governanceCommand = new Command("governance")
198
347
  { name: "Other", value: "other" },
199
348
  ],
200
349
  }));
201
- const reference = options.reference || await input({ message: "Reference (ticket ID, URL, doc name):", default: "" });
350
+ const reference = options.reference || await input({ message: "Reference (ticket ID, URL, or document path):", default: "" });
202
351
  const evidenceType = await select({
203
352
  message: "Evidence type:",
204
353
  choices: [
@@ -214,7 +363,7 @@ export const governanceCommand = new Command("governance")
214
363
  { name: "Other", value: "other" },
215
364
  ],
216
365
  });
217
- const locationDesc = await input({ message: "Location description:", default: "" });
366
+ const locationDesc = await input({ message: "Location description (where to find it, optional):", default: "" });
218
367
  const evidence = createEvidenceRef({
219
368
  type: evidenceType,
220
369
  title,
@@ -247,12 +396,13 @@ export const governanceCommand = new Command("governance")
247
396
  }))
248
397
  .addCommand(new Command("list")
249
398
  .description("List all governance records")
250
- .action(() => {
399
+ .action(async () => {
251
400
  const root = ensureGESInitialized();
252
401
  const records = loadGovernanceRecords(root);
253
402
  if (records.length === 0) {
254
403
  info("No governance records found.");
255
404
  console.log(` ${DIM("Create one with:")} ${GREEN("ges governance add")}\n`);
405
+ await showGovernanceNextAction(root, records);
256
406
  return;
257
407
  }
258
408
  blank();
@@ -262,11 +412,12 @@ export const governanceCommand = new Command("governance")
262
412
  printRecordSummary(r);
263
413
  console.log();
264
414
  });
415
+ await showGovernanceNextAction(root, records);
265
416
  }))
266
417
  .addCommand(new Command("show")
267
418
  .description("Show full provenance chain for a governance record")
268
419
  .argument("<id>", "Record ID or system name")
269
- .action((id) => {
420
+ .action(async (id) => {
270
421
  const root = ensureGESInitialized();
271
422
  const record = findGovernanceRecord(root, id);
272
423
  if (!record) {
@@ -409,11 +560,12 @@ export const governanceCommand = new Command("governance")
409
560
  }
410
561
  console.log(`\n Created: ${record.created_at} by ${record.created_by}`);
411
562
  console.log(` Updated: ${record.updated_at} by ${record.updated_by} (v${record.record_version})\n`);
563
+ await showGovernanceNextAction(root, [record], record.id);
412
564
  }))
413
565
  .addCommand(new Command("verify")
414
566
  .description("Verify the provenance chain completeness of a governance record")
415
567
  .argument("<id>", "Record ID or system name")
416
- .action((id) => {
568
+ .action(async (id) => {
417
569
  const root = ensureGESInitialized();
418
570
  const record = findGovernanceRecord(root, id);
419
571
  if (!record) {
@@ -458,6 +610,7 @@ export const governanceCommand = new Command("governance")
458
610
  result.warnings.forEach(w => console.log(` ${YELLOW("△")} ${w}`));
459
611
  }
460
612
  console.log();
613
+ await showGovernanceNextAction(root, [record], record.id);
461
614
  }))
462
615
  .addCommand(new Command("delete")
463
616
  .description("Delete a governance record")
@@ -507,12 +660,12 @@ export const governanceCommand = new Command("governance")
507
660
  console.error(` Error: Governance record "${id}" not found.`);
508
661
  process.exit(1);
509
662
  }
510
- const assessor = options.assessor || await input({ message: "Assessor name:", default: "" });
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: "" });
663
+ const assessor = options.assessor || await input({ message: "Risk assessor name:", default: "" });
664
+ const methodology = options.methodology || await input({ message: "Methodology (e.g., 'NIST RMF', 'ISO 27005'):", default: "" });
665
+ const score = options.score || await input({ message: "Risk score (e.g., '7.5/10', 'High'):", default: "" });
666
+ const residual = options.residual || await input({ message: "Residual risk level (low/medium/high):", default: "" });
667
+ const risksStr = await input({ message: "Identified risks (comma-separated, or Enter to skip):", default: "" });
668
+ const mitigationsStr = await input({ message: "Mitigation measures (comma-separated, or Enter to skip):", default: "" });
516
669
  const updated = setGovernanceRiskAssessment(root, record.id, {
517
670
  id: `risk-${Date.now()}`,
518
671
  assessor,
@@ -552,11 +705,11 @@ export const governanceCommand = new Command("governance")
552
705
  console.error(` Error: Governance record "${id}" not found.`);
553
706
  process.exit(1);
554
707
  }
555
- const policyId = options.policyId || await input({ message: "Policy ID:", default: "" });
556
- const policyName = options.policyName || await input({ message: "Policy name:", default: "" });
708
+ const policyId = options.policyId || await input({ message: "Policy ID (e.g., 'POL-SEC-001'):", default: "" });
709
+ const policyName = options.policyName || await input({ message: "Policy name (e.g., 'Information Security Policy'):", default: "" });
557
710
  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: "" });
711
+ const standard = options.standard || await input({ message: "Standard (e.g., 'GDPR', 'ISO 27001'):", default: "" });
712
+ const clausesStr = await input({ message: "Applicable clauses (comma-separated, or Enter to skip):", default: "" });
560
713
  const updated = setGovernancePolicyBasis(root, record.id, {
561
714
  policy_id: policyId,
562
715
  policy_name: policyName,
@@ -633,11 +786,11 @@ export const governanceCommand = new Command("governance")
633
786
  console.error(` Error: Governance record "${id}" not found.`);
634
787
  process.exit(1);
635
788
  }
636
- const categoriesStr = options.categories || await input({ message: "Personal data categories (comma-separated):", default: "" });
637
- const purposesStr = options.purposes || await input({ message: "Processing purposes (comma-separated):", default: "" });
638
- const subjectsStr = await input({ message: "Data subjects (comma-separated):", default: "" });
639
- const transfersStr = await input({ message: "Cross-border transfers (comma-separated):", default: "" });
640
- const retention = options.retention || await input({ message: "Retention period:", default: "" });
789
+ const categoriesStr = options.categories || await input({ message: "Personal data categories (e.g., 'names,emails,IP addresses'):", default: "" });
790
+ const purposesStr = options.purposes || await input({ message: "Processing purposes (e.g., 'user auth,analytics'):", default: "" });
791
+ const subjectsStr = await input({ message: "Data subjects (e.g., 'customers,employees', or Enter to skip):", default: "" });
792
+ const transfersStr = await input({ message: "Cross-border transfers (e.g., 'US,EU', or Enter to skip):", default: "" });
793
+ const retention = options.retention || await input({ message: "Retention period (e.g., '2 years', '90 days'):", default: "" });
641
794
  const updated = setGovernanceDataInventory(root, record.id, {
642
795
  personal_data_categories: categoriesStr ? categoriesStr.split(",").map((s) => s.trim()).filter(Boolean) : [],
643
796
  processing_purposes: purposesStr ? purposesStr.split(",").map((s) => s.trim()).filter(Boolean) : [],
@@ -669,11 +822,11 @@ export const governanceCommand = new Command("governance")
669
822
  console.error(` Error: Governance record "${id}" not found.`);
670
823
  process.exit(1);
671
824
  }
672
- const committeeName = options.committee || await input({ message: "Committee name:", default: "" });
673
- const meetingRef = options.meetingRef || await input({ message: "Meeting reference:", default: "" });
825
+ const committeeName = options.committee || await input({ message: "Committee name (e.g., 'AI Ethics Board'):", default: "" });
826
+ const meetingRef = options.meetingRef || await input({ message: "Meeting reference (e.g., 'MIN-2026-001'):", default: "" });
674
827
  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: "" });
828
+ const attendeesStr = await input({ message: "Attendees (comma-separated names, or Enter to skip):", default: "" });
829
+ const summary = await input({ message: "Decision summary (what was decided):", default: "" });
677
830
  const updated = setGovernanceCommittee(root, record.id, {
678
831
  committee_name: committeeName,
679
832
  meeting_date: meetingDate,
@@ -707,9 +860,9 @@ export const governanceCommand = new Command("governance")
707
860
  console.error(` Error: Governance record "${id}" not found.`);
708
861
  process.exit(1);
709
862
  }
710
- const frameworksStr = options.frameworks || await input({ message: "Frameworks (comma-separated):", default: "" });
711
- const controlsStr = options.controls || await input({ message: "Controls satisfied (comma-separated):", default: "" });
712
- const packsStr = await input({ message: "Control pack IDs (comma-separated):", default: "" });
863
+ const frameworksStr = options.frameworks || await input({ message: "Frameworks (e.g., 'GDPR,OWASP'):", default: "" });
864
+ const controlsStr = options.controls || await input({ message: "Controls satisfied (e.g., 'GDPR-ART32-002', or Enter to skip):", default: "" });
865
+ const packsStr = await input({ message: "Control pack IDs (comma-separated, or Enter to skip):", default: "" });
713
866
  const updated = setGovernanceComplianceLinks(root, record.id, {
714
867
  frameworks: frameworksStr ? frameworksStr.split(",").map((s) => s.trim()).filter(Boolean) : [],
715
868
  controls_satisfied: controlsStr ? controlsStr.split(",").map((s) => s.trim()).filter(Boolean) : [],
@@ -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 () => {
@@ -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(() => {
@@ -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.2",
7
- "@greenarmor/ges-cicd-generator": "1.5.2",
8
- "@greenarmor/ges-compliance-engine": "1.5.2",
9
- "@greenarmor/ges-core": "1.5.2",
10
- "@greenarmor/ges-doc-generator": "1.5.2",
11
- "@greenarmor/ges-git-hooks": "1.5.2",
12
- "@greenarmor/ges-mcp-server": "1.5.2",
13
- "@greenarmor/ges-policy-engine": "1.5.2",
14
- "@greenarmor/ges-report-generator": "1.5.2",
15
- "@greenarmor/ges-rules-engine": "1.5.2",
16
- "@greenarmor/ges-scanner-integration": "1.5.2",
17
- "@greenarmor/ges-scoring-engine": "1.5.2",
18
- "@greenarmor/ges-web-dashboard": "1.5.2",
6
+ "@greenarmor/ges-audit-engine": "1.5.4",
7
+ "@greenarmor/ges-cicd-generator": "1.5.4",
8
+ "@greenarmor/ges-compliance-engine": "1.5.4",
9
+ "@greenarmor/ges-core": "1.5.4",
10
+ "@greenarmor/ges-doc-generator": "1.5.4",
11
+ "@greenarmor/ges-git-hooks": "1.5.4",
12
+ "@greenarmor/ges-mcp-server": "1.5.4",
13
+ "@greenarmor/ges-policy-engine": "1.5.4",
14
+ "@greenarmor/ges-report-generator": "1.5.4",
15
+ "@greenarmor/ges-rules-engine": "1.5.4",
16
+ "@greenarmor/ges-scanner-integration": "1.5.4",
17
+ "@greenarmor/ges-scoring-engine": "1.5.4",
18
+ "@greenarmor/ges-web-dashboard": "1.5.4",
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.2",
63
+ "version": "1.5.4",
64
64
  "scripts": {
65
65
  "build": "tsc",
66
66
  "clean": "rm -rf dist tsconfig.tsbuildinfo",