@aigrc/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1029 @@
1
+ // src/commands/scan.ts
2
+ import { Command } from "commander";
3
+ import chalk2 from "chalk";
4
+ import ora from "ora";
5
+ import path from "path";
6
+ import {
7
+ scan,
8
+ suggestAssetCard,
9
+ initializePatterns
10
+ } from "@aigrc/core";
11
+
12
+ // src/utils/output.ts
13
+ import chalk from "chalk";
14
+ function printHeader() {
15
+ console.log();
16
+ console.log(chalk.cyan.bold("AIGRC") + chalk.dim(" - AI Governance, Risk, Compliance"));
17
+ console.log(chalk.dim("\u2500".repeat(50)));
18
+ console.log();
19
+ }
20
+ function printSubheader(text) {
21
+ console.log();
22
+ console.log(chalk.bold.cyan(`\u25B8 ${text}`));
23
+ console.log(chalk.cyan("\u2500".repeat(40)));
24
+ }
25
+ function printInfo(text) {
26
+ console.log(chalk.gray(`\u2139 ${text}`));
27
+ }
28
+ function printScanSummary(result) {
29
+ const { summary, scannedFiles, skippedFiles, duration } = result;
30
+ printSubheader("Scan Summary");
31
+ console.log(` Files scanned: ${chalk.bold(scannedFiles)}`);
32
+ if (skippedFiles > 0) {
33
+ console.log(` Files skipped: ${chalk.yellow(skippedFiles)}`);
34
+ }
35
+ console.log(` Duration: ${chalk.gray(`${duration}ms`)}`);
36
+ console.log();
37
+ if (summary.totalDetections === 0) {
38
+ printInfo("No AI frameworks or models detected.");
39
+ return;
40
+ }
41
+ console.log(` ${chalk.bold("Total detections:")} ${summary.totalDetections}`);
42
+ console.log();
43
+ console.log(chalk.dim(" By confidence:"));
44
+ if (summary.byConfidence.high > 0) {
45
+ console.log(` ${confidenceLabel("high")}: ${summary.byConfidence.high}`);
46
+ }
47
+ if (summary.byConfidence.medium > 0) {
48
+ console.log(` ${confidenceLabel("medium")}: ${summary.byConfidence.medium}`);
49
+ }
50
+ if (summary.byConfidence.low > 0) {
51
+ console.log(` ${confidenceLabel("low")}: ${summary.byConfidence.low}`);
52
+ }
53
+ console.log();
54
+ if (Object.keys(summary.byFramework).length > 0) {
55
+ console.log(chalk.dim(" By framework:"));
56
+ for (const [framework, count] of Object.entries(summary.byFramework)) {
57
+ console.log(` ${chalk.bold(framework)}: ${count}`);
58
+ }
59
+ console.log();
60
+ }
61
+ if (summary.primaryFramework) {
62
+ console.log(
63
+ ` ${chalk.bold("Primary framework:")} ${chalk.cyan(summary.primaryFramework)}`
64
+ );
65
+ }
66
+ if (summary.primaryCategory) {
67
+ console.log(
68
+ ` ${chalk.bold("Primary category:")} ${formatCategory(summary.primaryCategory)}`
69
+ );
70
+ }
71
+ }
72
+ function printDetections(detections, verbose = false, limit = 20) {
73
+ if (detections.length === 0) {
74
+ return;
75
+ }
76
+ printSubheader(`Detections (${detections.length} total)`);
77
+ const toShow = detections.slice(0, limit);
78
+ for (const detection of toShow) {
79
+ const confidence = confidenceLabel(detection.confidence);
80
+ const location = chalk.gray(`${detection.filePath}:${detection.line}`);
81
+ const framework = chalk.bold(detection.framework);
82
+ console.log(` ${confidence} ${framework}`);
83
+ console.log(` ${location}`);
84
+ console.log(` ${chalk.dim(truncate(detection.match, 60))}`);
85
+ if (verbose) {
86
+ console.log(` ${chalk.dim(`Strategy: ${detection.strategy}`)}`);
87
+ if (detection.implies.length > 0) {
88
+ console.log(
89
+ ` ${chalk.dim(`Implies: ${detection.implies.map((i) => i.factor).join(", ")}`)}`
90
+ );
91
+ }
92
+ }
93
+ console.log();
94
+ }
95
+ if (detections.length > limit) {
96
+ printInfo(`... and ${detections.length - limit} more detections`);
97
+ }
98
+ }
99
+ function printRiskFactors(riskFactors) {
100
+ printSubheader("Inferred Risk Factors");
101
+ const factors = [
102
+ { key: "autonomousDecisions", label: "Autonomous Decisions", value: riskFactors.autonomousDecisions },
103
+ { key: "customerFacing", label: "Customer Facing", value: riskFactors.customerFacing },
104
+ { key: "toolExecution", label: "Tool Execution", value: riskFactors.toolExecution },
105
+ { key: "externalDataAccess", label: "External Data Access", value: riskFactors.externalDataAccess },
106
+ { key: "piiProcessing", label: "PII Processing", value: riskFactors.piiProcessing },
107
+ { key: "highStakesDecisions", label: "High-Stakes Decisions", value: riskFactors.highStakesDecisions }
108
+ ];
109
+ for (const factor of factors) {
110
+ const icon = factor.value === true || factor.value === "yes" ? chalk.red("\u25CF") : factor.value === false || factor.value === "no" ? chalk.green("\u25CB") : chalk.yellow("\u25D0");
111
+ const valueStr = factor.value === true ? chalk.red("Yes") : factor.value === false ? chalk.green("No") : factor.value === "yes" ? chalk.red("Yes") : factor.value === "no" ? chalk.green("No") : chalk.yellow("Unknown");
112
+ console.log(` ${icon} ${factor.label}: ${valueStr}`);
113
+ }
114
+ }
115
+ function confidenceLabel(confidence) {
116
+ switch (confidence) {
117
+ case "high":
118
+ return chalk.green("HIGH ");
119
+ case "medium":
120
+ return chalk.yellow("MEDIUM");
121
+ case "low":
122
+ return chalk.gray("LOW ");
123
+ }
124
+ }
125
+ function formatCategory(category) {
126
+ const labels = {
127
+ llm_provider: "LLM Provider",
128
+ framework: "Framework",
129
+ agent_framework: "Agent Framework",
130
+ ml_framework: "ML Framework",
131
+ ml_ops: "ML Ops",
132
+ model_file: "Model File"
133
+ };
134
+ return labels[category] ?? category;
135
+ }
136
+ function truncate(text, maxLength) {
137
+ const cleaned = text.replace(/\s+/g, " ").trim();
138
+ if (cleaned.length <= maxLength) {
139
+ return cleaned;
140
+ }
141
+ return cleaned.slice(0, maxLength - 3) + "...";
142
+ }
143
+ function printAssetSuggestion(suggestion) {
144
+ printSubheader("Asset Card Suggestion");
145
+ console.log(` ${chalk.bold("Name:")} ${chalk.cyan(suggestion.name)}`);
146
+ console.log(` ${chalk.bold("Description:")} ${suggestion.description}`);
147
+ console.log(` ${chalk.bold("Type:")} ${suggestion.technical.type}`);
148
+ console.log(` ${chalk.bold("Framework:")} ${suggestion.technical.framework}`);
149
+ console.log(` ${chalk.bold("Confidence:")} ${confidenceLabel(suggestion.confidence)}`);
150
+ console.log();
151
+ if (suggestion.technical.components.length > 0) {
152
+ console.log(chalk.dim(" Components:"));
153
+ for (const comp of suggestion.technical.components) {
154
+ const details = [comp.type];
155
+ if (comp.provider) details.push(`provider: ${comp.provider}`);
156
+ if (comp.model) details.push(`model: ${comp.model}`);
157
+ console.log(` - ${details.join(", ")}`);
158
+ }
159
+ console.log();
160
+ }
161
+ if (Object.keys(suggestion.riskFactors).length > 0) {
162
+ console.log(chalk.dim(" Risk Factors:"));
163
+ printRiskFactorsInline(suggestion.riskFactors);
164
+ }
165
+ }
166
+ function printRiskFactorsInline(riskFactors) {
167
+ const factors = [
168
+ { key: "autonomousDecisions", label: "Autonomous", value: riskFactors.autonomousDecisions },
169
+ { key: "customerFacing", label: "Customer-Facing", value: riskFactors.customerFacing },
170
+ { key: "toolExecution", label: "Tool Execution", value: riskFactors.toolExecution },
171
+ { key: "externalDataAccess", label: "External Data", value: riskFactors.externalDataAccess },
172
+ { key: "piiProcessing", label: "PII", value: riskFactors.piiProcessing },
173
+ { key: "highStakesDecisions", label: "High-Stakes", value: riskFactors.highStakesDecisions }
174
+ ];
175
+ for (const factor of factors) {
176
+ if (factor.value === void 0) continue;
177
+ const icon = factor.value === true || factor.value === "yes" ? chalk.red("\u25CF") : factor.value === false || factor.value === "no" ? chalk.green("\u25CB") : chalk.yellow("\u25D0");
178
+ const valueStr = factor.value === true || factor.value === "yes" ? chalk.red("Yes") : factor.value === false || factor.value === "no" ? chalk.green("No") : chalk.yellow("Unknown");
179
+ console.log(` ${icon} ${factor.label}: ${valueStr}`);
180
+ }
181
+ }
182
+
183
+ // src/commands/scan.ts
184
+ var scanCommand = new Command("scan").description("Scan a directory for AI/ML frameworks and generate risk assessments").argument("[directory]", "Directory to scan", ".").option("-o, --output <format>", "Output format (text, json, yaml)", "text").option("-v, --verbose", "Show detailed detection information").option("-i, --include <patterns...>", "Include glob patterns").option("-e, --exclude <patterns...>", "Exclude glob patterns").option("-d, --max-depth <depth>", "Maximum directory depth", parseInt).option("-s, --suggest", "Generate asset card suggestion from detections").option("--no-progress", "Disable progress spinner").action(async (directory, options) => {
185
+ await runScan(directory, options);
186
+ });
187
+ async function runScan(directory, options) {
188
+ const targetDir = path.resolve(process.cwd(), directory);
189
+ if (options.output === "text") {
190
+ printHeader();
191
+ console.log(chalk2.dim(`Scanning: ${targetDir}
192
+ `));
193
+ }
194
+ initializePatterns();
195
+ const scanOptions = {
196
+ directory: targetDir,
197
+ ignorePatterns: options.exclude
198
+ };
199
+ let spinner;
200
+ let lastFile = "";
201
+ if (options.output === "text" && options.noProgress !== false) {
202
+ spinner = ora("Scanning...").start();
203
+ }
204
+ try {
205
+ const result = await scan(scanOptions, (progress) => {
206
+ if (spinner && progress.currentFile !== lastFile) {
207
+ lastFile = progress.currentFile;
208
+ const pct = Math.round(progress.scannedFiles / Math.max(progress.totalFiles, 1) * 100);
209
+ spinner.text = `Scanning (${pct}%): ${path.basename(progress.currentFile)}`;
210
+ }
211
+ });
212
+ spinner?.succeed(`Scanned ${result.scannedFiles} files`);
213
+ if (options.output === "json") {
214
+ outputJson(result);
215
+ } else if (options.output === "yaml") {
216
+ await outputYaml(result);
217
+ } else {
218
+ outputText(result, options);
219
+ }
220
+ if (options.suggest && result.detections.length > 0) {
221
+ const suggestion = suggestAssetCard(result);
222
+ if (options.output === "json") {
223
+ console.log(JSON.stringify({ suggestion }, null, 2));
224
+ } else if (options.output === "yaml") {
225
+ const { stringify: stringify2 } = await import("yaml");
226
+ console.log(stringify2({ suggestion }));
227
+ } else {
228
+ console.log();
229
+ printAssetSuggestion(suggestion);
230
+ }
231
+ }
232
+ if (result.summary.byConfidence.high > 0) {
233
+ process.exitCode = 1;
234
+ }
235
+ } catch (error) {
236
+ spinner?.fail("Scan failed");
237
+ if (error instanceof Error) {
238
+ console.error(chalk2.red(`
239
+ Error: ${error.message}`));
240
+ if (options.verbose) {
241
+ console.error(chalk2.dim(error.stack));
242
+ }
243
+ }
244
+ process.exit(1);
245
+ }
246
+ }
247
+ function outputText(result, options) {
248
+ console.log();
249
+ printScanSummary(result);
250
+ if (result.detections.length > 0) {
251
+ console.log();
252
+ printDetections(result.detections, options.verbose);
253
+ } else {
254
+ console.log(chalk2.dim("\nNo AI/ML frameworks detected."));
255
+ }
256
+ if (result.inferredRiskFactors && Object.keys(result.inferredRiskFactors).length > 0) {
257
+ console.log();
258
+ printRiskFactors(result.inferredRiskFactors);
259
+ }
260
+ }
261
+ function outputJson(result) {
262
+ console.log(JSON.stringify(result, null, 2));
263
+ }
264
+ async function outputYaml(result) {
265
+ const { stringify: stringify2 } = await import("yaml");
266
+ console.log(stringify2(result));
267
+ }
268
+
269
+ // src/commands/init.ts
270
+ import { Command as Command2 } from "commander";
271
+ import chalk3 from "chalk";
272
+ import ora2 from "ora";
273
+ import path2 from "path";
274
+ import fs from "fs/promises";
275
+ import { stringify } from "yaml";
276
+ import {
277
+ scan as scan2,
278
+ suggestAssetCard as suggestAssetCard2,
279
+ initializePatterns as initializePatterns2,
280
+ createAssetCard,
281
+ saveAssetCard
282
+ } from "@aigrc/core";
283
+
284
+ // src/utils/prompts.ts
285
+ import inquirer from "inquirer";
286
+ async function promptForOwner() {
287
+ const answers = await inquirer.prompt([
288
+ {
289
+ type: "input",
290
+ name: "name",
291
+ message: "Owner name:",
292
+ validate: (input) => input.length > 0 || "Name is required"
293
+ },
294
+ {
295
+ type: "input",
296
+ name: "email",
297
+ message: "Owner email:",
298
+ validate: (input) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input) || "Valid email is required"
299
+ },
300
+ {
301
+ type: "input",
302
+ name: "team",
303
+ message: "Team name (optional):"
304
+ }
305
+ ]);
306
+ return {
307
+ name: answers.name,
308
+ email: answers.email,
309
+ team: answers.team || void 0
310
+ };
311
+ }
312
+ async function promptForRegistration(suggestion) {
313
+ console.log();
314
+ const basicAnswers = await inquirer.prompt([
315
+ {
316
+ type: "input",
317
+ name: "name",
318
+ message: "Asset name:",
319
+ default: suggestion.name,
320
+ validate: (input) => input.length > 0 || "Name is required"
321
+ },
322
+ {
323
+ type: "input",
324
+ name: "description",
325
+ message: "Description:",
326
+ default: suggestion.description
327
+ }
328
+ ]);
329
+ console.log("\n-- Owner Information --\n");
330
+ const owner = await promptForOwner();
331
+ console.log("\n-- Risk Factors --\n");
332
+ console.log("Based on the scan, the following risk factors were inferred:");
333
+ console.log();
334
+ const riskFactorLabels = [
335
+ { key: "autonomousDecisions", label: "Autonomous Decisions", value: suggestion.riskFactors.autonomousDecisions },
336
+ { key: "customerFacing", label: "Customer Facing", value: suggestion.riskFactors.customerFacing },
337
+ { key: "toolExecution", label: "Tool Execution", value: suggestion.riskFactors.toolExecution },
338
+ { key: "externalDataAccess", label: "External Data Access", value: suggestion.riskFactors.externalDataAccess },
339
+ { key: "piiProcessing", label: "PII Processing", value: suggestion.riskFactors.piiProcessing },
340
+ { key: "highStakesDecisions", label: "High-Stakes Decisions", value: suggestion.riskFactors.highStakesDecisions }
341
+ ];
342
+ for (const factor of riskFactorLabels) {
343
+ const value = factor.value === true || factor.value === "yes" ? "Yes" : factor.value === false || factor.value === "no" ? "No" : "Unknown";
344
+ console.log(` ${factor.label}: ${value}`);
345
+ }
346
+ console.log();
347
+ const { confirmRiskFactors } = await inquirer.prompt([
348
+ {
349
+ type: "confirm",
350
+ name: "confirmRiskFactors",
351
+ message: "Are these risk factors correct?",
352
+ default: true
353
+ }
354
+ ]);
355
+ let riskFactorOverrides;
356
+ if (!confirmRiskFactors) {
357
+ riskFactorOverrides = await promptForRiskFactorOverrides(suggestion.riskFactors);
358
+ }
359
+ return {
360
+ name: basicAnswers.name,
361
+ description: basicAnswers.description,
362
+ owner,
363
+ confirmRiskFactors,
364
+ riskFactorOverrides
365
+ };
366
+ }
367
+ async function promptForRiskFactorOverrides(current) {
368
+ const answers = await inquirer.prompt([
369
+ {
370
+ type: "confirm",
371
+ name: "autonomousDecisions",
372
+ message: "Does this AI make autonomous decisions?",
373
+ default: current.autonomousDecisions ?? false
374
+ },
375
+ {
376
+ type: "confirm",
377
+ name: "customerFacing",
378
+ message: "Is this AI customer-facing?",
379
+ default: current.customerFacing ?? false
380
+ },
381
+ {
382
+ type: "confirm",
383
+ name: "toolExecution",
384
+ message: "Does this AI execute tools or functions?",
385
+ default: current.toolExecution ?? false
386
+ },
387
+ {
388
+ type: "confirm",
389
+ name: "externalDataAccess",
390
+ message: "Does this AI access external data?",
391
+ default: current.externalDataAccess ?? false
392
+ },
393
+ {
394
+ type: "list",
395
+ name: "piiProcessing",
396
+ message: "Does this AI process PII (personal data)?",
397
+ choices: [
398
+ { name: "Yes", value: "yes" },
399
+ { name: "No", value: "no" },
400
+ { name: "Unknown", value: "unknown" }
401
+ ],
402
+ default: current.piiProcessing ?? "unknown"
403
+ },
404
+ {
405
+ type: "confirm",
406
+ name: "highStakesDecisions",
407
+ message: "Does this AI make high-stakes decisions (medical, legal, financial, hiring)?",
408
+ default: current.highStakesDecisions ?? false
409
+ }
410
+ ]);
411
+ return answers;
412
+ }
413
+
414
+ // src/commands/init.ts
415
+ var DEFAULT_CONFIG_FILE = ".aigrc.yaml";
416
+ var DEFAULT_CARDS_DIR = ".aigrc/cards";
417
+ var initCommand = new Command2("init").description("Initialize AIGRC in a project - scan, detect, and create asset card").argument("[directory]", "Directory to initialize", ".").option("-f, --force", "Overwrite existing configuration").option("-y, --yes", "Accept all defaults without prompting").option("-o, --output <path>", "Output path for asset card").action(async (directory, options) => {
418
+ await runInit(directory, options);
419
+ });
420
+ async function runInit(directory, options) {
421
+ const targetDir = path2.resolve(process.cwd(), directory);
422
+ printHeader();
423
+ console.log(chalk3.cyan("Initializing AIGRC in:"), targetDir);
424
+ console.log();
425
+ const configPath = path2.join(targetDir, DEFAULT_CONFIG_FILE);
426
+ const configExists = await fileExists(configPath);
427
+ if (configExists && !options.force) {
428
+ console.log(chalk3.yellow("\u26A0 AIGRC already initialized in this directory."));
429
+ console.log(chalk3.dim(` Config file: ${configPath}`));
430
+ console.log(chalk3.dim(" Use --force to reinitialize."));
431
+ return;
432
+ }
433
+ initializePatterns2();
434
+ const spinner = ora2("Scanning for AI/ML frameworks...").start();
435
+ try {
436
+ const result = await scan2({
437
+ directory: targetDir,
438
+ ignorePatterns: ["node_modules", ".git", "dist", "build", "__pycache__", ".venv", "venv"]
439
+ });
440
+ spinner.succeed(`Found ${result.detections.length} AI/ML detections`);
441
+ if (result.detections.length === 0) {
442
+ console.log();
443
+ console.log(chalk3.yellow("No AI/ML frameworks detected."));
444
+ console.log(chalk3.dim("You can still create an asset card manually using:"));
445
+ console.log(chalk3.dim(" aigrc register --manual"));
446
+ return;
447
+ }
448
+ const suggestion = suggestAssetCard2(result);
449
+ console.log();
450
+ printAssetSuggestion(suggestion);
451
+ console.log();
452
+ let registrationData;
453
+ if (options.yes) {
454
+ registrationData = {
455
+ name: suggestion.name,
456
+ description: suggestion.description,
457
+ owner: {
458
+ name: process.env.USER || process.env.USERNAME || "Unknown",
459
+ email: `${process.env.USER || process.env.USERNAME || "user"}@example.com`
460
+ },
461
+ confirmRiskFactors: true
462
+ };
463
+ } else {
464
+ registrationData = await promptForRegistration(suggestion);
465
+ }
466
+ const riskFactors = registrationData.confirmRiskFactors ? suggestion.riskFactors : { ...suggestion.riskFactors, ...registrationData.riskFactorOverrides };
467
+ const assetCard = createAssetCard({
468
+ name: registrationData.name,
469
+ description: registrationData.description,
470
+ owner: registrationData.owner,
471
+ technical: {
472
+ type: suggestion.technical.type,
473
+ framework: suggestion.technical.framework
474
+ },
475
+ riskFactors: {
476
+ autonomousDecisions: riskFactors.autonomousDecisions ?? false,
477
+ customerFacing: riskFactors.customerFacing ?? false,
478
+ toolExecution: riskFactors.toolExecution ?? false,
479
+ externalDataAccess: riskFactors.externalDataAccess ?? false,
480
+ piiProcessing: riskFactors.piiProcessing ?? "unknown",
481
+ highStakesDecisions: riskFactors.highStakesDecisions ?? false
482
+ }
483
+ });
484
+ const cardsDir = path2.join(targetDir, DEFAULT_CARDS_DIR);
485
+ await fs.mkdir(cardsDir, { recursive: true });
486
+ const cardFileName = `${sanitizeFileName(assetCard.name)}.yaml`;
487
+ const cardPath = options.output || path2.join(cardsDir, cardFileName);
488
+ saveAssetCard(assetCard, cardPath);
489
+ console.log();
490
+ console.log(chalk3.green("\u2713 Asset card created:"), chalk3.cyan(cardPath));
491
+ const config = {
492
+ version: "1.0",
493
+ cardsDir: DEFAULT_CARDS_DIR,
494
+ scan: {
495
+ exclude: ["node_modules", ".git", "dist", "build", "__pycache__", ".venv", "venv"]
496
+ }
497
+ };
498
+ await fs.writeFile(configPath, stringify(config), "utf-8");
499
+ console.log(chalk3.green("\u2713 Config file created:"), chalk3.cyan(configPath));
500
+ console.log();
501
+ console.log(chalk3.green("AIGRC initialized successfully!"));
502
+ console.log();
503
+ console.log(chalk3.dim("Next steps:"));
504
+ console.log(chalk3.dim(" 1. Review and edit the asset card: ") + chalk3.cyan(cardPath));
505
+ console.log(chalk3.dim(" 2. Run ") + chalk3.cyan("aigrc validate") + chalk3.dim(" to check compliance"));
506
+ console.log(chalk3.dim(" 3. Run ") + chalk3.cyan("aigrc status") + chalk3.dim(" to see current status"));
507
+ } catch (error) {
508
+ spinner.fail("Initialization failed");
509
+ if (error instanceof Error) {
510
+ console.error(chalk3.red(`
511
+ Error: ${error.message}`));
512
+ }
513
+ process.exit(1);
514
+ }
515
+ }
516
+ async function fileExists(filePath) {
517
+ try {
518
+ await fs.access(filePath);
519
+ return true;
520
+ } catch {
521
+ return false;
522
+ }
523
+ }
524
+ function sanitizeFileName(name) {
525
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
526
+ }
527
+
528
+ // src/commands/register.ts
529
+ import { Command as Command3 } from "commander";
530
+ import chalk4 from "chalk";
531
+ import path3 from "path";
532
+ import fs2 from "fs/promises";
533
+ import inquirer2 from "inquirer";
534
+ import {
535
+ createAssetCard as createAssetCard2,
536
+ saveAssetCard as saveAssetCard2
537
+ } from "@aigrc/core";
538
+ var DEFAULT_CARDS_DIR2 = ".aigrc/cards";
539
+ var registerCommand = new Command3("register").description("Manually register an AI asset without scanning").option("-o, --output <path>", "Output path for asset card").option("-m, --manual", "Skip detection and enter all details manually").action(async (options) => {
540
+ await runRegister(options);
541
+ });
542
+ async function runRegister(options) {
543
+ printHeader();
544
+ console.log(chalk4.cyan("Register a new AI asset\n"));
545
+ const basicInfo = await inquirer2.prompt([
546
+ {
547
+ type: "input",
548
+ name: "name",
549
+ message: "Asset name:",
550
+ validate: (input) => input.length > 0 || "Name is required"
551
+ },
552
+ {
553
+ type: "input",
554
+ name: "description",
555
+ message: "Description:",
556
+ validate: (input) => input.length > 0 || "Description is required"
557
+ },
558
+ {
559
+ type: "input",
560
+ name: "purpose",
561
+ message: "Business purpose:"
562
+ }
563
+ ]);
564
+ console.log("\n-- Owner Information --\n");
565
+ const owner = await promptForOwner();
566
+ console.log("\n-- Risk Factors --\n");
567
+ const riskFactors = await promptForRiskFactors();
568
+ console.log("\n-- Framework Information --\n");
569
+ const frameworks = await promptForFrameworks();
570
+ const frameworkTypes = {
571
+ openai: "api_client",
572
+ anthropic: "api_client",
573
+ langchain: "framework",
574
+ llamaindex: "framework",
575
+ "vercel-ai-sdk": "framework",
576
+ pytorch: "model",
577
+ tensorflow: "model",
578
+ huggingface: "model",
579
+ crewai: "agent",
580
+ autogen: "agent"
581
+ };
582
+ const primaryFramework = frameworks[0] || "unknown";
583
+ const technicalType = frameworkTypes[primaryFramework] || "framework";
584
+ const assetCard = createAssetCard2({
585
+ name: basicInfo.name,
586
+ description: basicInfo.description,
587
+ owner,
588
+ technical: {
589
+ type: technicalType,
590
+ framework: primaryFramework
591
+ },
592
+ riskFactors
593
+ });
594
+ const cardsDir = path3.join(process.cwd(), DEFAULT_CARDS_DIR2);
595
+ await fs2.mkdir(cardsDir, { recursive: true });
596
+ const cardFileName = `${sanitizeFileName2(assetCard.name)}.yaml`;
597
+ const cardPath = options.output || path3.join(cardsDir, cardFileName);
598
+ saveAssetCard2(assetCard, cardPath);
599
+ console.log();
600
+ console.log(chalk4.green("\u2713 Asset card created:"), chalk4.cyan(cardPath));
601
+ console.log();
602
+ console.log(chalk4.dim("Next steps:"));
603
+ console.log(chalk4.dim(" 1. Review and edit the asset card"));
604
+ console.log(chalk4.dim(" 2. Run ") + chalk4.cyan("aigrc validate") + chalk4.dim(" to check compliance"));
605
+ }
606
+ async function promptForRiskFactors() {
607
+ const answers = await inquirer2.prompt([
608
+ {
609
+ type: "confirm",
610
+ name: "autonomousDecisions",
611
+ message: "Does this AI make autonomous decisions?",
612
+ default: false
613
+ },
614
+ {
615
+ type: "confirm",
616
+ name: "customerFacing",
617
+ message: "Is this AI customer-facing?",
618
+ default: false
619
+ },
620
+ {
621
+ type: "confirm",
622
+ name: "toolExecution",
623
+ message: "Does this AI execute tools or functions?",
624
+ default: false
625
+ },
626
+ {
627
+ type: "confirm",
628
+ name: "externalDataAccess",
629
+ message: "Does this AI access external data?",
630
+ default: false
631
+ },
632
+ {
633
+ type: "list",
634
+ name: "piiProcessing",
635
+ message: "Does this AI process PII (personal data)?",
636
+ choices: [
637
+ { name: "Yes", value: "yes" },
638
+ { name: "No", value: "no" },
639
+ { name: "Unknown", value: "unknown" }
640
+ ],
641
+ default: "unknown"
642
+ },
643
+ {
644
+ type: "confirm",
645
+ name: "highStakesDecisions",
646
+ message: "Does this AI make high-stakes decisions (medical, legal, financial, hiring)?",
647
+ default: false
648
+ }
649
+ ]);
650
+ return answers;
651
+ }
652
+ async function promptForFrameworks() {
653
+ const { hasFrameworks } = await inquirer2.prompt([
654
+ {
655
+ type: "confirm",
656
+ name: "hasFrameworks",
657
+ message: "Do you want to specify AI/ML frameworks used?",
658
+ default: true
659
+ }
660
+ ]);
661
+ if (!hasFrameworks) {
662
+ return [];
663
+ }
664
+ const { frameworkList } = await inquirer2.prompt([
665
+ {
666
+ type: "checkbox",
667
+ name: "frameworkList",
668
+ message: "Select frameworks used:",
669
+ choices: [
670
+ { name: "OpenAI API", value: "openai" },
671
+ { name: "Anthropic API", value: "anthropic" },
672
+ { name: "LangChain", value: "langchain" },
673
+ { name: "LlamaIndex", value: "llamaindex" },
674
+ { name: "Vercel AI SDK", value: "vercel-ai-sdk" },
675
+ { name: "PyTorch", value: "pytorch" },
676
+ { name: "TensorFlow", value: "tensorflow" },
677
+ { name: "Hugging Face", value: "huggingface" },
678
+ { name: "CrewAI", value: "crewai" },
679
+ { name: "AutoGen", value: "autogen" },
680
+ { name: "Other", value: "other" }
681
+ ]
682
+ }
683
+ ]);
684
+ if (frameworkList.includes("other")) {
685
+ const { otherFrameworks } = await inquirer2.prompt([
686
+ {
687
+ type: "input",
688
+ name: "otherFrameworks",
689
+ message: "Enter other frameworks (comma-separated):"
690
+ }
691
+ ]);
692
+ const others = otherFrameworks.split(",").map((f) => f.trim()).filter((f) => f.length > 0);
693
+ return [...frameworkList.filter((f) => f !== "other"), ...others];
694
+ }
695
+ return frameworkList;
696
+ }
697
+ function sanitizeFileName2(name) {
698
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
699
+ }
700
+
701
+ // src/commands/validate.ts
702
+ import { Command as Command4 } from "commander";
703
+ import chalk5 from "chalk";
704
+ import ora3 from "ora";
705
+ import path4 from "path";
706
+ import fs3 from "fs/promises";
707
+ import {
708
+ loadAssetCard,
709
+ classifyRisk,
710
+ validateAssetCard
711
+ } from "@aigrc/core";
712
+ var DEFAULT_CARDS_DIR3 = ".aigrc/cards";
713
+ var validateCommand = new Command4("validate").description("Validate asset cards against compliance requirements").argument("[path]", "Path to asset card or cards directory").option("-s, --strict", "Fail on warnings as well as errors").option("-o, --output <format>", "Output format (text, json)", "text").option("-a, --all", "Validate all cards in the cards directory").action(async (cardPath, options) => {
714
+ await runValidate(cardPath, options);
715
+ });
716
+ async function runValidate(cardPath, options) {
717
+ if (options.output === "text") {
718
+ printHeader();
719
+ }
720
+ const cardsToValidate = [];
721
+ if (options.all || !cardPath) {
722
+ const cardsDir = path4.join(process.cwd(), DEFAULT_CARDS_DIR3);
723
+ try {
724
+ const files = await fs3.readdir(cardsDir);
725
+ const yamlFiles = files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
726
+ for (const file of yamlFiles) {
727
+ cardsToValidate.push(path4.join(cardsDir, file));
728
+ }
729
+ } catch {
730
+ if (options.output === "text") {
731
+ console.log(chalk5.yellow("No cards directory found."));
732
+ console.log(chalk5.dim(`Expected: ${cardsDir}`));
733
+ console.log(chalk5.dim("Run `aigrc init` to initialize AIGRC."));
734
+ } else {
735
+ console.log(JSON.stringify({ error: "No cards directory found" }));
736
+ }
737
+ process.exit(1);
738
+ }
739
+ } else {
740
+ cardsToValidate.push(path4.resolve(process.cwd(), cardPath));
741
+ }
742
+ if (cardsToValidate.length === 0) {
743
+ if (options.output === "text") {
744
+ console.log(chalk5.yellow("No asset cards found to validate."));
745
+ } else {
746
+ console.log(JSON.stringify({ error: "No asset cards found" }));
747
+ }
748
+ process.exit(1);
749
+ }
750
+ const results = [];
751
+ let hasErrors = false;
752
+ for (const cardFile of cardsToValidate) {
753
+ const spinner = options.output === "text" ? ora3(`Validating ${path4.basename(cardFile)}...`).start() : void 0;
754
+ try {
755
+ const card = loadAssetCard(cardFile);
756
+ const validation = validateAssetCard(card);
757
+ const classification = classifyRisk(card.classification.riskFactors);
758
+ results.push({
759
+ path: cardFile,
760
+ card,
761
+ validation: {
762
+ valid: validation.valid,
763
+ errors: validation.errors ?? []
764
+ },
765
+ classification
766
+ });
767
+ if (!validation.valid) {
768
+ hasErrors = true;
769
+ spinner?.fail(`${path4.basename(cardFile)}: Invalid`);
770
+ } else {
771
+ spinner?.succeed(`${path4.basename(cardFile)}: Valid`);
772
+ }
773
+ } catch (error) {
774
+ hasErrors = true;
775
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
776
+ results.push({
777
+ path: cardFile,
778
+ validation: {
779
+ valid: false,
780
+ errors: [errorMessage]
781
+ }
782
+ });
783
+ spinner?.fail(`${path4.basename(cardFile)}: Parse error`);
784
+ }
785
+ }
786
+ if (options.output === "json") {
787
+ console.log(JSON.stringify({ results }, null, 2));
788
+ } else {
789
+ console.log();
790
+ printValidationSummary(results);
791
+ }
792
+ if (hasErrors) {
793
+ process.exit(1);
794
+ }
795
+ }
796
+ function printValidationSummary(results) {
797
+ console.log(chalk5.bold("Validation Summary"));
798
+ console.log(chalk5.dim("\u2500".repeat(50)));
799
+ console.log();
800
+ for (const result of results) {
801
+ const fileName = path4.basename(result.path);
802
+ if (!result.validation.valid) {
803
+ console.log(chalk5.red(`\u2717 ${fileName}`));
804
+ for (const error of result.validation.errors) {
805
+ console.log(chalk5.red(` Error: ${error}`));
806
+ }
807
+ } else {
808
+ console.log(chalk5.green(`\u2713 ${fileName}`));
809
+ if (result.card && result.classification) {
810
+ const tierColor = getRiskLevelColor(result.classification.riskLevel);
811
+ console.log(
812
+ chalk5.dim(" Risk Level: ") + tierColor(result.classification.riskLevel)
813
+ );
814
+ if (result.classification.euAiActCategory) {
815
+ console.log(
816
+ chalk5.dim(" EU AI Act: ") + chalk5.yellow(result.classification.euAiActCategory)
817
+ );
818
+ }
819
+ }
820
+ }
821
+ console.log();
822
+ }
823
+ const valid = results.filter((r) => r.validation.valid).length;
824
+ const invalid = results.filter((r) => !r.validation.valid).length;
825
+ console.log(chalk5.dim("\u2500".repeat(50)));
826
+ console.log(
827
+ `Total: ${results.length} | ` + chalk5.green(`Valid: ${valid}`) + ` | ` + chalk5.red(`Invalid: ${invalid}`)
828
+ );
829
+ }
830
+ function getRiskLevelColor(level) {
831
+ switch (level) {
832
+ case "minimal":
833
+ return chalk5.green;
834
+ case "limited":
835
+ return chalk5.yellow;
836
+ case "high":
837
+ return chalk5.red;
838
+ case "unacceptable":
839
+ return chalk5.magenta;
840
+ default:
841
+ return chalk5.white;
842
+ }
843
+ }
844
+
845
+ // src/commands/status.ts
846
+ import { Command as Command5 } from "commander";
847
+ import chalk6 from "chalk";
848
+ import path5 from "path";
849
+ import fs4 from "fs/promises";
850
+ import {
851
+ loadAssetCard as loadAssetCard2,
852
+ classifyRisk as classifyRisk2
853
+ } from "@aigrc/core";
854
+ var DEFAULT_CARDS_DIR4 = ".aigrc/cards";
855
+ var DEFAULT_CONFIG_FILE2 = ".aigrc.yaml";
856
+ var statusCommand = new Command5("status").description("Show the current AIGRC status and registered assets").option("-o, --output <format>", "Output format (text, json)", "text").action(async (options) => {
857
+ await runStatus(options);
858
+ });
859
+ async function runStatus(options) {
860
+ const cwd = process.cwd();
861
+ if (options.output === "text") {
862
+ printHeader();
863
+ }
864
+ const configPath = path5.join(cwd, DEFAULT_CONFIG_FILE2);
865
+ const cardsDir = path5.join(cwd, DEFAULT_CARDS_DIR4);
866
+ const configExists = await fileExists2(configPath);
867
+ const cardsDirExists = await directoryExists(cardsDir);
868
+ if (!configExists && !cardsDirExists) {
869
+ if (options.output === "text") {
870
+ console.log(chalk6.yellow("AIGRC is not initialized in this directory."));
871
+ console.log(chalk6.dim("\nRun `aigrc init` to get started."));
872
+ } else {
873
+ console.log(JSON.stringify({ initialized: false }));
874
+ }
875
+ return;
876
+ }
877
+ const cards = [];
878
+ if (cardsDirExists) {
879
+ try {
880
+ const files = await fs4.readdir(cardsDir);
881
+ const yamlFiles = files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
882
+ for (const file of yamlFiles) {
883
+ try {
884
+ const filePath = path5.join(cardsDir, file);
885
+ const card = loadAssetCard2(filePath);
886
+ const classification = classifyRisk2(card.classification.riskFactors);
887
+ cards.push({ path: filePath, card, classification });
888
+ } catch {
889
+ }
890
+ }
891
+ } catch {
892
+ }
893
+ }
894
+ if (options.output === "json") {
895
+ console.log(
896
+ JSON.stringify(
897
+ {
898
+ initialized: true,
899
+ configPath,
900
+ cardsDir,
901
+ cards: cards.map((c) => ({
902
+ path: c.path,
903
+ name: c.card.name,
904
+ id: c.card.id,
905
+ riskLevel: c.classification.riskLevel,
906
+ euAiActCategory: c.classification.euAiActCategory
907
+ }))
908
+ },
909
+ null,
910
+ 2
911
+ )
912
+ );
913
+ return;
914
+ }
915
+ console.log(chalk6.bold("AIGRC Status"));
916
+ console.log(chalk6.dim("\u2500".repeat(50)));
917
+ console.log();
918
+ console.log(chalk6.dim("Config:"), configExists ? chalk6.green("\u2713") : chalk6.red("\u2717"), configPath);
919
+ console.log(chalk6.dim("Cards:"), cardsDirExists ? chalk6.green("\u2713") : chalk6.red("\u2717"), cardsDir);
920
+ console.log();
921
+ if (cards.length === 0) {
922
+ console.log(chalk6.yellow("No asset cards registered."));
923
+ console.log(chalk6.dim("\nRun `aigrc init` or `aigrc register` to create an asset card."));
924
+ return;
925
+ }
926
+ console.log(chalk6.bold(`Registered Assets (${cards.length})`));
927
+ console.log(chalk6.dim("\u2500".repeat(50)));
928
+ console.log();
929
+ const byLevel = groupByRiskLevel(cards);
930
+ for (const level of ["unacceptable", "high", "limited", "minimal"]) {
931
+ const levelCards = byLevel.get(level);
932
+ if (!levelCards || levelCards.length === 0) continue;
933
+ const levelColor = getRiskLevelColor2(level);
934
+ console.log(levelColor(`${level.toUpperCase()} (${levelCards.length})`));
935
+ console.log();
936
+ for (const { card, classification } of levelCards) {
937
+ console.log(` ${chalk6.bold(card.name)}`);
938
+ console.log(chalk6.dim(` ID: ${card.id}`));
939
+ console.log(chalk6.dim(` Risk Level: ${classification.riskLevel}`));
940
+ if (classification.euAiActCategory) {
941
+ console.log(chalk6.dim(` EU AI Act: `) + chalk6.yellow(classification.euAiActCategory));
942
+ }
943
+ if (card.ownership?.owner) {
944
+ console.log(chalk6.dim(` Owner: ${card.ownership.owner.name} <${card.ownership.owner.email}>`));
945
+ }
946
+ const activeRisks = getActiveRiskFactors(card);
947
+ if (activeRisks.length > 0) {
948
+ console.log(chalk6.dim(` Risks: `) + activeRisks.join(", "));
949
+ }
950
+ console.log();
951
+ }
952
+ }
953
+ printStatusSummary(cards);
954
+ }
955
+ function groupByRiskLevel(cards) {
956
+ const byLevel = /* @__PURE__ */ new Map();
957
+ for (const item of cards) {
958
+ const level = item.classification.riskLevel;
959
+ if (!byLevel.has(level)) {
960
+ byLevel.set(level, []);
961
+ }
962
+ byLevel.get(level).push(item);
963
+ }
964
+ return byLevel;
965
+ }
966
+ function printStatusSummary(cards) {
967
+ console.log(chalk6.dim("\u2500".repeat(50)));
968
+ const minimal = cards.filter((c) => c.classification.riskLevel === "minimal").length;
969
+ const limited = cards.filter((c) => c.classification.riskLevel === "limited").length;
970
+ const high = cards.filter((c) => c.classification.riskLevel === "high").length;
971
+ const unacceptable = cards.filter((c) => c.classification.riskLevel === "unacceptable").length;
972
+ console.log(
973
+ `Total: ${cards.length} | ` + chalk6.green(`Minimal: ${minimal}`) + ` | ` + chalk6.yellow(`Limited: ${limited}`) + ` | ` + chalk6.red(`High: ${high}`) + ` | ` + chalk6.magenta(`Unacceptable: ${unacceptable}`)
974
+ );
975
+ if (high > 0 || unacceptable > 0) {
976
+ console.log();
977
+ console.log(chalk6.yellow("\u26A0 High-risk assets detected. Review compliance requirements."));
978
+ }
979
+ }
980
+ function getActiveRiskFactors(card) {
981
+ const risks = [];
982
+ const rf = card.classification?.riskFactors;
983
+ if (!rf) return risks;
984
+ if (rf.autonomousDecisions) risks.push("Autonomous");
985
+ if (rf.customerFacing) risks.push("Customer-Facing");
986
+ if (rf.toolExecution) risks.push("Tool Execution");
987
+ if (rf.externalDataAccess) risks.push("External Data");
988
+ if (rf.piiProcessing === "yes") risks.push("PII");
989
+ if (rf.highStakesDecisions) risks.push("High-Stakes");
990
+ return risks;
991
+ }
992
+ function getRiskLevelColor2(level) {
993
+ switch (level) {
994
+ case "minimal":
995
+ return chalk6.green;
996
+ case "limited":
997
+ return chalk6.yellow;
998
+ case "high":
999
+ return chalk6.red;
1000
+ case "unacceptable":
1001
+ return chalk6.magenta;
1002
+ default:
1003
+ return chalk6.white;
1004
+ }
1005
+ }
1006
+ async function fileExists2(filePath) {
1007
+ try {
1008
+ const stat = await fs4.stat(filePath);
1009
+ return stat.isFile();
1010
+ } catch {
1011
+ return false;
1012
+ }
1013
+ }
1014
+ async function directoryExists(dirPath) {
1015
+ try {
1016
+ const stat = await fs4.stat(dirPath);
1017
+ return stat.isDirectory();
1018
+ } catch {
1019
+ return false;
1020
+ }
1021
+ }
1022
+ export {
1023
+ initCommand,
1024
+ registerCommand,
1025
+ scanCommand,
1026
+ statusCommand,
1027
+ validateCommand
1028
+ };
1029
+ //# sourceMappingURL=index.js.map