@clef-sh/cli 0.1.12 → 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.cjs
CHANGED
|
@@ -97317,11 +97317,23 @@ function sym(key) {
|
|
|
97317
97317
|
}
|
|
97318
97318
|
|
|
97319
97319
|
// src/output/formatter.ts
|
|
97320
|
+
var _jsonMode = false;
|
|
97321
|
+
var _yesMode = false;
|
|
97322
|
+
function setJsonMode(json) {
|
|
97323
|
+
_jsonMode = json;
|
|
97324
|
+
}
|
|
97325
|
+
function isJsonMode() {
|
|
97326
|
+
return _jsonMode;
|
|
97327
|
+
}
|
|
97328
|
+
function setYesMode(yes) {
|
|
97329
|
+
_yesMode = yes;
|
|
97330
|
+
}
|
|
97320
97331
|
function color(fn, str2) {
|
|
97321
97332
|
return isPlainMode() ? str2 : fn(str2);
|
|
97322
97333
|
}
|
|
97323
97334
|
var formatter = {
|
|
97324
97335
|
success(message) {
|
|
97336
|
+
if (_jsonMode) return;
|
|
97325
97337
|
const icon = sym("success");
|
|
97326
97338
|
process.stdout.write(color(import_picocolors.default.green, `${icon} ${message}`) + "\n");
|
|
97327
97339
|
},
|
|
@@ -97338,15 +97350,18 @@ var formatter = {
|
|
|
97338
97350
|
process.stderr.write(color(import_picocolors.default.yellow, `${icon} ${message}`) + "\n");
|
|
97339
97351
|
},
|
|
97340
97352
|
info(message) {
|
|
97353
|
+
if (_jsonMode) return;
|
|
97341
97354
|
const icon = sym("info");
|
|
97342
97355
|
process.stdout.write(color(import_picocolors.default.blue, `${icon} ${message}`) + "\n");
|
|
97343
97356
|
},
|
|
97344
97357
|
hint(message) {
|
|
97358
|
+
if (_jsonMode) return;
|
|
97345
97359
|
const icon = sym("arrow");
|
|
97346
97360
|
process.stdout.write(`${icon} ${message}
|
|
97347
97361
|
`);
|
|
97348
97362
|
},
|
|
97349
97363
|
keyValue(key, value) {
|
|
97364
|
+
if (_jsonMode) return;
|
|
97350
97365
|
const icon = sym("key");
|
|
97351
97366
|
const arrow = sym("arrow");
|
|
97352
97367
|
const prefix = icon ? `${icon} ` : "";
|
|
@@ -97354,30 +97369,38 @@ var formatter = {
|
|
|
97354
97369
|
`);
|
|
97355
97370
|
},
|
|
97356
97371
|
pendingItem(key, days) {
|
|
97372
|
+
if (_jsonMode) return;
|
|
97357
97373
|
const icon = sym("pending");
|
|
97358
97374
|
const prefix = icon ? `${icon} ` : "[pending] ";
|
|
97359
97375
|
process.stdout.write(`${prefix}${key} \u2014 ${days} day${days !== 1 ? "s" : ""} pending
|
|
97360
97376
|
`);
|
|
97361
97377
|
},
|
|
97362
97378
|
recipientItem(label, keyPreview2) {
|
|
97379
|
+
if (_jsonMode) return;
|
|
97363
97380
|
const icon = sym("recipient");
|
|
97364
97381
|
const prefix = icon ? `${icon} ` : "";
|
|
97365
97382
|
process.stdout.write(`${prefix}${label.padEnd(15)}${keyPreview2}
|
|
97366
97383
|
`);
|
|
97367
97384
|
},
|
|
97368
97385
|
section(label) {
|
|
97386
|
+
if (_jsonMode) return;
|
|
97369
97387
|
process.stdout.write(`
|
|
97370
97388
|
${label}
|
|
97371
97389
|
|
|
97372
97390
|
`);
|
|
97373
97391
|
},
|
|
97374
97392
|
print(message) {
|
|
97393
|
+
if (_jsonMode) return;
|
|
97375
97394
|
process.stdout.write(message + "\n");
|
|
97376
97395
|
},
|
|
97377
97396
|
raw(message) {
|
|
97378
97397
|
process.stdout.write(message);
|
|
97379
97398
|
},
|
|
97399
|
+
json(data) {
|
|
97400
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
97401
|
+
},
|
|
97380
97402
|
table(rows, columns) {
|
|
97403
|
+
if (_jsonMode) return;
|
|
97381
97404
|
const widths = columns.map((col, i) => {
|
|
97382
97405
|
const maxDataWidth = rows.reduce(
|
|
97383
97406
|
(max, row) => Math.max(max, stripAnsi(row[i] ?? "").length),
|
|
@@ -97394,6 +97417,10 @@ ${label}
|
|
|
97394
97417
|
}
|
|
97395
97418
|
},
|
|
97396
97419
|
async confirm(prompt) {
|
|
97420
|
+
if (_yesMode) return true;
|
|
97421
|
+
if (_jsonMode) {
|
|
97422
|
+
throw new Error("--json requires --yes for destructive operations");
|
|
97423
|
+
}
|
|
97397
97424
|
const rl = readline.createInterface({
|
|
97398
97425
|
input: process.stdin,
|
|
97399
97426
|
output: process.stderr
|
|
@@ -97428,6 +97455,9 @@ ${label}
|
|
|
97428
97455
|
`);
|
|
97429
97456
|
},
|
|
97430
97457
|
async secretPrompt(prompt) {
|
|
97458
|
+
if (_jsonMode) {
|
|
97459
|
+
throw new Error("--json mode requires value on the command line (no interactive prompt)");
|
|
97460
|
+
}
|
|
97431
97461
|
return new Promise((resolve7, reject) => {
|
|
97432
97462
|
process.stderr.write(color(import_picocolors.default.cyan, `${prompt}: `));
|
|
97433
97463
|
if (process.stdin.isTTY) {
|
|
@@ -97470,7 +97500,14 @@ function pad(str2, width) {
|
|
|
97470
97500
|
}
|
|
97471
97501
|
|
|
97472
97502
|
// src/handle-error.ts
|
|
97503
|
+
function exitJsonError(message) {
|
|
97504
|
+
formatter.json({ error: true, message });
|
|
97505
|
+
process.exit(1);
|
|
97506
|
+
}
|
|
97473
97507
|
function handleCommandError(err) {
|
|
97508
|
+
if (isJsonMode()) {
|
|
97509
|
+
exitJsonError(err.message);
|
|
97510
|
+
}
|
|
97474
97511
|
if (err instanceof SopsMissingError || err instanceof SopsVersionError) {
|
|
97475
97512
|
formatter.formatDependencyError(err);
|
|
97476
97513
|
} else {
|
|
@@ -97970,7 +98007,7 @@ async function handleSecondDevOnboarding(repoRoot, clefConfigPath, deps2, option
|
|
|
97970
98007
|
"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."
|
|
97971
98008
|
);
|
|
97972
98009
|
let keyPath;
|
|
97973
|
-
if (options.nonInteractive || !process.stdin.isTTY) {
|
|
98010
|
+
if (options.nonInteractive || isJsonMode() || !process.stdin.isTTY) {
|
|
97974
98011
|
keyPath = process.env.CLEF_AGE_KEY_FILE || defaultAgeKeyPath(label);
|
|
97975
98012
|
keyPath = path23.resolve(keyPath);
|
|
97976
98013
|
} else {
|
|
@@ -98003,6 +98040,10 @@ async function handleSecondDevOnboarding(repoRoot, clefConfigPath, deps2, option
|
|
|
98003
98040
|
formatter.success("Created .clef/.gitignore");
|
|
98004
98041
|
}
|
|
98005
98042
|
formatter.success(`Key label: ${label}`);
|
|
98043
|
+
if (isJsonMode()) {
|
|
98044
|
+
formatter.json({ action: "onboarded", manifest: "clef.yaml", config: ".clef/config.yaml" });
|
|
98045
|
+
return;
|
|
98046
|
+
}
|
|
98006
98047
|
formatter.section("Next steps:");
|
|
98007
98048
|
formatter.hint("clef recipients request \u2014 request access to encrypted secrets");
|
|
98008
98049
|
formatter.hint("clef update \u2014 scaffold new environments");
|
|
@@ -98013,7 +98054,7 @@ async function handleFullSetup(repoRoot, manifestPath, clefConfigPath, deps2, op
|
|
|
98013
98054
|
let namespaces = options.namespaces ? options.namespaces.split(",").map((s) => s.trim()) : [];
|
|
98014
98055
|
const backend = options.backend ?? "age";
|
|
98015
98056
|
let secretsDir = options.secretsDir ?? "secrets";
|
|
98016
|
-
if (!options.nonInteractive && process.stdin.isTTY) {
|
|
98057
|
+
if (!options.nonInteractive && !isJsonMode() && process.stdin.isTTY) {
|
|
98017
98058
|
const envAnswer = await promptWithDefault(
|
|
98018
98059
|
"Environments (comma-separated)",
|
|
98019
98060
|
environments.join(",")
|
|
@@ -98068,7 +98109,7 @@ async function handleFullSetup(repoRoot, manifestPath, clefConfigPath, deps2, op
|
|
|
98068
98109
|
formatter.warn(
|
|
98069
98110
|
"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."
|
|
98070
98111
|
);
|
|
98071
|
-
if (!options.nonInteractive && process.stdin.isTTY) {
|
|
98112
|
+
if (!options.nonInteractive && !isJsonMode() && process.stdin.isTTY) {
|
|
98072
98113
|
const confirmed = await formatter.confirm("Write the private key to the filesystem?");
|
|
98073
98114
|
if (!confirmed) {
|
|
98074
98115
|
formatter.error(
|
|
@@ -98079,7 +98120,7 @@ async function handleFullSetup(repoRoot, manifestPath, clefConfigPath, deps2, op
|
|
|
98079
98120
|
}
|
|
98080
98121
|
}
|
|
98081
98122
|
let keyPath;
|
|
98082
|
-
if (options.nonInteractive || !process.stdin.isTTY) {
|
|
98123
|
+
if (options.nonInteractive || isJsonMode() || !process.stdin.isTTY) {
|
|
98083
98124
|
keyPath = defaultAgeKeyPath(label);
|
|
98084
98125
|
if (await isInsideAnyGitRepo(path23.resolve(keyPath))) {
|
|
98085
98126
|
throw new Error(
|
|
@@ -98199,6 +98240,17 @@ async function handleFullSetup(repoRoot, manifestPath, clefConfigPath, deps2, op
|
|
|
98199
98240
|
formatter.print(" clef config set analytics false (permanent)\n");
|
|
98200
98241
|
} catch {
|
|
98201
98242
|
}
|
|
98243
|
+
if (isJsonMode()) {
|
|
98244
|
+
formatter.json({
|
|
98245
|
+
action: "initialized",
|
|
98246
|
+
manifest: "clef.yaml",
|
|
98247
|
+
environments: manifest.environments.map((e) => e.name),
|
|
98248
|
+
namespaces: manifest.namespaces.map((n) => n.name),
|
|
98249
|
+
backend,
|
|
98250
|
+
scaffolded: scaffoldedCount
|
|
98251
|
+
});
|
|
98252
|
+
return;
|
|
98253
|
+
}
|
|
98202
98254
|
formatter.section("Next steps:");
|
|
98203
98255
|
formatter.hint("clef set <namespace>/<env> <KEY> <value> \u2014 add a secret");
|
|
98204
98256
|
formatter.hint("clef scan \u2014 check for existing plaintext secrets");
|
|
@@ -98459,7 +98511,9 @@ function registerGetCommand(program3, deps2) {
|
|
|
98459
98511
|
return;
|
|
98460
98512
|
}
|
|
98461
98513
|
const val = decrypted.values[key];
|
|
98462
|
-
if (
|
|
98514
|
+
if (isJsonMode()) {
|
|
98515
|
+
formatter.json({ key, value: val, namespace, environment });
|
|
98516
|
+
} else if (opts.raw) {
|
|
98463
98517
|
formatter.raw(val);
|
|
98464
98518
|
} else {
|
|
98465
98519
|
const copied = copyToClipboard(val);
|
|
@@ -98564,6 +98618,16 @@ function registerSetCommand(program3, deps2) {
|
|
|
98564
98618
|
pendingErrors.push(env.name);
|
|
98565
98619
|
}
|
|
98566
98620
|
}
|
|
98621
|
+
if (isJsonMode()) {
|
|
98622
|
+
formatter.json({
|
|
98623
|
+
key,
|
|
98624
|
+
namespace: namespace2,
|
|
98625
|
+
environments: manifest.environments.map((e) => e.name),
|
|
98626
|
+
action: "created",
|
|
98627
|
+
pending: true
|
|
98628
|
+
});
|
|
98629
|
+
return;
|
|
98630
|
+
}
|
|
98567
98631
|
formatter.success(
|
|
98568
98632
|
`'${key}' set in ${namespace2} across all environments ${sym("locked")}`
|
|
98569
98633
|
);
|
|
@@ -98587,6 +98651,16 @@ function registerSetCommand(program3, deps2) {
|
|
|
98587
98651
|
} catch {
|
|
98588
98652
|
}
|
|
98589
98653
|
}
|
|
98654
|
+
if (isJsonMode()) {
|
|
98655
|
+
formatter.json({
|
|
98656
|
+
key,
|
|
98657
|
+
namespace: namespace2,
|
|
98658
|
+
environments: manifest.environments.map((e) => e.name),
|
|
98659
|
+
action: "created",
|
|
98660
|
+
pending: false
|
|
98661
|
+
});
|
|
98662
|
+
return;
|
|
98663
|
+
}
|
|
98590
98664
|
formatter.success(`'${key}' set in ${namespace2} across all environments`);
|
|
98591
98665
|
formatter.hint(`git add ${namespace2}/ # stage all updated files`);
|
|
98592
98666
|
}
|
|
@@ -98650,6 +98724,10 @@ function registerSetCommand(program3, deps2) {
|
|
|
98650
98724
|
process.exit(1);
|
|
98651
98725
|
return;
|
|
98652
98726
|
}
|
|
98727
|
+
if (isJsonMode()) {
|
|
98728
|
+
formatter.json({ key, namespace, environment, action: "created", pending: true });
|
|
98729
|
+
return;
|
|
98730
|
+
}
|
|
98653
98731
|
formatter.success(`${key} set in ${namespace}/${environment} ${sym("locked")}`);
|
|
98654
98732
|
formatter.print(
|
|
98655
98733
|
` ${sym("pending")} Marked as pending \u2014 replace with a real value before deploying`
|
|
@@ -98664,6 +98742,10 @@ function registerSetCommand(program3, deps2) {
|
|
|
98664
98742
|
The value is saved. Run clef lint to check for stale pending markers.`
|
|
98665
98743
|
);
|
|
98666
98744
|
}
|
|
98745
|
+
if (isJsonMode()) {
|
|
98746
|
+
formatter.json({ key, namespace, environment, action: "created", pending: false });
|
|
98747
|
+
return;
|
|
98748
|
+
}
|
|
98667
98749
|
formatter.success(`${key} set in ${namespace}/${environment}`);
|
|
98668
98750
|
formatter.hint(
|
|
98669
98751
|
`Commit: git add ${manifest.file_pattern.replace("{namespace}", namespace).replace("{environment}", environment)}`
|
|
@@ -98732,7 +98814,10 @@ function registerCompareCommand(program3, deps2) {
|
|
|
98732
98814
|
compareBuf.copy(paddedCompare);
|
|
98733
98815
|
const timingEqual = crypto5.timingSafeEqual(paddedStored, paddedCompare);
|
|
98734
98816
|
const match = storedBuf.length === compareBuf.length && timingEqual;
|
|
98735
|
-
if (
|
|
98817
|
+
if (isJsonMode()) {
|
|
98818
|
+
formatter.json({ match, key, namespace, environment });
|
|
98819
|
+
if (!match) process.exit(1);
|
|
98820
|
+
} else if (match) {
|
|
98736
98821
|
formatter.success(`${key} ${sym("arrow")} values match`);
|
|
98737
98822
|
} else {
|
|
98738
98823
|
formatter.failure(`${key} ${sym("arrow")} values do not match`);
|
|
@@ -98780,6 +98865,15 @@ Type the key name to confirm:`
|
|
|
98780
98865
|
}
|
|
98781
98866
|
const bulkOps = new BulkOps();
|
|
98782
98867
|
await bulkOps.deleteAcrossEnvironments(namespace, key, manifest, sopsClient, repoRoot);
|
|
98868
|
+
if (isJsonMode()) {
|
|
98869
|
+
formatter.json({
|
|
98870
|
+
key,
|
|
98871
|
+
namespace,
|
|
98872
|
+
environments: manifest.environments.map((e) => e.name),
|
|
98873
|
+
action: "deleted"
|
|
98874
|
+
});
|
|
98875
|
+
return;
|
|
98876
|
+
}
|
|
98783
98877
|
formatter.success(`Deleted '${key}' from ${namespace} in all environments`);
|
|
98784
98878
|
} else {
|
|
98785
98879
|
const [namespace, environment] = parseTarget(target);
|
|
@@ -98818,6 +98912,10 @@ Type the key name to confirm:`
|
|
|
98818
98912
|
`Key deleted but pending metadata could not be cleaned up. Run clef lint to verify.`
|
|
98819
98913
|
);
|
|
98820
98914
|
}
|
|
98915
|
+
if (isJsonMode()) {
|
|
98916
|
+
formatter.json({ key, namespace, environment, action: "deleted" });
|
|
98917
|
+
return;
|
|
98918
|
+
}
|
|
98821
98919
|
formatter.success(`Deleted '${key}' from ${namespace}/${environment}`);
|
|
98822
98920
|
formatter.hint(
|
|
98823
98921
|
`Commit: git add ${manifest.file_pattern.replace("{namespace}", namespace).replace("{environment}", environment)}`
|
|
@@ -98836,10 +98934,11 @@ Type the key name to confirm:`
|
|
|
98836
98934
|
var path33 = __toESM(require("path"));
|
|
98837
98935
|
var import_picocolors2 = __toESM(require_picocolors());
|
|
98838
98936
|
init_src();
|
|
98937
|
+
var MASKED = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
98839
98938
|
function registerDiffCommand(program3, deps2) {
|
|
98840
98939
|
program3.command("diff <namespace> <env-a> <env-b>").description(
|
|
98841
98940
|
"Compare secrets between two environments for a namespace.\n\nExit codes:\n 0 No differences\n 1 Differences found"
|
|
98842
|
-
).option("--show-identical", "Include identical keys in the output").option("--show-values", "Show plaintext values instead of masking them").
|
|
98941
|
+
).option("--show-identical", "Include identical keys in the output").option("--show-values", "Show plaintext values instead of masking them").action(
|
|
98843
98942
|
async (namespace, envA, envB, options) => {
|
|
98844
98943
|
try {
|
|
98845
98944
|
const repoRoot = program3.opts().dir || process.cwd();
|
|
@@ -98866,19 +98965,20 @@ function registerDiffCommand(program3, deps2) {
|
|
|
98866
98965
|
formatter.warn("Warning: printing plaintext values for protected environment.");
|
|
98867
98966
|
}
|
|
98868
98967
|
}
|
|
98869
|
-
if (
|
|
98870
|
-
const
|
|
98968
|
+
if (isJsonMode()) {
|
|
98969
|
+
const jsonData = options.showValues ? result : {
|
|
98871
98970
|
...result,
|
|
98872
98971
|
rows: result.rows.map((r) => ({
|
|
98873
98972
|
...r,
|
|
98874
|
-
valueA: r.valueA !== null ?
|
|
98875
|
-
valueB: r.valueB !== null ?
|
|
98973
|
+
valueA: r.valueA !== null ? MASKED : null,
|
|
98974
|
+
valueB: r.valueB !== null ? MASKED : null,
|
|
98876
98975
|
masked: true
|
|
98877
98976
|
}))
|
|
98878
98977
|
};
|
|
98879
|
-
formatter.
|
|
98978
|
+
formatter.json(jsonData);
|
|
98880
98979
|
const hasDiffs2 = result.rows.some((r) => r.status !== "identical");
|
|
98881
98980
|
process.exit(hasDiffs2 ? 1 : 0);
|
|
98981
|
+
return;
|
|
98882
98982
|
}
|
|
98883
98983
|
formatDiffOutput(
|
|
98884
98984
|
result,
|
|
@@ -98898,7 +98998,6 @@ function registerDiffCommand(program3, deps2) {
|
|
|
98898
98998
|
}
|
|
98899
98999
|
);
|
|
98900
99000
|
}
|
|
98901
|
-
var MASKED = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
98902
99001
|
function formatDiffOutput(result, envA, envB, showIdentical, showValues) {
|
|
98903
99002
|
const filteredRows = showIdentical ? result.rows : result.rows.filter((r) => r.status !== "identical");
|
|
98904
99003
|
if (filteredRows.length === 0) {
|
|
@@ -99180,7 +99279,7 @@ async function fetchCheckpoint(config) {
|
|
|
99180
99279
|
}
|
|
99181
99280
|
|
|
99182
99281
|
// package.json
|
|
99183
|
-
var version2 = "0.1.
|
|
99282
|
+
var version2 = "0.1.13-beta.88";
|
|
99184
99283
|
var package_default = {
|
|
99185
99284
|
name: "@clef-sh/cli",
|
|
99186
99285
|
version: version2,
|
|
@@ -99251,7 +99350,7 @@ var package_default = {
|
|
|
99251
99350
|
function registerLintCommand(program3, deps2) {
|
|
99252
99351
|
program3.command("lint").description(
|
|
99253
99352
|
"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"
|
|
99254
|
-
).option("--fix", "Auto-fix safe issues (scaffold missing files)").option("--
|
|
99353
|
+
).option("--fix", "Auto-fix safe issues (scaffold missing files)").option("--push", "Push results as OTLP to CLEF_TELEMETRY_URL").action(async (options) => {
|
|
99255
99354
|
try {
|
|
99256
99355
|
const repoRoot = program3.opts().dir || process.cwd();
|
|
99257
99356
|
const parser = new ManifestParser();
|
|
@@ -99286,8 +99385,8 @@ function registerLintCommand(program3, deps2) {
|
|
|
99286
99385
|
await pushOtlp(payload, config);
|
|
99287
99386
|
formatter.success("Lint results pushed to telemetry endpoint.");
|
|
99288
99387
|
}
|
|
99289
|
-
if (
|
|
99290
|
-
formatter.
|
|
99388
|
+
if (isJsonMode()) {
|
|
99389
|
+
formatter.json(result);
|
|
99291
99390
|
const hasErrors2 = result.issues.some((i) => i.severity === "error");
|
|
99292
99391
|
process.exit(hasErrors2 ? 1 : 0);
|
|
99293
99392
|
return;
|
|
@@ -99396,6 +99495,10 @@ function registerRotateCommand(program3, deps2) {
|
|
|
99396
99495
|
const relativeFile = manifest.file_pattern.replace("{namespace}", namespace).replace("{environment}", environment);
|
|
99397
99496
|
formatter.print(`${sym("working")} Rotating ${namespace}/${environment}...`);
|
|
99398
99497
|
await sopsClient.reEncrypt(filePath, options.newKey);
|
|
99498
|
+
if (isJsonMode()) {
|
|
99499
|
+
formatter.json({ namespace, environment, file: relativeFile, action: "rotated" });
|
|
99500
|
+
return;
|
|
99501
|
+
}
|
|
99399
99502
|
formatter.success(`Rotated. New values encrypted. ${sym("locked")}`);
|
|
99400
99503
|
formatter.hint(
|
|
99401
99504
|
`git add ${relativeFile} && git commit -m "rotate: ${namespace}/${environment}"`
|
|
@@ -99441,15 +99544,28 @@ function registerHooksCommand(program3, deps2) {
|
|
|
99441
99544
|
}
|
|
99442
99545
|
const git = new GitIntegration(deps2.runner);
|
|
99443
99546
|
await git.installPreCommitHook(repoRoot);
|
|
99547
|
+
let mergeDriverOk = false;
|
|
99548
|
+
try {
|
|
99549
|
+
await git.installMergeDriver(repoRoot);
|
|
99550
|
+
mergeDriverOk = true;
|
|
99551
|
+
} catch {
|
|
99552
|
+
}
|
|
99553
|
+
if (isJsonMode()) {
|
|
99554
|
+
formatter.json({
|
|
99555
|
+
preCommitHook: true,
|
|
99556
|
+
mergeDriver: mergeDriverOk,
|
|
99557
|
+
hookPath
|
|
99558
|
+
});
|
|
99559
|
+
return;
|
|
99560
|
+
}
|
|
99444
99561
|
formatter.success("Pre-commit hook installed");
|
|
99445
99562
|
formatter.print(` ${sym("pending")} ${hookPath}`);
|
|
99446
99563
|
formatter.hint(
|
|
99447
99564
|
"Hook checks SOPS metadata on staged .enc files and runs: clef scan --staged"
|
|
99448
99565
|
);
|
|
99449
|
-
|
|
99450
|
-
await git.installMergeDriver(repoRoot);
|
|
99566
|
+
if (mergeDriverOk) {
|
|
99451
99567
|
formatter.success("SOPS merge driver configured");
|
|
99452
|
-
}
|
|
99568
|
+
} else {
|
|
99453
99569
|
formatter.warn("Could not configure SOPS merge driver. Run inside a git repository.");
|
|
99454
99570
|
}
|
|
99455
99571
|
} catch (err) {
|
|
@@ -99748,6 +99864,14 @@ Usage: clef export payments/production --format env`
|
|
|
99748
99864
|
);
|
|
99749
99865
|
try {
|
|
99750
99866
|
const decrypted = await sopsClient.decrypt(filePath);
|
|
99867
|
+
if (isJsonMode()) {
|
|
99868
|
+
const pairs = Object.entries(decrypted.values).map(([k, v]) => ({
|
|
99869
|
+
key: k,
|
|
99870
|
+
value: v
|
|
99871
|
+
}));
|
|
99872
|
+
formatter.json({ pairs, namespace, environment });
|
|
99873
|
+
return;
|
|
99874
|
+
}
|
|
99751
99875
|
const consumption = new ConsumptionClient();
|
|
99752
99876
|
const output = consumption.formatExport(decrypted, "env", !options.export);
|
|
99753
99877
|
if (options.raw) {
|
|
@@ -99788,7 +99912,7 @@ init_src();
|
|
|
99788
99912
|
function registerDoctorCommand(program3, deps2) {
|
|
99789
99913
|
program3.command("doctor").description(
|
|
99790
99914
|
"Check your environment for required dependencies and configuration.\n\nExit codes:\n 0 All checks pass\n 1 One or more checks failed"
|
|
99791
|
-
).option("--
|
|
99915
|
+
).option("--fix", "Attempt to auto-fix issues").action(async (options) => {
|
|
99792
99916
|
const repoRoot = program3.opts().dir || process.cwd();
|
|
99793
99917
|
const clefVersion = program3.version() ?? "unknown";
|
|
99794
99918
|
const checks = [];
|
|
@@ -99875,7 +99999,7 @@ function registerDoctorCommand(program3, deps2) {
|
|
|
99875
99999
|
formatter.warn("--fix cannot resolve these issues automatically.");
|
|
99876
100000
|
}
|
|
99877
100001
|
}
|
|
99878
|
-
if (
|
|
100002
|
+
if (isJsonMode()) {
|
|
99879
100003
|
const json = {
|
|
99880
100004
|
clef: { version: clefVersion, ok: true },
|
|
99881
100005
|
sops: {
|
|
@@ -99905,7 +100029,7 @@ function registerDoctorCommand(program3, deps2) {
|
|
|
99905
100029
|
ok: mergeDriverOk
|
|
99906
100030
|
}
|
|
99907
100031
|
};
|
|
99908
|
-
formatter.
|
|
100032
|
+
formatter.json(json);
|
|
99909
100033
|
const hasFailures = checks.some((c) => !c.ok);
|
|
99910
100034
|
process.exit(hasFailures ? 1 : 0);
|
|
99911
100035
|
return;
|
|
@@ -100039,6 +100163,11 @@ function registerUpdateCommand(program3, deps2) {
|
|
|
100039
100163
|
);
|
|
100040
100164
|
}
|
|
100041
100165
|
}
|
|
100166
|
+
if (isJsonMode()) {
|
|
100167
|
+
formatter.json({ scaffolded: scaffoldedCount, failed: failedCount });
|
|
100168
|
+
process.exit(failedCount > 0 ? 1 : 0);
|
|
100169
|
+
return;
|
|
100170
|
+
}
|
|
100042
100171
|
if (scaffoldedCount > 0) {
|
|
100043
100172
|
formatter.success(`Scaffolded ${scaffoldedCount} encrypted file(s)`);
|
|
100044
100173
|
}
|
|
@@ -100064,69 +100193,57 @@ function registerScanCommand(program3, deps2) {
|
|
|
100064
100193
|
"--severity <level>",
|
|
100065
100194
|
"Detection level: all (patterns+entropy) or high (patterns only)",
|
|
100066
100195
|
"all"
|
|
100067
|
-
).
|
|
100068
|
-
|
|
100069
|
-
|
|
100070
|
-
|
|
100071
|
-
|
|
100072
|
-
|
|
100073
|
-
|
|
100074
|
-
|
|
100075
|
-
|
|
100076
|
-
|
|
100077
|
-
|
|
100078
|
-
formatter.error(err.message);
|
|
100079
|
-
}
|
|
100080
|
-
process.exit(2);
|
|
100081
|
-
return;
|
|
100082
|
-
}
|
|
100083
|
-
if (options.severity && options.severity !== "all" && options.severity !== "high") {
|
|
100084
|
-
formatter.error(`Invalid severity '${options.severity}'. Must be 'all' or 'high'.`);
|
|
100085
|
-
process.exit(2);
|
|
100086
|
-
return;
|
|
100087
|
-
}
|
|
100088
|
-
const severity = options.severity === "high" ? "high" : "all";
|
|
100089
|
-
const scanRunner = new ScanRunner(deps2.runner);
|
|
100090
|
-
if (!options.json) {
|
|
100091
|
-
formatter.print(import_picocolors4.default.dim("Scanning repository for unencrypted secrets..."));
|
|
100092
|
-
}
|
|
100093
|
-
let result;
|
|
100094
|
-
try {
|
|
100095
|
-
result = await scanRunner.scan(repoRoot, manifest, {
|
|
100096
|
-
stagedOnly: options.staged,
|
|
100097
|
-
paths: paths.length > 0 ? paths : void 0,
|
|
100098
|
-
severity
|
|
100099
|
-
});
|
|
100100
|
-
} catch (err) {
|
|
100101
|
-
formatter.error(`Scan failed: ${err.message}`);
|
|
100102
|
-
process.exit(2);
|
|
100103
|
-
return;
|
|
100104
|
-
}
|
|
100105
|
-
if (options.json) {
|
|
100106
|
-
const totalIssues = result.matches.length + result.unencryptedMatrixFiles.length;
|
|
100107
|
-
formatter.raw(
|
|
100108
|
-
JSON.stringify(
|
|
100109
|
-
{
|
|
100110
|
-
matches: result.matches,
|
|
100111
|
-
unencryptedMatrixFiles: result.unencryptedMatrixFiles,
|
|
100112
|
-
filesScanned: result.filesScanned,
|
|
100113
|
-
filesSkipped: result.filesSkipped,
|
|
100114
|
-
durationMs: result.durationMs,
|
|
100115
|
-
summary: `${totalIssues} issue${totalIssues !== 1 ? "s" : ""} found`
|
|
100116
|
-
},
|
|
100117
|
-
null,
|
|
100118
|
-
2
|
|
100119
|
-
) + "\n"
|
|
100120
|
-
);
|
|
100121
|
-
const hasIssues2 = result.matches.length > 0 || result.unencryptedMatrixFiles.length > 0;
|
|
100122
|
-
process.exit(hasIssues2 ? 1 : 0);
|
|
100123
|
-
return;
|
|
100196
|
+
).action(async (paths, options) => {
|
|
100197
|
+
const repoRoot = program3.opts().dir || process.cwd();
|
|
100198
|
+
let manifest;
|
|
100199
|
+
try {
|
|
100200
|
+
const parser = new ManifestParser();
|
|
100201
|
+
manifest = parser.parse(path43.join(repoRoot, "clef.yaml"));
|
|
100202
|
+
} catch (err) {
|
|
100203
|
+
if (err instanceof ManifestValidationError || err.message?.includes("clef.yaml")) {
|
|
100204
|
+
formatter.error("No clef.yaml found. Run 'clef init' to set up this repository.");
|
|
100205
|
+
} else {
|
|
100206
|
+
formatter.error(err.message);
|
|
100124
100207
|
}
|
|
100125
|
-
|
|
100126
|
-
|
|
100127
|
-
process.exit(hasIssues ? 1 : 0);
|
|
100208
|
+
process.exit(2);
|
|
100209
|
+
return;
|
|
100128
100210
|
}
|
|
100129
|
-
|
|
100211
|
+
if (options.severity && options.severity !== "all" && options.severity !== "high") {
|
|
100212
|
+
formatter.error(`Invalid severity '${options.severity}'. Must be 'all' or 'high'.`);
|
|
100213
|
+
process.exit(2);
|
|
100214
|
+
return;
|
|
100215
|
+
}
|
|
100216
|
+
const severity = options.severity === "high" ? "high" : "all";
|
|
100217
|
+
const scanRunner = new ScanRunner(deps2.runner);
|
|
100218
|
+
formatter.print(import_picocolors4.default.dim("Scanning repository for unencrypted secrets..."));
|
|
100219
|
+
let result;
|
|
100220
|
+
try {
|
|
100221
|
+
result = await scanRunner.scan(repoRoot, manifest, {
|
|
100222
|
+
stagedOnly: options.staged,
|
|
100223
|
+
paths: paths.length > 0 ? paths : void 0,
|
|
100224
|
+
severity
|
|
100225
|
+
});
|
|
100226
|
+
} catch (err) {
|
|
100227
|
+
formatter.error(`Scan failed: ${err.message}`);
|
|
100228
|
+
process.exit(2);
|
|
100229
|
+
return;
|
|
100230
|
+
}
|
|
100231
|
+
if (isJsonMode()) {
|
|
100232
|
+
formatter.json({
|
|
100233
|
+
matches: result.matches,
|
|
100234
|
+
unencryptedMatrixFiles: result.unencryptedMatrixFiles,
|
|
100235
|
+
filesScanned: result.filesScanned,
|
|
100236
|
+
filesSkipped: result.filesSkipped,
|
|
100237
|
+
durationMs: result.durationMs
|
|
100238
|
+
});
|
|
100239
|
+
const hasIssues2 = result.matches.length > 0 || result.unencryptedMatrixFiles.length > 0;
|
|
100240
|
+
process.exit(hasIssues2 ? 1 : 0);
|
|
100241
|
+
return;
|
|
100242
|
+
}
|
|
100243
|
+
formatScanOutput(result);
|
|
100244
|
+
const hasIssues = result.matches.length > 0 || result.unencryptedMatrixFiles.length > 0;
|
|
100245
|
+
process.exit(hasIssues ? 1 : 0);
|
|
100246
|
+
});
|
|
100130
100247
|
}
|
|
100131
100248
|
function formatScanOutput(result) {
|
|
100132
100249
|
const totalIssues = result.matches.length + result.unencryptedMatrixFiles.length;
|
|
@@ -100291,6 +100408,17 @@ function registerImportCommand(program3, deps2) {
|
|
|
100291
100408
|
process.exit(2);
|
|
100292
100409
|
return;
|
|
100293
100410
|
}
|
|
100411
|
+
if (isJsonMode()) {
|
|
100412
|
+
formatter.json({
|
|
100413
|
+
imported: result.imported,
|
|
100414
|
+
skipped: result.skipped,
|
|
100415
|
+
failed: result.failed,
|
|
100416
|
+
warnings: result.warnings,
|
|
100417
|
+
dryRun: opts.dryRun
|
|
100418
|
+
});
|
|
100419
|
+
process.exit(result.failed.length > 0 ? 1 : 0);
|
|
100420
|
+
return;
|
|
100421
|
+
}
|
|
100294
100422
|
for (const warning of result.warnings) {
|
|
100295
100423
|
formatter.print(` ${sym("warning")} ${warning}`);
|
|
100296
100424
|
}
|
|
@@ -100350,6 +100478,7 @@ var path45 = __toESM(require("path"));
|
|
|
100350
100478
|
var readline4 = __toESM(require("readline"));
|
|
100351
100479
|
init_src();
|
|
100352
100480
|
function waitForEnter(message) {
|
|
100481
|
+
if (isJsonMode()) return Promise.resolve();
|
|
100353
100482
|
return new Promise((resolve7) => {
|
|
100354
100483
|
const rl = readline4.createInterface({
|
|
100355
100484
|
input: process.stdin,
|
|
@@ -100383,6 +100512,10 @@ function registerRecipientsCommand(program3, deps2) {
|
|
|
100383
100512
|
const sopsClient = await createSopsClient(repoRoot, deps2.runner);
|
|
100384
100513
|
const recipientManager = new RecipientManager(sopsClient, matrixManager);
|
|
100385
100514
|
const recipients = await recipientManager.list(manifest, repoRoot, opts.environment);
|
|
100515
|
+
if (isJsonMode()) {
|
|
100516
|
+
formatter.json(recipients);
|
|
100517
|
+
return;
|
|
100518
|
+
}
|
|
100386
100519
|
if (recipients.length === 0) {
|
|
100387
100520
|
const scope2 = opts.environment ? ` for environment '${opts.environment}'` : "";
|
|
100388
100521
|
formatter.info(`No recipients configured${scope2}.`);
|
|
@@ -100413,7 +100546,7 @@ function registerRecipientsCommand(program3, deps2) {
|
|
|
100413
100546
|
return;
|
|
100414
100547
|
}
|
|
100415
100548
|
const normalizedKey = validation.key;
|
|
100416
|
-
const
|
|
100549
|
+
const result = await executeRecipientAdd(
|
|
100417
100550
|
repoRoot,
|
|
100418
100551
|
program3,
|
|
100419
100552
|
deps2,
|
|
@@ -100421,11 +100554,21 @@ function registerRecipientsCommand(program3, deps2) {
|
|
|
100421
100554
|
opts.label,
|
|
100422
100555
|
opts.environment
|
|
100423
100556
|
);
|
|
100424
|
-
if (
|
|
100425
|
-
|
|
100426
|
-
|
|
100427
|
-
|
|
100428
|
-
|
|
100557
|
+
if (result) {
|
|
100558
|
+
if (isJsonMode()) {
|
|
100559
|
+
formatter.json({
|
|
100560
|
+
action: "added",
|
|
100561
|
+
key: normalizedKey,
|
|
100562
|
+
label: opts.label || keyPreview(normalizedKey),
|
|
100563
|
+
environment: opts.environment,
|
|
100564
|
+
reEncryptedFiles: result.reEncryptedFiles.length
|
|
100565
|
+
});
|
|
100566
|
+
} else {
|
|
100567
|
+
const label = opts.label || keyPreview(normalizedKey);
|
|
100568
|
+
formatter.hint(
|
|
100569
|
+
`git add clef.yaml && git add -A && git commit -m "add recipient: ${label} [${opts.environment}]"`
|
|
100570
|
+
);
|
|
100571
|
+
}
|
|
100429
100572
|
}
|
|
100430
100573
|
} catch (err) {
|
|
100431
100574
|
handleCommandError(err);
|
|
@@ -100521,6 +100664,16 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`
|
|
|
100521
100664
|
const relative7 = path45.relative(repoRoot, file);
|
|
100522
100665
|
formatter.print(` ${sym("success")} ${relative7}`);
|
|
100523
100666
|
}
|
|
100667
|
+
if (isJsonMode()) {
|
|
100668
|
+
formatter.json({
|
|
100669
|
+
action: "removed",
|
|
100670
|
+
key: trimmedKey,
|
|
100671
|
+
label,
|
|
100672
|
+
environment: opts.environment ?? null,
|
|
100673
|
+
reEncryptedFiles: result.reEncryptedFiles.length
|
|
100674
|
+
});
|
|
100675
|
+
return;
|
|
100676
|
+
}
|
|
100524
100677
|
formatter.success(
|
|
100525
100678
|
`${label} removed. ${result.reEncryptedFiles.length} files re-encrypted. ${sym("locked")}`
|
|
100526
100679
|
);
|
|
@@ -100585,6 +100738,15 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`
|
|
|
100585
100738
|
return;
|
|
100586
100739
|
}
|
|
100587
100740
|
upsertRequest(repoRoot, publicKey, label, opts.environment);
|
|
100741
|
+
if (isJsonMode()) {
|
|
100742
|
+
formatter.json({
|
|
100743
|
+
action: "requested",
|
|
100744
|
+
label,
|
|
100745
|
+
key: publicKey,
|
|
100746
|
+
environment: opts.environment ?? null
|
|
100747
|
+
});
|
|
100748
|
+
return;
|
|
100749
|
+
}
|
|
100588
100750
|
const scope = opts.environment ? ` for environment '${opts.environment}'` : "";
|
|
100589
100751
|
formatter.success(`Access requested as '${label}'${scope}`);
|
|
100590
100752
|
formatter.print(` Key: ${keyPreview(publicKey)}`);
|
|
@@ -100600,6 +100762,15 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`
|
|
|
100600
100762
|
try {
|
|
100601
100763
|
const repoRoot = program3.opts().dir || process.cwd();
|
|
100602
100764
|
const requests = loadRequests(repoRoot);
|
|
100765
|
+
if (isJsonMode()) {
|
|
100766
|
+
formatter.json(
|
|
100767
|
+
requests.map((r) => ({
|
|
100768
|
+
...r,
|
|
100769
|
+
requestedAt: r.requestedAt.toISOString()
|
|
100770
|
+
}))
|
|
100771
|
+
);
|
|
100772
|
+
return;
|
|
100773
|
+
}
|
|
100603
100774
|
if (requests.length === 0) {
|
|
100604
100775
|
formatter.info("No pending access requests.");
|
|
100605
100776
|
return;
|
|
@@ -100639,7 +100810,7 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`
|
|
|
100639
100810
|
process.exit(2);
|
|
100640
100811
|
return;
|
|
100641
100812
|
}
|
|
100642
|
-
const
|
|
100813
|
+
const result = await executeRecipientAdd(
|
|
100643
100814
|
repoRoot,
|
|
100644
100815
|
program3,
|
|
100645
100816
|
deps2,
|
|
@@ -100647,11 +100818,21 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`
|
|
|
100647
100818
|
request.label,
|
|
100648
100819
|
environment
|
|
100649
100820
|
);
|
|
100650
|
-
if (
|
|
100821
|
+
if (result) {
|
|
100651
100822
|
removeRequest(repoRoot, identifier);
|
|
100652
|
-
|
|
100653
|
-
|
|
100654
|
-
|
|
100823
|
+
if (isJsonMode()) {
|
|
100824
|
+
formatter.json({
|
|
100825
|
+
action: "approved",
|
|
100826
|
+
identifier,
|
|
100827
|
+
label: request.label,
|
|
100828
|
+
environment,
|
|
100829
|
+
reEncryptedFiles: result.reEncryptedFiles.length
|
|
100830
|
+
});
|
|
100831
|
+
} else {
|
|
100832
|
+
formatter.hint(
|
|
100833
|
+
`git add clef.yaml ${REQUESTS_FILENAME} && git add -A && git commit -m "approve recipient: ${request.label} [${environment}]"`
|
|
100834
|
+
);
|
|
100835
|
+
}
|
|
100655
100836
|
}
|
|
100656
100837
|
} catch (err) {
|
|
100657
100838
|
handleCommandError(err);
|
|
@@ -100667,7 +100848,7 @@ async function executeRecipientAdd(repoRoot, _program, deps2, key, label, enviro
|
|
|
100667
100848
|
`Environment '${environment}' not found. Available: ${manifest.environments.map((e) => e.name).join(", ")}`
|
|
100668
100849
|
);
|
|
100669
100850
|
process.exit(2);
|
|
100670
|
-
return
|
|
100851
|
+
return null;
|
|
100671
100852
|
}
|
|
100672
100853
|
const matrixManager = new MatrixManager();
|
|
100673
100854
|
const sopsClient = await createSopsClient(repoRoot, deps2.runner);
|
|
@@ -100676,7 +100857,7 @@ async function executeRecipientAdd(repoRoot, _program, deps2, key, label, enviro
|
|
|
100676
100857
|
if (existing.some((r) => r.key === key)) {
|
|
100677
100858
|
formatter.error(`Recipient '${keyPreview(key)}' is already present.`);
|
|
100678
100859
|
process.exit(2);
|
|
100679
|
-
return
|
|
100860
|
+
return null;
|
|
100680
100861
|
}
|
|
100681
100862
|
const allCells = matrixManager.resolveMatrix(manifest, repoRoot).filter((c) => c.exists && c.environment === environment);
|
|
100682
100863
|
const fileCount = allCells.length;
|
|
@@ -100693,7 +100874,7 @@ This will re-encrypt ${fileCount} files in the matrix.`);
|
|
|
100693
100874
|
const confirmed = await formatter.confirm("Proceed?");
|
|
100694
100875
|
if (!confirmed) {
|
|
100695
100876
|
formatter.info("Aborted.");
|
|
100696
|
-
return
|
|
100877
|
+
return null;
|
|
100697
100878
|
}
|
|
100698
100879
|
formatter.print(`
|
|
100699
100880
|
${sym("working")} Re-encrypting matrix...`);
|
|
@@ -100710,7 +100891,7 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`);
|
|
|
100710
100891
|
);
|
|
100711
100892
|
formatter.print("\nNo changes were applied. Investigate the error above and retry.");
|
|
100712
100893
|
process.exit(1);
|
|
100713
|
-
return
|
|
100894
|
+
return null;
|
|
100714
100895
|
}
|
|
100715
100896
|
for (const file of result.reEncryptedFiles) {
|
|
100716
100897
|
const relative7 = path45.relative(repoRoot, file);
|
|
@@ -100720,7 +100901,7 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`);
|
|
|
100720
100901
|
formatter.success(
|
|
100721
100902
|
`${displayLabel} added. ${result.reEncryptedFiles.length} files re-encrypted. ${sym("locked")}`
|
|
100722
100903
|
);
|
|
100723
|
-
return
|
|
100904
|
+
return { reEncryptedFiles: result.reEncryptedFiles };
|
|
100724
100905
|
}
|
|
100725
100906
|
|
|
100726
100907
|
// src/commands/merge-driver.ts
|
|
@@ -100883,6 +101064,16 @@ function registerServiceCommand(program3, deps2) {
|
|
|
100883
101064
|
repoRoot,
|
|
100884
101065
|
kmsEnvConfigs
|
|
100885
101066
|
);
|
|
101067
|
+
if (isJsonMode()) {
|
|
101068
|
+
formatter.json({
|
|
101069
|
+
action: "created",
|
|
101070
|
+
identity: result.identity.name,
|
|
101071
|
+
namespaces: result.identity.namespaces,
|
|
101072
|
+
environments: Object.keys(result.identity.environments),
|
|
101073
|
+
privateKeys: result.privateKeys
|
|
101074
|
+
});
|
|
101075
|
+
return;
|
|
101076
|
+
}
|
|
100886
101077
|
formatter.success(`Service identity '${name}' created.`);
|
|
100887
101078
|
formatter.print(`
|
|
100888
101079
|
Namespaces: ${result.identity.namespaces.join(", ")}`);
|
|
@@ -100936,6 +101127,10 @@ function registerServiceCommand(program3, deps2) {
|
|
|
100936
101127
|
const sopsClient = await createSopsClient(repoRoot, deps2.runner);
|
|
100937
101128
|
const manager = new ServiceIdentityManager(sopsClient, matrixManager);
|
|
100938
101129
|
const identities = manager.list(manifest);
|
|
101130
|
+
if (isJsonMode()) {
|
|
101131
|
+
formatter.json(identities);
|
|
101132
|
+
return;
|
|
101133
|
+
}
|
|
100939
101134
|
if (identities.length === 0) {
|
|
100940
101135
|
formatter.info("No service identities configured.");
|
|
100941
101136
|
return;
|
|
@@ -100965,6 +101160,10 @@ function registerServiceCommand(program3, deps2) {
|
|
|
100965
101160
|
process.exit(1);
|
|
100966
101161
|
return;
|
|
100967
101162
|
}
|
|
101163
|
+
if (isJsonMode()) {
|
|
101164
|
+
formatter.json(identity);
|
|
101165
|
+
return;
|
|
101166
|
+
}
|
|
100968
101167
|
formatter.print(`
|
|
100969
101168
|
Service Identity: ${identity.name}`);
|
|
100970
101169
|
formatter.print(`Description: ${identity.description}`);
|
|
@@ -100993,6 +101192,11 @@ Service Identity: ${identity.name}`);
|
|
|
100993
101192
|
const sopsClient = await createSopsClient(repoRoot, deps2.runner);
|
|
100994
101193
|
const manager = new ServiceIdentityManager(sopsClient, matrixManager);
|
|
100995
101194
|
const issues = await manager.validate(manifest, repoRoot);
|
|
101195
|
+
if (isJsonMode()) {
|
|
101196
|
+
formatter.json({ issues });
|
|
101197
|
+
process.exit(issues.length > 0 ? 1 : 0);
|
|
101198
|
+
return;
|
|
101199
|
+
}
|
|
100996
101200
|
if (issues.length === 0) {
|
|
100997
101201
|
formatter.success("All service identities are valid.");
|
|
100998
101202
|
return;
|
|
@@ -101043,6 +101247,17 @@ Service Identity: ${identity.name}`);
|
|
|
101043
101247
|
const manager = new ServiceIdentityManager(sopsClient, matrixManager);
|
|
101044
101248
|
formatter.print(`${sym("working")} Updating service identity '${name}'...`);
|
|
101045
101249
|
await manager.updateEnvironments(name, kmsEnvConfigs, manifest, repoRoot);
|
|
101250
|
+
if (isJsonMode()) {
|
|
101251
|
+
formatter.json({
|
|
101252
|
+
action: "updated",
|
|
101253
|
+
identity: name,
|
|
101254
|
+
changed: Object.entries(kmsEnvConfigs).map(([env, cfg]) => ({
|
|
101255
|
+
environment: env,
|
|
101256
|
+
provider: cfg.provider
|
|
101257
|
+
}))
|
|
101258
|
+
});
|
|
101259
|
+
return;
|
|
101260
|
+
}
|
|
101046
101261
|
formatter.success(`Service identity '${name}' updated.`);
|
|
101047
101262
|
for (const [envName, kmsConfig] of Object.entries(kmsEnvConfigs)) {
|
|
101048
101263
|
formatter.print(` ${envName}: switched to KMS envelope (${kmsConfig.provider})`);
|
|
@@ -101078,6 +101293,10 @@ Service Identity: ${identity.name}`);
|
|
|
101078
101293
|
const manager = new ServiceIdentityManager(sopsClient, matrixManager);
|
|
101079
101294
|
formatter.print(`${sym("working")} Deleting service identity '${name}'...`);
|
|
101080
101295
|
await manager.delete(name, manifest, repoRoot);
|
|
101296
|
+
if (isJsonMode()) {
|
|
101297
|
+
formatter.json({ action: "deleted", identity: name });
|
|
101298
|
+
return;
|
|
101299
|
+
}
|
|
101081
101300
|
formatter.success(`Service identity '${name}' deleted.`);
|
|
101082
101301
|
formatter.hint(
|
|
101083
101302
|
`git add clef.yaml && git commit -m "chore: delete service identity '${name}'"`
|
|
@@ -101114,6 +101333,15 @@ Service Identity: ${identity.name}`);
|
|
|
101114
101333
|
}
|
|
101115
101334
|
formatter.print(`${sym("working")} Rotating key for '${name}'...`);
|
|
101116
101335
|
const newKeys = await manager.rotateKey(name, manifest, repoRoot, opts.environment);
|
|
101336
|
+
if (isJsonMode()) {
|
|
101337
|
+
formatter.json({
|
|
101338
|
+
action: "rotated",
|
|
101339
|
+
identity: name,
|
|
101340
|
+
environments: Object.keys(newKeys),
|
|
101341
|
+
privateKeys: newKeys
|
|
101342
|
+
});
|
|
101343
|
+
return;
|
|
101344
|
+
}
|
|
101117
101345
|
formatter.success(`Key rotated for '${name}'.`);
|
|
101118
101346
|
const entries = Object.entries(newKeys);
|
|
101119
101347
|
const block = entries.map(([env, key]) => `${env}: ${key}`).join("\n");
|
|
@@ -101289,6 +101517,18 @@ function registerPackCommand(program3, deps2) {
|
|
|
101289
101517
|
const fileOut = new FilePackOutput(outputPath);
|
|
101290
101518
|
await fileOut.write(memOutput.artifact, memOutput.json);
|
|
101291
101519
|
}
|
|
101520
|
+
if (isJsonMode()) {
|
|
101521
|
+
formatter.json({
|
|
101522
|
+
identity,
|
|
101523
|
+
environment,
|
|
101524
|
+
keyCount: result.keyCount,
|
|
101525
|
+
namespaceCount: result.namespaceCount,
|
|
101526
|
+
artifactSize: result.artifactSize,
|
|
101527
|
+
revision: result.revision,
|
|
101528
|
+
output: outputPath ?? null
|
|
101529
|
+
});
|
|
101530
|
+
return;
|
|
101531
|
+
}
|
|
101292
101532
|
formatter.success(
|
|
101293
101533
|
`Artifact packed: ${result.keyCount} keys from ${result.namespaceCount} namespace(s).`
|
|
101294
101534
|
);
|
|
@@ -101366,6 +101606,15 @@ function registerRevokeCommand(program3, _deps) {
|
|
|
101366
101606
|
fs32.mkdirSync(artifactDir, { recursive: true });
|
|
101367
101607
|
fs32.writeFileSync(artifactPath, JSON.stringify(revoked, null, 2) + "\n", "utf-8");
|
|
101368
101608
|
const relPath = path49.relative(repoRoot, artifactPath);
|
|
101609
|
+
if (isJsonMode()) {
|
|
101610
|
+
formatter.json({
|
|
101611
|
+
identity,
|
|
101612
|
+
environment,
|
|
101613
|
+
revokedAt: revoked.revokedAt,
|
|
101614
|
+
markerPath: relPath
|
|
101615
|
+
});
|
|
101616
|
+
return;
|
|
101617
|
+
}
|
|
101369
101618
|
formatter.success(`Artifact revoked: ${relPath}`);
|
|
101370
101619
|
formatter.print("");
|
|
101371
101620
|
formatter.print(`${sym("arrow")} If your agent fetches artifacts from git (VCS source):`);
|
|
@@ -101394,37 +101643,35 @@ init_src();
|
|
|
101394
101643
|
function registerDriftCommand(program3, _deps) {
|
|
101395
101644
|
program3.command("drift <path>").description(
|
|
101396
101645
|
"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"
|
|
101397
|
-
).option("--
|
|
101398
|
-
|
|
101399
|
-
|
|
101400
|
-
|
|
101401
|
-
|
|
101402
|
-
|
|
101403
|
-
|
|
101404
|
-
|
|
101405
|
-
|
|
101406
|
-
|
|
101407
|
-
|
|
101408
|
-
process.exit(1);
|
|
101409
|
-
return;
|
|
101410
|
-
}
|
|
101411
|
-
const payload = driftResultToOtlp(result, version2);
|
|
101412
|
-
await pushOtlp(payload, config);
|
|
101413
|
-
formatter.success("Drift results pushed to telemetry endpoint.");
|
|
101414
|
-
}
|
|
101415
|
-
if (options.json) {
|
|
101416
|
-
formatter.raw(JSON.stringify(result, null, 2) + "\n");
|
|
101417
|
-
process.exit(result.issues.length > 0 ? 1 : 0);
|
|
101646
|
+
).option("--push", "Push results as OTLP to CLEF_TELEMETRY_URL").option("--namespace <name...>", "Scope to specific namespace(s)").action(async (remotePath, options) => {
|
|
101647
|
+
try {
|
|
101648
|
+
const localRoot = program3.opts().dir || process.cwd();
|
|
101649
|
+
const remoteRoot = path50.resolve(localRoot, remotePath);
|
|
101650
|
+
const detector = new DriftDetector();
|
|
101651
|
+
const result = detector.detect(localRoot, remoteRoot, options.namespace);
|
|
101652
|
+
if (options.push) {
|
|
101653
|
+
const config = resolveTelemetryConfig();
|
|
101654
|
+
if (!config) {
|
|
101655
|
+
formatter.error("--push requires CLEF_TELEMETRY_URL to be set.");
|
|
101656
|
+
process.exit(1);
|
|
101418
101657
|
return;
|
|
101419
101658
|
}
|
|
101420
|
-
|
|
101659
|
+
const payload = driftResultToOtlp(result, version2);
|
|
101660
|
+
await pushOtlp(payload, config);
|
|
101661
|
+
formatter.success("Drift results pushed to telemetry endpoint.");
|
|
101662
|
+
}
|
|
101663
|
+
if (isJsonMode()) {
|
|
101664
|
+
formatter.json(result);
|
|
101421
101665
|
process.exit(result.issues.length > 0 ? 1 : 0);
|
|
101422
|
-
|
|
101423
|
-
formatter.error(err.message);
|
|
101424
|
-
process.exit(1);
|
|
101666
|
+
return;
|
|
101425
101667
|
}
|
|
101668
|
+
formatDriftOutput(result);
|
|
101669
|
+
process.exit(result.issues.length > 0 ? 1 : 0);
|
|
101670
|
+
} catch (err) {
|
|
101671
|
+
formatter.error(err.message);
|
|
101672
|
+
process.exit(1);
|
|
101426
101673
|
}
|
|
101427
|
-
);
|
|
101674
|
+
});
|
|
101428
101675
|
}
|
|
101429
101676
|
function formatDriftOutput(result) {
|
|
101430
101677
|
if (result.namespacesCompared === 0) {
|
|
@@ -101517,7 +101764,7 @@ async function getHeadSha(repoRoot, runner2) {
|
|
|
101517
101764
|
function registerReportCommand(program3, deps2) {
|
|
101518
101765
|
program3.command("report").description(
|
|
101519
101766
|
"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"
|
|
101520
|
-
).option("--
|
|
101767
|
+
).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(
|
|
101521
101768
|
async (options) => {
|
|
101522
101769
|
try {
|
|
101523
101770
|
const repoRoot = program3.opts().dir || process.cwd();
|
|
@@ -101529,7 +101776,7 @@ function registerReportCommand(program3, deps2) {
|
|
|
101529
101776
|
deps2.runner
|
|
101530
101777
|
);
|
|
101531
101778
|
await maybePush(report, options.push);
|
|
101532
|
-
outputReport(report
|
|
101779
|
+
outputReport(report);
|
|
101533
101780
|
return;
|
|
101534
101781
|
}
|
|
101535
101782
|
const sopsClient = await createSopsClient(repoRoot, deps2.runner);
|
|
@@ -101547,7 +101794,7 @@ function registerReportCommand(program3, deps2) {
|
|
|
101547
101794
|
});
|
|
101548
101795
|
if (options.push) {
|
|
101549
101796
|
await pushWithGapFill(repoRoot, headReport, deps2.runner);
|
|
101550
|
-
outputReport(headReport
|
|
101797
|
+
outputReport(headReport);
|
|
101551
101798
|
return;
|
|
101552
101799
|
}
|
|
101553
101800
|
if (options.since) {
|
|
@@ -101561,8 +101808,8 @@ function registerReportCommand(program3, deps2) {
|
|
|
101561
101808
|
reports.push(await generateReportAtCommit(repoRoot, sha, version2, deps2.runner));
|
|
101562
101809
|
}
|
|
101563
101810
|
}
|
|
101564
|
-
if (
|
|
101565
|
-
formatter.
|
|
101811
|
+
if (isJsonMode()) {
|
|
101812
|
+
formatter.json(reports);
|
|
101566
101813
|
} else {
|
|
101567
101814
|
formatter.print(
|
|
101568
101815
|
`Generated ${reports.length} report(s) for commits since ${options.since.slice(0, 8)}`
|
|
@@ -101577,7 +101824,7 @@ function registerReportCommand(program3, deps2) {
|
|
|
101577
101824
|
process.exit(reports.some((r) => r.policy.issueCount.error > 0) ? 1 : 0);
|
|
101578
101825
|
return;
|
|
101579
101826
|
}
|
|
101580
|
-
outputReport(headReport
|
|
101827
|
+
outputReport(headReport);
|
|
101581
101828
|
} catch (err) {
|
|
101582
101829
|
handleCommandError(err);
|
|
101583
101830
|
}
|
|
@@ -101628,9 +101875,9 @@ async function maybePush(report, push) {
|
|
|
101628
101875
|
await pushOtlp(payload, config);
|
|
101629
101876
|
formatter.success("Report pushed to telemetry endpoint.");
|
|
101630
101877
|
}
|
|
101631
|
-
function outputReport(report
|
|
101632
|
-
if (
|
|
101633
|
-
formatter.
|
|
101878
|
+
function outputReport(report) {
|
|
101879
|
+
if (isJsonMode()) {
|
|
101880
|
+
formatter.json(report);
|
|
101634
101881
|
process.exit(report.policy.issueCount.error > 0 ? 1 : 0);
|
|
101635
101882
|
return;
|
|
101636
101883
|
}
|
|
@@ -101799,6 +102046,16 @@ function registerInstallCommand(program3, _deps) {
|
|
|
101799
102046
|
}
|
|
101800
102047
|
const manifestFile = files.find((f2) => f2.name === "broker.yaml");
|
|
101801
102048
|
const manifest = manifestFile ? (0, import_yaml.parse)(manifestFile.content) : {};
|
|
102049
|
+
if (isJsonMode()) {
|
|
102050
|
+
formatter.json({
|
|
102051
|
+
broker: entry.name,
|
|
102052
|
+
provider: entry.provider,
|
|
102053
|
+
tier: entry.tier,
|
|
102054
|
+
files: files.map((f2) => `brokers/${entry.name}/${f2.name}`)
|
|
102055
|
+
});
|
|
102056
|
+
process.exit(0);
|
|
102057
|
+
return;
|
|
102058
|
+
}
|
|
101802
102059
|
formatter.print("");
|
|
101803
102060
|
formatter.print(` ${sym("success")} ${entry.name}`);
|
|
101804
102061
|
formatter.print("");
|
|
@@ -101862,6 +102119,11 @@ function registerSearchCommand(program3, _deps) {
|
|
|
101862
102119
|
if (options.tier) {
|
|
101863
102120
|
results = results.filter((b) => b.tier === Number(options.tier));
|
|
101864
102121
|
}
|
|
102122
|
+
if (isJsonMode()) {
|
|
102123
|
+
formatter.json(results);
|
|
102124
|
+
process.exit(0);
|
|
102125
|
+
return;
|
|
102126
|
+
}
|
|
101865
102127
|
if (results.length === 0) {
|
|
101866
102128
|
formatter.info("No brokers found matching your query.");
|
|
101867
102129
|
process.exit(0);
|
|
@@ -101992,6 +102254,20 @@ ${sym("working")} Backend migration summary:`);
|
|
|
101992
102254
|
}
|
|
101993
102255
|
}
|
|
101994
102256
|
);
|
|
102257
|
+
if (isJsonMode()) {
|
|
102258
|
+
formatter.json({
|
|
102259
|
+
backend: target.backend,
|
|
102260
|
+
migratedFiles: result.migratedFiles,
|
|
102261
|
+
skippedFiles: result.skippedFiles,
|
|
102262
|
+
verifiedFiles: result.verifiedFiles,
|
|
102263
|
+
warnings: result.warnings,
|
|
102264
|
+
rolledBack: result.rolledBack,
|
|
102265
|
+
error: result.error ?? null,
|
|
102266
|
+
dryRun: opts.dryRun ?? false
|
|
102267
|
+
});
|
|
102268
|
+
process.exit(result.rolledBack ? 1 : 0);
|
|
102269
|
+
return;
|
|
102270
|
+
}
|
|
101995
102271
|
if (result.rolledBack) {
|
|
101996
102272
|
formatter.error(`Migration failed: ${result.error}`);
|
|
101997
102273
|
formatter.info("All changes have been rolled back.");
|
|
@@ -102142,12 +102418,18 @@ var VERSION = package_default.version;
|
|
|
102142
102418
|
var program2 = new Command();
|
|
102143
102419
|
var runner = new NodeSubprocessRunner();
|
|
102144
102420
|
var deps = { runner };
|
|
102145
|
-
program2.name("clef").option("--dir <path>", "Path to a local Clef repository root (default: current directory)").option("--plain", "Plain output, no emoji or colour");
|
|
102421
|
+
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)");
|
|
102146
102422
|
program2.hook("preAction", async () => {
|
|
102147
102423
|
const opts = program2.opts();
|
|
102148
102424
|
if (opts.plain) {
|
|
102149
102425
|
setPlainMode(true);
|
|
102150
102426
|
}
|
|
102427
|
+
if (opts.json) {
|
|
102428
|
+
setJsonMode(true);
|
|
102429
|
+
}
|
|
102430
|
+
if (opts.yes) {
|
|
102431
|
+
setYesMode(true);
|
|
102432
|
+
}
|
|
102151
102433
|
});
|
|
102152
102434
|
program2.addHelpText("beforeAll", () => {
|
|
102153
102435
|
const clef = isPlainMode() ? "clef" : symbols.clef;
|
|
@@ -102246,6 +102528,9 @@ async function main() {
|
|
|
102246
102528
|
}
|
|
102247
102529
|
}
|
|
102248
102530
|
main().catch((err) => {
|
|
102531
|
+
if (isJsonMode()) {
|
|
102532
|
+
exitJsonError(err.message);
|
|
102533
|
+
}
|
|
102249
102534
|
formatter.error(err.message);
|
|
102250
102535
|
process.exit(1);
|
|
102251
102536
|
});
|