@neurcode-ai/cli 0.9.36 → 0.9.37
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/README.md +4 -2
- package/dist/api-client.d.ts +300 -1
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +225 -9
- package/dist/api-client.js.map +1 -1
- package/dist/commands/audit.d.ts +3 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +133 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/contract.d.ts +3 -0
- package/dist/commands/contract.d.ts.map +1 -0
- package/dist/commands/contract.js +235 -0
- package/dist/commands/contract.js.map +1 -0
- package/dist/commands/feedback.d.ts +3 -0
- package/dist/commands/feedback.d.ts.map +1 -0
- package/dist/commands/feedback.js +208 -0
- package/dist/commands/feedback.js.map +1 -0
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +19 -3
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/policy.d.ts.map +1 -1
- package/dist/commands/policy.js +329 -6
- package/dist/commands/policy.js.map +1 -1
- package/dist/commands/remediate.d.ts +17 -0
- package/dist/commands/remediate.d.ts.map +1 -0
- package/dist/commands/remediate.js +252 -0
- package/dist/commands/remediate.js.map +1 -0
- package/dist/commands/ship.d.ts.map +1 -1
- package/dist/commands/ship.js +67 -14
- package/dist/commands/ship.js.map +1 -1
- package/dist/commands/verify.d.ts +12 -0
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +477 -13
- package/dist/commands/verify.js.map +1 -1
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/artifact-signature.d.ts +34 -0
- package/dist/utils/artifact-signature.d.ts.map +1 -0
- package/dist/utils/artifact-signature.js +229 -0
- package/dist/utils/artifact-signature.js.map +1 -0
- package/dist/utils/change-contract.d.ts +2 -0
- package/dist/utils/change-contract.d.ts.map +1 -1
- package/dist/utils/change-contract.js +21 -1
- package/dist/utils/change-contract.js.map +1 -1
- package/dist/utils/policy-compiler.d.ts +2 -0
- package/dist/utils/policy-compiler.d.ts.map +1 -1
- package/dist/utils/policy-compiler.js +15 -0
- package/dist/utils/policy-compiler.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/policy.js
CHANGED
|
@@ -10,6 +10,7 @@ const policy_governance_1 = require("../utils/policy-governance");
|
|
|
10
10
|
const policy_audit_1 = require("../utils/policy-audit");
|
|
11
11
|
const policy_packs_1 = require("../utils/policy-packs");
|
|
12
12
|
const policy_compiler_1 = require("../utils/policy-compiler");
|
|
13
|
+
const artifact_signature_1 = require("../utils/artifact-signature");
|
|
13
14
|
// Import chalk with fallback
|
|
14
15
|
let chalk;
|
|
15
16
|
try {
|
|
@@ -73,6 +74,11 @@ function validateExceptionWindowByGovernance(expiresAt, maxExpiryDays) {
|
|
|
73
74
|
throw new Error(`exception expiry exceeds governance max window (${maxExpiryDays} days)`);
|
|
74
75
|
}
|
|
75
76
|
}
|
|
77
|
+
function normalizeListLimit(value, fallback, min, max) {
|
|
78
|
+
if (!Number.isFinite(value))
|
|
79
|
+
return fallback;
|
|
80
|
+
return Math.max(min, Math.min(max, Math.floor(Number(value))));
|
|
81
|
+
}
|
|
76
82
|
async function resolveCustomPolicies(client, includeDashboardPolicies, requireDashboardPolicies) {
|
|
77
83
|
if (!includeDashboardPolicies) {
|
|
78
84
|
return {
|
|
@@ -382,6 +388,7 @@ function policyCommand(program) {
|
|
|
382
388
|
.option('--intent <text>', 'Optional intent constraints to compile alongside policy rules')
|
|
383
389
|
.option('--no-dashboard', 'Exclude dashboard custom policies from compiled artifact')
|
|
384
390
|
.option('--require-dashboard', 'Fail if dashboard custom policies cannot be loaded')
|
|
391
|
+
.option('--require-deterministic-match', 'Fail if any intent statement cannot be compiled into deterministic enforcement rules')
|
|
385
392
|
.option('--output <path>', 'Output file path (default: neurcode.policy.compiled.json)')
|
|
386
393
|
.option('--json', 'Output as JSON')
|
|
387
394
|
.action(async (options) => {
|
|
@@ -402,7 +409,7 @@ function policyCommand(program) {
|
|
|
402
409
|
customRules,
|
|
403
410
|
includeDashboardPolicies: customPolicyResolution.includeDashboardPolicies,
|
|
404
411
|
});
|
|
405
|
-
const
|
|
412
|
+
const compiledUnsigned = (0, policy_compiler_1.buildCompiledPolicyArtifact)({
|
|
406
413
|
includeDashboardPolicies: customPolicyResolution.includeDashboardPolicies,
|
|
407
414
|
policyLockPath: (0, policy_packs_1.getPolicyLockPath)(cwd),
|
|
408
415
|
policyLockFingerprint: snapshot.effective.fingerprint,
|
|
@@ -420,6 +427,19 @@ function policyCommand(program) {
|
|
|
420
427
|
intentConstraints: options.intent,
|
|
421
428
|
policyRules: customPolicyResolution.customPolicies.map((policy) => policy.rule_text),
|
|
422
429
|
});
|
|
430
|
+
const artifactSigningConfig = (0, artifact_signature_1.resolveGovernanceArtifactSigningConfigFromEnv)();
|
|
431
|
+
const compiled = (0, artifact_signature_1.signGovernanceArtifact)(compiledUnsigned, artifactSigningConfig);
|
|
432
|
+
if (options.requireDeterministicMatch === true
|
|
433
|
+
&& compiled.compilation.unmatchedStatements.length > 0) {
|
|
434
|
+
const unmatchedError = new Error(`Deterministic policy compilation blocked: ${compiled.compilation.unmatchedStatements.length} intent statement(s) could not be converted into enforceable rules.`);
|
|
435
|
+
unmatchedError.code = 'POLICY_COMPILE_UNMATCHED_INTENT';
|
|
436
|
+
unmatchedError.unmatchedStatements = [
|
|
437
|
+
...compiled.compilation.unmatchedStatements,
|
|
438
|
+
];
|
|
439
|
+
unmatchedError.deterministicRuleCount =
|
|
440
|
+
compiled.compilation.deterministicRuleCount;
|
|
441
|
+
throw unmatchedError;
|
|
442
|
+
}
|
|
423
443
|
const outputPath = (0, policy_compiler_1.writeCompiledPolicyArtifact)(cwd, compiled, options.output);
|
|
424
444
|
const readBack = (0, policy_compiler_1.readCompiledPolicyArtifact)(cwd, options.output);
|
|
425
445
|
try {
|
|
@@ -433,6 +453,8 @@ function policyCommand(program) {
|
|
|
433
453
|
deterministicRuleCount: compiled.compilation.deterministicRuleCount,
|
|
434
454
|
unmatchedStatements: compiled.compilation.unmatchedStatements.length,
|
|
435
455
|
dashboardMode: compiled.source.includeDashboardPolicies ? 'dashboard' : 'disabled',
|
|
456
|
+
signaturePresent: Boolean(compiled.signature && compiled.signature.value),
|
|
457
|
+
signatureKeyId: compiled.signature?.keyId || null,
|
|
436
458
|
},
|
|
437
459
|
});
|
|
438
460
|
}
|
|
@@ -454,6 +476,12 @@ function policyCommand(program) {
|
|
|
454
476
|
console.log(chalk.dim(`Fingerprint: ${compiled.fingerprint}`));
|
|
455
477
|
console.log(chalk.dim(`Deterministic rules: ${compiled.compilation.deterministicRuleCount}`));
|
|
456
478
|
console.log(chalk.dim(`Unmatched statements: ${compiled.compilation.unmatchedStatements.length}`));
|
|
479
|
+
if (compiled.signature?.value) {
|
|
480
|
+
console.log(chalk.dim(`Artifact signature: signed (${compiled.signature.keyId ? `key ${compiled.signature.keyId}` : 'inline key'})`));
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
console.log(chalk.dim('Artifact signature: unsigned (set NEURCODE_GOVERNANCE_SIGNING_KEY to sign artifacts)'));
|
|
484
|
+
}
|
|
457
485
|
console.log(chalk.dim(`Policy source: ${compiled.source.includeDashboardPolicies ? 'dashboard + local packs' : 'local packs only'}`));
|
|
458
486
|
if (customPolicyResolution.dashboardWarning) {
|
|
459
487
|
console.log(chalk.yellow(`\n⚠️ ${customPolicyResolution.dashboardWarning}`));
|
|
@@ -463,7 +491,22 @@ function policyCommand(program) {
|
|
|
463
491
|
catch (error) {
|
|
464
492
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
465
493
|
if (options.json) {
|
|
466
|
-
|
|
494
|
+
const payload = { error: message };
|
|
495
|
+
if (error && typeof error === 'object') {
|
|
496
|
+
const maybeCode = error.code;
|
|
497
|
+
const maybeUnmatched = error.unmatchedStatements;
|
|
498
|
+
const maybeRuleCount = error.deterministicRuleCount;
|
|
499
|
+
if (typeof maybeCode === 'string') {
|
|
500
|
+
payload.code = maybeCode;
|
|
501
|
+
}
|
|
502
|
+
if (Array.isArray(maybeUnmatched)) {
|
|
503
|
+
payload.unmatchedStatements = maybeUnmatched.filter((item) => typeof item === 'string');
|
|
504
|
+
}
|
|
505
|
+
if (typeof maybeRuleCount === 'number' && Number.isFinite(maybeRuleCount)) {
|
|
506
|
+
payload.deterministicRuleCount = maybeRuleCount;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
467
510
|
process.exit(1);
|
|
468
511
|
}
|
|
469
512
|
console.error(chalk.red(`\n❌ ${message}\n`));
|
|
@@ -783,9 +826,59 @@ function policyCommand(program) {
|
|
|
783
826
|
exception
|
|
784
827
|
.command('list')
|
|
785
828
|
.description('List policy exceptions for this repository')
|
|
829
|
+
.option('--org', 'List centralized organization policy exceptions from Neurcode Cloud')
|
|
786
830
|
.option('--all', 'Include inactive/expired exceptions')
|
|
787
831
|
.option('--json', 'Output as JSON')
|
|
788
|
-
.action((options) => {
|
|
832
|
+
.action(async (options) => {
|
|
833
|
+
if (options.org) {
|
|
834
|
+
try {
|
|
835
|
+
const config = loadPolicyRuntimeConfig();
|
|
836
|
+
const client = new api_client_1.ApiClient(config);
|
|
837
|
+
const exceptions = await client.listOrgPolicyExceptions({ limit: 250 });
|
|
838
|
+
const items = options.all
|
|
839
|
+
? exceptions
|
|
840
|
+
: exceptions.filter((entry) => entry.effectiveState !== 'revoked' && entry.effectiveState !== 'expired');
|
|
841
|
+
if (options.json) {
|
|
842
|
+
console.log(JSON.stringify({
|
|
843
|
+
source: 'org',
|
|
844
|
+
total: exceptions.length,
|
|
845
|
+
exceptions: items,
|
|
846
|
+
}, null, 2));
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
if (items.length === 0) {
|
|
850
|
+
console.log(chalk.yellow('\n⚠️ No organization policy exceptions found.\n'));
|
|
851
|
+
console.log(chalk.dim('Add one: neurcode policy exception add --org --rule <pattern> --file <glob> --reason "<why>"\n'));
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
console.log(chalk.bold('\n🏢 Org Policy Exceptions\n'));
|
|
855
|
+
items.forEach((entry) => {
|
|
856
|
+
console.log(chalk.cyan(`• ${entry.id}`));
|
|
857
|
+
console.log(chalk.dim(` state=${entry.effectiveState} workflow=${entry.workflowState}`));
|
|
858
|
+
console.log(chalk.dim(` rule=${entry.rulePattern} file=${entry.filePattern}`));
|
|
859
|
+
console.log(chalk.dim(` expires=${entry.expiresAt} active=${entry.active ? 'yes' : 'no'}`));
|
|
860
|
+
console.log(chalk.dim(` approvals=${entry.approvalCount}` +
|
|
861
|
+
`${entry.requiredApprovals > 0 ? ` required=${entry.requiredApprovals}` : ''}` +
|
|
862
|
+
`${entry.critical ? ' critical=yes' : ''}`));
|
|
863
|
+
console.log(chalk.dim(` reason=${entry.reason}`));
|
|
864
|
+
if (entry.ticket) {
|
|
865
|
+
console.log(chalk.dim(` ticket=${entry.ticket}`));
|
|
866
|
+
}
|
|
867
|
+
console.log('');
|
|
868
|
+
});
|
|
869
|
+
console.log(chalk.dim('Source: Neurcode Cloud (/api/v1/org/policy-exceptions)\n'));
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
catch (error) {
|
|
873
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
874
|
+
if (options.json) {
|
|
875
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
876
|
+
process.exit(1);
|
|
877
|
+
}
|
|
878
|
+
console.error(chalk.red(`\n❌ ${message}\n`));
|
|
879
|
+
process.exit(1);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
789
882
|
const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
|
|
790
883
|
const data = (0, policy_exceptions_1.listPolicyExceptions)(cwd);
|
|
791
884
|
const governance = (0, policy_governance_1.readPolicyGovernanceConfig)(cwd);
|
|
@@ -865,6 +958,7 @@ function policyCommand(program) {
|
|
|
865
958
|
exception
|
|
866
959
|
.command('add')
|
|
867
960
|
.description('Add a policy exception entry')
|
|
961
|
+
.option('--org', 'Create centralized organization policy exception in Neurcode Cloud')
|
|
868
962
|
.requiredOption('--rule <pattern>', 'Rule pattern (exact, wildcard, or /regex/)')
|
|
869
963
|
.requiredOption('--file <pattern>', 'File pattern (exact, wildcard, or /regex/)')
|
|
870
964
|
.requiredOption('--reason <text>', 'Business justification for this exception')
|
|
@@ -873,7 +967,7 @@ function policyCommand(program) {
|
|
|
873
967
|
.option('--expires-at <iso>', 'Expiry timestamp in ISO-8601')
|
|
874
968
|
.option('--expires-in-days <n>', 'Expiry offset in days (default: 30)', (value) => parseInt(value, 10))
|
|
875
969
|
.option('--json', 'Output as JSON')
|
|
876
|
-
.action((options) => {
|
|
970
|
+
.action(async (options) => {
|
|
877
971
|
const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
|
|
878
972
|
try {
|
|
879
973
|
const severity = options.severity === 'allow' || options.severity === 'warn' || options.severity === 'block'
|
|
@@ -886,6 +980,37 @@ function policyCommand(program) {
|
|
|
886
980
|
expiresAt: options.expiresAt,
|
|
887
981
|
expiresInDays: options.expiresInDays,
|
|
888
982
|
});
|
|
983
|
+
if (options.org) {
|
|
984
|
+
const config = loadPolicyRuntimeConfig();
|
|
985
|
+
const client = new api_client_1.ApiClient(config);
|
|
986
|
+
const created = await client.createOrgPolicyException({
|
|
987
|
+
rulePattern: options.rule,
|
|
988
|
+
filePattern: options.file,
|
|
989
|
+
reason: options.reason,
|
|
990
|
+
ticket: options.ticket,
|
|
991
|
+
severity,
|
|
992
|
+
expiresAt,
|
|
993
|
+
});
|
|
994
|
+
if (options.json) {
|
|
995
|
+
console.log(JSON.stringify({
|
|
996
|
+
source: 'org',
|
|
997
|
+
exception: created,
|
|
998
|
+
}, null, 2));
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
console.log(chalk.green('\n✅ Organization policy exception created\n'));
|
|
1002
|
+
console.log(chalk.cyan(`ID: ${created.id}`));
|
|
1003
|
+
console.log(chalk.dim(`State: ${created.effectiveState}`));
|
|
1004
|
+
console.log(chalk.dim(`Rule: ${created.rulePattern}`));
|
|
1005
|
+
console.log(chalk.dim(`File: ${created.filePattern}`));
|
|
1006
|
+
console.log(chalk.dim(`Expires: ${created.expiresAt}`));
|
|
1007
|
+
console.log(chalk.dim(`Approvals: ${created.approvalCount}/${created.requiredApprovals}`));
|
|
1008
|
+
if (created.ticket) {
|
|
1009
|
+
console.log(chalk.dim(`Ticket: ${created.ticket}`));
|
|
1010
|
+
}
|
|
1011
|
+
console.log(chalk.dim('Source: Neurcode Cloud (/api/v1/org/policy-exceptions)\n'));
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
889
1014
|
const governance = (0, policy_governance_1.readPolicyGovernanceConfig)(cwd);
|
|
890
1015
|
if (governance.exceptionApprovals.requireReason
|
|
891
1016
|
&& options.reason.trim().length < governance.exceptionApprovals.minReasonLength) {
|
|
@@ -966,13 +1091,38 @@ function policyCommand(program) {
|
|
|
966
1091
|
.command('approve')
|
|
967
1092
|
.description('Approve a policy exception by ID')
|
|
968
1093
|
.argument('<id>', 'Exception ID to approve')
|
|
1094
|
+
.option('--org', 'Approve centralized organization policy exception in Neurcode Cloud')
|
|
969
1095
|
.option('--by <actor>', 'Approver identity (defaults to NEURCODE_ACTOR/GITHUB_ACTOR/USER)')
|
|
970
1096
|
.option('--comment <text>', 'Approval comment')
|
|
971
1097
|
.option('--json', 'Output as JSON')
|
|
972
|
-
.action((id, options) => {
|
|
1098
|
+
.action(async (id, options) => {
|
|
973
1099
|
const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
|
|
974
1100
|
const approver = resolveActor(options.by);
|
|
975
1101
|
try {
|
|
1102
|
+
if (options.org) {
|
|
1103
|
+
if (options.by) {
|
|
1104
|
+
throw new Error('--by is not supported with --org (identity comes from authenticated Neurcode user)');
|
|
1105
|
+
}
|
|
1106
|
+
const config = loadPolicyRuntimeConfig();
|
|
1107
|
+
const client = new api_client_1.ApiClient(config);
|
|
1108
|
+
const updated = await client.approveOrgPolicyException(String(id).trim(), {
|
|
1109
|
+
note: options.comment,
|
|
1110
|
+
});
|
|
1111
|
+
if (options.json) {
|
|
1112
|
+
console.log(JSON.stringify({
|
|
1113
|
+
source: 'org',
|
|
1114
|
+
approved: true,
|
|
1115
|
+
exception: updated,
|
|
1116
|
+
}, null, 2));
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
console.log(chalk.green('\n✅ Organization policy exception approval recorded.\n'));
|
|
1120
|
+
console.log(chalk.dim(`ID: ${updated.id}`));
|
|
1121
|
+
console.log(chalk.dim(`State: ${updated.effectiveState}`));
|
|
1122
|
+
console.log(chalk.dim(`Approvals: ${updated.approvalCount}/${updated.requiredApprovals}`));
|
|
1123
|
+
console.log(chalk.dim('Source: Neurcode Cloud (/api/v1/org/policy-exceptions)\n'));
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
976
1126
|
const governance = (0, policy_governance_1.readPolicyGovernanceConfig)(cwd);
|
|
977
1127
|
const target = (0, policy_exceptions_1.listPolicyExceptions)(cwd).all.find((entry) => entry.id === String(id).trim());
|
|
978
1128
|
if (!target) {
|
|
@@ -1066,12 +1216,185 @@ function policyCommand(program) {
|
|
|
1066
1216
|
process.exit(1);
|
|
1067
1217
|
}
|
|
1068
1218
|
});
|
|
1219
|
+
exception
|
|
1220
|
+
.command('reject')
|
|
1221
|
+
.description('Reject a pending organization policy exception by ID')
|
|
1222
|
+
.argument('<id>', 'Exception ID to reject')
|
|
1223
|
+
.requiredOption('--reason <text>', 'Reason for rejecting this exception')
|
|
1224
|
+
.option('--org', 'Reject centralized organization policy exception in Neurcode Cloud')
|
|
1225
|
+
.option('--json', 'Output as JSON')
|
|
1226
|
+
.action(async (id, options) => {
|
|
1227
|
+
if (!options.org) {
|
|
1228
|
+
const message = '`policy exception reject` is only supported for --org exceptions. Use `policy exception remove` for local exceptions.';
|
|
1229
|
+
if (options.json) {
|
|
1230
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
1231
|
+
process.exit(1);
|
|
1232
|
+
}
|
|
1233
|
+
console.error(chalk.red(`\n❌ ${message}\n`));
|
|
1234
|
+
process.exit(1);
|
|
1235
|
+
}
|
|
1236
|
+
const reason = typeof options.reason === 'string' ? options.reason.trim() : '';
|
|
1237
|
+
if (!reason) {
|
|
1238
|
+
const message = '--reason is required';
|
|
1239
|
+
if (options.json) {
|
|
1240
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
1241
|
+
process.exit(1);
|
|
1242
|
+
}
|
|
1243
|
+
console.error(chalk.red(`\n❌ ${message}\n`));
|
|
1244
|
+
process.exit(1);
|
|
1245
|
+
}
|
|
1246
|
+
try {
|
|
1247
|
+
const config = loadPolicyRuntimeConfig();
|
|
1248
|
+
const client = new api_client_1.ApiClient(config);
|
|
1249
|
+
const updated = await client.rejectOrgPolicyException(String(id).trim(), { reason });
|
|
1250
|
+
if (options.json) {
|
|
1251
|
+
console.log(JSON.stringify({
|
|
1252
|
+
source: 'org',
|
|
1253
|
+
rejected: true,
|
|
1254
|
+
exception: updated,
|
|
1255
|
+
}, null, 2));
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
console.log(chalk.green('\n✅ Organization policy exception rejected.\n'));
|
|
1259
|
+
console.log(chalk.dim(`ID: ${updated.id}`));
|
|
1260
|
+
console.log(chalk.dim(`State: ${updated.effectiveState}`));
|
|
1261
|
+
if (updated.rejectionReason) {
|
|
1262
|
+
console.log(chalk.dim(`Reason: ${updated.rejectionReason}`));
|
|
1263
|
+
}
|
|
1264
|
+
console.log(chalk.dim('Source: Neurcode Cloud (/api/v1/org/policy-exceptions)\n'));
|
|
1265
|
+
}
|
|
1266
|
+
catch (error) {
|
|
1267
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
1268
|
+
if (options.json) {
|
|
1269
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
1270
|
+
process.exit(1);
|
|
1271
|
+
}
|
|
1272
|
+
console.error(chalk.red(`\n❌ ${message}\n`));
|
|
1273
|
+
process.exit(1);
|
|
1274
|
+
}
|
|
1275
|
+
});
|
|
1276
|
+
exception
|
|
1277
|
+
.command('events')
|
|
1278
|
+
.description('Show policy exception audit events')
|
|
1279
|
+
.argument('<id>', 'Exception ID')
|
|
1280
|
+
.option('--org', 'Read centralized organization policy exception events from Neurcode Cloud')
|
|
1281
|
+
.option('--limit <n>', 'Maximum events to return (default: 30)', (value) => parseInt(value, 10))
|
|
1282
|
+
.option('--json', 'Output as JSON')
|
|
1283
|
+
.action(async (id, options) => {
|
|
1284
|
+
const exceptionId = String(id).trim();
|
|
1285
|
+
const limit = normalizeListLimit(options.limit, 30, 1, 300);
|
|
1286
|
+
if (options.org) {
|
|
1287
|
+
try {
|
|
1288
|
+
const config = loadPolicyRuntimeConfig();
|
|
1289
|
+
const client = new api_client_1.ApiClient(config);
|
|
1290
|
+
const events = await client.listOrgPolicyExceptionEvents(exceptionId, limit);
|
|
1291
|
+
if (options.json) {
|
|
1292
|
+
console.log(JSON.stringify({
|
|
1293
|
+
source: 'org',
|
|
1294
|
+
exceptionId,
|
|
1295
|
+
total: events.length,
|
|
1296
|
+
events,
|
|
1297
|
+
}, null, 2));
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
if (events.length === 0) {
|
|
1301
|
+
console.log(chalk.yellow('\n⚠️ No organization exception events found.\n'));
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
console.log(chalk.bold('\n🧾 Organization Exception Events\n'));
|
|
1305
|
+
events.forEach((event) => {
|
|
1306
|
+
const actor = event.actorEmail ||
|
|
1307
|
+
[event.actorFirstName, event.actorLastName].filter(Boolean).join(' ').trim() ||
|
|
1308
|
+
event.actorUserId ||
|
|
1309
|
+
'unknown';
|
|
1310
|
+
console.log(chalk.cyan(`• ${event.createdAt} ${event.action}`));
|
|
1311
|
+
console.log(chalk.dim(` actor=${actor}`));
|
|
1312
|
+
if (event.note) {
|
|
1313
|
+
console.log(chalk.dim(` note=${event.note}`));
|
|
1314
|
+
}
|
|
1315
|
+
console.log(chalk.dim(` eventId=${event.id}`));
|
|
1316
|
+
console.log('');
|
|
1317
|
+
});
|
|
1318
|
+
console.log(chalk.dim('Source: Neurcode Cloud (/api/v1/org/policy-exceptions/:id/events)\n'));
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
catch (error) {
|
|
1322
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
1323
|
+
if (options.json) {
|
|
1324
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
1325
|
+
process.exit(1);
|
|
1326
|
+
}
|
|
1327
|
+
console.error(chalk.red(`\n❌ ${message}\n`));
|
|
1328
|
+
process.exit(1);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
|
|
1332
|
+
const events = (0, policy_audit_1.readPolicyAuditEvents)(cwd)
|
|
1333
|
+
.filter((event) => event.entityType === 'policy_exception' && event.entityId === exceptionId)
|
|
1334
|
+
.sort((left, right) => right.timestamp.localeCompare(left.timestamp))
|
|
1335
|
+
.slice(0, limit);
|
|
1336
|
+
if (options.json) {
|
|
1337
|
+
console.log(JSON.stringify({
|
|
1338
|
+
source: 'local',
|
|
1339
|
+
exceptionId,
|
|
1340
|
+
total: events.length,
|
|
1341
|
+
events,
|
|
1342
|
+
path: (0, policy_audit_1.getPolicyAuditPath)(cwd),
|
|
1343
|
+
}, null, 2));
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
if (events.length === 0) {
|
|
1347
|
+
console.log(chalk.yellow('\n⚠️ No local exception audit events found.\n'));
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
console.log(chalk.bold('\n🧾 Local Exception Events\n'));
|
|
1351
|
+
events.forEach((event) => {
|
|
1352
|
+
console.log(chalk.cyan(`• ${event.timestamp} ${event.action}`));
|
|
1353
|
+
console.log(chalk.dim(` actor=${event.actor}`));
|
|
1354
|
+
if (event.metadata && Object.keys(event.metadata).length > 0) {
|
|
1355
|
+
console.log(chalk.dim(` metadata=${JSON.stringify(event.metadata)}`));
|
|
1356
|
+
}
|
|
1357
|
+
console.log(chalk.dim(` hash=${event.hash.slice(0, 12)}...`));
|
|
1358
|
+
console.log('');
|
|
1359
|
+
});
|
|
1360
|
+
console.log(chalk.dim(`Source: ${(0, policy_audit_1.getPolicyAuditPath)(cwd)}\n`));
|
|
1361
|
+
});
|
|
1069
1362
|
exception
|
|
1070
1363
|
.command('remove')
|
|
1071
1364
|
.description('Deactivate a policy exception by ID')
|
|
1072
1365
|
.argument('<id>', 'Exception ID to deactivate')
|
|
1366
|
+
.option('--org', 'Revoke centralized organization policy exception in Neurcode Cloud')
|
|
1073
1367
|
.option('--json', 'Output as JSON')
|
|
1074
|
-
.action((id, options) => {
|
|
1368
|
+
.action(async (id, options) => {
|
|
1369
|
+
if (options.org) {
|
|
1370
|
+
try {
|
|
1371
|
+
const config = loadPolicyRuntimeConfig();
|
|
1372
|
+
const client = new api_client_1.ApiClient(config);
|
|
1373
|
+
const updated = await client.revokeOrgPolicyException(String(id).trim());
|
|
1374
|
+
if (options.json) {
|
|
1375
|
+
console.log(JSON.stringify({
|
|
1376
|
+
source: 'org',
|
|
1377
|
+
removed: true,
|
|
1378
|
+
exception: updated,
|
|
1379
|
+
}, null, 2));
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
console.log(chalk.green('\n✅ Organization policy exception revoked.\n'));
|
|
1383
|
+
console.log(chalk.dim(`ID: ${updated.id}`));
|
|
1384
|
+
console.log(chalk.dim(`State: ${updated.effectiveState}`));
|
|
1385
|
+
console.log(chalk.dim('Source: Neurcode Cloud (/api/v1/org/policy-exceptions)\n'));
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
catch (error) {
|
|
1389
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
1390
|
+
if (options.json) {
|
|
1391
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
1392
|
+
process.exit(1);
|
|
1393
|
+
}
|
|
1394
|
+
console.error(chalk.red(`\n❌ ${message}\n`));
|
|
1395
|
+
process.exit(1);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1075
1398
|
const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
|
|
1076
1399
|
const removed = (0, policy_exceptions_1.revokePolicyException)(cwd, String(id).trim());
|
|
1077
1400
|
if (removed) {
|