@clef-sh/cli 0.1.12-beta.85 → 0.1.13-beta.88
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.cjs +421 -136
- package/dist/index.cjs.map +2 -2
- package/dist/index.mjs +421 -136
- package/dist/index.mjs.map +3 -3
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -97715,11 +97715,23 @@ function sym(key) {
|
|
|
97715
97715
|
}
|
|
97716
97716
|
|
|
97717
97717
|
// src/output/formatter.ts
|
|
97718
|
+
var _jsonMode = false;
|
|
97719
|
+
var _yesMode = false;
|
|
97720
|
+
function setJsonMode(json) {
|
|
97721
|
+
_jsonMode = json;
|
|
97722
|
+
}
|
|
97723
|
+
function isJsonMode() {
|
|
97724
|
+
return _jsonMode;
|
|
97725
|
+
}
|
|
97726
|
+
function setYesMode(yes) {
|
|
97727
|
+
_yesMode = yes;
|
|
97728
|
+
}
|
|
97718
97729
|
function color(fn, str2) {
|
|
97719
97730
|
return isPlainMode() ? str2 : fn(str2);
|
|
97720
97731
|
}
|
|
97721
97732
|
var formatter = {
|
|
97722
97733
|
success(message) {
|
|
97734
|
+
if (_jsonMode) return;
|
|
97723
97735
|
const icon = sym("success");
|
|
97724
97736
|
process.stdout.write(color(import_picocolors.default.green, `${icon} ${message}`) + "\n");
|
|
97725
97737
|
},
|
|
@@ -97736,15 +97748,18 @@ var formatter = {
|
|
|
97736
97748
|
process.stderr.write(color(import_picocolors.default.yellow, `${icon} ${message}`) + "\n");
|
|
97737
97749
|
},
|
|
97738
97750
|
info(message) {
|
|
97751
|
+
if (_jsonMode) return;
|
|
97739
97752
|
const icon = sym("info");
|
|
97740
97753
|
process.stdout.write(color(import_picocolors.default.blue, `${icon} ${message}`) + "\n");
|
|
97741
97754
|
},
|
|
97742
97755
|
hint(message) {
|
|
97756
|
+
if (_jsonMode) return;
|
|
97743
97757
|
const icon = sym("arrow");
|
|
97744
97758
|
process.stdout.write(`${icon} ${message}
|
|
97745
97759
|
`);
|
|
97746
97760
|
},
|
|
97747
97761
|
keyValue(key, value) {
|
|
97762
|
+
if (_jsonMode) return;
|
|
97748
97763
|
const icon = sym("key");
|
|
97749
97764
|
const arrow = sym("arrow");
|
|
97750
97765
|
const prefix2 = icon ? `${icon} ` : "";
|
|
@@ -97752,30 +97767,38 @@ var formatter = {
|
|
|
97752
97767
|
`);
|
|
97753
97768
|
},
|
|
97754
97769
|
pendingItem(key, days) {
|
|
97770
|
+
if (_jsonMode) return;
|
|
97755
97771
|
const icon = sym("pending");
|
|
97756
97772
|
const prefix2 = icon ? `${icon} ` : "[pending] ";
|
|
97757
97773
|
process.stdout.write(`${prefix2}${key} \u2014 ${days} day${days !== 1 ? "s" : ""} pending
|
|
97758
97774
|
`);
|
|
97759
97775
|
},
|
|
97760
97776
|
recipientItem(label2, keyPreview2) {
|
|
97777
|
+
if (_jsonMode) return;
|
|
97761
97778
|
const icon = sym("recipient");
|
|
97762
97779
|
const prefix2 = icon ? `${icon} ` : "";
|
|
97763
97780
|
process.stdout.write(`${prefix2}${label2.padEnd(15)}${keyPreview2}
|
|
97764
97781
|
`);
|
|
97765
97782
|
},
|
|
97766
97783
|
section(label2) {
|
|
97784
|
+
if (_jsonMode) return;
|
|
97767
97785
|
process.stdout.write(`
|
|
97768
97786
|
${label2}
|
|
97769
97787
|
|
|
97770
97788
|
`);
|
|
97771
97789
|
},
|
|
97772
97790
|
print(message) {
|
|
97791
|
+
if (_jsonMode) return;
|
|
97773
97792
|
process.stdout.write(message + "\n");
|
|
97774
97793
|
},
|
|
97775
97794
|
raw(message) {
|
|
97776
97795
|
process.stdout.write(message);
|
|
97777
97796
|
},
|
|
97797
|
+
json(data) {
|
|
97798
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
97799
|
+
},
|
|
97778
97800
|
table(rows, columns) {
|
|
97801
|
+
if (_jsonMode) return;
|
|
97779
97802
|
const widths = columns.map((col, i) => {
|
|
97780
97803
|
const maxDataWidth = rows.reduce(
|
|
97781
97804
|
(max, row) => Math.max(max, stripAnsi(row[i] ?? "").length),
|
|
@@ -97792,6 +97815,10 @@ ${label2}
|
|
|
97792
97815
|
}
|
|
97793
97816
|
},
|
|
97794
97817
|
async confirm(prompt) {
|
|
97818
|
+
if (_yesMode) return true;
|
|
97819
|
+
if (_jsonMode) {
|
|
97820
|
+
throw new Error("--json requires --yes for destructive operations");
|
|
97821
|
+
}
|
|
97795
97822
|
const rl = readline.createInterface({
|
|
97796
97823
|
input: process.stdin,
|
|
97797
97824
|
output: process.stderr
|
|
@@ -97826,6 +97853,9 @@ ${label2}
|
|
|
97826
97853
|
`);
|
|
97827
97854
|
},
|
|
97828
97855
|
async secretPrompt(prompt) {
|
|
97856
|
+
if (_jsonMode) {
|
|
97857
|
+
throw new Error("--json mode requires value on the command line (no interactive prompt)");
|
|
97858
|
+
}
|
|
97829
97859
|
return new Promise((resolve7, reject) => {
|
|
97830
97860
|
process.stderr.write(color(import_picocolors.default.cyan, `${prompt}: `));
|
|
97831
97861
|
if (process.stdin.isTTY) {
|
|
@@ -97868,7 +97898,14 @@ function pad(str2, width) {
|
|
|
97868
97898
|
}
|
|
97869
97899
|
|
|
97870
97900
|
// src/handle-error.ts
|
|
97901
|
+
function exitJsonError(message) {
|
|
97902
|
+
formatter.json({ error: true, message });
|
|
97903
|
+
process.exit(1);
|
|
97904
|
+
}
|
|
97871
97905
|
function handleCommandError(err) {
|
|
97906
|
+
if (isJsonMode()) {
|
|
97907
|
+
exitJsonError(err.message);
|
|
97908
|
+
}
|
|
97872
97909
|
if (err instanceof SopsMissingError || err instanceof SopsVersionError) {
|
|
97873
97910
|
formatter.formatDependencyError(err);
|
|
97874
97911
|
} else {
|
|
@@ -98368,7 +98405,7 @@ async function handleSecondDevOnboarding(repoRoot, clefConfigPath, deps2, option
|
|
|
98368
98405
|
"OS keychain is not available on this system.\n The private key will be written to the filesystem instead.\n See https://docs.clef.sh/guide/key-storage for security implications."
|
|
98369
98406
|
);
|
|
98370
98407
|
let keyPath;
|
|
98371
|
-
if (options.nonInteractive || !process.stdin.isTTY) {
|
|
98408
|
+
if (options.nonInteractive || isJsonMode() || !process.stdin.isTTY) {
|
|
98372
98409
|
keyPath = process.env.CLEF_AGE_KEY_FILE || defaultAgeKeyPath(label2);
|
|
98373
98410
|
keyPath = path23.resolve(keyPath);
|
|
98374
98411
|
} else {
|
|
@@ -98401,6 +98438,10 @@ async function handleSecondDevOnboarding(repoRoot, clefConfigPath, deps2, option
|
|
|
98401
98438
|
formatter.success("Created .clef/.gitignore");
|
|
98402
98439
|
}
|
|
98403
98440
|
formatter.success(`Key label: ${label2}`);
|
|
98441
|
+
if (isJsonMode()) {
|
|
98442
|
+
formatter.json({ action: "onboarded", manifest: "clef.yaml", config: ".clef/config.yaml" });
|
|
98443
|
+
return;
|
|
98444
|
+
}
|
|
98404
98445
|
formatter.section("Next steps:");
|
|
98405
98446
|
formatter.hint("clef recipients request \u2014 request access to encrypted secrets");
|
|
98406
98447
|
formatter.hint("clef update \u2014 scaffold new environments");
|
|
@@ -98411,7 +98452,7 @@ async function handleFullSetup(repoRoot, manifestPath, clefConfigPath, deps2, op
|
|
|
98411
98452
|
let namespaces = options.namespaces ? options.namespaces.split(",").map((s) => s.trim()) : [];
|
|
98412
98453
|
const backend = options.backend ?? "age";
|
|
98413
98454
|
let secretsDir = options.secretsDir ?? "secrets";
|
|
98414
|
-
if (!options.nonInteractive && process.stdin.isTTY) {
|
|
98455
|
+
if (!options.nonInteractive && !isJsonMode() && process.stdin.isTTY) {
|
|
98415
98456
|
const envAnswer = await promptWithDefault(
|
|
98416
98457
|
"Environments (comma-separated)",
|
|
98417
98458
|
environments.join(",")
|
|
@@ -98466,7 +98507,7 @@ async function handleFullSetup(repoRoot, manifestPath, clefConfigPath, deps2, op
|
|
|
98466
98507
|
formatter.warn(
|
|
98467
98508
|
"OS keychain is not available on this system.\n The private key must be written to the filesystem instead.\n See https://docs.clef.sh/guide/key-storage for security implications."
|
|
98468
98509
|
);
|
|
98469
|
-
if (!options.nonInteractive && process.stdin.isTTY) {
|
|
98510
|
+
if (!options.nonInteractive && !isJsonMode() && process.stdin.isTTY) {
|
|
98470
98511
|
const confirmed = await formatter.confirm("Write the private key to the filesystem?");
|
|
98471
98512
|
if (!confirmed) {
|
|
98472
98513
|
formatter.error(
|
|
@@ -98477,7 +98518,7 @@ async function handleFullSetup(repoRoot, manifestPath, clefConfigPath, deps2, op
|
|
|
98477
98518
|
}
|
|
98478
98519
|
}
|
|
98479
98520
|
let keyPath;
|
|
98480
|
-
if (options.nonInteractive || !process.stdin.isTTY) {
|
|
98521
|
+
if (options.nonInteractive || isJsonMode() || !process.stdin.isTTY) {
|
|
98481
98522
|
keyPath = defaultAgeKeyPath(label2);
|
|
98482
98523
|
if (await isInsideAnyGitRepo(path23.resolve(keyPath))) {
|
|
98483
98524
|
throw new Error(
|
|
@@ -98597,6 +98638,17 @@ async function handleFullSetup(repoRoot, manifestPath, clefConfigPath, deps2, op
|
|
|
98597
98638
|
formatter.print(" clef config set analytics false (permanent)\n");
|
|
98598
98639
|
} catch {
|
|
98599
98640
|
}
|
|
98641
|
+
if (isJsonMode()) {
|
|
98642
|
+
formatter.json({
|
|
98643
|
+
action: "initialized",
|
|
98644
|
+
manifest: "clef.yaml",
|
|
98645
|
+
environments: manifest.environments.map((e) => e.name),
|
|
98646
|
+
namespaces: manifest.namespaces.map((n) => n.name),
|
|
98647
|
+
backend,
|
|
98648
|
+
scaffolded: scaffoldedCount
|
|
98649
|
+
});
|
|
98650
|
+
return;
|
|
98651
|
+
}
|
|
98600
98652
|
formatter.section("Next steps:");
|
|
98601
98653
|
formatter.hint("clef set <namespace>/<env> <KEY> <value> \u2014 add a secret");
|
|
98602
98654
|
formatter.hint("clef scan \u2014 check for existing plaintext secrets");
|
|
@@ -98857,7 +98909,9 @@ function registerGetCommand(program3, deps2) {
|
|
|
98857
98909
|
return;
|
|
98858
98910
|
}
|
|
98859
98911
|
const val = decrypted.values[key];
|
|
98860
|
-
if (
|
|
98912
|
+
if (isJsonMode()) {
|
|
98913
|
+
formatter.json({ key, value: val, namespace, environment });
|
|
98914
|
+
} else if (opts2.raw) {
|
|
98861
98915
|
formatter.raw(val);
|
|
98862
98916
|
} else {
|
|
98863
98917
|
const copied = copyToClipboard(val);
|
|
@@ -98962,6 +99016,16 @@ function registerSetCommand(program3, deps2) {
|
|
|
98962
99016
|
pendingErrors.push(env.name);
|
|
98963
99017
|
}
|
|
98964
99018
|
}
|
|
99019
|
+
if (isJsonMode()) {
|
|
99020
|
+
formatter.json({
|
|
99021
|
+
key,
|
|
99022
|
+
namespace: namespace2,
|
|
99023
|
+
environments: manifest.environments.map((e) => e.name),
|
|
99024
|
+
action: "created",
|
|
99025
|
+
pending: true
|
|
99026
|
+
});
|
|
99027
|
+
return;
|
|
99028
|
+
}
|
|
98965
99029
|
formatter.success(
|
|
98966
99030
|
`'${key}' set in ${namespace2} across all environments ${sym("locked")}`
|
|
98967
99031
|
);
|
|
@@ -98985,6 +99049,16 @@ function registerSetCommand(program3, deps2) {
|
|
|
98985
99049
|
} catch {
|
|
98986
99050
|
}
|
|
98987
99051
|
}
|
|
99052
|
+
if (isJsonMode()) {
|
|
99053
|
+
formatter.json({
|
|
99054
|
+
key,
|
|
99055
|
+
namespace: namespace2,
|
|
99056
|
+
environments: manifest.environments.map((e) => e.name),
|
|
99057
|
+
action: "created",
|
|
99058
|
+
pending: false
|
|
99059
|
+
});
|
|
99060
|
+
return;
|
|
99061
|
+
}
|
|
98988
99062
|
formatter.success(`'${key}' set in ${namespace2} across all environments`);
|
|
98989
99063
|
formatter.hint(`git add ${namespace2}/ # stage all updated files`);
|
|
98990
99064
|
}
|
|
@@ -99048,6 +99122,10 @@ function registerSetCommand(program3, deps2) {
|
|
|
99048
99122
|
process.exit(1);
|
|
99049
99123
|
return;
|
|
99050
99124
|
}
|
|
99125
|
+
if (isJsonMode()) {
|
|
99126
|
+
formatter.json({ key, namespace, environment, action: "created", pending: true });
|
|
99127
|
+
return;
|
|
99128
|
+
}
|
|
99051
99129
|
formatter.success(`${key} set in ${namespace}/${environment} ${sym("locked")}`);
|
|
99052
99130
|
formatter.print(
|
|
99053
99131
|
` ${sym("pending")} Marked as pending \u2014 replace with a real value before deploying`
|
|
@@ -99062,6 +99140,10 @@ function registerSetCommand(program3, deps2) {
|
|
|
99062
99140
|
The value is saved. Run clef lint to check for stale pending markers.`
|
|
99063
99141
|
);
|
|
99064
99142
|
}
|
|
99143
|
+
if (isJsonMode()) {
|
|
99144
|
+
formatter.json({ key, namespace, environment, action: "created", pending: false });
|
|
99145
|
+
return;
|
|
99146
|
+
}
|
|
99065
99147
|
formatter.success(`${key} set in ${namespace}/${environment}`);
|
|
99066
99148
|
formatter.hint(
|
|
99067
99149
|
`Commit: git add ${manifest.file_pattern.replace("{namespace}", namespace).replace("{environment}", environment)}`
|
|
@@ -99130,7 +99212,10 @@ function registerCompareCommand(program3, deps2) {
|
|
|
99130
99212
|
compareBuf.copy(paddedCompare);
|
|
99131
99213
|
const timingEqual = crypto5.timingSafeEqual(paddedStored, paddedCompare);
|
|
99132
99214
|
const match = storedBuf.length === compareBuf.length && timingEqual;
|
|
99133
|
-
if (
|
|
99215
|
+
if (isJsonMode()) {
|
|
99216
|
+
formatter.json({ match, key, namespace, environment });
|
|
99217
|
+
if (!match) process.exit(1);
|
|
99218
|
+
} else if (match) {
|
|
99134
99219
|
formatter.success(`${key} ${sym("arrow")} values match`);
|
|
99135
99220
|
} else {
|
|
99136
99221
|
formatter.failure(`${key} ${sym("arrow")} values do not match`);
|
|
@@ -99178,6 +99263,15 @@ Type the key name to confirm:`
|
|
|
99178
99263
|
}
|
|
99179
99264
|
const bulkOps = new BulkOps();
|
|
99180
99265
|
await bulkOps.deleteAcrossEnvironments(namespace, key, manifest, sopsClient, repoRoot);
|
|
99266
|
+
if (isJsonMode()) {
|
|
99267
|
+
formatter.json({
|
|
99268
|
+
key,
|
|
99269
|
+
namespace,
|
|
99270
|
+
environments: manifest.environments.map((e) => e.name),
|
|
99271
|
+
action: "deleted"
|
|
99272
|
+
});
|
|
99273
|
+
return;
|
|
99274
|
+
}
|
|
99181
99275
|
formatter.success(`Deleted '${key}' from ${namespace} in all environments`);
|
|
99182
99276
|
} else {
|
|
99183
99277
|
const [namespace, environment] = parseTarget(target);
|
|
@@ -99216,6 +99310,10 @@ Type the key name to confirm:`
|
|
|
99216
99310
|
`Key deleted but pending metadata could not be cleaned up. Run clef lint to verify.`
|
|
99217
99311
|
);
|
|
99218
99312
|
}
|
|
99313
|
+
if (isJsonMode()) {
|
|
99314
|
+
formatter.json({ key, namespace, environment, action: "deleted" });
|
|
99315
|
+
return;
|
|
99316
|
+
}
|
|
99219
99317
|
formatter.success(`Deleted '${key}' from ${namespace}/${environment}`);
|
|
99220
99318
|
formatter.hint(
|
|
99221
99319
|
`Commit: git add ${manifest.file_pattern.replace("{namespace}", namespace).replace("{environment}", environment)}`
|
|
@@ -99234,10 +99332,11 @@ Type the key name to confirm:`
|
|
|
99234
99332
|
var import_picocolors2 = __toESM(require_picocolors());
|
|
99235
99333
|
init_src();
|
|
99236
99334
|
import * as path33 from "path";
|
|
99335
|
+
var MASKED = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
99237
99336
|
function registerDiffCommand(program3, deps2) {
|
|
99238
99337
|
program3.command("diff <namespace> <env-a> <env-b>").description(
|
|
99239
99338
|
"Compare secrets between two environments for a namespace.\n\nExit codes:\n 0 No differences\n 1 Differences found"
|
|
99240
|
-
).option("--show-identical", "Include identical keys in the output").option("--show-values", "Show plaintext values instead of masking them").
|
|
99339
|
+
).option("--show-identical", "Include identical keys in the output").option("--show-values", "Show plaintext values instead of masking them").action(
|
|
99241
99340
|
async (namespace, envA, envB, options) => {
|
|
99242
99341
|
try {
|
|
99243
99342
|
const repoRoot = program3.opts().dir || process.cwd();
|
|
@@ -99264,19 +99363,20 @@ function registerDiffCommand(program3, deps2) {
|
|
|
99264
99363
|
formatter.warn("Warning: printing plaintext values for protected environment.");
|
|
99265
99364
|
}
|
|
99266
99365
|
}
|
|
99267
|
-
if (
|
|
99268
|
-
const
|
|
99366
|
+
if (isJsonMode()) {
|
|
99367
|
+
const jsonData = options.showValues ? result : {
|
|
99269
99368
|
...result,
|
|
99270
99369
|
rows: result.rows.map((r) => ({
|
|
99271
99370
|
...r,
|
|
99272
|
-
valueA: r.valueA !== null ?
|
|
99273
|
-
valueB: r.valueB !== null ?
|
|
99371
|
+
valueA: r.valueA !== null ? MASKED : null,
|
|
99372
|
+
valueB: r.valueB !== null ? MASKED : null,
|
|
99274
99373
|
masked: true
|
|
99275
99374
|
}))
|
|
99276
99375
|
};
|
|
99277
|
-
formatter.
|
|
99376
|
+
formatter.json(jsonData);
|
|
99278
99377
|
const hasDiffs2 = result.rows.some((r) => r.status !== "identical");
|
|
99279
99378
|
process.exit(hasDiffs2 ? 1 : 0);
|
|
99379
|
+
return;
|
|
99280
99380
|
}
|
|
99281
99381
|
formatDiffOutput(
|
|
99282
99382
|
result,
|
|
@@ -99296,7 +99396,6 @@ function registerDiffCommand(program3, deps2) {
|
|
|
99296
99396
|
}
|
|
99297
99397
|
);
|
|
99298
99398
|
}
|
|
99299
|
-
var MASKED = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
99300
99399
|
function formatDiffOutput(result, envA, envB, showIdentical, showValues) {
|
|
99301
99400
|
const filteredRows = showIdentical ? result.rows : result.rows.filter((r) => r.status !== "identical");
|
|
99302
99401
|
if (filteredRows.length === 0) {
|
|
@@ -99578,7 +99677,7 @@ async function fetchCheckpoint(config) {
|
|
|
99578
99677
|
}
|
|
99579
99678
|
|
|
99580
99679
|
// package.json
|
|
99581
|
-
var version2 = "0.1.
|
|
99680
|
+
var version2 = "0.1.13-beta.88";
|
|
99582
99681
|
var package_default = {
|
|
99583
99682
|
name: "@clef-sh/cli",
|
|
99584
99683
|
version: version2,
|
|
@@ -99649,7 +99748,7 @@ var package_default = {
|
|
|
99649
99748
|
function registerLintCommand(program3, deps2) {
|
|
99650
99749
|
program3.command("lint").description(
|
|
99651
99750
|
"Full repo health check \u2014 matrix completeness, schema validation, SOPS integrity.\n\nExit codes:\n 0 No errors (warnings are allowed)\n 1 Errors found"
|
|
99652
|
-
).option("--fix", "Auto-fix safe issues (scaffold missing files)").option("--
|
|
99751
|
+
).option("--fix", "Auto-fix safe issues (scaffold missing files)").option("--push", "Push results as OTLP to CLEF_TELEMETRY_URL").action(async (options) => {
|
|
99653
99752
|
try {
|
|
99654
99753
|
const repoRoot = program3.opts().dir || process.cwd();
|
|
99655
99754
|
const parser = new ManifestParser();
|
|
@@ -99684,8 +99783,8 @@ function registerLintCommand(program3, deps2) {
|
|
|
99684
99783
|
await pushOtlp(payload, config);
|
|
99685
99784
|
formatter.success("Lint results pushed to telemetry endpoint.");
|
|
99686
99785
|
}
|
|
99687
|
-
if (
|
|
99688
|
-
formatter.
|
|
99786
|
+
if (isJsonMode()) {
|
|
99787
|
+
formatter.json(result);
|
|
99689
99788
|
const hasErrors2 = result.issues.some((i) => i.severity === "error");
|
|
99690
99789
|
process.exit(hasErrors2 ? 1 : 0);
|
|
99691
99790
|
return;
|
|
@@ -99794,6 +99893,10 @@ function registerRotateCommand(program3, deps2) {
|
|
|
99794
99893
|
const relativeFile = manifest.file_pattern.replace("{namespace}", namespace).replace("{environment}", environment);
|
|
99795
99894
|
formatter.print(`${sym("working")} Rotating ${namespace}/${environment}...`);
|
|
99796
99895
|
await sopsClient.reEncrypt(filePath, options.newKey);
|
|
99896
|
+
if (isJsonMode()) {
|
|
99897
|
+
formatter.json({ namespace, environment, file: relativeFile, action: "rotated" });
|
|
99898
|
+
return;
|
|
99899
|
+
}
|
|
99797
99900
|
formatter.success(`Rotated. New values encrypted. ${sym("locked")}`);
|
|
99798
99901
|
formatter.hint(
|
|
99799
99902
|
`git add ${relativeFile} && git commit -m "rotate: ${namespace}/${environment}"`
|
|
@@ -99839,15 +99942,28 @@ function registerHooksCommand(program3, deps2) {
|
|
|
99839
99942
|
}
|
|
99840
99943
|
const git = new GitIntegration(deps2.runner);
|
|
99841
99944
|
await git.installPreCommitHook(repoRoot);
|
|
99945
|
+
let mergeDriverOk = false;
|
|
99946
|
+
try {
|
|
99947
|
+
await git.installMergeDriver(repoRoot);
|
|
99948
|
+
mergeDriverOk = true;
|
|
99949
|
+
} catch {
|
|
99950
|
+
}
|
|
99951
|
+
if (isJsonMode()) {
|
|
99952
|
+
formatter.json({
|
|
99953
|
+
preCommitHook: true,
|
|
99954
|
+
mergeDriver: mergeDriverOk,
|
|
99955
|
+
hookPath
|
|
99956
|
+
});
|
|
99957
|
+
return;
|
|
99958
|
+
}
|
|
99842
99959
|
formatter.success("Pre-commit hook installed");
|
|
99843
99960
|
formatter.print(` ${sym("pending")} ${hookPath}`);
|
|
99844
99961
|
formatter.hint(
|
|
99845
99962
|
"Hook checks SOPS metadata on staged .enc files and runs: clef scan --staged"
|
|
99846
99963
|
);
|
|
99847
|
-
|
|
99848
|
-
await git.installMergeDriver(repoRoot);
|
|
99964
|
+
if (mergeDriverOk) {
|
|
99849
99965
|
formatter.success("SOPS merge driver configured");
|
|
99850
|
-
}
|
|
99966
|
+
} else {
|
|
99851
99967
|
formatter.warn("Could not configure SOPS merge driver. Run inside a git repository.");
|
|
99852
99968
|
}
|
|
99853
99969
|
} catch (err) {
|
|
@@ -100146,6 +100262,14 @@ Usage: clef export payments/production --format env`
|
|
|
100146
100262
|
);
|
|
100147
100263
|
try {
|
|
100148
100264
|
const decrypted = await sopsClient.decrypt(filePath);
|
|
100265
|
+
if (isJsonMode()) {
|
|
100266
|
+
const pairs = Object.entries(decrypted.values).map(([k, v]) => ({
|
|
100267
|
+
key: k,
|
|
100268
|
+
value: v
|
|
100269
|
+
}));
|
|
100270
|
+
formatter.json({ pairs, namespace, environment });
|
|
100271
|
+
return;
|
|
100272
|
+
}
|
|
100149
100273
|
const consumption = new ConsumptionClient();
|
|
100150
100274
|
const output = consumption.formatExport(decrypted, "env", !options.export);
|
|
100151
100275
|
if (options.raw) {
|
|
@@ -100186,7 +100310,7 @@ import * as path41 from "path";
|
|
|
100186
100310
|
function registerDoctorCommand(program3, deps2) {
|
|
100187
100311
|
program3.command("doctor").description(
|
|
100188
100312
|
"Check your environment for required dependencies and configuration.\n\nExit codes:\n 0 All checks pass\n 1 One or more checks failed"
|
|
100189
|
-
).option("--
|
|
100313
|
+
).option("--fix", "Attempt to auto-fix issues").action(async (options) => {
|
|
100190
100314
|
const repoRoot = program3.opts().dir || process.cwd();
|
|
100191
100315
|
const clefVersion = program3.version() ?? "unknown";
|
|
100192
100316
|
const checks = [];
|
|
@@ -100273,7 +100397,7 @@ function registerDoctorCommand(program3, deps2) {
|
|
|
100273
100397
|
formatter.warn("--fix cannot resolve these issues automatically.");
|
|
100274
100398
|
}
|
|
100275
100399
|
}
|
|
100276
|
-
if (
|
|
100400
|
+
if (isJsonMode()) {
|
|
100277
100401
|
const json = {
|
|
100278
100402
|
clef: { version: clefVersion, ok: true },
|
|
100279
100403
|
sops: {
|
|
@@ -100303,7 +100427,7 @@ function registerDoctorCommand(program3, deps2) {
|
|
|
100303
100427
|
ok: mergeDriverOk
|
|
100304
100428
|
}
|
|
100305
100429
|
};
|
|
100306
|
-
formatter.
|
|
100430
|
+
formatter.json(json);
|
|
100307
100431
|
const hasFailures = checks.some((c) => !c.ok);
|
|
100308
100432
|
process.exit(hasFailures ? 1 : 0);
|
|
100309
100433
|
return;
|
|
@@ -100437,6 +100561,11 @@ function registerUpdateCommand(program3, deps2) {
|
|
|
100437
100561
|
);
|
|
100438
100562
|
}
|
|
100439
100563
|
}
|
|
100564
|
+
if (isJsonMode()) {
|
|
100565
|
+
formatter.json({ scaffolded: scaffoldedCount, failed: failedCount });
|
|
100566
|
+
process.exit(failedCount > 0 ? 1 : 0);
|
|
100567
|
+
return;
|
|
100568
|
+
}
|
|
100440
100569
|
if (scaffoldedCount > 0) {
|
|
100441
100570
|
formatter.success(`Scaffolded ${scaffoldedCount} encrypted file(s)`);
|
|
100442
100571
|
}
|
|
@@ -100462,69 +100591,57 @@ function registerScanCommand(program3, deps2) {
|
|
|
100462
100591
|
"--severity <level>",
|
|
100463
100592
|
"Detection level: all (patterns+entropy) or high (patterns only)",
|
|
100464
100593
|
"all"
|
|
100465
|
-
).
|
|
100466
|
-
|
|
100467
|
-
|
|
100468
|
-
|
|
100469
|
-
|
|
100470
|
-
|
|
100471
|
-
|
|
100472
|
-
|
|
100473
|
-
|
|
100474
|
-
|
|
100475
|
-
|
|
100476
|
-
formatter.error(err.message);
|
|
100477
|
-
}
|
|
100478
|
-
process.exit(2);
|
|
100479
|
-
return;
|
|
100480
|
-
}
|
|
100481
|
-
if (options.severity && options.severity !== "all" && options.severity !== "high") {
|
|
100482
|
-
formatter.error(`Invalid severity '${options.severity}'. Must be 'all' or 'high'.`);
|
|
100483
|
-
process.exit(2);
|
|
100484
|
-
return;
|
|
100485
|
-
}
|
|
100486
|
-
const severity = options.severity === "high" ? "high" : "all";
|
|
100487
|
-
const scanRunner = new ScanRunner(deps2.runner);
|
|
100488
|
-
if (!options.json) {
|
|
100489
|
-
formatter.print(import_picocolors4.default.dim("Scanning repository for unencrypted secrets..."));
|
|
100490
|
-
}
|
|
100491
|
-
let result;
|
|
100492
|
-
try {
|
|
100493
|
-
result = await scanRunner.scan(repoRoot, manifest, {
|
|
100494
|
-
stagedOnly: options.staged,
|
|
100495
|
-
paths: paths.length > 0 ? paths : void 0,
|
|
100496
|
-
severity
|
|
100497
|
-
});
|
|
100498
|
-
} catch (err) {
|
|
100499
|
-
formatter.error(`Scan failed: ${err.message}`);
|
|
100500
|
-
process.exit(2);
|
|
100501
|
-
return;
|
|
100502
|
-
}
|
|
100503
|
-
if (options.json) {
|
|
100504
|
-
const totalIssues = result.matches.length + result.unencryptedMatrixFiles.length;
|
|
100505
|
-
formatter.raw(
|
|
100506
|
-
JSON.stringify(
|
|
100507
|
-
{
|
|
100508
|
-
matches: result.matches,
|
|
100509
|
-
unencryptedMatrixFiles: result.unencryptedMatrixFiles,
|
|
100510
|
-
filesScanned: result.filesScanned,
|
|
100511
|
-
filesSkipped: result.filesSkipped,
|
|
100512
|
-
durationMs: result.durationMs,
|
|
100513
|
-
summary: `${totalIssues} issue${totalIssues !== 1 ? "s" : ""} found`
|
|
100514
|
-
},
|
|
100515
|
-
null,
|
|
100516
|
-
2
|
|
100517
|
-
) + "\n"
|
|
100518
|
-
);
|
|
100519
|
-
const hasIssues2 = result.matches.length > 0 || result.unencryptedMatrixFiles.length > 0;
|
|
100520
|
-
process.exit(hasIssues2 ? 1 : 0);
|
|
100521
|
-
return;
|
|
100594
|
+
).action(async (paths, options) => {
|
|
100595
|
+
const repoRoot = program3.opts().dir || process.cwd();
|
|
100596
|
+
let manifest;
|
|
100597
|
+
try {
|
|
100598
|
+
const parser = new ManifestParser();
|
|
100599
|
+
manifest = parser.parse(path43.join(repoRoot, "clef.yaml"));
|
|
100600
|
+
} catch (err) {
|
|
100601
|
+
if (err instanceof ManifestValidationError || err.message?.includes("clef.yaml")) {
|
|
100602
|
+
formatter.error("No clef.yaml found. Run 'clef init' to set up this repository.");
|
|
100603
|
+
} else {
|
|
100604
|
+
formatter.error(err.message);
|
|
100522
100605
|
}
|
|
100523
|
-
|
|
100524
|
-
|
|
100525
|
-
process.exit(hasIssues ? 1 : 0);
|
|
100606
|
+
process.exit(2);
|
|
100607
|
+
return;
|
|
100526
100608
|
}
|
|
100527
|
-
|
|
100609
|
+
if (options.severity && options.severity !== "all" && options.severity !== "high") {
|
|
100610
|
+
formatter.error(`Invalid severity '${options.severity}'. Must be 'all' or 'high'.`);
|
|
100611
|
+
process.exit(2);
|
|
100612
|
+
return;
|
|
100613
|
+
}
|
|
100614
|
+
const severity = options.severity === "high" ? "high" : "all";
|
|
100615
|
+
const scanRunner = new ScanRunner(deps2.runner);
|
|
100616
|
+
formatter.print(import_picocolors4.default.dim("Scanning repository for unencrypted secrets..."));
|
|
100617
|
+
let result;
|
|
100618
|
+
try {
|
|
100619
|
+
result = await scanRunner.scan(repoRoot, manifest, {
|
|
100620
|
+
stagedOnly: options.staged,
|
|
100621
|
+
paths: paths.length > 0 ? paths : void 0,
|
|
100622
|
+
severity
|
|
100623
|
+
});
|
|
100624
|
+
} catch (err) {
|
|
100625
|
+
formatter.error(`Scan failed: ${err.message}`);
|
|
100626
|
+
process.exit(2);
|
|
100627
|
+
return;
|
|
100628
|
+
}
|
|
100629
|
+
if (isJsonMode()) {
|
|
100630
|
+
formatter.json({
|
|
100631
|
+
matches: result.matches,
|
|
100632
|
+
unencryptedMatrixFiles: result.unencryptedMatrixFiles,
|
|
100633
|
+
filesScanned: result.filesScanned,
|
|
100634
|
+
filesSkipped: result.filesSkipped,
|
|
100635
|
+
durationMs: result.durationMs
|
|
100636
|
+
});
|
|
100637
|
+
const hasIssues2 = result.matches.length > 0 || result.unencryptedMatrixFiles.length > 0;
|
|
100638
|
+
process.exit(hasIssues2 ? 1 : 0);
|
|
100639
|
+
return;
|
|
100640
|
+
}
|
|
100641
|
+
formatScanOutput(result);
|
|
100642
|
+
const hasIssues = result.matches.length > 0 || result.unencryptedMatrixFiles.length > 0;
|
|
100643
|
+
process.exit(hasIssues ? 1 : 0);
|
|
100644
|
+
});
|
|
100528
100645
|
}
|
|
100529
100646
|
function formatScanOutput(result) {
|
|
100530
100647
|
const totalIssues = result.matches.length + result.unencryptedMatrixFiles.length;
|
|
@@ -100689,6 +100806,17 @@ function registerImportCommand(program3, deps2) {
|
|
|
100689
100806
|
process.exit(2);
|
|
100690
100807
|
return;
|
|
100691
100808
|
}
|
|
100809
|
+
if (isJsonMode()) {
|
|
100810
|
+
formatter.json({
|
|
100811
|
+
imported: result.imported,
|
|
100812
|
+
skipped: result.skipped,
|
|
100813
|
+
failed: result.failed,
|
|
100814
|
+
warnings: result.warnings,
|
|
100815
|
+
dryRun: opts2.dryRun
|
|
100816
|
+
});
|
|
100817
|
+
process.exit(result.failed.length > 0 ? 1 : 0);
|
|
100818
|
+
return;
|
|
100819
|
+
}
|
|
100692
100820
|
for (const warning of result.warnings) {
|
|
100693
100821
|
formatter.print(` ${sym("warning")} ${warning}`);
|
|
100694
100822
|
}
|
|
@@ -100748,6 +100876,7 @@ init_src();
|
|
|
100748
100876
|
import * as path45 from "path";
|
|
100749
100877
|
import * as readline4 from "readline";
|
|
100750
100878
|
function waitForEnter(message) {
|
|
100879
|
+
if (isJsonMode()) return Promise.resolve();
|
|
100751
100880
|
return new Promise((resolve7) => {
|
|
100752
100881
|
const rl = readline4.createInterface({
|
|
100753
100882
|
input: process.stdin,
|
|
@@ -100781,6 +100910,10 @@ function registerRecipientsCommand(program3, deps2) {
|
|
|
100781
100910
|
const sopsClient = await createSopsClient(repoRoot, deps2.runner);
|
|
100782
100911
|
const recipientManager = new RecipientManager(sopsClient, matrixManager);
|
|
100783
100912
|
const recipients = await recipientManager.list(manifest, repoRoot, opts2.environment);
|
|
100913
|
+
if (isJsonMode()) {
|
|
100914
|
+
formatter.json(recipients);
|
|
100915
|
+
return;
|
|
100916
|
+
}
|
|
100784
100917
|
if (recipients.length === 0) {
|
|
100785
100918
|
const scope2 = opts2.environment ? ` for environment '${opts2.environment}'` : "";
|
|
100786
100919
|
formatter.info(`No recipients configured${scope2}.`);
|
|
@@ -100811,7 +100944,7 @@ function registerRecipientsCommand(program3, deps2) {
|
|
|
100811
100944
|
return;
|
|
100812
100945
|
}
|
|
100813
100946
|
const normalizedKey = validation.key;
|
|
100814
|
-
const
|
|
100947
|
+
const result = await executeRecipientAdd(
|
|
100815
100948
|
repoRoot,
|
|
100816
100949
|
program3,
|
|
100817
100950
|
deps2,
|
|
@@ -100819,11 +100952,21 @@ function registerRecipientsCommand(program3, deps2) {
|
|
|
100819
100952
|
opts2.label,
|
|
100820
100953
|
opts2.environment
|
|
100821
100954
|
);
|
|
100822
|
-
if (
|
|
100823
|
-
|
|
100824
|
-
|
|
100825
|
-
|
|
100826
|
-
|
|
100955
|
+
if (result) {
|
|
100956
|
+
if (isJsonMode()) {
|
|
100957
|
+
formatter.json({
|
|
100958
|
+
action: "added",
|
|
100959
|
+
key: normalizedKey,
|
|
100960
|
+
label: opts2.label || keyPreview(normalizedKey),
|
|
100961
|
+
environment: opts2.environment,
|
|
100962
|
+
reEncryptedFiles: result.reEncryptedFiles.length
|
|
100963
|
+
});
|
|
100964
|
+
} else {
|
|
100965
|
+
const label2 = opts2.label || keyPreview(normalizedKey);
|
|
100966
|
+
formatter.hint(
|
|
100967
|
+
`git add clef.yaml && git add -A && git commit -m "add recipient: ${label2} [${opts2.environment}]"`
|
|
100968
|
+
);
|
|
100969
|
+
}
|
|
100827
100970
|
}
|
|
100828
100971
|
} catch (err) {
|
|
100829
100972
|
handleCommandError(err);
|
|
@@ -100919,6 +101062,16 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`
|
|
|
100919
101062
|
const relative7 = path45.relative(repoRoot, file);
|
|
100920
101063
|
formatter.print(` ${sym("success")} ${relative7}`);
|
|
100921
101064
|
}
|
|
101065
|
+
if (isJsonMode()) {
|
|
101066
|
+
formatter.json({
|
|
101067
|
+
action: "removed",
|
|
101068
|
+
key: trimmedKey,
|
|
101069
|
+
label: label2,
|
|
101070
|
+
environment: opts2.environment ?? null,
|
|
101071
|
+
reEncryptedFiles: result.reEncryptedFiles.length
|
|
101072
|
+
});
|
|
101073
|
+
return;
|
|
101074
|
+
}
|
|
100922
101075
|
formatter.success(
|
|
100923
101076
|
`${label2} removed. ${result.reEncryptedFiles.length} files re-encrypted. ${sym("locked")}`
|
|
100924
101077
|
);
|
|
@@ -100983,6 +101136,15 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`
|
|
|
100983
101136
|
return;
|
|
100984
101137
|
}
|
|
100985
101138
|
upsertRequest(repoRoot, publicKey, label2, opts2.environment);
|
|
101139
|
+
if (isJsonMode()) {
|
|
101140
|
+
formatter.json({
|
|
101141
|
+
action: "requested",
|
|
101142
|
+
label: label2,
|
|
101143
|
+
key: publicKey,
|
|
101144
|
+
environment: opts2.environment ?? null
|
|
101145
|
+
});
|
|
101146
|
+
return;
|
|
101147
|
+
}
|
|
100986
101148
|
const scope = opts2.environment ? ` for environment '${opts2.environment}'` : "";
|
|
100987
101149
|
formatter.success(`Access requested as '${label2}'${scope}`);
|
|
100988
101150
|
formatter.print(` Key: ${keyPreview(publicKey)}`);
|
|
@@ -100998,6 +101160,15 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`
|
|
|
100998
101160
|
try {
|
|
100999
101161
|
const repoRoot = program3.opts().dir || process.cwd();
|
|
101000
101162
|
const requests = loadRequests(repoRoot);
|
|
101163
|
+
if (isJsonMode()) {
|
|
101164
|
+
formatter.json(
|
|
101165
|
+
requests.map((r) => ({
|
|
101166
|
+
...r,
|
|
101167
|
+
requestedAt: r.requestedAt.toISOString()
|
|
101168
|
+
}))
|
|
101169
|
+
);
|
|
101170
|
+
return;
|
|
101171
|
+
}
|
|
101001
101172
|
if (requests.length === 0) {
|
|
101002
101173
|
formatter.info("No pending access requests.");
|
|
101003
101174
|
return;
|
|
@@ -101037,7 +101208,7 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`
|
|
|
101037
101208
|
process.exit(2);
|
|
101038
101209
|
return;
|
|
101039
101210
|
}
|
|
101040
|
-
const
|
|
101211
|
+
const result = await executeRecipientAdd(
|
|
101041
101212
|
repoRoot,
|
|
101042
101213
|
program3,
|
|
101043
101214
|
deps2,
|
|
@@ -101045,11 +101216,21 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`
|
|
|
101045
101216
|
request.label,
|
|
101046
101217
|
environment
|
|
101047
101218
|
);
|
|
101048
|
-
if (
|
|
101219
|
+
if (result) {
|
|
101049
101220
|
removeRequest(repoRoot, identifier);
|
|
101050
|
-
|
|
101051
|
-
|
|
101052
|
-
|
|
101221
|
+
if (isJsonMode()) {
|
|
101222
|
+
formatter.json({
|
|
101223
|
+
action: "approved",
|
|
101224
|
+
identifier,
|
|
101225
|
+
label: request.label,
|
|
101226
|
+
environment,
|
|
101227
|
+
reEncryptedFiles: result.reEncryptedFiles.length
|
|
101228
|
+
});
|
|
101229
|
+
} else {
|
|
101230
|
+
formatter.hint(
|
|
101231
|
+
`git add clef.yaml ${REQUESTS_FILENAME} && git add -A && git commit -m "approve recipient: ${request.label} [${environment}]"`
|
|
101232
|
+
);
|
|
101233
|
+
}
|
|
101053
101234
|
}
|
|
101054
101235
|
} catch (err) {
|
|
101055
101236
|
handleCommandError(err);
|
|
@@ -101065,7 +101246,7 @@ async function executeRecipientAdd(repoRoot, _program, deps2, key, label2, envir
|
|
|
101065
101246
|
`Environment '${environment}' not found. Available: ${manifest.environments.map((e) => e.name).join(", ")}`
|
|
101066
101247
|
);
|
|
101067
101248
|
process.exit(2);
|
|
101068
|
-
return
|
|
101249
|
+
return null;
|
|
101069
101250
|
}
|
|
101070
101251
|
const matrixManager = new MatrixManager();
|
|
101071
101252
|
const sopsClient = await createSopsClient(repoRoot, deps2.runner);
|
|
@@ -101074,7 +101255,7 @@ async function executeRecipientAdd(repoRoot, _program, deps2, key, label2, envir
|
|
|
101074
101255
|
if (existing.some((r) => r.key === key)) {
|
|
101075
101256
|
formatter.error(`Recipient '${keyPreview(key)}' is already present.`);
|
|
101076
101257
|
process.exit(2);
|
|
101077
|
-
return
|
|
101258
|
+
return null;
|
|
101078
101259
|
}
|
|
101079
101260
|
const allCells = matrixManager.resolveMatrix(manifest, repoRoot).filter((c) => c.exists && c.environment === environment);
|
|
101080
101261
|
const fileCount = allCells.length;
|
|
@@ -101091,7 +101272,7 @@ This will re-encrypt ${fileCount} files in the matrix.`);
|
|
|
101091
101272
|
const confirmed = await formatter.confirm("Proceed?");
|
|
101092
101273
|
if (!confirmed) {
|
|
101093
101274
|
formatter.info("Aborted.");
|
|
101094
|
-
return
|
|
101275
|
+
return null;
|
|
101095
101276
|
}
|
|
101096
101277
|
formatter.print(`
|
|
101097
101278
|
${sym("working")} Re-encrypting matrix...`);
|
|
@@ -101108,7 +101289,7 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`);
|
|
|
101108
101289
|
);
|
|
101109
101290
|
formatter.print("\nNo changes were applied. Investigate the error above and retry.");
|
|
101110
101291
|
process.exit(1);
|
|
101111
|
-
return
|
|
101292
|
+
return null;
|
|
101112
101293
|
}
|
|
101113
101294
|
for (const file of result.reEncryptedFiles) {
|
|
101114
101295
|
const relative7 = path45.relative(repoRoot, file);
|
|
@@ -101118,7 +101299,7 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`);
|
|
|
101118
101299
|
formatter.success(
|
|
101119
101300
|
`${displayLabel} added. ${result.reEncryptedFiles.length} files re-encrypted. ${sym("locked")}`
|
|
101120
101301
|
);
|
|
101121
|
-
return
|
|
101302
|
+
return { reEncryptedFiles: result.reEncryptedFiles };
|
|
101122
101303
|
}
|
|
101123
101304
|
|
|
101124
101305
|
// src/commands/merge-driver.ts
|
|
@@ -101281,6 +101462,16 @@ function registerServiceCommand(program3, deps2) {
|
|
|
101281
101462
|
repoRoot,
|
|
101282
101463
|
kmsEnvConfigs
|
|
101283
101464
|
);
|
|
101465
|
+
if (isJsonMode()) {
|
|
101466
|
+
formatter.json({
|
|
101467
|
+
action: "created",
|
|
101468
|
+
identity: result.identity.name,
|
|
101469
|
+
namespaces: result.identity.namespaces,
|
|
101470
|
+
environments: Object.keys(result.identity.environments),
|
|
101471
|
+
privateKeys: result.privateKeys
|
|
101472
|
+
});
|
|
101473
|
+
return;
|
|
101474
|
+
}
|
|
101284
101475
|
formatter.success(`Service identity '${name}' created.`);
|
|
101285
101476
|
formatter.print(`
|
|
101286
101477
|
Namespaces: ${result.identity.namespaces.join(", ")}`);
|
|
@@ -101334,6 +101525,10 @@ function registerServiceCommand(program3, deps2) {
|
|
|
101334
101525
|
const sopsClient = await createSopsClient(repoRoot, deps2.runner);
|
|
101335
101526
|
const manager = new ServiceIdentityManager(sopsClient, matrixManager);
|
|
101336
101527
|
const identities = manager.list(manifest);
|
|
101528
|
+
if (isJsonMode()) {
|
|
101529
|
+
formatter.json(identities);
|
|
101530
|
+
return;
|
|
101531
|
+
}
|
|
101337
101532
|
if (identities.length === 0) {
|
|
101338
101533
|
formatter.info("No service identities configured.");
|
|
101339
101534
|
return;
|
|
@@ -101363,6 +101558,10 @@ function registerServiceCommand(program3, deps2) {
|
|
|
101363
101558
|
process.exit(1);
|
|
101364
101559
|
return;
|
|
101365
101560
|
}
|
|
101561
|
+
if (isJsonMode()) {
|
|
101562
|
+
formatter.json(identity);
|
|
101563
|
+
return;
|
|
101564
|
+
}
|
|
101366
101565
|
formatter.print(`
|
|
101367
101566
|
Service Identity: ${identity.name}`);
|
|
101368
101567
|
formatter.print(`Description: ${identity.description}`);
|
|
@@ -101391,6 +101590,11 @@ Service Identity: ${identity.name}`);
|
|
|
101391
101590
|
const sopsClient = await createSopsClient(repoRoot, deps2.runner);
|
|
101392
101591
|
const manager = new ServiceIdentityManager(sopsClient, matrixManager);
|
|
101393
101592
|
const issues = await manager.validate(manifest, repoRoot);
|
|
101593
|
+
if (isJsonMode()) {
|
|
101594
|
+
formatter.json({ issues });
|
|
101595
|
+
process.exit(issues.length > 0 ? 1 : 0);
|
|
101596
|
+
return;
|
|
101597
|
+
}
|
|
101394
101598
|
if (issues.length === 0) {
|
|
101395
101599
|
formatter.success("All service identities are valid.");
|
|
101396
101600
|
return;
|
|
@@ -101441,6 +101645,17 @@ Service Identity: ${identity.name}`);
|
|
|
101441
101645
|
const manager = new ServiceIdentityManager(sopsClient, matrixManager);
|
|
101442
101646
|
formatter.print(`${sym("working")} Updating service identity '${name}'...`);
|
|
101443
101647
|
await manager.updateEnvironments(name, kmsEnvConfigs, manifest, repoRoot);
|
|
101648
|
+
if (isJsonMode()) {
|
|
101649
|
+
formatter.json({
|
|
101650
|
+
action: "updated",
|
|
101651
|
+
identity: name,
|
|
101652
|
+
changed: Object.entries(kmsEnvConfigs).map(([env, cfg]) => ({
|
|
101653
|
+
environment: env,
|
|
101654
|
+
provider: cfg.provider
|
|
101655
|
+
}))
|
|
101656
|
+
});
|
|
101657
|
+
return;
|
|
101658
|
+
}
|
|
101444
101659
|
formatter.success(`Service identity '${name}' updated.`);
|
|
101445
101660
|
for (const [envName, kmsConfig] of Object.entries(kmsEnvConfigs)) {
|
|
101446
101661
|
formatter.print(` ${envName}: switched to KMS envelope (${kmsConfig.provider})`);
|
|
@@ -101476,6 +101691,10 @@ Service Identity: ${identity.name}`);
|
|
|
101476
101691
|
const manager = new ServiceIdentityManager(sopsClient, matrixManager);
|
|
101477
101692
|
formatter.print(`${sym("working")} Deleting service identity '${name}'...`);
|
|
101478
101693
|
await manager.delete(name, manifest, repoRoot);
|
|
101694
|
+
if (isJsonMode()) {
|
|
101695
|
+
formatter.json({ action: "deleted", identity: name });
|
|
101696
|
+
return;
|
|
101697
|
+
}
|
|
101479
101698
|
formatter.success(`Service identity '${name}' deleted.`);
|
|
101480
101699
|
formatter.hint(
|
|
101481
101700
|
`git add clef.yaml && git commit -m "chore: delete service identity '${name}'"`
|
|
@@ -101512,6 +101731,15 @@ Service Identity: ${identity.name}`);
|
|
|
101512
101731
|
}
|
|
101513
101732
|
formatter.print(`${sym("working")} Rotating key for '${name}'...`);
|
|
101514
101733
|
const newKeys = await manager.rotateKey(name, manifest, repoRoot, opts2.environment);
|
|
101734
|
+
if (isJsonMode()) {
|
|
101735
|
+
formatter.json({
|
|
101736
|
+
action: "rotated",
|
|
101737
|
+
identity: name,
|
|
101738
|
+
environments: Object.keys(newKeys),
|
|
101739
|
+
privateKeys: newKeys
|
|
101740
|
+
});
|
|
101741
|
+
return;
|
|
101742
|
+
}
|
|
101515
101743
|
formatter.success(`Key rotated for '${name}'.`);
|
|
101516
101744
|
const entries = Object.entries(newKeys);
|
|
101517
101745
|
const block = entries.map(([env, key]) => `${env}: ${key}`).join("\n");
|
|
@@ -101687,6 +101915,18 @@ function registerPackCommand(program3, deps2) {
|
|
|
101687
101915
|
const fileOut = new FilePackOutput(outputPath);
|
|
101688
101916
|
await fileOut.write(memOutput.artifact, memOutput.json);
|
|
101689
101917
|
}
|
|
101918
|
+
if (isJsonMode()) {
|
|
101919
|
+
formatter.json({
|
|
101920
|
+
identity,
|
|
101921
|
+
environment,
|
|
101922
|
+
keyCount: result.keyCount,
|
|
101923
|
+
namespaceCount: result.namespaceCount,
|
|
101924
|
+
artifactSize: result.artifactSize,
|
|
101925
|
+
revision: result.revision,
|
|
101926
|
+
output: outputPath ?? null
|
|
101927
|
+
});
|
|
101928
|
+
return;
|
|
101929
|
+
}
|
|
101690
101930
|
formatter.success(
|
|
101691
101931
|
`Artifact packed: ${result.keyCount} keys from ${result.namespaceCount} namespace(s).`
|
|
101692
101932
|
);
|
|
@@ -101764,6 +102004,15 @@ function registerRevokeCommand(program3, _deps) {
|
|
|
101764
102004
|
fs32.mkdirSync(artifactDir, { recursive: true });
|
|
101765
102005
|
fs32.writeFileSync(artifactPath, JSON.stringify(revoked, null, 2) + "\n", "utf-8");
|
|
101766
102006
|
const relPath = path49.relative(repoRoot, artifactPath);
|
|
102007
|
+
if (isJsonMode()) {
|
|
102008
|
+
formatter.json({
|
|
102009
|
+
identity,
|
|
102010
|
+
environment,
|
|
102011
|
+
revokedAt: revoked.revokedAt,
|
|
102012
|
+
markerPath: relPath
|
|
102013
|
+
});
|
|
102014
|
+
return;
|
|
102015
|
+
}
|
|
101767
102016
|
formatter.success(`Artifact revoked: ${relPath}`);
|
|
101768
102017
|
formatter.print("");
|
|
101769
102018
|
formatter.print(`${sym("arrow")} If your agent fetches artifacts from git (VCS source):`);
|
|
@@ -101792,37 +102041,35 @@ import * as path50 from "path";
|
|
|
101792
102041
|
function registerDriftCommand(program3, _deps) {
|
|
101793
102042
|
program3.command("drift <path>").description(
|
|
101794
102043
|
"Compare key sets across two local Clef repos without decryption.\n\nReads encrypted YAML files as plaintext (key names are not encrypted)\nand reports keys that exist in some environments but not others.\n\nDoes not require sops to be installed.\n\nExit codes:\n 0 No drift\n 1 Drift found"
|
|
101795
|
-
).option("--
|
|
101796
|
-
|
|
101797
|
-
|
|
101798
|
-
|
|
101799
|
-
|
|
101800
|
-
|
|
101801
|
-
|
|
101802
|
-
|
|
101803
|
-
|
|
101804
|
-
|
|
101805
|
-
|
|
101806
|
-
process.exit(1);
|
|
101807
|
-
return;
|
|
101808
|
-
}
|
|
101809
|
-
const payload = driftResultToOtlp(result, version2);
|
|
101810
|
-
await pushOtlp(payload, config);
|
|
101811
|
-
formatter.success("Drift results pushed to telemetry endpoint.");
|
|
101812
|
-
}
|
|
101813
|
-
if (options.json) {
|
|
101814
|
-
formatter.raw(JSON.stringify(result, null, 2) + "\n");
|
|
101815
|
-
process.exit(result.issues.length > 0 ? 1 : 0);
|
|
102044
|
+
).option("--push", "Push results as OTLP to CLEF_TELEMETRY_URL").option("--namespace <name...>", "Scope to specific namespace(s)").action(async (remotePath, options) => {
|
|
102045
|
+
try {
|
|
102046
|
+
const localRoot = program3.opts().dir || process.cwd();
|
|
102047
|
+
const remoteRoot = path50.resolve(localRoot, remotePath);
|
|
102048
|
+
const detector = new DriftDetector();
|
|
102049
|
+
const result = detector.detect(localRoot, remoteRoot, options.namespace);
|
|
102050
|
+
if (options.push) {
|
|
102051
|
+
const config = resolveTelemetryConfig();
|
|
102052
|
+
if (!config) {
|
|
102053
|
+
formatter.error("--push requires CLEF_TELEMETRY_URL to be set.");
|
|
102054
|
+
process.exit(1);
|
|
101816
102055
|
return;
|
|
101817
102056
|
}
|
|
101818
|
-
|
|
102057
|
+
const payload = driftResultToOtlp(result, version2);
|
|
102058
|
+
await pushOtlp(payload, config);
|
|
102059
|
+
formatter.success("Drift results pushed to telemetry endpoint.");
|
|
102060
|
+
}
|
|
102061
|
+
if (isJsonMode()) {
|
|
102062
|
+
formatter.json(result);
|
|
101819
102063
|
process.exit(result.issues.length > 0 ? 1 : 0);
|
|
101820
|
-
|
|
101821
|
-
formatter.error(err.message);
|
|
101822
|
-
process.exit(1);
|
|
102064
|
+
return;
|
|
101823
102065
|
}
|
|
102066
|
+
formatDriftOutput(result);
|
|
102067
|
+
process.exit(result.issues.length > 0 ? 1 : 0);
|
|
102068
|
+
} catch (err) {
|
|
102069
|
+
formatter.error(err.message);
|
|
102070
|
+
process.exit(1);
|
|
101824
102071
|
}
|
|
101825
|
-
);
|
|
102072
|
+
});
|
|
101826
102073
|
}
|
|
101827
102074
|
function formatDriftOutput(result) {
|
|
101828
102075
|
if (result.namespacesCompared === 0) {
|
|
@@ -101915,7 +102162,7 @@ async function getHeadSha(repoRoot, runner2) {
|
|
|
101915
102162
|
function registerReportCommand(program3, deps2) {
|
|
101916
102163
|
program3.command("report").description(
|
|
101917
102164
|
"Generate a metadata report for this Clef repository.\n\nIncludes repo identity, matrix status, policy issues, and recipient\nsummaries. Never exposes ciphertext, key names, or decrypted values.\n\nExit codes:\n 0 No errors\n 1 Errors found"
|
|
101918
|
-
).option("--
|
|
102165
|
+
).option("--push", "Push report as OTLP to CLEF_TELEMETRY_URL (with automatic gap-fill)").option("--at <sha>", "Generate report at a specific commit").option("--since <sha>", "Generate reports for all commits since <sha>").option("--namespace <name...>", "Filter to namespace(s)").option("--environment <name...>", "Filter to environment(s)").action(
|
|
101919
102166
|
async (options) => {
|
|
101920
102167
|
try {
|
|
101921
102168
|
const repoRoot = program3.opts().dir || process.cwd();
|
|
@@ -101927,7 +102174,7 @@ function registerReportCommand(program3, deps2) {
|
|
|
101927
102174
|
deps2.runner
|
|
101928
102175
|
);
|
|
101929
102176
|
await maybePush(report, options.push);
|
|
101930
|
-
outputReport(report
|
|
102177
|
+
outputReport(report);
|
|
101931
102178
|
return;
|
|
101932
102179
|
}
|
|
101933
102180
|
const sopsClient = await createSopsClient(repoRoot, deps2.runner);
|
|
@@ -101945,7 +102192,7 @@ function registerReportCommand(program3, deps2) {
|
|
|
101945
102192
|
});
|
|
101946
102193
|
if (options.push) {
|
|
101947
102194
|
await pushWithGapFill(repoRoot, headReport, deps2.runner);
|
|
101948
|
-
outputReport(headReport
|
|
102195
|
+
outputReport(headReport);
|
|
101949
102196
|
return;
|
|
101950
102197
|
}
|
|
101951
102198
|
if (options.since) {
|
|
@@ -101959,8 +102206,8 @@ function registerReportCommand(program3, deps2) {
|
|
|
101959
102206
|
reports.push(await generateReportAtCommit(repoRoot, sha, version2, deps2.runner));
|
|
101960
102207
|
}
|
|
101961
102208
|
}
|
|
101962
|
-
if (
|
|
101963
|
-
formatter.
|
|
102209
|
+
if (isJsonMode()) {
|
|
102210
|
+
formatter.json(reports);
|
|
101964
102211
|
} else {
|
|
101965
102212
|
formatter.print(
|
|
101966
102213
|
`Generated ${reports.length} report(s) for commits since ${options.since.slice(0, 8)}`
|
|
@@ -101975,7 +102222,7 @@ function registerReportCommand(program3, deps2) {
|
|
|
101975
102222
|
process.exit(reports.some((r) => r.policy.issueCount.error > 0) ? 1 : 0);
|
|
101976
102223
|
return;
|
|
101977
102224
|
}
|
|
101978
|
-
outputReport(headReport
|
|
102225
|
+
outputReport(headReport);
|
|
101979
102226
|
} catch (err) {
|
|
101980
102227
|
handleCommandError(err);
|
|
101981
102228
|
}
|
|
@@ -102026,9 +102273,9 @@ async function maybePush(report, push) {
|
|
|
102026
102273
|
await pushOtlp(payload, config);
|
|
102027
102274
|
formatter.success("Report pushed to telemetry endpoint.");
|
|
102028
102275
|
}
|
|
102029
|
-
function outputReport(report
|
|
102030
|
-
if (
|
|
102031
|
-
formatter.
|
|
102276
|
+
function outputReport(report) {
|
|
102277
|
+
if (isJsonMode()) {
|
|
102278
|
+
formatter.json(report);
|
|
102032
102279
|
process.exit(report.policy.issueCount.error > 0 ? 1 : 0);
|
|
102033
102280
|
return;
|
|
102034
102281
|
}
|
|
@@ -102197,6 +102444,16 @@ function registerInstallCommand(program3, _deps) {
|
|
|
102197
102444
|
}
|
|
102198
102445
|
const manifestFile = files.find((f2) => f2.name === "broker.yaml");
|
|
102199
102446
|
const manifest = manifestFile ? (0, import_yaml.parse)(manifestFile.content) : {};
|
|
102447
|
+
if (isJsonMode()) {
|
|
102448
|
+
formatter.json({
|
|
102449
|
+
broker: entry.name,
|
|
102450
|
+
provider: entry.provider,
|
|
102451
|
+
tier: entry.tier,
|
|
102452
|
+
files: files.map((f2) => `brokers/${entry.name}/${f2.name}`)
|
|
102453
|
+
});
|
|
102454
|
+
process.exit(0);
|
|
102455
|
+
return;
|
|
102456
|
+
}
|
|
102200
102457
|
formatter.print("");
|
|
102201
102458
|
formatter.print(` ${sym("success")} ${entry.name}`);
|
|
102202
102459
|
formatter.print("");
|
|
@@ -102260,6 +102517,11 @@ function registerSearchCommand(program3, _deps) {
|
|
|
102260
102517
|
if (options.tier) {
|
|
102261
102518
|
results = results.filter((b) => b.tier === Number(options.tier));
|
|
102262
102519
|
}
|
|
102520
|
+
if (isJsonMode()) {
|
|
102521
|
+
formatter.json(results);
|
|
102522
|
+
process.exit(0);
|
|
102523
|
+
return;
|
|
102524
|
+
}
|
|
102263
102525
|
if (results.length === 0) {
|
|
102264
102526
|
formatter.info("No brokers found matching your query.");
|
|
102265
102527
|
process.exit(0);
|
|
@@ -102390,6 +102652,20 @@ ${sym("working")} Backend migration summary:`);
|
|
|
102390
102652
|
}
|
|
102391
102653
|
}
|
|
102392
102654
|
);
|
|
102655
|
+
if (isJsonMode()) {
|
|
102656
|
+
formatter.json({
|
|
102657
|
+
backend: target.backend,
|
|
102658
|
+
migratedFiles: result.migratedFiles,
|
|
102659
|
+
skippedFiles: result.skippedFiles,
|
|
102660
|
+
verifiedFiles: result.verifiedFiles,
|
|
102661
|
+
warnings: result.warnings,
|
|
102662
|
+
rolledBack: result.rolledBack,
|
|
102663
|
+
error: result.error ?? null,
|
|
102664
|
+
dryRun: opts2.dryRun ?? false
|
|
102665
|
+
});
|
|
102666
|
+
process.exit(result.rolledBack ? 1 : 0);
|
|
102667
|
+
return;
|
|
102668
|
+
}
|
|
102393
102669
|
if (result.rolledBack) {
|
|
102394
102670
|
formatter.error(`Migration failed: ${result.error}`);
|
|
102395
102671
|
formatter.info("All changes have been rolled back.");
|
|
@@ -102540,12 +102816,18 @@ var VERSION = package_default.version;
|
|
|
102540
102816
|
var program2 = new Command();
|
|
102541
102817
|
var runner = new NodeSubprocessRunner();
|
|
102542
102818
|
var deps = { runner };
|
|
102543
|
-
program2.name("clef").option("--dir <path>", "Path to a local Clef repository root (default: current directory)").option("--plain", "Plain output, no emoji or colour");
|
|
102819
|
+
program2.name("clef").option("--dir <path>", "Path to a local Clef repository root (default: current directory)").option("--plain", "Plain output, no emoji or colour").option("--json", "Output machine-readable JSON (suppresses human output)").option("--yes", "Auto-confirm destructive operations (required with --json for writes)");
|
|
102544
102820
|
program2.hook("preAction", async () => {
|
|
102545
102821
|
const opts2 = program2.opts();
|
|
102546
102822
|
if (opts2.plain) {
|
|
102547
102823
|
setPlainMode(true);
|
|
102548
102824
|
}
|
|
102825
|
+
if (opts2.json) {
|
|
102826
|
+
setJsonMode(true);
|
|
102827
|
+
}
|
|
102828
|
+
if (opts2.yes) {
|
|
102829
|
+
setYesMode(true);
|
|
102830
|
+
}
|
|
102549
102831
|
});
|
|
102550
102832
|
program2.addHelpText("beforeAll", () => {
|
|
102551
102833
|
const clef = isPlainMode() ? "clef" : symbols.clef;
|
|
@@ -102644,6 +102926,9 @@ async function main() {
|
|
|
102644
102926
|
}
|
|
102645
102927
|
}
|
|
102646
102928
|
main().catch((err) => {
|
|
102929
|
+
if (isJsonMode()) {
|
|
102930
|
+
exitJsonError(err.message);
|
|
102931
|
+
}
|
|
102647
102932
|
formatter.error(err.message);
|
|
102648
102933
|
process.exit(1);
|
|
102649
102934
|
});
|