@clef-sh/cli 0.1.12 → 0.1.13-beta.92

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 CHANGED
@@ -21959,7 +21959,12 @@ var VALID_KMS_PROVIDERS;
21959
21959
  var init_types2 = __esm({
21960
21960
  "../core/src/kms/types.ts"() {
21961
21961
  "use strict";
21962
- VALID_KMS_PROVIDERS = ["aws", "gcp", "azure"];
21962
+ VALID_KMS_PROVIDERS = [
21963
+ "aws",
21964
+ "gcp",
21965
+ "azure",
21966
+ "cloud"
21967
+ ];
21963
21968
  }
21964
21969
  });
21965
21970
 
@@ -94826,10 +94831,177 @@ var require_azure = __commonJS({
94826
94831
  }
94827
94832
  });
94828
94833
 
94829
- // ../runtime/dist/kms/index.js
94834
+ // ../client/dist/kms.js
94830
94835
  var require_kms = __commonJS({
94836
+ "../client/dist/kms.js"(exports2, module2) {
94837
+ "use strict";
94838
+ var __defProp2 = Object.defineProperty;
94839
+ var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
94840
+ var __getOwnPropNames2 = Object.getOwnPropertyNames;
94841
+ var __hasOwnProp2 = Object.prototype.hasOwnProperty;
94842
+ var __export2 = (target, all) => {
94843
+ for (var name in all)
94844
+ __defProp2(target, name, { get: all[name], enumerable: true });
94845
+ };
94846
+ var __copyProps2 = (to, from, except, desc) => {
94847
+ if (from && typeof from === "object" || typeof from === "function") {
94848
+ for (let key of __getOwnPropNames2(from))
94849
+ if (!__hasOwnProp2.call(to, key) && key !== except)
94850
+ __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
94851
+ }
94852
+ return to;
94853
+ };
94854
+ var __toCommonJS = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
94855
+ var kms_exports = {};
94856
+ __export2(kms_exports, {
94857
+ ClefClientError: () => ClefClientError,
94858
+ CloudKmsProvider: () => CloudKmsProvider
94859
+ });
94860
+ module2.exports = __toCommonJS(kms_exports);
94861
+ var ClefClientError = class extends Error {
94862
+ constructor(message, statusCode, fix) {
94863
+ super(message);
94864
+ this.statusCode = statusCode;
94865
+ this.fix = fix;
94866
+ this.name = "ClefClientError";
94867
+ }
94868
+ statusCode;
94869
+ fix;
94870
+ };
94871
+ function resolveToken(explicit) {
94872
+ if (explicit) return explicit;
94873
+ if (typeof process !== "undefined" && process.env?.CLEF_SERVICE_TOKEN) {
94874
+ return process.env.CLEF_SERVICE_TOKEN;
94875
+ }
94876
+ throw new ClefClientError(
94877
+ "No service token configured",
94878
+ void 0,
94879
+ "Set CLEF_SERVICE_TOKEN or pass token in options."
94880
+ );
94881
+ }
94882
+ async function request(baseUrl, opts) {
94883
+ const url = `${baseUrl}${opts.path}`;
94884
+ const headers = {
94885
+ Authorization: `Bearer ${opts.token}`,
94886
+ Accept: "application/json"
94887
+ };
94888
+ if (opts.body !== void 0) {
94889
+ headers["Content-Type"] = "application/json";
94890
+ }
94891
+ const init = {
94892
+ method: opts.method,
94893
+ headers,
94894
+ body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
94895
+ };
94896
+ let response;
94897
+ try {
94898
+ response = await opts.fetchFn(url, init);
94899
+ } catch (err) {
94900
+ try {
94901
+ response = await opts.fetchFn(url, init);
94902
+ } catch {
94903
+ throw new ClefClientError(
94904
+ `Connection failed: ${err.message}`,
94905
+ void 0,
94906
+ "Is the endpoint reachable? Check your CLEF_ENDPOINT setting."
94907
+ );
94908
+ }
94909
+ }
94910
+ if (response.status >= 500) {
94911
+ response = await opts.fetchFn(url, init);
94912
+ }
94913
+ if (response.status === 401) {
94914
+ throw new ClefClientError("Authentication failed", 401, "Check your CLEF_SERVICE_TOKEN.");
94915
+ }
94916
+ if (response.status === 503) {
94917
+ throw new ClefClientError("Secrets expired or not loaded", 503, "Check the agent logs.");
94918
+ }
94919
+ if (!response.ok) {
94920
+ const text = await response.text().catch(() => "");
94921
+ throw new ClefClientError(
94922
+ `HTTP ${response.status}: ${text || response.statusText}`,
94923
+ response.status
94924
+ );
94925
+ }
94926
+ const json = await response.json();
94927
+ if (json && typeof json === "object" && "success" in json) {
94928
+ if (!json.success) {
94929
+ throw new ClefClientError(json.message || "Request failed", response.status);
94930
+ }
94931
+ return json.data;
94932
+ }
94933
+ return json;
94934
+ }
94935
+ var CloudKmsProvider = class {
94936
+ endpoint;
94937
+ token;
94938
+ constructor(options) {
94939
+ this.endpoint = options.endpoint;
94940
+ this.token = resolveToken(options.token);
94941
+ }
94942
+ async wrap(_keyId, _plaintext) {
94943
+ throw new ClefClientError(
94944
+ "CloudKmsProvider.wrap() is not supported. Use the keyservice sidecar for encryption."
94945
+ );
94946
+ }
94947
+ async unwrap(keyId, wrappedKey, _algorithm) {
94948
+ const result = await request(this.endpoint, {
94949
+ method: "POST",
94950
+ path: "/api/v1/cloud/kms/decrypt",
94951
+ body: {
94952
+ keyArn: keyId,
94953
+ ciphertext: wrappedKey.toString("base64")
94954
+ },
94955
+ token: this.token,
94956
+ fetchFn: globalThis.fetch
94957
+ });
94958
+ return Buffer.from(result.plaintext, "base64");
94959
+ }
94960
+ };
94961
+ }
94962
+ });
94963
+
94964
+ // ../runtime/dist/kms/index.js
94965
+ var require_kms2 = __commonJS({
94831
94966
  "../runtime/dist/kms/index.js"(exports2) {
94832
94967
  "use strict";
94968
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
94969
+ if (k2 === void 0) k2 = k;
94970
+ var desc = Object.getOwnPropertyDescriptor(m, k);
94971
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
94972
+ desc = { enumerable: true, get: function() {
94973
+ return m[k];
94974
+ } };
94975
+ }
94976
+ Object.defineProperty(o, k2, desc);
94977
+ }) : (function(o, m, k, k2) {
94978
+ if (k2 === void 0) k2 = k;
94979
+ o[k2] = m[k];
94980
+ }));
94981
+ var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? (function(o, v) {
94982
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
94983
+ }) : function(o, v) {
94984
+ o["default"] = v;
94985
+ });
94986
+ var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ (function() {
94987
+ var ownKeys = function(o) {
94988
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
94989
+ var ar = [];
94990
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
94991
+ return ar;
94992
+ };
94993
+ return ownKeys(o);
94994
+ };
94995
+ return function(mod) {
94996
+ if (mod && mod.__esModule) return mod;
94997
+ var result = {};
94998
+ if (mod != null) {
94999
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
95000
+ }
95001
+ __setModuleDefault(result, mod);
95002
+ return result;
95003
+ };
95004
+ })();
94833
95005
  Object.defineProperty(exports2, "__esModule", { value: true });
94834
95006
  exports2.AzureKmsProvider = exports2.GcpKmsProvider = exports2.AwsKmsProvider = void 0;
94835
95007
  exports2.createKmsProvider = createKmsProvider;
@@ -94848,7 +95020,7 @@ var require_kms = __commonJS({
94848
95020
  Object.defineProperty(exports2, "AzureKmsProvider", { enumerable: true, get: function() {
94849
95021
  return azure_2.AzureKmsProvider;
94850
95022
  } });
94851
- function createKmsProvider(provider, options) {
95023
+ async function createKmsProvider(provider, options) {
94852
95024
  switch (provider) {
94853
95025
  case "aws":
94854
95026
  return new aws_1.AwsKmsProvider(options?.region);
@@ -94856,6 +95028,17 @@ var require_kms = __commonJS({
94856
95028
  return new gcp_1.GcpKmsProvider();
94857
95029
  case "azure":
94858
95030
  return new azure_1.AzureKmsProvider();
95031
+ case "cloud": {
95032
+ try {
95033
+ const { CloudKmsProvider } = await Promise.resolve().then(() => __importStar(require_kms()));
95034
+ return new CloudKmsProvider({
95035
+ endpoint: options?.endpoint ?? "",
95036
+ token: options?.token
95037
+ });
95038
+ } catch {
95039
+ throw new Error("Clef Cloud KMS requires @clef-sh/client. Install it with: npm install @clef-sh/client");
95040
+ }
95041
+ }
94859
95042
  default:
94860
95043
  throw new Error(`Unknown KMS provider: ${provider}`);
94861
95044
  }
@@ -94908,7 +95091,7 @@ var require_artifact_decryptor = __commonJS({
94908
95091
  exports2.ArtifactDecryptor = void 0;
94909
95092
  var crypto6 = __importStar(require("crypto"));
94910
95093
  var decrypt_1 = require_decrypt();
94911
- var kms_1 = require_kms();
95094
+ var kms_1 = require_kms2();
94912
95095
  var ArtifactDecryptor = class {
94913
95096
  ageDecryptor = new decrypt_1.AgeDecryptor();
94914
95097
  privateKey;
@@ -94957,7 +95140,7 @@ var require_artifact_decryptor = __commonJS({
94957
95140
  const envelope = artifact.envelope;
94958
95141
  let dek;
94959
95142
  try {
94960
- const kms = (0, kms_1.createKmsProvider)(envelope.provider);
95143
+ const kms = await (0, kms_1.createKmsProvider)(envelope.provider);
94961
95144
  const wrappedKey = Buffer.from(envelope.wrappedKey, "base64");
94962
95145
  dek = await kms.unwrap(envelope.keyId, wrappedKey, envelope.algorithm);
94963
95146
  } catch (err) {
@@ -96225,11 +96408,11 @@ var require_dist3 = __commonJS({
96225
96408
  Object.defineProperty(exports2, "createVcsProvider", { enumerable: true, get: function() {
96226
96409
  return index_1.createVcsProvider;
96227
96410
  } });
96228
- var kms_1 = require_kms();
96411
+ var kms_1 = require_kms2();
96229
96412
  Object.defineProperty(exports2, "AwsKmsProvider", { enumerable: true, get: function() {
96230
96413
  return kms_1.AwsKmsProvider;
96231
96414
  } });
96232
- var kms_2 = require_kms();
96415
+ var kms_2 = require_kms2();
96233
96416
  Object.defineProperty(exports2, "createKmsProvider", { enumerable: true, get: function() {
96234
96417
  return kms_2.createKmsProvider;
96235
96418
  } });
@@ -97317,11 +97500,23 @@ function sym(key) {
97317
97500
  }
97318
97501
 
97319
97502
  // src/output/formatter.ts
97503
+ var _jsonMode = false;
97504
+ var _yesMode = false;
97505
+ function setJsonMode(json) {
97506
+ _jsonMode = json;
97507
+ }
97508
+ function isJsonMode() {
97509
+ return _jsonMode;
97510
+ }
97511
+ function setYesMode(yes) {
97512
+ _yesMode = yes;
97513
+ }
97320
97514
  function color(fn, str2) {
97321
97515
  return isPlainMode() ? str2 : fn(str2);
97322
97516
  }
97323
97517
  var formatter = {
97324
97518
  success(message) {
97519
+ if (_jsonMode) return;
97325
97520
  const icon = sym("success");
97326
97521
  process.stdout.write(color(import_picocolors.default.green, `${icon} ${message}`) + "\n");
97327
97522
  },
@@ -97338,15 +97533,18 @@ var formatter = {
97338
97533
  process.stderr.write(color(import_picocolors.default.yellow, `${icon} ${message}`) + "\n");
97339
97534
  },
97340
97535
  info(message) {
97536
+ if (_jsonMode) return;
97341
97537
  const icon = sym("info");
97342
97538
  process.stdout.write(color(import_picocolors.default.blue, `${icon} ${message}`) + "\n");
97343
97539
  },
97344
97540
  hint(message) {
97541
+ if (_jsonMode) return;
97345
97542
  const icon = sym("arrow");
97346
97543
  process.stdout.write(`${icon} ${message}
97347
97544
  `);
97348
97545
  },
97349
97546
  keyValue(key, value) {
97547
+ if (_jsonMode) return;
97350
97548
  const icon = sym("key");
97351
97549
  const arrow = sym("arrow");
97352
97550
  const prefix = icon ? `${icon} ` : "";
@@ -97354,30 +97552,38 @@ var formatter = {
97354
97552
  `);
97355
97553
  },
97356
97554
  pendingItem(key, days) {
97555
+ if (_jsonMode) return;
97357
97556
  const icon = sym("pending");
97358
97557
  const prefix = icon ? `${icon} ` : "[pending] ";
97359
97558
  process.stdout.write(`${prefix}${key} \u2014 ${days} day${days !== 1 ? "s" : ""} pending
97360
97559
  `);
97361
97560
  },
97362
97561
  recipientItem(label, keyPreview2) {
97562
+ if (_jsonMode) return;
97363
97563
  const icon = sym("recipient");
97364
97564
  const prefix = icon ? `${icon} ` : "";
97365
97565
  process.stdout.write(`${prefix}${label.padEnd(15)}${keyPreview2}
97366
97566
  `);
97367
97567
  },
97368
97568
  section(label) {
97569
+ if (_jsonMode) return;
97369
97570
  process.stdout.write(`
97370
97571
  ${label}
97371
97572
 
97372
97573
  `);
97373
97574
  },
97374
97575
  print(message) {
97576
+ if (_jsonMode) return;
97375
97577
  process.stdout.write(message + "\n");
97376
97578
  },
97377
97579
  raw(message) {
97378
97580
  process.stdout.write(message);
97379
97581
  },
97582
+ json(data) {
97583
+ process.stdout.write(JSON.stringify(data) + "\n");
97584
+ },
97380
97585
  table(rows, columns) {
97586
+ if (_jsonMode) return;
97381
97587
  const widths = columns.map((col, i) => {
97382
97588
  const maxDataWidth = rows.reduce(
97383
97589
  (max, row) => Math.max(max, stripAnsi(row[i] ?? "").length),
@@ -97394,6 +97600,10 @@ ${label}
97394
97600
  }
97395
97601
  },
97396
97602
  async confirm(prompt) {
97603
+ if (_yesMode) return true;
97604
+ if (_jsonMode) {
97605
+ throw new Error("--json requires --yes for destructive operations");
97606
+ }
97397
97607
  const rl = readline.createInterface({
97398
97608
  input: process.stdin,
97399
97609
  output: process.stderr
@@ -97428,6 +97638,9 @@ ${label}
97428
97638
  `);
97429
97639
  },
97430
97640
  async secretPrompt(prompt) {
97641
+ if (_jsonMode) {
97642
+ throw new Error("--json mode requires value on the command line (no interactive prompt)");
97643
+ }
97431
97644
  return new Promise((resolve7, reject) => {
97432
97645
  process.stderr.write(color(import_picocolors.default.cyan, `${prompt}: `));
97433
97646
  if (process.stdin.isTTY) {
@@ -97470,7 +97683,14 @@ function pad(str2, width) {
97470
97683
  }
97471
97684
 
97472
97685
  // src/handle-error.ts
97686
+ function exitJsonError(message) {
97687
+ formatter.json({ error: true, message });
97688
+ process.exit(1);
97689
+ }
97473
97690
  function handleCommandError(err) {
97691
+ if (isJsonMode()) {
97692
+ exitJsonError(err.message);
97693
+ }
97474
97694
  if (err instanceof SopsMissingError || err instanceof SopsVersionError) {
97475
97695
  formatter.formatDependencyError(err);
97476
97696
  } else {
@@ -97970,7 +98190,7 @@ async function handleSecondDevOnboarding(repoRoot, clefConfigPath, deps2, option
97970
98190
  "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
98191
  );
97972
98192
  let keyPath;
97973
- if (options.nonInteractive || !process.stdin.isTTY) {
98193
+ if (options.nonInteractive || isJsonMode() || !process.stdin.isTTY) {
97974
98194
  keyPath = process.env.CLEF_AGE_KEY_FILE || defaultAgeKeyPath(label);
97975
98195
  keyPath = path23.resolve(keyPath);
97976
98196
  } else {
@@ -98003,6 +98223,10 @@ async function handleSecondDevOnboarding(repoRoot, clefConfigPath, deps2, option
98003
98223
  formatter.success("Created .clef/.gitignore");
98004
98224
  }
98005
98225
  formatter.success(`Key label: ${label}`);
98226
+ if (isJsonMode()) {
98227
+ formatter.json({ action: "onboarded", manifest: "clef.yaml", config: ".clef/config.yaml" });
98228
+ return;
98229
+ }
98006
98230
  formatter.section("Next steps:");
98007
98231
  formatter.hint("clef recipients request \u2014 request access to encrypted secrets");
98008
98232
  formatter.hint("clef update \u2014 scaffold new environments");
@@ -98013,7 +98237,7 @@ async function handleFullSetup(repoRoot, manifestPath, clefConfigPath, deps2, op
98013
98237
  let namespaces = options.namespaces ? options.namespaces.split(",").map((s) => s.trim()) : [];
98014
98238
  const backend = options.backend ?? "age";
98015
98239
  let secretsDir = options.secretsDir ?? "secrets";
98016
- if (!options.nonInteractive && process.stdin.isTTY) {
98240
+ if (!options.nonInteractive && !isJsonMode() && process.stdin.isTTY) {
98017
98241
  const envAnswer = await promptWithDefault(
98018
98242
  "Environments (comma-separated)",
98019
98243
  environments.join(",")
@@ -98068,7 +98292,7 @@ async function handleFullSetup(repoRoot, manifestPath, clefConfigPath, deps2, op
98068
98292
  formatter.warn(
98069
98293
  "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
98294
  );
98071
- if (!options.nonInteractive && process.stdin.isTTY) {
98295
+ if (!options.nonInteractive && !isJsonMode() && process.stdin.isTTY) {
98072
98296
  const confirmed = await formatter.confirm("Write the private key to the filesystem?");
98073
98297
  if (!confirmed) {
98074
98298
  formatter.error(
@@ -98079,7 +98303,7 @@ async function handleFullSetup(repoRoot, manifestPath, clefConfigPath, deps2, op
98079
98303
  }
98080
98304
  }
98081
98305
  let keyPath;
98082
- if (options.nonInteractive || !process.stdin.isTTY) {
98306
+ if (options.nonInteractive || isJsonMode() || !process.stdin.isTTY) {
98083
98307
  keyPath = defaultAgeKeyPath(label);
98084
98308
  if (await isInsideAnyGitRepo(path23.resolve(keyPath))) {
98085
98309
  throw new Error(
@@ -98199,6 +98423,17 @@ async function handleFullSetup(repoRoot, manifestPath, clefConfigPath, deps2, op
98199
98423
  formatter.print(" clef config set analytics false (permanent)\n");
98200
98424
  } catch {
98201
98425
  }
98426
+ if (isJsonMode()) {
98427
+ formatter.json({
98428
+ action: "initialized",
98429
+ manifest: "clef.yaml",
98430
+ environments: manifest.environments.map((e) => e.name),
98431
+ namespaces: manifest.namespaces.map((n) => n.name),
98432
+ backend,
98433
+ scaffolded: scaffoldedCount
98434
+ });
98435
+ return;
98436
+ }
98202
98437
  formatter.section("Next steps:");
98203
98438
  formatter.hint("clef set <namespace>/<env> <KEY> <value> \u2014 add a secret");
98204
98439
  formatter.hint("clef scan \u2014 check for existing plaintext secrets");
@@ -98459,7 +98694,9 @@ function registerGetCommand(program3, deps2) {
98459
98694
  return;
98460
98695
  }
98461
98696
  const val = decrypted.values[key];
98462
- if (opts.raw) {
98697
+ if (isJsonMode()) {
98698
+ formatter.json({ key, value: val, namespace, environment });
98699
+ } else if (opts.raw) {
98463
98700
  formatter.raw(val);
98464
98701
  } else {
98465
98702
  const copied = copyToClipboard(val);
@@ -98564,6 +98801,16 @@ function registerSetCommand(program3, deps2) {
98564
98801
  pendingErrors.push(env.name);
98565
98802
  }
98566
98803
  }
98804
+ if (isJsonMode()) {
98805
+ formatter.json({
98806
+ key,
98807
+ namespace: namespace2,
98808
+ environments: manifest.environments.map((e) => e.name),
98809
+ action: "created",
98810
+ pending: true
98811
+ });
98812
+ return;
98813
+ }
98567
98814
  formatter.success(
98568
98815
  `'${key}' set in ${namespace2} across all environments ${sym("locked")}`
98569
98816
  );
@@ -98587,6 +98834,16 @@ function registerSetCommand(program3, deps2) {
98587
98834
  } catch {
98588
98835
  }
98589
98836
  }
98837
+ if (isJsonMode()) {
98838
+ formatter.json({
98839
+ key,
98840
+ namespace: namespace2,
98841
+ environments: manifest.environments.map((e) => e.name),
98842
+ action: "created",
98843
+ pending: false
98844
+ });
98845
+ return;
98846
+ }
98590
98847
  formatter.success(`'${key}' set in ${namespace2} across all environments`);
98591
98848
  formatter.hint(`git add ${namespace2}/ # stage all updated files`);
98592
98849
  }
@@ -98650,6 +98907,10 @@ function registerSetCommand(program3, deps2) {
98650
98907
  process.exit(1);
98651
98908
  return;
98652
98909
  }
98910
+ if (isJsonMode()) {
98911
+ formatter.json({ key, namespace, environment, action: "created", pending: true });
98912
+ return;
98913
+ }
98653
98914
  formatter.success(`${key} set in ${namespace}/${environment} ${sym("locked")}`);
98654
98915
  formatter.print(
98655
98916
  ` ${sym("pending")} Marked as pending \u2014 replace with a real value before deploying`
@@ -98664,6 +98925,10 @@ function registerSetCommand(program3, deps2) {
98664
98925
  The value is saved. Run clef lint to check for stale pending markers.`
98665
98926
  );
98666
98927
  }
98928
+ if (isJsonMode()) {
98929
+ formatter.json({ key, namespace, environment, action: "created", pending: false });
98930
+ return;
98931
+ }
98667
98932
  formatter.success(`${key} set in ${namespace}/${environment}`);
98668
98933
  formatter.hint(
98669
98934
  `Commit: git add ${manifest.file_pattern.replace("{namespace}", namespace).replace("{environment}", environment)}`
@@ -98732,7 +98997,10 @@ function registerCompareCommand(program3, deps2) {
98732
98997
  compareBuf.copy(paddedCompare);
98733
98998
  const timingEqual = crypto5.timingSafeEqual(paddedStored, paddedCompare);
98734
98999
  const match = storedBuf.length === compareBuf.length && timingEqual;
98735
- if (match) {
99000
+ if (isJsonMode()) {
99001
+ formatter.json({ match, key, namespace, environment });
99002
+ if (!match) process.exit(1);
99003
+ } else if (match) {
98736
99004
  formatter.success(`${key} ${sym("arrow")} values match`);
98737
99005
  } else {
98738
99006
  formatter.failure(`${key} ${sym("arrow")} values do not match`);
@@ -98780,6 +99048,15 @@ Type the key name to confirm:`
98780
99048
  }
98781
99049
  const bulkOps = new BulkOps();
98782
99050
  await bulkOps.deleteAcrossEnvironments(namespace, key, manifest, sopsClient, repoRoot);
99051
+ if (isJsonMode()) {
99052
+ formatter.json({
99053
+ key,
99054
+ namespace,
99055
+ environments: manifest.environments.map((e) => e.name),
99056
+ action: "deleted"
99057
+ });
99058
+ return;
99059
+ }
98783
99060
  formatter.success(`Deleted '${key}' from ${namespace} in all environments`);
98784
99061
  } else {
98785
99062
  const [namespace, environment] = parseTarget(target);
@@ -98818,6 +99095,10 @@ Type the key name to confirm:`
98818
99095
  `Key deleted but pending metadata could not be cleaned up. Run clef lint to verify.`
98819
99096
  );
98820
99097
  }
99098
+ if (isJsonMode()) {
99099
+ formatter.json({ key, namespace, environment, action: "deleted" });
99100
+ return;
99101
+ }
98821
99102
  formatter.success(`Deleted '${key}' from ${namespace}/${environment}`);
98822
99103
  formatter.hint(
98823
99104
  `Commit: git add ${manifest.file_pattern.replace("{namespace}", namespace).replace("{environment}", environment)}`
@@ -98836,10 +99117,11 @@ Type the key name to confirm:`
98836
99117
  var path33 = __toESM(require("path"));
98837
99118
  var import_picocolors2 = __toESM(require_picocolors());
98838
99119
  init_src();
99120
+ var MASKED = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
98839
99121
  function registerDiffCommand(program3, deps2) {
98840
99122
  program3.command("diff <namespace> <env-a> <env-b>").description(
98841
99123
  "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").option("--json", "Output raw DiffResult JSON").action(
99124
+ ).option("--show-identical", "Include identical keys in the output").option("--show-values", "Show plaintext values instead of masking them").action(
98843
99125
  async (namespace, envA, envB, options) => {
98844
99126
  try {
98845
99127
  const repoRoot = program3.opts().dir || process.cwd();
@@ -98866,19 +99148,20 @@ function registerDiffCommand(program3, deps2) {
98866
99148
  formatter.warn("Warning: printing plaintext values for protected environment.");
98867
99149
  }
98868
99150
  }
98869
- if (options.json) {
98870
- const jsonOutput = options.showValues ? result : {
99151
+ if (isJsonMode()) {
99152
+ const jsonData = options.showValues ? result : {
98871
99153
  ...result,
98872
99154
  rows: result.rows.map((r) => ({
98873
99155
  ...r,
98874
- valueA: r.valueA !== null ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : null,
98875
- valueB: r.valueB !== null ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : null,
99156
+ valueA: r.valueA !== null ? MASKED : null,
99157
+ valueB: r.valueB !== null ? MASKED : null,
98876
99158
  masked: true
98877
99159
  }))
98878
99160
  };
98879
- formatter.raw(JSON.stringify(jsonOutput, null, 2) + "\n");
99161
+ formatter.json(jsonData);
98880
99162
  const hasDiffs2 = result.rows.some((r) => r.status !== "identical");
98881
99163
  process.exit(hasDiffs2 ? 1 : 0);
99164
+ return;
98882
99165
  }
98883
99166
  formatDiffOutput(
98884
99167
  result,
@@ -98898,7 +99181,6 @@ function registerDiffCommand(program3, deps2) {
98898
99181
  }
98899
99182
  );
98900
99183
  }
98901
- var MASKED = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
98902
99184
  function formatDiffOutput(result, envA, envB, showIdentical, showValues) {
98903
99185
  const filteredRows = showIdentical ? result.rows : result.rows.filter((r) => r.status !== "identical");
98904
99186
  if (filteredRows.length === 0) {
@@ -99180,7 +99462,7 @@ async function fetchCheckpoint(config) {
99180
99462
  }
99181
99463
 
99182
99464
  // package.json
99183
- var version2 = "0.1.12";
99465
+ var version2 = "0.1.13-beta.92";
99184
99466
  var package_default = {
99185
99467
  name: "@clef-sh/cli",
99186
99468
  version: version2,
@@ -99251,7 +99533,7 @@ var package_default = {
99251
99533
  function registerLintCommand(program3, deps2) {
99252
99534
  program3.command("lint").description(
99253
99535
  "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("--json", "Output raw LintResult JSON").option("--push", "Push results as OTLP to CLEF_TELEMETRY_URL").action(async (options) => {
99536
+ ).option("--fix", "Auto-fix safe issues (scaffold missing files)").option("--push", "Push results as OTLP to CLEF_TELEMETRY_URL").action(async (options) => {
99255
99537
  try {
99256
99538
  const repoRoot = program3.opts().dir || process.cwd();
99257
99539
  const parser = new ManifestParser();
@@ -99286,8 +99568,8 @@ function registerLintCommand(program3, deps2) {
99286
99568
  await pushOtlp(payload, config);
99287
99569
  formatter.success("Lint results pushed to telemetry endpoint.");
99288
99570
  }
99289
- if (options.json) {
99290
- formatter.raw(JSON.stringify(result, null, 2) + "\n");
99571
+ if (isJsonMode()) {
99572
+ formatter.json(result);
99291
99573
  const hasErrors2 = result.issues.some((i) => i.severity === "error");
99292
99574
  process.exit(hasErrors2 ? 1 : 0);
99293
99575
  return;
@@ -99396,6 +99678,10 @@ function registerRotateCommand(program3, deps2) {
99396
99678
  const relativeFile = manifest.file_pattern.replace("{namespace}", namespace).replace("{environment}", environment);
99397
99679
  formatter.print(`${sym("working")} Rotating ${namespace}/${environment}...`);
99398
99680
  await sopsClient.reEncrypt(filePath, options.newKey);
99681
+ if (isJsonMode()) {
99682
+ formatter.json({ namespace, environment, file: relativeFile, action: "rotated" });
99683
+ return;
99684
+ }
99399
99685
  formatter.success(`Rotated. New values encrypted. ${sym("locked")}`);
99400
99686
  formatter.hint(
99401
99687
  `git add ${relativeFile} && git commit -m "rotate: ${namespace}/${environment}"`
@@ -99441,15 +99727,28 @@ function registerHooksCommand(program3, deps2) {
99441
99727
  }
99442
99728
  const git = new GitIntegration(deps2.runner);
99443
99729
  await git.installPreCommitHook(repoRoot);
99730
+ let mergeDriverOk = false;
99731
+ try {
99732
+ await git.installMergeDriver(repoRoot);
99733
+ mergeDriverOk = true;
99734
+ } catch {
99735
+ }
99736
+ if (isJsonMode()) {
99737
+ formatter.json({
99738
+ preCommitHook: true,
99739
+ mergeDriver: mergeDriverOk,
99740
+ hookPath
99741
+ });
99742
+ return;
99743
+ }
99444
99744
  formatter.success("Pre-commit hook installed");
99445
99745
  formatter.print(` ${sym("pending")} ${hookPath}`);
99446
99746
  formatter.hint(
99447
99747
  "Hook checks SOPS metadata on staged .enc files and runs: clef scan --staged"
99448
99748
  );
99449
- try {
99450
- await git.installMergeDriver(repoRoot);
99749
+ if (mergeDriverOk) {
99451
99750
  formatter.success("SOPS merge driver configured");
99452
- } catch {
99751
+ } else {
99453
99752
  formatter.warn("Could not configure SOPS merge driver. Run inside a git repository.");
99454
99753
  }
99455
99754
  } catch (err) {
@@ -99748,6 +100047,14 @@ Usage: clef export payments/production --format env`
99748
100047
  );
99749
100048
  try {
99750
100049
  const decrypted = await sopsClient.decrypt(filePath);
100050
+ if (isJsonMode()) {
100051
+ const pairs = Object.entries(decrypted.values).map(([k, v]) => ({
100052
+ key: k,
100053
+ value: v
100054
+ }));
100055
+ formatter.json({ pairs, namespace, environment });
100056
+ return;
100057
+ }
99751
100058
  const consumption = new ConsumptionClient();
99752
100059
  const output = consumption.formatExport(decrypted, "env", !options.export);
99753
100060
  if (options.raw) {
@@ -99788,7 +100095,7 @@ init_src();
99788
100095
  function registerDoctorCommand(program3, deps2) {
99789
100096
  program3.command("doctor").description(
99790
100097
  "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("--json", "Output the full status as JSON").option("--fix", "Attempt to auto-fix issues").action(async (options) => {
100098
+ ).option("--fix", "Attempt to auto-fix issues").action(async (options) => {
99792
100099
  const repoRoot = program3.opts().dir || process.cwd();
99793
100100
  const clefVersion = program3.version() ?? "unknown";
99794
100101
  const checks = [];
@@ -99875,7 +100182,7 @@ function registerDoctorCommand(program3, deps2) {
99875
100182
  formatter.warn("--fix cannot resolve these issues automatically.");
99876
100183
  }
99877
100184
  }
99878
- if (options.json) {
100185
+ if (isJsonMode()) {
99879
100186
  const json = {
99880
100187
  clef: { version: clefVersion, ok: true },
99881
100188
  sops: {
@@ -99905,7 +100212,7 @@ function registerDoctorCommand(program3, deps2) {
99905
100212
  ok: mergeDriverOk
99906
100213
  }
99907
100214
  };
99908
- formatter.raw(JSON.stringify(json, null, 2) + "\n");
100215
+ formatter.json(json);
99909
100216
  const hasFailures = checks.some((c) => !c.ok);
99910
100217
  process.exit(hasFailures ? 1 : 0);
99911
100218
  return;
@@ -100039,6 +100346,11 @@ function registerUpdateCommand(program3, deps2) {
100039
100346
  );
100040
100347
  }
100041
100348
  }
100349
+ if (isJsonMode()) {
100350
+ formatter.json({ scaffolded: scaffoldedCount, failed: failedCount });
100351
+ process.exit(failedCount > 0 ? 1 : 0);
100352
+ return;
100353
+ }
100042
100354
  if (scaffoldedCount > 0) {
100043
100355
  formatter.success(`Scaffolded ${scaffoldedCount} encrypted file(s)`);
100044
100356
  }
@@ -100064,69 +100376,57 @@ function registerScanCommand(program3, deps2) {
100064
100376
  "--severity <level>",
100065
100377
  "Detection level: all (patterns+entropy) or high (patterns only)",
100066
100378
  "all"
100067
- ).option("--json", "Output machine-readable JSON").action(
100068
- async (paths, options) => {
100069
- const repoRoot = program3.opts().dir || process.cwd();
100070
- let manifest;
100071
- try {
100072
- const parser = new ManifestParser();
100073
- manifest = parser.parse(path43.join(repoRoot, "clef.yaml"));
100074
- } catch (err) {
100075
- if (err instanceof ManifestValidationError || err.message?.includes("clef.yaml")) {
100076
- formatter.error("No clef.yaml found. Run 'clef init' to set up this repository.");
100077
- } else {
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;
100379
+ ).action(async (paths, options) => {
100380
+ const repoRoot = program3.opts().dir || process.cwd();
100381
+ let manifest;
100382
+ try {
100383
+ const parser = new ManifestParser();
100384
+ manifest = parser.parse(path43.join(repoRoot, "clef.yaml"));
100385
+ } catch (err) {
100386
+ if (err instanceof ManifestValidationError || err.message?.includes("clef.yaml")) {
100387
+ formatter.error("No clef.yaml found. Run 'clef init' to set up this repository.");
100388
+ } else {
100389
+ formatter.error(err.message);
100124
100390
  }
100125
- formatScanOutput(result);
100126
- const hasIssues = result.matches.length > 0 || result.unencryptedMatrixFiles.length > 0;
100127
- process.exit(hasIssues ? 1 : 0);
100391
+ process.exit(2);
100392
+ return;
100128
100393
  }
100129
- );
100394
+ if (options.severity && options.severity !== "all" && options.severity !== "high") {
100395
+ formatter.error(`Invalid severity '${options.severity}'. Must be 'all' or 'high'.`);
100396
+ process.exit(2);
100397
+ return;
100398
+ }
100399
+ const severity = options.severity === "high" ? "high" : "all";
100400
+ const scanRunner = new ScanRunner(deps2.runner);
100401
+ formatter.print(import_picocolors4.default.dim("Scanning repository for unencrypted secrets..."));
100402
+ let result;
100403
+ try {
100404
+ result = await scanRunner.scan(repoRoot, manifest, {
100405
+ stagedOnly: options.staged,
100406
+ paths: paths.length > 0 ? paths : void 0,
100407
+ severity
100408
+ });
100409
+ } catch (err) {
100410
+ formatter.error(`Scan failed: ${err.message}`);
100411
+ process.exit(2);
100412
+ return;
100413
+ }
100414
+ if (isJsonMode()) {
100415
+ formatter.json({
100416
+ matches: result.matches,
100417
+ unencryptedMatrixFiles: result.unencryptedMatrixFiles,
100418
+ filesScanned: result.filesScanned,
100419
+ filesSkipped: result.filesSkipped,
100420
+ durationMs: result.durationMs
100421
+ });
100422
+ const hasIssues2 = result.matches.length > 0 || result.unencryptedMatrixFiles.length > 0;
100423
+ process.exit(hasIssues2 ? 1 : 0);
100424
+ return;
100425
+ }
100426
+ formatScanOutput(result);
100427
+ const hasIssues = result.matches.length > 0 || result.unencryptedMatrixFiles.length > 0;
100428
+ process.exit(hasIssues ? 1 : 0);
100429
+ });
100130
100430
  }
100131
100431
  function formatScanOutput(result) {
100132
100432
  const totalIssues = result.matches.length + result.unencryptedMatrixFiles.length;
@@ -100291,6 +100591,17 @@ function registerImportCommand(program3, deps2) {
100291
100591
  process.exit(2);
100292
100592
  return;
100293
100593
  }
100594
+ if (isJsonMode()) {
100595
+ formatter.json({
100596
+ imported: result.imported,
100597
+ skipped: result.skipped,
100598
+ failed: result.failed,
100599
+ warnings: result.warnings,
100600
+ dryRun: opts.dryRun
100601
+ });
100602
+ process.exit(result.failed.length > 0 ? 1 : 0);
100603
+ return;
100604
+ }
100294
100605
  for (const warning of result.warnings) {
100295
100606
  formatter.print(` ${sym("warning")} ${warning}`);
100296
100607
  }
@@ -100350,6 +100661,7 @@ var path45 = __toESM(require("path"));
100350
100661
  var readline4 = __toESM(require("readline"));
100351
100662
  init_src();
100352
100663
  function waitForEnter(message) {
100664
+ if (isJsonMode()) return Promise.resolve();
100353
100665
  return new Promise((resolve7) => {
100354
100666
  const rl = readline4.createInterface({
100355
100667
  input: process.stdin,
@@ -100383,6 +100695,10 @@ function registerRecipientsCommand(program3, deps2) {
100383
100695
  const sopsClient = await createSopsClient(repoRoot, deps2.runner);
100384
100696
  const recipientManager = new RecipientManager(sopsClient, matrixManager);
100385
100697
  const recipients = await recipientManager.list(manifest, repoRoot, opts.environment);
100698
+ if (isJsonMode()) {
100699
+ formatter.json(recipients);
100700
+ return;
100701
+ }
100386
100702
  if (recipients.length === 0) {
100387
100703
  const scope2 = opts.environment ? ` for environment '${opts.environment}'` : "";
100388
100704
  formatter.info(`No recipients configured${scope2}.`);
@@ -100413,7 +100729,7 @@ function registerRecipientsCommand(program3, deps2) {
100413
100729
  return;
100414
100730
  }
100415
100731
  const normalizedKey = validation.key;
100416
- const success = await executeRecipientAdd(
100732
+ const result = await executeRecipientAdd(
100417
100733
  repoRoot,
100418
100734
  program3,
100419
100735
  deps2,
@@ -100421,11 +100737,21 @@ function registerRecipientsCommand(program3, deps2) {
100421
100737
  opts.label,
100422
100738
  opts.environment
100423
100739
  );
100424
- if (success) {
100425
- const label = opts.label || keyPreview(normalizedKey);
100426
- formatter.hint(
100427
- `git add clef.yaml && git add -A && git commit -m "add recipient: ${label} [${opts.environment}]"`
100428
- );
100740
+ if (result) {
100741
+ if (isJsonMode()) {
100742
+ formatter.json({
100743
+ action: "added",
100744
+ key: normalizedKey,
100745
+ label: opts.label || keyPreview(normalizedKey),
100746
+ environment: opts.environment,
100747
+ reEncryptedFiles: result.reEncryptedFiles.length
100748
+ });
100749
+ } else {
100750
+ const label = opts.label || keyPreview(normalizedKey);
100751
+ formatter.hint(
100752
+ `git add clef.yaml && git add -A && git commit -m "add recipient: ${label} [${opts.environment}]"`
100753
+ );
100754
+ }
100429
100755
  }
100430
100756
  } catch (err) {
100431
100757
  handleCommandError(err);
@@ -100521,6 +100847,16 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`
100521
100847
  const relative7 = path45.relative(repoRoot, file);
100522
100848
  formatter.print(` ${sym("success")} ${relative7}`);
100523
100849
  }
100850
+ if (isJsonMode()) {
100851
+ formatter.json({
100852
+ action: "removed",
100853
+ key: trimmedKey,
100854
+ label,
100855
+ environment: opts.environment ?? null,
100856
+ reEncryptedFiles: result.reEncryptedFiles.length
100857
+ });
100858
+ return;
100859
+ }
100524
100860
  formatter.success(
100525
100861
  `${label} removed. ${result.reEncryptedFiles.length} files re-encrypted. ${sym("locked")}`
100526
100862
  );
@@ -100585,6 +100921,15 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`
100585
100921
  return;
100586
100922
  }
100587
100923
  upsertRequest(repoRoot, publicKey, label, opts.environment);
100924
+ if (isJsonMode()) {
100925
+ formatter.json({
100926
+ action: "requested",
100927
+ label,
100928
+ key: publicKey,
100929
+ environment: opts.environment ?? null
100930
+ });
100931
+ return;
100932
+ }
100588
100933
  const scope = opts.environment ? ` for environment '${opts.environment}'` : "";
100589
100934
  formatter.success(`Access requested as '${label}'${scope}`);
100590
100935
  formatter.print(` Key: ${keyPreview(publicKey)}`);
@@ -100600,6 +100945,15 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`
100600
100945
  try {
100601
100946
  const repoRoot = program3.opts().dir || process.cwd();
100602
100947
  const requests = loadRequests(repoRoot);
100948
+ if (isJsonMode()) {
100949
+ formatter.json(
100950
+ requests.map((r) => ({
100951
+ ...r,
100952
+ requestedAt: r.requestedAt.toISOString()
100953
+ }))
100954
+ );
100955
+ return;
100956
+ }
100603
100957
  if (requests.length === 0) {
100604
100958
  formatter.info("No pending access requests.");
100605
100959
  return;
@@ -100639,7 +100993,7 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`
100639
100993
  process.exit(2);
100640
100994
  return;
100641
100995
  }
100642
- const success = await executeRecipientAdd(
100996
+ const result = await executeRecipientAdd(
100643
100997
  repoRoot,
100644
100998
  program3,
100645
100999
  deps2,
@@ -100647,11 +101001,21 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`
100647
101001
  request.label,
100648
101002
  environment
100649
101003
  );
100650
- if (success) {
101004
+ if (result) {
100651
101005
  removeRequest(repoRoot, identifier);
100652
- formatter.hint(
100653
- `git add clef.yaml ${REQUESTS_FILENAME} && git add -A && git commit -m "approve recipient: ${request.label} [${environment}]"`
100654
- );
101006
+ if (isJsonMode()) {
101007
+ formatter.json({
101008
+ action: "approved",
101009
+ identifier,
101010
+ label: request.label,
101011
+ environment,
101012
+ reEncryptedFiles: result.reEncryptedFiles.length
101013
+ });
101014
+ } else {
101015
+ formatter.hint(
101016
+ `git add clef.yaml ${REQUESTS_FILENAME} && git add -A && git commit -m "approve recipient: ${request.label} [${environment}]"`
101017
+ );
101018
+ }
100655
101019
  }
100656
101020
  } catch (err) {
100657
101021
  handleCommandError(err);
@@ -100667,7 +101031,7 @@ async function executeRecipientAdd(repoRoot, _program, deps2, key, label, enviro
100667
101031
  `Environment '${environment}' not found. Available: ${manifest.environments.map((e) => e.name).join(", ")}`
100668
101032
  );
100669
101033
  process.exit(2);
100670
- return false;
101034
+ return null;
100671
101035
  }
100672
101036
  const matrixManager = new MatrixManager();
100673
101037
  const sopsClient = await createSopsClient(repoRoot, deps2.runner);
@@ -100676,7 +101040,7 @@ async function executeRecipientAdd(repoRoot, _program, deps2, key, label, enviro
100676
101040
  if (existing.some((r) => r.key === key)) {
100677
101041
  formatter.error(`Recipient '${keyPreview(key)}' is already present.`);
100678
101042
  process.exit(2);
100679
- return false;
101043
+ return null;
100680
101044
  }
100681
101045
  const allCells = matrixManager.resolveMatrix(manifest, repoRoot).filter((c) => c.exists && c.environment === environment);
100682
101046
  const fileCount = allCells.length;
@@ -100693,7 +101057,7 @@ This will re-encrypt ${fileCount} files in the matrix.`);
100693
101057
  const confirmed = await formatter.confirm("Proceed?");
100694
101058
  if (!confirmed) {
100695
101059
  formatter.info("Aborted.");
100696
- return false;
101060
+ return null;
100697
101061
  }
100698
101062
  formatter.print(`
100699
101063
  ${sym("working")} Re-encrypting matrix...`);
@@ -100710,7 +101074,7 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`);
100710
101074
  );
100711
101075
  formatter.print("\nNo changes were applied. Investigate the error above and retry.");
100712
101076
  process.exit(1);
100713
- return false;
101077
+ return null;
100714
101078
  }
100715
101079
  for (const file of result.reEncryptedFiles) {
100716
101080
  const relative7 = path45.relative(repoRoot, file);
@@ -100720,7 +101084,7 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`);
100720
101084
  formatter.success(
100721
101085
  `${displayLabel} added. ${result.reEncryptedFiles.length} files re-encrypted. ${sym("locked")}`
100722
101086
  );
100723
- return true;
101087
+ return { reEncryptedFiles: result.reEncryptedFiles };
100724
101088
  }
100725
101089
 
100726
101090
  // src/commands/merge-driver.ts
@@ -100883,6 +101247,16 @@ function registerServiceCommand(program3, deps2) {
100883
101247
  repoRoot,
100884
101248
  kmsEnvConfigs
100885
101249
  );
101250
+ if (isJsonMode()) {
101251
+ formatter.json({
101252
+ action: "created",
101253
+ identity: result.identity.name,
101254
+ namespaces: result.identity.namespaces,
101255
+ environments: Object.keys(result.identity.environments),
101256
+ privateKeys: result.privateKeys
101257
+ });
101258
+ return;
101259
+ }
100886
101260
  formatter.success(`Service identity '${name}' created.`);
100887
101261
  formatter.print(`
100888
101262
  Namespaces: ${result.identity.namespaces.join(", ")}`);
@@ -100936,6 +101310,10 @@ function registerServiceCommand(program3, deps2) {
100936
101310
  const sopsClient = await createSopsClient(repoRoot, deps2.runner);
100937
101311
  const manager = new ServiceIdentityManager(sopsClient, matrixManager);
100938
101312
  const identities = manager.list(manifest);
101313
+ if (isJsonMode()) {
101314
+ formatter.json(identities);
101315
+ return;
101316
+ }
100939
101317
  if (identities.length === 0) {
100940
101318
  formatter.info("No service identities configured.");
100941
101319
  return;
@@ -100965,6 +101343,10 @@ function registerServiceCommand(program3, deps2) {
100965
101343
  process.exit(1);
100966
101344
  return;
100967
101345
  }
101346
+ if (isJsonMode()) {
101347
+ formatter.json(identity);
101348
+ return;
101349
+ }
100968
101350
  formatter.print(`
100969
101351
  Service Identity: ${identity.name}`);
100970
101352
  formatter.print(`Description: ${identity.description}`);
@@ -100993,6 +101375,11 @@ Service Identity: ${identity.name}`);
100993
101375
  const sopsClient = await createSopsClient(repoRoot, deps2.runner);
100994
101376
  const manager = new ServiceIdentityManager(sopsClient, matrixManager);
100995
101377
  const issues = await manager.validate(manifest, repoRoot);
101378
+ if (isJsonMode()) {
101379
+ formatter.json({ issues });
101380
+ process.exit(issues.length > 0 ? 1 : 0);
101381
+ return;
101382
+ }
100996
101383
  if (issues.length === 0) {
100997
101384
  formatter.success("All service identities are valid.");
100998
101385
  return;
@@ -101043,6 +101430,17 @@ Service Identity: ${identity.name}`);
101043
101430
  const manager = new ServiceIdentityManager(sopsClient, matrixManager);
101044
101431
  formatter.print(`${sym("working")} Updating service identity '${name}'...`);
101045
101432
  await manager.updateEnvironments(name, kmsEnvConfigs, manifest, repoRoot);
101433
+ if (isJsonMode()) {
101434
+ formatter.json({
101435
+ action: "updated",
101436
+ identity: name,
101437
+ changed: Object.entries(kmsEnvConfigs).map(([env, cfg]) => ({
101438
+ environment: env,
101439
+ provider: cfg.provider
101440
+ }))
101441
+ });
101442
+ return;
101443
+ }
101046
101444
  formatter.success(`Service identity '${name}' updated.`);
101047
101445
  for (const [envName, kmsConfig] of Object.entries(kmsEnvConfigs)) {
101048
101446
  formatter.print(` ${envName}: switched to KMS envelope (${kmsConfig.provider})`);
@@ -101078,6 +101476,10 @@ Service Identity: ${identity.name}`);
101078
101476
  const manager = new ServiceIdentityManager(sopsClient, matrixManager);
101079
101477
  formatter.print(`${sym("working")} Deleting service identity '${name}'...`);
101080
101478
  await manager.delete(name, manifest, repoRoot);
101479
+ if (isJsonMode()) {
101480
+ formatter.json({ action: "deleted", identity: name });
101481
+ return;
101482
+ }
101081
101483
  formatter.success(`Service identity '${name}' deleted.`);
101082
101484
  formatter.hint(
101083
101485
  `git add clef.yaml && git commit -m "chore: delete service identity '${name}'"`
@@ -101114,6 +101516,15 @@ Service Identity: ${identity.name}`);
101114
101516
  }
101115
101517
  formatter.print(`${sym("working")} Rotating key for '${name}'...`);
101116
101518
  const newKeys = await manager.rotateKey(name, manifest, repoRoot, opts.environment);
101519
+ if (isJsonMode()) {
101520
+ formatter.json({
101521
+ action: "rotated",
101522
+ identity: name,
101523
+ environments: Object.keys(newKeys),
101524
+ privateKeys: newKeys
101525
+ });
101526
+ return;
101527
+ }
101117
101528
  formatter.success(`Key rotated for '${name}'.`);
101118
101529
  const entries = Object.entries(newKeys);
101119
101530
  const block = entries.map(([env, key]) => `${env}: ${key}`).join("\n");
@@ -101254,7 +101665,7 @@ function registerPackCommand(program3, deps2) {
101254
101665
  const envConfig = si?.environments[environment];
101255
101666
  if (envConfig && isKmsEnvelope(envConfig)) {
101256
101667
  const { createKmsProvider } = await Promise.resolve().then(() => __toESM(require_dist3()));
101257
- kmsProvider = createKmsProvider(envConfig.kms.provider, {
101668
+ kmsProvider = await createKmsProvider(envConfig.kms.provider, {
101258
101669
  region: envConfig.kms.region
101259
101670
  });
101260
101671
  }
@@ -101289,6 +101700,18 @@ function registerPackCommand(program3, deps2) {
101289
101700
  const fileOut = new FilePackOutput(outputPath);
101290
101701
  await fileOut.write(memOutput.artifact, memOutput.json);
101291
101702
  }
101703
+ if (isJsonMode()) {
101704
+ formatter.json({
101705
+ identity,
101706
+ environment,
101707
+ keyCount: result.keyCount,
101708
+ namespaceCount: result.namespaceCount,
101709
+ artifactSize: result.artifactSize,
101710
+ revision: result.revision,
101711
+ output: outputPath ?? null
101712
+ });
101713
+ return;
101714
+ }
101292
101715
  formatter.success(
101293
101716
  `Artifact packed: ${result.keyCount} keys from ${result.namespaceCount} namespace(s).`
101294
101717
  );
@@ -101366,6 +101789,15 @@ function registerRevokeCommand(program3, _deps) {
101366
101789
  fs32.mkdirSync(artifactDir, { recursive: true });
101367
101790
  fs32.writeFileSync(artifactPath, JSON.stringify(revoked, null, 2) + "\n", "utf-8");
101368
101791
  const relPath = path49.relative(repoRoot, artifactPath);
101792
+ if (isJsonMode()) {
101793
+ formatter.json({
101794
+ identity,
101795
+ environment,
101796
+ revokedAt: revoked.revokedAt,
101797
+ markerPath: relPath
101798
+ });
101799
+ return;
101800
+ }
101369
101801
  formatter.success(`Artifact revoked: ${relPath}`);
101370
101802
  formatter.print("");
101371
101803
  formatter.print(`${sym("arrow")} If your agent fetches artifacts from git (VCS source):`);
@@ -101394,37 +101826,35 @@ init_src();
101394
101826
  function registerDriftCommand(program3, _deps) {
101395
101827
  program3.command("drift <path>").description(
101396
101828
  "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("--json", "Output raw DriftResult JSON for CI parsing").option("--push", "Push results as OTLP to CLEF_TELEMETRY_URL").option("--namespace <name...>", "Scope to specific namespace(s)").action(
101398
- async (remotePath, options) => {
101399
- try {
101400
- const localRoot = program3.opts().dir || process.cwd();
101401
- const remoteRoot = path50.resolve(localRoot, remotePath);
101402
- const detector = new DriftDetector();
101403
- const result = detector.detect(localRoot, remoteRoot, options.namespace);
101404
- if (options.push) {
101405
- const config = resolveTelemetryConfig();
101406
- if (!config) {
101407
- formatter.error("--push requires CLEF_TELEMETRY_URL to be set.");
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);
101829
+ ).option("--push", "Push results as OTLP to CLEF_TELEMETRY_URL").option("--namespace <name...>", "Scope to specific namespace(s)").action(async (remotePath, options) => {
101830
+ try {
101831
+ const localRoot = program3.opts().dir || process.cwd();
101832
+ const remoteRoot = path50.resolve(localRoot, remotePath);
101833
+ const detector = new DriftDetector();
101834
+ const result = detector.detect(localRoot, remoteRoot, options.namespace);
101835
+ if (options.push) {
101836
+ const config = resolveTelemetryConfig();
101837
+ if (!config) {
101838
+ formatter.error("--push requires CLEF_TELEMETRY_URL to be set.");
101839
+ process.exit(1);
101418
101840
  return;
101419
101841
  }
101420
- formatDriftOutput(result);
101842
+ const payload = driftResultToOtlp(result, version2);
101843
+ await pushOtlp(payload, config);
101844
+ formatter.success("Drift results pushed to telemetry endpoint.");
101845
+ }
101846
+ if (isJsonMode()) {
101847
+ formatter.json(result);
101421
101848
  process.exit(result.issues.length > 0 ? 1 : 0);
101422
- } catch (err) {
101423
- formatter.error(err.message);
101424
- process.exit(1);
101849
+ return;
101425
101850
  }
101851
+ formatDriftOutput(result);
101852
+ process.exit(result.issues.length > 0 ? 1 : 0);
101853
+ } catch (err) {
101854
+ formatter.error(err.message);
101855
+ process.exit(1);
101426
101856
  }
101427
- );
101857
+ });
101428
101858
  }
101429
101859
  function formatDriftOutput(result) {
101430
101860
  if (result.namespacesCompared === 0) {
@@ -101517,7 +101947,7 @@ async function getHeadSha(repoRoot, runner2) {
101517
101947
  function registerReportCommand(program3, deps2) {
101518
101948
  program3.command("report").description(
101519
101949
  "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("--json", "Output full report as JSON").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(
101950
+ ).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
101951
  async (options) => {
101522
101952
  try {
101523
101953
  const repoRoot = program3.opts().dir || process.cwd();
@@ -101529,7 +101959,7 @@ function registerReportCommand(program3, deps2) {
101529
101959
  deps2.runner
101530
101960
  );
101531
101961
  await maybePush(report, options.push);
101532
- outputReport(report, options);
101962
+ outputReport(report);
101533
101963
  return;
101534
101964
  }
101535
101965
  const sopsClient = await createSopsClient(repoRoot, deps2.runner);
@@ -101547,7 +101977,7 @@ function registerReportCommand(program3, deps2) {
101547
101977
  });
101548
101978
  if (options.push) {
101549
101979
  await pushWithGapFill(repoRoot, headReport, deps2.runner);
101550
- outputReport(headReport, options);
101980
+ outputReport(headReport);
101551
101981
  return;
101552
101982
  }
101553
101983
  if (options.since) {
@@ -101561,8 +101991,8 @@ function registerReportCommand(program3, deps2) {
101561
101991
  reports.push(await generateReportAtCommit(repoRoot, sha, version2, deps2.runner));
101562
101992
  }
101563
101993
  }
101564
- if (options.json) {
101565
- formatter.raw(JSON.stringify(reports, null, 2) + "\n");
101994
+ if (isJsonMode()) {
101995
+ formatter.json(reports);
101566
101996
  } else {
101567
101997
  formatter.print(
101568
101998
  `Generated ${reports.length} report(s) for commits since ${options.since.slice(0, 8)}`
@@ -101577,7 +102007,7 @@ function registerReportCommand(program3, deps2) {
101577
102007
  process.exit(reports.some((r) => r.policy.issueCount.error > 0) ? 1 : 0);
101578
102008
  return;
101579
102009
  }
101580
- outputReport(headReport, options);
102010
+ outputReport(headReport);
101581
102011
  } catch (err) {
101582
102012
  handleCommandError(err);
101583
102013
  }
@@ -101628,9 +102058,9 @@ async function maybePush(report, push) {
101628
102058
  await pushOtlp(payload, config);
101629
102059
  formatter.success("Report pushed to telemetry endpoint.");
101630
102060
  }
101631
- function outputReport(report, opts) {
101632
- if (opts.json) {
101633
- formatter.raw(JSON.stringify(report, null, 2) + "\n");
102061
+ function outputReport(report) {
102062
+ if (isJsonMode()) {
102063
+ formatter.json(report);
101634
102064
  process.exit(report.policy.issueCount.error > 0 ? 1 : 0);
101635
102065
  return;
101636
102066
  }
@@ -101799,6 +102229,16 @@ function registerInstallCommand(program3, _deps) {
101799
102229
  }
101800
102230
  const manifestFile = files.find((f2) => f2.name === "broker.yaml");
101801
102231
  const manifest = manifestFile ? (0, import_yaml.parse)(manifestFile.content) : {};
102232
+ if (isJsonMode()) {
102233
+ formatter.json({
102234
+ broker: entry.name,
102235
+ provider: entry.provider,
102236
+ tier: entry.tier,
102237
+ files: files.map((f2) => `brokers/${entry.name}/${f2.name}`)
102238
+ });
102239
+ process.exit(0);
102240
+ return;
102241
+ }
101802
102242
  formatter.print("");
101803
102243
  formatter.print(` ${sym("success")} ${entry.name}`);
101804
102244
  formatter.print("");
@@ -101862,6 +102302,11 @@ function registerSearchCommand(program3, _deps) {
101862
102302
  if (options.tier) {
101863
102303
  results = results.filter((b) => b.tier === Number(options.tier));
101864
102304
  }
102305
+ if (isJsonMode()) {
102306
+ formatter.json(results);
102307
+ process.exit(0);
102308
+ return;
102309
+ }
101865
102310
  if (results.length === 0) {
101866
102311
  formatter.info("No brokers found matching your query.");
101867
102312
  process.exit(0);
@@ -101992,6 +102437,20 @@ ${sym("working")} Backend migration summary:`);
101992
102437
  }
101993
102438
  }
101994
102439
  );
102440
+ if (isJsonMode()) {
102441
+ formatter.json({
102442
+ backend: target.backend,
102443
+ migratedFiles: result.migratedFiles,
102444
+ skippedFiles: result.skippedFiles,
102445
+ verifiedFiles: result.verifiedFiles,
102446
+ warnings: result.warnings,
102447
+ rolledBack: result.rolledBack,
102448
+ error: result.error ?? null,
102449
+ dryRun: opts.dryRun ?? false
102450
+ });
102451
+ process.exit(result.rolledBack ? 1 : 0);
102452
+ return;
102453
+ }
101995
102454
  if (result.rolledBack) {
101996
102455
  formatter.error(`Migration failed: ${result.error}`);
101997
102456
  formatter.info("All changes have been rolled back.");
@@ -102080,7 +102539,7 @@ function registerServeCommand(program3, deps2) {
102080
102539
  const envConfig = si.environments[opts.env];
102081
102540
  if (envConfig && isKmsEnvelope(envConfig)) {
102082
102541
  const { createKmsProvider } = await Promise.resolve().then(() => __toESM(require_dist3()));
102083
- kmsProvider = createKmsProvider(envConfig.kms.provider, {
102542
+ kmsProvider = await createKmsProvider(envConfig.kms.provider, {
102084
102543
  region: envConfig.kms.region
102085
102544
  });
102086
102545
  }
@@ -102142,12 +102601,18 @@ var VERSION = package_default.version;
102142
102601
  var program2 = new Command();
102143
102602
  var runner = new NodeSubprocessRunner();
102144
102603
  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");
102604
+ 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
102605
  program2.hook("preAction", async () => {
102147
102606
  const opts = program2.opts();
102148
102607
  if (opts.plain) {
102149
102608
  setPlainMode(true);
102150
102609
  }
102610
+ if (opts.json) {
102611
+ setJsonMode(true);
102612
+ }
102613
+ if (opts.yes) {
102614
+ setYesMode(true);
102615
+ }
102151
102616
  });
102152
102617
  program2.addHelpText("beforeAll", () => {
102153
102618
  const clef = isPlainMode() ? "clef" : symbols.clef;
@@ -102246,6 +102711,9 @@ async function main() {
102246
102711
  }
102247
102712
  }
102248
102713
  main().catch((err) => {
102714
+ if (isJsonMode()) {
102715
+ exitJsonError(err.message);
102716
+ }
102249
102717
  formatter.error(err.message);
102250
102718
  process.exit(1);
102251
102719
  });