chainlesschain 0.37.12 → 0.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/package.json +3 -2
  2. package/src/commands/agent.js +7 -1
  3. package/src/commands/ask.js +24 -9
  4. package/src/commands/chat.js +7 -1
  5. package/src/commands/cli-anything.js +266 -0
  6. package/src/commands/compliance.js +216 -0
  7. package/src/commands/dao.js +312 -0
  8. package/src/commands/dlp.js +278 -0
  9. package/src/commands/evomap.js +558 -0
  10. package/src/commands/hardening.js +230 -0
  11. package/src/commands/matrix.js +168 -0
  12. package/src/commands/nostr.js +185 -0
  13. package/src/commands/pqc.js +162 -0
  14. package/src/commands/scim.js +218 -0
  15. package/src/commands/serve.js +109 -0
  16. package/src/commands/siem.js +156 -0
  17. package/src/commands/social.js +480 -0
  18. package/src/commands/terraform.js +148 -0
  19. package/src/constants.js +1 -0
  20. package/src/index.js +60 -0
  21. package/src/lib/autonomous-agent.js +487 -0
  22. package/src/lib/cli-anything-bridge.js +379 -0
  23. package/src/lib/cli-context-engineering.js +472 -0
  24. package/src/lib/compliance-manager.js +290 -0
  25. package/src/lib/content-recommender.js +205 -0
  26. package/src/lib/dao-governance.js +296 -0
  27. package/src/lib/dlp-engine.js +304 -0
  28. package/src/lib/evomap-client.js +135 -0
  29. package/src/lib/evomap-federation.js +240 -0
  30. package/src/lib/evomap-governance.js +250 -0
  31. package/src/lib/evomap-manager.js +227 -0
  32. package/src/lib/git-integration.js +1 -1
  33. package/src/lib/hardening-manager.js +275 -0
  34. package/src/lib/llm-providers.js +14 -1
  35. package/src/lib/matrix-bridge.js +196 -0
  36. package/src/lib/nostr-bridge.js +195 -0
  37. package/src/lib/permanent-memory.js +370 -0
  38. package/src/lib/plan-mode.js +211 -0
  39. package/src/lib/pqc-manager.js +196 -0
  40. package/src/lib/scim-manager.js +212 -0
  41. package/src/lib/session-manager.js +38 -0
  42. package/src/lib/siem-exporter.js +137 -0
  43. package/src/lib/social-manager.js +283 -0
  44. package/src/lib/task-model-selector.js +232 -0
  45. package/src/lib/terraform-manager.js +201 -0
  46. package/src/lib/ws-server.js +474 -0
  47. package/src/repl/agent-repl.js +796 -41
  48. package/src/repl/chat-repl.js +14 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chainlesschain",
3
- "version": "0.37.12",
3
+ "version": "0.40.1",
4
4
  "description": "CLI for ChainlessChain - install, configure, and manage your personal AI management system",
5
5
  "type": "module",
6
6
  "bin": {
@@ -50,7 +50,8 @@
50
50
  "@chainlesschain/shared-logger": "0.1.0",
51
51
  "@chainlesschain/core-db": "0.1.0",
52
52
  "@chainlesschain/core-config": "0.1.0",
53
- "@chainlesschain/core-infra": "0.1.0"
53
+ "@chainlesschain/core-infra": "0.1.0",
54
+ "ws": "^8.14.2"
54
55
  },
55
56
  "devDependencies": {
56
57
  "vitest": "^3.1.1"
@@ -16,15 +16,21 @@ export function registerAgentCommand(program) {
16
16
  "Start an agentic AI session (reads/writes files, runs commands)",
17
17
  )
18
18
  .option("--model <model>", "Model name", "qwen2:7b")
19
- .option("--provider <provider>", "LLM provider (ollama, openai)", "ollama")
19
+ .option(
20
+ "--provider <provider>",
21
+ "LLM provider (ollama, openai, volcengine, deepseek, ...)",
22
+ "ollama",
23
+ )
20
24
  .option("--base-url <url>", "API base URL")
21
25
  .option("--api-key <key>", "API key")
26
+ .option("--session <id>", "Resume a previous agent session")
22
27
  .action(async (options) => {
23
28
  await startAgentRepl({
24
29
  model: options.model,
25
30
  provider: options.provider,
26
31
  baseUrl: options.baseUrl,
27
32
  apiKey: options.apiKey,
33
+ sessionId: options.session,
28
34
  });
29
35
  });
30
36
  }
@@ -6,6 +6,7 @@
6
6
  import ora from "ora";
7
7
  import chalk from "chalk";
8
8
  import { logger } from "../lib/logger.js";
9
+ import { BUILT_IN_PROVIDERS } from "../lib/llm-providers.js";
9
10
 
10
11
  /**
11
12
  * Send a single question to an LLM provider
@@ -37,12 +38,24 @@ async function queryLLM(question, options = {}) {
37
38
 
38
39
  const data = await response.json();
39
40
  return data.response;
40
- } else if (provider === "openai") {
41
- const apiKey = options.apiKey || process.env.OPENAI_API_KEY;
41
+ } else {
42
+ // OpenAI-compatible providers (openai, volcengine, deepseek, dashscope, mistral, gemini)
43
+ const providerDef = BUILT_IN_PROVIDERS[provider];
44
+ if (!providerDef) {
45
+ throw new Error(
46
+ `Unsupported provider: ${provider}. Supported: ollama, openai, volcengine, deepseek, dashscope, gemini, mistral, anthropic`,
47
+ );
48
+ }
49
+
50
+ const apiKey =
51
+ options.apiKey ||
52
+ (providerDef.apiKeyEnv ? process.env[providerDef.apiKeyEnv] : null);
42
53
  if (!apiKey)
43
- throw new Error("OpenAI API key required (--api-key or OPENAI_API_KEY)");
54
+ throw new Error(
55
+ `API key required for ${provider} (--api-key or ${providerDef.apiKeyEnv})`,
56
+ );
44
57
 
45
- const apiBase = options.baseUrl || "https://api.openai.com/v1";
58
+ const apiBase = options.baseUrl || providerDef.baseUrl;
46
59
  const response = await fetch(`${apiBase}/chat/completions`, {
47
60
  method: "POST",
48
61
  headers: {
@@ -50,22 +63,20 @@ async function queryLLM(question, options = {}) {
50
63
  Authorization: `Bearer ${apiKey}`,
51
64
  },
52
65
  body: JSON.stringify({
53
- model: model || "gpt-4o-mini",
66
+ model: model || providerDef.models[0],
54
67
  messages: [{ role: "user", content: question }],
55
68
  }),
56
69
  });
57
70
 
58
71
  if (!response.ok) {
59
72
  throw new Error(
60
- `OpenAI error: ${response.status} ${response.statusText}`,
73
+ `${provider} error: ${response.status} ${response.statusText}`,
61
74
  );
62
75
  }
63
76
 
64
77
  const data = await response.json();
65
78
  return data.choices[0].message.content;
66
79
  }
67
-
68
- throw new Error(`Unsupported provider: ${provider}`);
69
80
  }
70
81
 
71
82
  export function registerAskCommand(program) {
@@ -74,7 +85,11 @@ export function registerAskCommand(program) {
74
85
  .description("Ask a question to the AI (single-shot)")
75
86
  .argument("<question>", "The question to ask")
76
87
  .option("--model <model>", "Model name", "qwen2:7b")
77
- .option("--provider <provider>", "LLM provider (ollama, openai)", "ollama")
88
+ .option(
89
+ "--provider <provider>",
90
+ "LLM provider (ollama, openai, volcengine, deepseek, ...)",
91
+ "ollama",
92
+ )
78
93
  .option("--base-url <url>", "API base URL")
79
94
  .option("--api-key <key>", "API key")
80
95
  .option("--json", "Output as JSON")
@@ -11,19 +11,25 @@ export function registerChatCommand(program) {
11
11
  .command("chat")
12
12
  .description("Start an interactive AI chat session")
13
13
  .option("--model <model>", "Model name", "qwen2:7b")
14
- .option("--provider <provider>", "LLM provider (ollama, openai)", "ollama")
14
+ .option(
15
+ "--provider <provider>",
16
+ "LLM provider (ollama, openai, volcengine, deepseek, ...)",
17
+ "ollama",
18
+ )
15
19
  .option("--base-url <url>", "API base URL")
16
20
  .option("--api-key <key>", "API key")
17
21
  .option(
18
22
  "--agent",
19
23
  "Agentic mode - AI can read/write files and run commands (like Claude Code)",
20
24
  )
25
+ .option("--session <id>", "Resume a previous session (agent mode)")
21
26
  .action(async (options) => {
22
27
  const replOptions = {
23
28
  model: options.model,
24
29
  provider: options.provider,
25
30
  baseUrl: options.baseUrl,
26
31
  apiKey: options.apiKey,
32
+ sessionId: options.session,
27
33
  };
28
34
 
29
35
  if (options.agent) {
@@ -0,0 +1,266 @@
1
+ /**
2
+ * CLI-Anything commands — discover & register CLI-Anything generated tools
3
+ * chainlesschain cli-anything doctor|scan|register|list|remove
4
+ */
5
+
6
+ import chalk from "chalk";
7
+ import { logger } from "../lib/logger.js";
8
+ import { bootstrap, shutdown } from "../runtime/bootstrap.js";
9
+ import {
10
+ ensureCliAnythingTables,
11
+ detectPython,
12
+ detectCliAnything,
13
+ scanPathForTools,
14
+ parseToolHelp,
15
+ registerTool,
16
+ removeTool,
17
+ listTools,
18
+ } from "../lib/cli-anything-bridge.js";
19
+
20
+ export function registerCliAnythingCommand(program) {
21
+ const cliAny = program
22
+ .command("cli-anything")
23
+ .description(
24
+ "CLI-Anything — discover and register Agent-native CLI tools as skills",
25
+ );
26
+
27
+ /* ---- doctor ---- */
28
+ cliAny
29
+ .command("doctor")
30
+ .description("Check Python & CLI-Anything environment")
31
+ .option("--json", "Output as JSON")
32
+ .action(async (opts) => {
33
+ const py = detectPython();
34
+ const clia = py.found ? detectCliAnything() : { installed: false };
35
+ const tools = scanPathForTools();
36
+
37
+ const report = {
38
+ python: py,
39
+ cliAnything: clia,
40
+ toolsOnPath: tools.length,
41
+ };
42
+
43
+ if (opts.json) {
44
+ console.log(JSON.stringify(report, null, 2));
45
+ return;
46
+ }
47
+
48
+ logger.log("");
49
+ logger.log(chalk.bold(" CLI-Anything Environment"));
50
+ logger.log("");
51
+
52
+ // Python
53
+ if (py.found) {
54
+ logger.log(
55
+ ` ${chalk.green("✓")} Python ${chalk.cyan(py.version)} (${py.command})`,
56
+ );
57
+ } else {
58
+ logger.log(` ${chalk.red("✗")} Python not found`);
59
+ }
60
+
61
+ // CLI-Anything
62
+ if (clia.installed) {
63
+ logger.log(
64
+ ` ${chalk.green("✓")} CLI-Anything ${chalk.cyan(clia.version)}`,
65
+ );
66
+ } else {
67
+ logger.log(` ${chalk.red("✗")} CLI-Anything not installed`);
68
+ if (py.found) {
69
+ logger.log(
70
+ ` ${chalk.gray(`Install: ${py.command} -m pip install cli-anything`)}`,
71
+ );
72
+ }
73
+ }
74
+
75
+ // Tools
76
+ logger.log(
77
+ ` ${tools.length > 0 ? chalk.green("✓") : chalk.yellow("○")} ${tools.length} tool(s) on PATH`,
78
+ );
79
+ for (const t of tools) {
80
+ logger.log(
81
+ ` ${chalk.gray(`cli-anything-${t.name}`)} → ${chalk.gray(t.path)}`,
82
+ );
83
+ }
84
+ logger.log("");
85
+ });
86
+
87
+ /* ---- scan ---- */
88
+ cliAny
89
+ .command("scan")
90
+ .description("Scan PATH for cli-anything-* tools")
91
+ .option("--json", "Output as JSON")
92
+ .action(async (opts) => {
93
+ const tools = scanPathForTools();
94
+
95
+ if (opts.json) {
96
+ console.log(JSON.stringify(tools, null, 2));
97
+ return;
98
+ }
99
+
100
+ if (tools.length === 0) {
101
+ logger.info("No cli-anything-* tools found on PATH.");
102
+ logger.log(
103
+ chalk.gray(
104
+ " Use CLI-Anything to generate tools first: /cli-anything <software>",
105
+ ),
106
+ );
107
+ return;
108
+ }
109
+
110
+ logger.log("");
111
+ logger.log(chalk.bold(` Found ${tools.length} tool(s):`));
112
+ logger.log("");
113
+ for (const t of tools) {
114
+ const help = parseToolHelp(t.command);
115
+ logger.log(` ${chalk.cyan(t.name)}`);
116
+ logger.log(` Command: ${chalk.gray(t.command)}`);
117
+ logger.log(` Path: ${chalk.gray(t.path)}`);
118
+ if (help.description) {
119
+ logger.log(` Desc: ${chalk.gray(help.description)}`);
120
+ }
121
+ if (help.subcommands.length > 0) {
122
+ logger.log(
123
+ ` Subs: ${chalk.gray(help.subcommands.map((s) => s.name).join(", "))}`,
124
+ );
125
+ }
126
+ }
127
+ logger.log("");
128
+ });
129
+
130
+ /* ---- register ---- */
131
+ cliAny
132
+ .command("register <name>")
133
+ .description("Register a cli-anything-* tool as a ChainlessChain skill")
134
+ .option("--force", "Overwrite existing registration")
135
+ .option("--json", "Output as JSON")
136
+ .action(async (name, opts) => {
137
+ try {
138
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
139
+ if (!ctx.db) {
140
+ logger.error(
141
+ "Database not available. Run `chainlesschain setup` first.",
142
+ );
143
+ process.exit(1);
144
+ }
145
+ const db = ctx.db.getDatabase();
146
+ ensureCliAnythingTables(db);
147
+
148
+ const command = `cli-anything-${name}`;
149
+ const helpData = parseToolHelp(command);
150
+
151
+ const result = registerTool(db, name, {
152
+ command,
153
+ helpData,
154
+ force: opts.force,
155
+ });
156
+
157
+ await shutdown();
158
+
159
+ if (opts.json) {
160
+ console.log(JSON.stringify(result, null, 2));
161
+ return;
162
+ }
163
+
164
+ logger.success(
165
+ `Registered ${chalk.cyan(name)} as skill ${chalk.bold(result.skillName)}`,
166
+ );
167
+ logger.log(` Skill dir: ${chalk.gray(result.dir)}`);
168
+ if (result.subcommands.length > 0) {
169
+ logger.log(
170
+ ` Subcommands: ${chalk.gray(result.subcommands.map((s) => s.name).join(", "))}`,
171
+ );
172
+ }
173
+ logger.log(
174
+ chalk.gray(
175
+ ` Use in Agent: /skill ${result.skillName} <subcommand> [args]`,
176
+ ),
177
+ );
178
+ } catch (err) {
179
+ logger.error(`Register failed: ${err.message}`);
180
+ process.exit(1);
181
+ }
182
+ });
183
+
184
+ /* ---- list (default) ---- */
185
+ cliAny
186
+ .command("list", { isDefault: true })
187
+ .description("List registered CLI-Anything tools")
188
+ .option("--json", "Output as JSON")
189
+ .action(async (opts) => {
190
+ try {
191
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
192
+ if (!ctx.db) {
193
+ logger.error("Database not available.");
194
+ process.exit(1);
195
+ }
196
+ const db = ctx.db.getDatabase();
197
+ ensureCliAnythingTables(db);
198
+
199
+ const tools = listTools(db);
200
+ await shutdown();
201
+
202
+ if (opts.json) {
203
+ console.log(JSON.stringify(tools, null, 2));
204
+ return;
205
+ }
206
+
207
+ if (tools.length === 0) {
208
+ logger.info("No CLI-Anything tools registered.");
209
+ logger.log(
210
+ chalk.gray(
211
+ " Run `chainlesschain cli-anything scan` to discover tools.",
212
+ ),
213
+ );
214
+ return;
215
+ }
216
+
217
+ logger.log("");
218
+ logger.log(chalk.bold(` ${tools.length} registered tool(s):`));
219
+ logger.log("");
220
+ for (const t of tools) {
221
+ const statusColor =
222
+ t.status === "registered" ? chalk.green : chalk.yellow;
223
+ logger.log(
224
+ ` ${chalk.cyan(t.name)} ${statusColor(`[${t.status}]`)} → ${chalk.gray(t.skill_name)}`,
225
+ );
226
+ if (t.description) {
227
+ logger.log(` ${chalk.gray(t.description)}`);
228
+ }
229
+ }
230
+ logger.log("");
231
+ } catch (err) {
232
+ logger.error(`List failed: ${err.message}`);
233
+ process.exit(1);
234
+ }
235
+ });
236
+
237
+ /* ---- remove ---- */
238
+ cliAny
239
+ .command("remove <name>")
240
+ .description("Remove a registered CLI-Anything tool")
241
+ .option("--json", "Output as JSON")
242
+ .action(async (name, opts) => {
243
+ try {
244
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
245
+ if (!ctx.db) {
246
+ logger.error("Database not available.");
247
+ process.exit(1);
248
+ }
249
+ const db = ctx.db.getDatabase();
250
+ ensureCliAnythingTables(db);
251
+
252
+ const result = removeTool(db, name);
253
+ await shutdown();
254
+
255
+ if (opts.json) {
256
+ console.log(JSON.stringify(result, null, 2));
257
+ return;
258
+ }
259
+
260
+ logger.success(`Removed tool ${chalk.cyan(name)}`);
261
+ } catch (err) {
262
+ logger.error(`Remove failed: ${err.message}`);
263
+ process.exit(1);
264
+ }
265
+ });
266
+ }
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Compliance commands
3
+ * chainlesschain compliance evidence|report|classify|scan|policies|check-access
4
+ */
5
+
6
+ import chalk from "chalk";
7
+ import { logger } from "../lib/logger.js";
8
+ import { bootstrap, shutdown } from "../runtime/bootstrap.js";
9
+ import {
10
+ ensureComplianceTables,
11
+ collectEvidence,
12
+ generateReport,
13
+ classifyData,
14
+ scanCompliance,
15
+ listPolicies,
16
+ addPolicy,
17
+ checkAccess,
18
+ } from "../lib/compliance-manager.js";
19
+
20
+ export function registerComplianceCommand(program) {
21
+ const compliance = program
22
+ .command("compliance")
23
+ .description(
24
+ "Compliance management — evidence, reports, scanning, policies",
25
+ );
26
+
27
+ // compliance evidence
28
+ compliance
29
+ .command("evidence <framework>")
30
+ .description("Collect compliance evidence (gdpr, soc2, hipaa, iso27001)")
31
+ .option("-t, --type <type>", "Evidence type", "general")
32
+ .option("-d, --description <text>", "Evidence description")
33
+ .option("-s, --source <source>", "Evidence source", "cli")
34
+ .action(async (framework, options) => {
35
+ try {
36
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
37
+ if (!ctx.db) {
38
+ logger.error("Database not available");
39
+ process.exit(1);
40
+ }
41
+ const db = ctx.db.getDatabase();
42
+ ensureComplianceTables(db);
43
+
44
+ const result = collectEvidence(
45
+ db,
46
+ framework,
47
+ options.type,
48
+ options.description,
49
+ options.source,
50
+ );
51
+ logger.success("Evidence collected");
52
+ logger.log(` ${chalk.bold("ID:")} ${chalk.cyan(result.id)}`);
53
+ logger.log(` ${chalk.bold("Framework:")} ${result.framework}`);
54
+ logger.log(` ${chalk.bold("Type:")} ${result.type}`);
55
+
56
+ await shutdown();
57
+ } catch (err) {
58
+ logger.error(`Failed: ${err.message}`);
59
+ process.exit(1);
60
+ }
61
+ });
62
+
63
+ // compliance report
64
+ compliance
65
+ .command("report <framework>")
66
+ .description("Generate compliance report")
67
+ .option("-t, --title <title>", "Report title")
68
+ .option("--json", "Output as JSON")
69
+ .action(async (framework, options) => {
70
+ try {
71
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
72
+ if (!ctx.db) {
73
+ logger.error("Database not available");
74
+ process.exit(1);
75
+ }
76
+ const db = ctx.db.getDatabase();
77
+ ensureComplianceTables(db);
78
+
79
+ const result = generateReport(db, framework, options.title);
80
+ if (options.json) {
81
+ console.log(JSON.stringify(result, null, 2));
82
+ } else {
83
+ logger.success("Report generated");
84
+ logger.log(` ${chalk.bold("ID:")} ${chalk.cyan(result.id)}`);
85
+ logger.log(` ${chalk.bold("Title:")} ${result.title}`);
86
+ logger.log(` ${chalk.bold("Score:")} ${result.score}`);
87
+ logger.log(` ${chalk.bold("Summary:")} ${result.summary}`);
88
+ }
89
+
90
+ await shutdown();
91
+ } catch (err) {
92
+ logger.error(`Failed: ${err.message}`);
93
+ process.exit(1);
94
+ }
95
+ });
96
+
97
+ // compliance classify
98
+ compliance
99
+ .command("classify <content>")
100
+ .description("Classify data sensitivity")
101
+ .option("--json", "Output as JSON")
102
+ .action(async (content, options) => {
103
+ try {
104
+ const result = classifyData(content);
105
+ if (options.json) {
106
+ console.log(JSON.stringify(result, null, 2));
107
+ } else {
108
+ logger.log(
109
+ ` ${chalk.bold("Sensitive:")} ${result.hasClassifiedData ? "Yes" : "No"}`,
110
+ );
111
+ logger.log(
112
+ ` ${chalk.bold("Classifications:")} ${result.classifications.join(", ") || "none"}`,
113
+ );
114
+ logger.log(
115
+ ` ${chalk.bold("Sensitivity:")} ${result.sensitivity}`,
116
+ );
117
+ }
118
+ } catch (err) {
119
+ logger.error(`Failed: ${err.message}`);
120
+ process.exit(1);
121
+ }
122
+ });
123
+
124
+ // compliance scan
125
+ compliance
126
+ .command("scan <framework>")
127
+ .description("Scan compliance posture against policies")
128
+ .option("--json", "Output as JSON")
129
+ .action(async (framework, options) => {
130
+ try {
131
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
132
+ if (!ctx.db) {
133
+ logger.error("Database not available");
134
+ process.exit(1);
135
+ }
136
+ const db = ctx.db.getDatabase();
137
+ ensureComplianceTables(db);
138
+
139
+ const result = scanCompliance(db, framework);
140
+ if (options.json) {
141
+ console.log(JSON.stringify(result, null, 2));
142
+ } else {
143
+ logger.log(` ${chalk.bold("Framework:")} ${result.framework}`);
144
+ logger.log(` ${chalk.bold("Score:")} ${result.score}%`);
145
+ logger.log(
146
+ ` ${chalk.bold("Passed:")} ${result.passed}/${result.total}`,
147
+ );
148
+ logger.log(
149
+ ` ${chalk.bold("Failed:")} ${result.failed}/${result.total}`,
150
+ );
151
+ }
152
+
153
+ await shutdown();
154
+ } catch (err) {
155
+ logger.error(`Failed: ${err.message}`);
156
+ process.exit(1);
157
+ }
158
+ });
159
+
160
+ // compliance policies
161
+ compliance
162
+ .command("policies")
163
+ .description("List compliance policies")
164
+ .option("--framework <fw>", "Filter by framework")
165
+ .option("--json", "Output as JSON")
166
+ .action(async (options) => {
167
+ try {
168
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
169
+ if (!ctx.db) {
170
+ logger.error("Database not available");
171
+ process.exit(1);
172
+ }
173
+ const db = ctx.db.getDatabase();
174
+ ensureComplianceTables(db);
175
+
176
+ const policies = listPolicies(db, { framework: options.framework });
177
+ if (options.json) {
178
+ console.log(JSON.stringify(policies, null, 2));
179
+ } else if (policies.length === 0) {
180
+ logger.info("No policies configured.");
181
+ } else {
182
+ for (const p of policies) {
183
+ logger.log(
184
+ ` ${chalk.cyan(p.id.slice(0, 8))} ${p.name} [${p.type}] ${p.framework} severity=${p.severity}`,
185
+ );
186
+ }
187
+ }
188
+
189
+ await shutdown();
190
+ } catch (err) {
191
+ logger.error(`Failed: ${err.message}`);
192
+ process.exit(1);
193
+ }
194
+ });
195
+
196
+ // compliance check-access
197
+ compliance
198
+ .command("check-access <resource> <action> <role>")
199
+ .description("Check RBAC access permission")
200
+ .option("--json", "Output as JSON")
201
+ .action(async (resource, action, role, options) => {
202
+ try {
203
+ const result = checkAccess(resource, action, role);
204
+ if (options.json) {
205
+ console.log(JSON.stringify(result, null, 2));
206
+ } else {
207
+ logger.log(
208
+ ` ${chalk.bold("Access:")} ${result.granted ? chalk.green("GRANTED") : chalk.red("DENIED")} (${role} → ${action} on ${resource})`,
209
+ );
210
+ }
211
+ } catch (err) {
212
+ logger.error(`Failed: ${err.message}`);
213
+ process.exit(1);
214
+ }
215
+ });
216
+ }