@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.mjs CHANGED
@@ -22358,7 +22358,12 @@ var VALID_KMS_PROVIDERS;
22358
22358
  var init_types2 = __esm({
22359
22359
  "../core/src/kms/types.ts"() {
22360
22360
  "use strict";
22361
- VALID_KMS_PROVIDERS = ["aws", "gcp", "azure"];
22361
+ VALID_KMS_PROVIDERS = [
22362
+ "aws",
22363
+ "gcp",
22364
+ "azure",
22365
+ "cloud"
22366
+ ];
22362
22367
  }
22363
22368
  });
22364
22369
 
@@ -95224,10 +95229,177 @@ var require_azure = __commonJS({
95224
95229
  }
95225
95230
  });
95226
95231
 
95227
- // ../runtime/dist/kms/index.js
95232
+ // ../client/dist/kms.js
95228
95233
  var require_kms = __commonJS({
95234
+ "../client/dist/kms.js"(exports, module) {
95235
+ "use strict";
95236
+ var __defProp2 = Object.defineProperty;
95237
+ var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
95238
+ var __getOwnPropNames2 = Object.getOwnPropertyNames;
95239
+ var __hasOwnProp2 = Object.prototype.hasOwnProperty;
95240
+ var __export2 = (target, all) => {
95241
+ for (var name in all)
95242
+ __defProp2(target, name, { get: all[name], enumerable: true });
95243
+ };
95244
+ var __copyProps2 = (to, from, except, desc) => {
95245
+ if (from && typeof from === "object" || typeof from === "function") {
95246
+ for (let key of __getOwnPropNames2(from))
95247
+ if (!__hasOwnProp2.call(to, key) && key !== except)
95248
+ __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
95249
+ }
95250
+ return to;
95251
+ };
95252
+ var __toCommonJS = (mod3) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod3);
95253
+ var kms_exports = {};
95254
+ __export2(kms_exports, {
95255
+ ClefClientError: () => ClefClientError,
95256
+ CloudKmsProvider: () => CloudKmsProvider
95257
+ });
95258
+ module.exports = __toCommonJS(kms_exports);
95259
+ var ClefClientError = class extends Error {
95260
+ constructor(message, statusCode, fix) {
95261
+ super(message);
95262
+ this.statusCode = statusCode;
95263
+ this.fix = fix;
95264
+ this.name = "ClefClientError";
95265
+ }
95266
+ statusCode;
95267
+ fix;
95268
+ };
95269
+ function resolveToken(explicit) {
95270
+ if (explicit) return explicit;
95271
+ if (typeof process !== "undefined" && process.env?.CLEF_SERVICE_TOKEN) {
95272
+ return process.env.CLEF_SERVICE_TOKEN;
95273
+ }
95274
+ throw new ClefClientError(
95275
+ "No service token configured",
95276
+ void 0,
95277
+ "Set CLEF_SERVICE_TOKEN or pass token in options."
95278
+ );
95279
+ }
95280
+ async function request(baseUrl, opts2) {
95281
+ const url = `${baseUrl}${opts2.path}`;
95282
+ const headers = {
95283
+ Authorization: `Bearer ${opts2.token}`,
95284
+ Accept: "application/json"
95285
+ };
95286
+ if (opts2.body !== void 0) {
95287
+ headers["Content-Type"] = "application/json";
95288
+ }
95289
+ const init = {
95290
+ method: opts2.method,
95291
+ headers,
95292
+ body: opts2.body !== void 0 ? JSON.stringify(opts2.body) : void 0
95293
+ };
95294
+ let response;
95295
+ try {
95296
+ response = await opts2.fetchFn(url, init);
95297
+ } catch (err) {
95298
+ try {
95299
+ response = await opts2.fetchFn(url, init);
95300
+ } catch {
95301
+ throw new ClefClientError(
95302
+ `Connection failed: ${err.message}`,
95303
+ void 0,
95304
+ "Is the endpoint reachable? Check your CLEF_ENDPOINT setting."
95305
+ );
95306
+ }
95307
+ }
95308
+ if (response.status >= 500) {
95309
+ response = await opts2.fetchFn(url, init);
95310
+ }
95311
+ if (response.status === 401) {
95312
+ throw new ClefClientError("Authentication failed", 401, "Check your CLEF_SERVICE_TOKEN.");
95313
+ }
95314
+ if (response.status === 503) {
95315
+ throw new ClefClientError("Secrets expired or not loaded", 503, "Check the agent logs.");
95316
+ }
95317
+ if (!response.ok) {
95318
+ const text = await response.text().catch(() => "");
95319
+ throw new ClefClientError(
95320
+ `HTTP ${response.status}: ${text || response.statusText}`,
95321
+ response.status
95322
+ );
95323
+ }
95324
+ const json = await response.json();
95325
+ if (json && typeof json === "object" && "success" in json) {
95326
+ if (!json.success) {
95327
+ throw new ClefClientError(json.message || "Request failed", response.status);
95328
+ }
95329
+ return json.data;
95330
+ }
95331
+ return json;
95332
+ }
95333
+ var CloudKmsProvider = class {
95334
+ endpoint;
95335
+ token;
95336
+ constructor(options) {
95337
+ this.endpoint = options.endpoint;
95338
+ this.token = resolveToken(options.token);
95339
+ }
95340
+ async wrap(_keyId, _plaintext) {
95341
+ throw new ClefClientError(
95342
+ "CloudKmsProvider.wrap() is not supported. Use the keyservice sidecar for encryption."
95343
+ );
95344
+ }
95345
+ async unwrap(keyId, wrappedKey, _algorithm) {
95346
+ const result = await request(this.endpoint, {
95347
+ method: "POST",
95348
+ path: "/api/v1/cloud/kms/decrypt",
95349
+ body: {
95350
+ keyArn: keyId,
95351
+ ciphertext: wrappedKey.toString("base64")
95352
+ },
95353
+ token: this.token,
95354
+ fetchFn: globalThis.fetch
95355
+ });
95356
+ return Buffer.from(result.plaintext, "base64");
95357
+ }
95358
+ };
95359
+ }
95360
+ });
95361
+
95362
+ // ../runtime/dist/kms/index.js
95363
+ var require_kms2 = __commonJS({
95229
95364
  "../runtime/dist/kms/index.js"(exports) {
95230
95365
  "use strict";
95366
+ var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
95367
+ if (k2 === void 0) k2 = k;
95368
+ var desc = Object.getOwnPropertyDescriptor(m, k);
95369
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
95370
+ desc = { enumerable: true, get: function() {
95371
+ return m[k];
95372
+ } };
95373
+ }
95374
+ Object.defineProperty(o, k2, desc);
95375
+ }) : (function(o, m, k, k2) {
95376
+ if (k2 === void 0) k2 = k;
95377
+ o[k2] = m[k];
95378
+ }));
95379
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
95380
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
95381
+ }) : function(o, v) {
95382
+ o["default"] = v;
95383
+ });
95384
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ (function() {
95385
+ var ownKeys = function(o) {
95386
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
95387
+ var ar = [];
95388
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
95389
+ return ar;
95390
+ };
95391
+ return ownKeys(o);
95392
+ };
95393
+ return function(mod3) {
95394
+ if (mod3 && mod3.__esModule) return mod3;
95395
+ var result = {};
95396
+ if (mod3 != null) {
95397
+ for (var k = ownKeys(mod3), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod3, k[i]);
95398
+ }
95399
+ __setModuleDefault(result, mod3);
95400
+ return result;
95401
+ };
95402
+ })();
95231
95403
  Object.defineProperty(exports, "__esModule", { value: true });
95232
95404
  exports.AzureKmsProvider = exports.GcpKmsProvider = exports.AwsKmsProvider = void 0;
95233
95405
  exports.createKmsProvider = createKmsProvider;
@@ -95246,7 +95418,7 @@ var require_kms = __commonJS({
95246
95418
  Object.defineProperty(exports, "AzureKmsProvider", { enumerable: true, get: function() {
95247
95419
  return azure_2.AzureKmsProvider;
95248
95420
  } });
95249
- function createKmsProvider(provider, options) {
95421
+ async function createKmsProvider(provider, options) {
95250
95422
  switch (provider) {
95251
95423
  case "aws":
95252
95424
  return new aws_1.AwsKmsProvider(options?.region);
@@ -95254,6 +95426,17 @@ var require_kms = __commonJS({
95254
95426
  return new gcp_1.GcpKmsProvider();
95255
95427
  case "azure":
95256
95428
  return new azure_1.AzureKmsProvider();
95429
+ case "cloud": {
95430
+ try {
95431
+ const { CloudKmsProvider } = await Promise.resolve().then(() => __importStar(require_kms()));
95432
+ return new CloudKmsProvider({
95433
+ endpoint: options?.endpoint ?? "",
95434
+ token: options?.token
95435
+ });
95436
+ } catch {
95437
+ throw new Error("Clef Cloud KMS requires @clef-sh/client. Install it with: npm install @clef-sh/client");
95438
+ }
95439
+ }
95257
95440
  default:
95258
95441
  throw new Error(`Unknown KMS provider: ${provider}`);
95259
95442
  }
@@ -95306,7 +95489,7 @@ var require_artifact_decryptor = __commonJS({
95306
95489
  exports.ArtifactDecryptor = void 0;
95307
95490
  var crypto6 = __importStar(__require("crypto"));
95308
95491
  var decrypt_1 = require_decrypt();
95309
- var kms_1 = require_kms();
95492
+ var kms_1 = require_kms2();
95310
95493
  var ArtifactDecryptor = class {
95311
95494
  ageDecryptor = new decrypt_1.AgeDecryptor();
95312
95495
  privateKey;
@@ -95355,7 +95538,7 @@ var require_artifact_decryptor = __commonJS({
95355
95538
  const envelope = artifact.envelope;
95356
95539
  let dek;
95357
95540
  try {
95358
- const kms = (0, kms_1.createKmsProvider)(envelope.provider);
95541
+ const kms = await (0, kms_1.createKmsProvider)(envelope.provider);
95359
95542
  const wrappedKey = Buffer.from(envelope.wrappedKey, "base64");
95360
95543
  dek = await kms.unwrap(envelope.keyId, wrappedKey, envelope.algorithm);
95361
95544
  } catch (err) {
@@ -96623,11 +96806,11 @@ var require_dist3 = __commonJS({
96623
96806
  Object.defineProperty(exports, "createVcsProvider", { enumerable: true, get: function() {
96624
96807
  return index_1.createVcsProvider;
96625
96808
  } });
96626
- var kms_1 = require_kms();
96809
+ var kms_1 = require_kms2();
96627
96810
  Object.defineProperty(exports, "AwsKmsProvider", { enumerable: true, get: function() {
96628
96811
  return kms_1.AwsKmsProvider;
96629
96812
  } });
96630
- var kms_2 = require_kms();
96813
+ var kms_2 = require_kms2();
96631
96814
  Object.defineProperty(exports, "createKmsProvider", { enumerable: true, get: function() {
96632
96815
  return kms_2.createKmsProvider;
96633
96816
  } });
@@ -97715,11 +97898,23 @@ function sym(key) {
97715
97898
  }
97716
97899
 
97717
97900
  // src/output/formatter.ts
97901
+ var _jsonMode = false;
97902
+ var _yesMode = false;
97903
+ function setJsonMode(json) {
97904
+ _jsonMode = json;
97905
+ }
97906
+ function isJsonMode() {
97907
+ return _jsonMode;
97908
+ }
97909
+ function setYesMode(yes) {
97910
+ _yesMode = yes;
97911
+ }
97718
97912
  function color(fn, str2) {
97719
97913
  return isPlainMode() ? str2 : fn(str2);
97720
97914
  }
97721
97915
  var formatter = {
97722
97916
  success(message) {
97917
+ if (_jsonMode) return;
97723
97918
  const icon = sym("success");
97724
97919
  process.stdout.write(color(import_picocolors.default.green, `${icon} ${message}`) + "\n");
97725
97920
  },
@@ -97736,15 +97931,18 @@ var formatter = {
97736
97931
  process.stderr.write(color(import_picocolors.default.yellow, `${icon} ${message}`) + "\n");
97737
97932
  },
97738
97933
  info(message) {
97934
+ if (_jsonMode) return;
97739
97935
  const icon = sym("info");
97740
97936
  process.stdout.write(color(import_picocolors.default.blue, `${icon} ${message}`) + "\n");
97741
97937
  },
97742
97938
  hint(message) {
97939
+ if (_jsonMode) return;
97743
97940
  const icon = sym("arrow");
97744
97941
  process.stdout.write(`${icon} ${message}
97745
97942
  `);
97746
97943
  },
97747
97944
  keyValue(key, value) {
97945
+ if (_jsonMode) return;
97748
97946
  const icon = sym("key");
97749
97947
  const arrow = sym("arrow");
97750
97948
  const prefix2 = icon ? `${icon} ` : "";
@@ -97752,30 +97950,38 @@ var formatter = {
97752
97950
  `);
97753
97951
  },
97754
97952
  pendingItem(key, days) {
97953
+ if (_jsonMode) return;
97755
97954
  const icon = sym("pending");
97756
97955
  const prefix2 = icon ? `${icon} ` : "[pending] ";
97757
97956
  process.stdout.write(`${prefix2}${key} \u2014 ${days} day${days !== 1 ? "s" : ""} pending
97758
97957
  `);
97759
97958
  },
97760
97959
  recipientItem(label2, keyPreview2) {
97960
+ if (_jsonMode) return;
97761
97961
  const icon = sym("recipient");
97762
97962
  const prefix2 = icon ? `${icon} ` : "";
97763
97963
  process.stdout.write(`${prefix2}${label2.padEnd(15)}${keyPreview2}
97764
97964
  `);
97765
97965
  },
97766
97966
  section(label2) {
97967
+ if (_jsonMode) return;
97767
97968
  process.stdout.write(`
97768
97969
  ${label2}
97769
97970
 
97770
97971
  `);
97771
97972
  },
97772
97973
  print(message) {
97974
+ if (_jsonMode) return;
97773
97975
  process.stdout.write(message + "\n");
97774
97976
  },
97775
97977
  raw(message) {
97776
97978
  process.stdout.write(message);
97777
97979
  },
97980
+ json(data) {
97981
+ process.stdout.write(JSON.stringify(data) + "\n");
97982
+ },
97778
97983
  table(rows, columns) {
97984
+ if (_jsonMode) return;
97779
97985
  const widths = columns.map((col, i) => {
97780
97986
  const maxDataWidth = rows.reduce(
97781
97987
  (max, row) => Math.max(max, stripAnsi(row[i] ?? "").length),
@@ -97792,6 +97998,10 @@ ${label2}
97792
97998
  }
97793
97999
  },
97794
98000
  async confirm(prompt) {
98001
+ if (_yesMode) return true;
98002
+ if (_jsonMode) {
98003
+ throw new Error("--json requires --yes for destructive operations");
98004
+ }
97795
98005
  const rl = readline.createInterface({
97796
98006
  input: process.stdin,
97797
98007
  output: process.stderr
@@ -97826,6 +98036,9 @@ ${label2}
97826
98036
  `);
97827
98037
  },
97828
98038
  async secretPrompt(prompt) {
98039
+ if (_jsonMode) {
98040
+ throw new Error("--json mode requires value on the command line (no interactive prompt)");
98041
+ }
97829
98042
  return new Promise((resolve7, reject) => {
97830
98043
  process.stderr.write(color(import_picocolors.default.cyan, `${prompt}: `));
97831
98044
  if (process.stdin.isTTY) {
@@ -97868,7 +98081,14 @@ function pad(str2, width) {
97868
98081
  }
97869
98082
 
97870
98083
  // src/handle-error.ts
98084
+ function exitJsonError(message) {
98085
+ formatter.json({ error: true, message });
98086
+ process.exit(1);
98087
+ }
97871
98088
  function handleCommandError(err) {
98089
+ if (isJsonMode()) {
98090
+ exitJsonError(err.message);
98091
+ }
97872
98092
  if (err instanceof SopsMissingError || err instanceof SopsVersionError) {
97873
98093
  formatter.formatDependencyError(err);
97874
98094
  } else {
@@ -98368,7 +98588,7 @@ async function handleSecondDevOnboarding(repoRoot, clefConfigPath, deps2, option
98368
98588
  "OS keychain is not available on this system.\n The private key will be written to the filesystem instead.\n See https://docs.clef.sh/guide/key-storage for security implications."
98369
98589
  );
98370
98590
  let keyPath;
98371
- if (options.nonInteractive || !process.stdin.isTTY) {
98591
+ if (options.nonInteractive || isJsonMode() || !process.stdin.isTTY) {
98372
98592
  keyPath = process.env.CLEF_AGE_KEY_FILE || defaultAgeKeyPath(label2);
98373
98593
  keyPath = path23.resolve(keyPath);
98374
98594
  } else {
@@ -98401,6 +98621,10 @@ async function handleSecondDevOnboarding(repoRoot, clefConfigPath, deps2, option
98401
98621
  formatter.success("Created .clef/.gitignore");
98402
98622
  }
98403
98623
  formatter.success(`Key label: ${label2}`);
98624
+ if (isJsonMode()) {
98625
+ formatter.json({ action: "onboarded", manifest: "clef.yaml", config: ".clef/config.yaml" });
98626
+ return;
98627
+ }
98404
98628
  formatter.section("Next steps:");
98405
98629
  formatter.hint("clef recipients request \u2014 request access to encrypted secrets");
98406
98630
  formatter.hint("clef update \u2014 scaffold new environments");
@@ -98411,7 +98635,7 @@ async function handleFullSetup(repoRoot, manifestPath, clefConfigPath, deps2, op
98411
98635
  let namespaces = options.namespaces ? options.namespaces.split(",").map((s) => s.trim()) : [];
98412
98636
  const backend = options.backend ?? "age";
98413
98637
  let secretsDir = options.secretsDir ?? "secrets";
98414
- if (!options.nonInteractive && process.stdin.isTTY) {
98638
+ if (!options.nonInteractive && !isJsonMode() && process.stdin.isTTY) {
98415
98639
  const envAnswer = await promptWithDefault(
98416
98640
  "Environments (comma-separated)",
98417
98641
  environments.join(",")
@@ -98466,7 +98690,7 @@ async function handleFullSetup(repoRoot, manifestPath, clefConfigPath, deps2, op
98466
98690
  formatter.warn(
98467
98691
  "OS keychain is not available on this system.\n The private key must be written to the filesystem instead.\n See https://docs.clef.sh/guide/key-storage for security implications."
98468
98692
  );
98469
- if (!options.nonInteractive && process.stdin.isTTY) {
98693
+ if (!options.nonInteractive && !isJsonMode() && process.stdin.isTTY) {
98470
98694
  const confirmed = await formatter.confirm("Write the private key to the filesystem?");
98471
98695
  if (!confirmed) {
98472
98696
  formatter.error(
@@ -98477,7 +98701,7 @@ async function handleFullSetup(repoRoot, manifestPath, clefConfigPath, deps2, op
98477
98701
  }
98478
98702
  }
98479
98703
  let keyPath;
98480
- if (options.nonInteractive || !process.stdin.isTTY) {
98704
+ if (options.nonInteractive || isJsonMode() || !process.stdin.isTTY) {
98481
98705
  keyPath = defaultAgeKeyPath(label2);
98482
98706
  if (await isInsideAnyGitRepo(path23.resolve(keyPath))) {
98483
98707
  throw new Error(
@@ -98597,6 +98821,17 @@ async function handleFullSetup(repoRoot, manifestPath, clefConfigPath, deps2, op
98597
98821
  formatter.print(" clef config set analytics false (permanent)\n");
98598
98822
  } catch {
98599
98823
  }
98824
+ if (isJsonMode()) {
98825
+ formatter.json({
98826
+ action: "initialized",
98827
+ manifest: "clef.yaml",
98828
+ environments: manifest.environments.map((e) => e.name),
98829
+ namespaces: manifest.namespaces.map((n) => n.name),
98830
+ backend,
98831
+ scaffolded: scaffoldedCount
98832
+ });
98833
+ return;
98834
+ }
98600
98835
  formatter.section("Next steps:");
98601
98836
  formatter.hint("clef set <namespace>/<env> <KEY> <value> \u2014 add a secret");
98602
98837
  formatter.hint("clef scan \u2014 check for existing plaintext secrets");
@@ -98857,7 +99092,9 @@ function registerGetCommand(program3, deps2) {
98857
99092
  return;
98858
99093
  }
98859
99094
  const val = decrypted.values[key];
98860
- if (opts2.raw) {
99095
+ if (isJsonMode()) {
99096
+ formatter.json({ key, value: val, namespace, environment });
99097
+ } else if (opts2.raw) {
98861
99098
  formatter.raw(val);
98862
99099
  } else {
98863
99100
  const copied = copyToClipboard(val);
@@ -98962,6 +99199,16 @@ function registerSetCommand(program3, deps2) {
98962
99199
  pendingErrors.push(env.name);
98963
99200
  }
98964
99201
  }
99202
+ if (isJsonMode()) {
99203
+ formatter.json({
99204
+ key,
99205
+ namespace: namespace2,
99206
+ environments: manifest.environments.map((e) => e.name),
99207
+ action: "created",
99208
+ pending: true
99209
+ });
99210
+ return;
99211
+ }
98965
99212
  formatter.success(
98966
99213
  `'${key}' set in ${namespace2} across all environments ${sym("locked")}`
98967
99214
  );
@@ -98985,6 +99232,16 @@ function registerSetCommand(program3, deps2) {
98985
99232
  } catch {
98986
99233
  }
98987
99234
  }
99235
+ if (isJsonMode()) {
99236
+ formatter.json({
99237
+ key,
99238
+ namespace: namespace2,
99239
+ environments: manifest.environments.map((e) => e.name),
99240
+ action: "created",
99241
+ pending: false
99242
+ });
99243
+ return;
99244
+ }
98988
99245
  formatter.success(`'${key}' set in ${namespace2} across all environments`);
98989
99246
  formatter.hint(`git add ${namespace2}/ # stage all updated files`);
98990
99247
  }
@@ -99048,6 +99305,10 @@ function registerSetCommand(program3, deps2) {
99048
99305
  process.exit(1);
99049
99306
  return;
99050
99307
  }
99308
+ if (isJsonMode()) {
99309
+ formatter.json({ key, namespace, environment, action: "created", pending: true });
99310
+ return;
99311
+ }
99051
99312
  formatter.success(`${key} set in ${namespace}/${environment} ${sym("locked")}`);
99052
99313
  formatter.print(
99053
99314
  ` ${sym("pending")} Marked as pending \u2014 replace with a real value before deploying`
@@ -99062,6 +99323,10 @@ function registerSetCommand(program3, deps2) {
99062
99323
  The value is saved. Run clef lint to check for stale pending markers.`
99063
99324
  );
99064
99325
  }
99326
+ if (isJsonMode()) {
99327
+ formatter.json({ key, namespace, environment, action: "created", pending: false });
99328
+ return;
99329
+ }
99065
99330
  formatter.success(`${key} set in ${namespace}/${environment}`);
99066
99331
  formatter.hint(
99067
99332
  `Commit: git add ${manifest.file_pattern.replace("{namespace}", namespace).replace("{environment}", environment)}`
@@ -99130,7 +99395,10 @@ function registerCompareCommand(program3, deps2) {
99130
99395
  compareBuf.copy(paddedCompare);
99131
99396
  const timingEqual = crypto5.timingSafeEqual(paddedStored, paddedCompare);
99132
99397
  const match = storedBuf.length === compareBuf.length && timingEqual;
99133
- if (match) {
99398
+ if (isJsonMode()) {
99399
+ formatter.json({ match, key, namespace, environment });
99400
+ if (!match) process.exit(1);
99401
+ } else if (match) {
99134
99402
  formatter.success(`${key} ${sym("arrow")} values match`);
99135
99403
  } else {
99136
99404
  formatter.failure(`${key} ${sym("arrow")} values do not match`);
@@ -99178,6 +99446,15 @@ Type the key name to confirm:`
99178
99446
  }
99179
99447
  const bulkOps = new BulkOps();
99180
99448
  await bulkOps.deleteAcrossEnvironments(namespace, key, manifest, sopsClient, repoRoot);
99449
+ if (isJsonMode()) {
99450
+ formatter.json({
99451
+ key,
99452
+ namespace,
99453
+ environments: manifest.environments.map((e) => e.name),
99454
+ action: "deleted"
99455
+ });
99456
+ return;
99457
+ }
99181
99458
  formatter.success(`Deleted '${key}' from ${namespace} in all environments`);
99182
99459
  } else {
99183
99460
  const [namespace, environment] = parseTarget(target);
@@ -99216,6 +99493,10 @@ Type the key name to confirm:`
99216
99493
  `Key deleted but pending metadata could not be cleaned up. Run clef lint to verify.`
99217
99494
  );
99218
99495
  }
99496
+ if (isJsonMode()) {
99497
+ formatter.json({ key, namespace, environment, action: "deleted" });
99498
+ return;
99499
+ }
99219
99500
  formatter.success(`Deleted '${key}' from ${namespace}/${environment}`);
99220
99501
  formatter.hint(
99221
99502
  `Commit: git add ${manifest.file_pattern.replace("{namespace}", namespace).replace("{environment}", environment)}`
@@ -99234,10 +99515,11 @@ Type the key name to confirm:`
99234
99515
  var import_picocolors2 = __toESM(require_picocolors());
99235
99516
  init_src();
99236
99517
  import * as path33 from "path";
99518
+ var MASKED = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
99237
99519
  function registerDiffCommand(program3, deps2) {
99238
99520
  program3.command("diff <namespace> <env-a> <env-b>").description(
99239
99521
  "Compare secrets between two environments for a namespace.\n\nExit codes:\n 0 No differences\n 1 Differences found"
99240
- ).option("--show-identical", "Include identical keys in the output").option("--show-values", "Show plaintext values instead of masking them").option("--json", "Output raw DiffResult JSON").action(
99522
+ ).option("--show-identical", "Include identical keys in the output").option("--show-values", "Show plaintext values instead of masking them").action(
99241
99523
  async (namespace, envA, envB, options) => {
99242
99524
  try {
99243
99525
  const repoRoot = program3.opts().dir || process.cwd();
@@ -99264,19 +99546,20 @@ function registerDiffCommand(program3, deps2) {
99264
99546
  formatter.warn("Warning: printing plaintext values for protected environment.");
99265
99547
  }
99266
99548
  }
99267
- if (options.json) {
99268
- const jsonOutput = options.showValues ? result : {
99549
+ if (isJsonMode()) {
99550
+ const jsonData = options.showValues ? result : {
99269
99551
  ...result,
99270
99552
  rows: result.rows.map((r) => ({
99271
99553
  ...r,
99272
- valueA: r.valueA !== null ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : null,
99273
- valueB: r.valueB !== null ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : null,
99554
+ valueA: r.valueA !== null ? MASKED : null,
99555
+ valueB: r.valueB !== null ? MASKED : null,
99274
99556
  masked: true
99275
99557
  }))
99276
99558
  };
99277
- formatter.raw(JSON.stringify(jsonOutput, null, 2) + "\n");
99559
+ formatter.json(jsonData);
99278
99560
  const hasDiffs2 = result.rows.some((r) => r.status !== "identical");
99279
99561
  process.exit(hasDiffs2 ? 1 : 0);
99562
+ return;
99280
99563
  }
99281
99564
  formatDiffOutput(
99282
99565
  result,
@@ -99296,7 +99579,6 @@ function registerDiffCommand(program3, deps2) {
99296
99579
  }
99297
99580
  );
99298
99581
  }
99299
- var MASKED = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
99300
99582
  function formatDiffOutput(result, envA, envB, showIdentical, showValues) {
99301
99583
  const filteredRows = showIdentical ? result.rows : result.rows.filter((r) => r.status !== "identical");
99302
99584
  if (filteredRows.length === 0) {
@@ -99578,7 +99860,7 @@ async function fetchCheckpoint(config) {
99578
99860
  }
99579
99861
 
99580
99862
  // package.json
99581
- var version2 = "0.1.12";
99863
+ var version2 = "0.1.13-beta.92";
99582
99864
  var package_default = {
99583
99865
  name: "@clef-sh/cli",
99584
99866
  version: version2,
@@ -99649,7 +99931,7 @@ var package_default = {
99649
99931
  function registerLintCommand(program3, deps2) {
99650
99932
  program3.command("lint").description(
99651
99933
  "Full repo health check \u2014 matrix completeness, schema validation, SOPS integrity.\n\nExit codes:\n 0 No errors (warnings are allowed)\n 1 Errors found"
99652
- ).option("--fix", "Auto-fix safe issues (scaffold missing files)").option("--json", "Output raw LintResult JSON").option("--push", "Push results as OTLP to CLEF_TELEMETRY_URL").action(async (options) => {
99934
+ ).option("--fix", "Auto-fix safe issues (scaffold missing files)").option("--push", "Push results as OTLP to CLEF_TELEMETRY_URL").action(async (options) => {
99653
99935
  try {
99654
99936
  const repoRoot = program3.opts().dir || process.cwd();
99655
99937
  const parser = new ManifestParser();
@@ -99684,8 +99966,8 @@ function registerLintCommand(program3, deps2) {
99684
99966
  await pushOtlp(payload, config);
99685
99967
  formatter.success("Lint results pushed to telemetry endpoint.");
99686
99968
  }
99687
- if (options.json) {
99688
- formatter.raw(JSON.stringify(result, null, 2) + "\n");
99969
+ if (isJsonMode()) {
99970
+ formatter.json(result);
99689
99971
  const hasErrors2 = result.issues.some((i) => i.severity === "error");
99690
99972
  process.exit(hasErrors2 ? 1 : 0);
99691
99973
  return;
@@ -99794,6 +100076,10 @@ function registerRotateCommand(program3, deps2) {
99794
100076
  const relativeFile = manifest.file_pattern.replace("{namespace}", namespace).replace("{environment}", environment);
99795
100077
  formatter.print(`${sym("working")} Rotating ${namespace}/${environment}...`);
99796
100078
  await sopsClient.reEncrypt(filePath, options.newKey);
100079
+ if (isJsonMode()) {
100080
+ formatter.json({ namespace, environment, file: relativeFile, action: "rotated" });
100081
+ return;
100082
+ }
99797
100083
  formatter.success(`Rotated. New values encrypted. ${sym("locked")}`);
99798
100084
  formatter.hint(
99799
100085
  `git add ${relativeFile} && git commit -m "rotate: ${namespace}/${environment}"`
@@ -99839,15 +100125,28 @@ function registerHooksCommand(program3, deps2) {
99839
100125
  }
99840
100126
  const git = new GitIntegration(deps2.runner);
99841
100127
  await git.installPreCommitHook(repoRoot);
100128
+ let mergeDriverOk = false;
100129
+ try {
100130
+ await git.installMergeDriver(repoRoot);
100131
+ mergeDriverOk = true;
100132
+ } catch {
100133
+ }
100134
+ if (isJsonMode()) {
100135
+ formatter.json({
100136
+ preCommitHook: true,
100137
+ mergeDriver: mergeDriverOk,
100138
+ hookPath
100139
+ });
100140
+ return;
100141
+ }
99842
100142
  formatter.success("Pre-commit hook installed");
99843
100143
  formatter.print(` ${sym("pending")} ${hookPath}`);
99844
100144
  formatter.hint(
99845
100145
  "Hook checks SOPS metadata on staged .enc files and runs: clef scan --staged"
99846
100146
  );
99847
- try {
99848
- await git.installMergeDriver(repoRoot);
100147
+ if (mergeDriverOk) {
99849
100148
  formatter.success("SOPS merge driver configured");
99850
- } catch {
100149
+ } else {
99851
100150
  formatter.warn("Could not configure SOPS merge driver. Run inside a git repository.");
99852
100151
  }
99853
100152
  } catch (err) {
@@ -100146,6 +100445,14 @@ Usage: clef export payments/production --format env`
100146
100445
  );
100147
100446
  try {
100148
100447
  const decrypted = await sopsClient.decrypt(filePath);
100448
+ if (isJsonMode()) {
100449
+ const pairs = Object.entries(decrypted.values).map(([k, v]) => ({
100450
+ key: k,
100451
+ value: v
100452
+ }));
100453
+ formatter.json({ pairs, namespace, environment });
100454
+ return;
100455
+ }
100149
100456
  const consumption = new ConsumptionClient();
100150
100457
  const output = consumption.formatExport(decrypted, "env", !options.export);
100151
100458
  if (options.raw) {
@@ -100186,7 +100493,7 @@ import * as path41 from "path";
100186
100493
  function registerDoctorCommand(program3, deps2) {
100187
100494
  program3.command("doctor").description(
100188
100495
  "Check your environment for required dependencies and configuration.\n\nExit codes:\n 0 All checks pass\n 1 One or more checks failed"
100189
- ).option("--json", "Output the full status as JSON").option("--fix", "Attempt to auto-fix issues").action(async (options) => {
100496
+ ).option("--fix", "Attempt to auto-fix issues").action(async (options) => {
100190
100497
  const repoRoot = program3.opts().dir || process.cwd();
100191
100498
  const clefVersion = program3.version() ?? "unknown";
100192
100499
  const checks = [];
@@ -100273,7 +100580,7 @@ function registerDoctorCommand(program3, deps2) {
100273
100580
  formatter.warn("--fix cannot resolve these issues automatically.");
100274
100581
  }
100275
100582
  }
100276
- if (options.json) {
100583
+ if (isJsonMode()) {
100277
100584
  const json = {
100278
100585
  clef: { version: clefVersion, ok: true },
100279
100586
  sops: {
@@ -100303,7 +100610,7 @@ function registerDoctorCommand(program3, deps2) {
100303
100610
  ok: mergeDriverOk
100304
100611
  }
100305
100612
  };
100306
- formatter.raw(JSON.stringify(json, null, 2) + "\n");
100613
+ formatter.json(json);
100307
100614
  const hasFailures = checks.some((c) => !c.ok);
100308
100615
  process.exit(hasFailures ? 1 : 0);
100309
100616
  return;
@@ -100437,6 +100744,11 @@ function registerUpdateCommand(program3, deps2) {
100437
100744
  );
100438
100745
  }
100439
100746
  }
100747
+ if (isJsonMode()) {
100748
+ formatter.json({ scaffolded: scaffoldedCount, failed: failedCount });
100749
+ process.exit(failedCount > 0 ? 1 : 0);
100750
+ return;
100751
+ }
100440
100752
  if (scaffoldedCount > 0) {
100441
100753
  formatter.success(`Scaffolded ${scaffoldedCount} encrypted file(s)`);
100442
100754
  }
@@ -100462,69 +100774,57 @@ function registerScanCommand(program3, deps2) {
100462
100774
  "--severity <level>",
100463
100775
  "Detection level: all (patterns+entropy) or high (patterns only)",
100464
100776
  "all"
100465
- ).option("--json", "Output machine-readable JSON").action(
100466
- async (paths, options) => {
100467
- const repoRoot = program3.opts().dir || process.cwd();
100468
- let manifest;
100469
- try {
100470
- const parser = new ManifestParser();
100471
- manifest = parser.parse(path43.join(repoRoot, "clef.yaml"));
100472
- } catch (err) {
100473
- if (err instanceof ManifestValidationError || err.message?.includes("clef.yaml")) {
100474
- formatter.error("No clef.yaml found. Run 'clef init' to set up this repository.");
100475
- } else {
100476
- formatter.error(err.message);
100477
- }
100478
- process.exit(2);
100479
- return;
100480
- }
100481
- if (options.severity && options.severity !== "all" && options.severity !== "high") {
100482
- formatter.error(`Invalid severity '${options.severity}'. Must be 'all' or 'high'.`);
100483
- process.exit(2);
100484
- return;
100485
- }
100486
- const severity = options.severity === "high" ? "high" : "all";
100487
- const scanRunner = new ScanRunner(deps2.runner);
100488
- if (!options.json) {
100489
- formatter.print(import_picocolors4.default.dim("Scanning repository for unencrypted secrets..."));
100490
- }
100491
- let result;
100492
- try {
100493
- result = await scanRunner.scan(repoRoot, manifest, {
100494
- stagedOnly: options.staged,
100495
- paths: paths.length > 0 ? paths : void 0,
100496
- severity
100497
- });
100498
- } catch (err) {
100499
- formatter.error(`Scan failed: ${err.message}`);
100500
- process.exit(2);
100501
- return;
100502
- }
100503
- if (options.json) {
100504
- const totalIssues = result.matches.length + result.unencryptedMatrixFiles.length;
100505
- formatter.raw(
100506
- JSON.stringify(
100507
- {
100508
- matches: result.matches,
100509
- unencryptedMatrixFiles: result.unencryptedMatrixFiles,
100510
- filesScanned: result.filesScanned,
100511
- filesSkipped: result.filesSkipped,
100512
- durationMs: result.durationMs,
100513
- summary: `${totalIssues} issue${totalIssues !== 1 ? "s" : ""} found`
100514
- },
100515
- null,
100516
- 2
100517
- ) + "\n"
100518
- );
100519
- const hasIssues2 = result.matches.length > 0 || result.unencryptedMatrixFiles.length > 0;
100520
- process.exit(hasIssues2 ? 1 : 0);
100521
- return;
100777
+ ).action(async (paths, options) => {
100778
+ const repoRoot = program3.opts().dir || process.cwd();
100779
+ let manifest;
100780
+ try {
100781
+ const parser = new ManifestParser();
100782
+ manifest = parser.parse(path43.join(repoRoot, "clef.yaml"));
100783
+ } catch (err) {
100784
+ if (err instanceof ManifestValidationError || err.message?.includes("clef.yaml")) {
100785
+ formatter.error("No clef.yaml found. Run 'clef init' to set up this repository.");
100786
+ } else {
100787
+ formatter.error(err.message);
100522
100788
  }
100523
- formatScanOutput(result);
100524
- const hasIssues = result.matches.length > 0 || result.unencryptedMatrixFiles.length > 0;
100525
- process.exit(hasIssues ? 1 : 0);
100789
+ process.exit(2);
100790
+ return;
100526
100791
  }
100527
- );
100792
+ if (options.severity && options.severity !== "all" && options.severity !== "high") {
100793
+ formatter.error(`Invalid severity '${options.severity}'. Must be 'all' or 'high'.`);
100794
+ process.exit(2);
100795
+ return;
100796
+ }
100797
+ const severity = options.severity === "high" ? "high" : "all";
100798
+ const scanRunner = new ScanRunner(deps2.runner);
100799
+ formatter.print(import_picocolors4.default.dim("Scanning repository for unencrypted secrets..."));
100800
+ let result;
100801
+ try {
100802
+ result = await scanRunner.scan(repoRoot, manifest, {
100803
+ stagedOnly: options.staged,
100804
+ paths: paths.length > 0 ? paths : void 0,
100805
+ severity
100806
+ });
100807
+ } catch (err) {
100808
+ formatter.error(`Scan failed: ${err.message}`);
100809
+ process.exit(2);
100810
+ return;
100811
+ }
100812
+ if (isJsonMode()) {
100813
+ formatter.json({
100814
+ matches: result.matches,
100815
+ unencryptedMatrixFiles: result.unencryptedMatrixFiles,
100816
+ filesScanned: result.filesScanned,
100817
+ filesSkipped: result.filesSkipped,
100818
+ durationMs: result.durationMs
100819
+ });
100820
+ const hasIssues2 = result.matches.length > 0 || result.unencryptedMatrixFiles.length > 0;
100821
+ process.exit(hasIssues2 ? 1 : 0);
100822
+ return;
100823
+ }
100824
+ formatScanOutput(result);
100825
+ const hasIssues = result.matches.length > 0 || result.unencryptedMatrixFiles.length > 0;
100826
+ process.exit(hasIssues ? 1 : 0);
100827
+ });
100528
100828
  }
100529
100829
  function formatScanOutput(result) {
100530
100830
  const totalIssues = result.matches.length + result.unencryptedMatrixFiles.length;
@@ -100689,6 +100989,17 @@ function registerImportCommand(program3, deps2) {
100689
100989
  process.exit(2);
100690
100990
  return;
100691
100991
  }
100992
+ if (isJsonMode()) {
100993
+ formatter.json({
100994
+ imported: result.imported,
100995
+ skipped: result.skipped,
100996
+ failed: result.failed,
100997
+ warnings: result.warnings,
100998
+ dryRun: opts2.dryRun
100999
+ });
101000
+ process.exit(result.failed.length > 0 ? 1 : 0);
101001
+ return;
101002
+ }
100692
101003
  for (const warning of result.warnings) {
100693
101004
  formatter.print(` ${sym("warning")} ${warning}`);
100694
101005
  }
@@ -100748,6 +101059,7 @@ init_src();
100748
101059
  import * as path45 from "path";
100749
101060
  import * as readline4 from "readline";
100750
101061
  function waitForEnter(message) {
101062
+ if (isJsonMode()) return Promise.resolve();
100751
101063
  return new Promise((resolve7) => {
100752
101064
  const rl = readline4.createInterface({
100753
101065
  input: process.stdin,
@@ -100781,6 +101093,10 @@ function registerRecipientsCommand(program3, deps2) {
100781
101093
  const sopsClient = await createSopsClient(repoRoot, deps2.runner);
100782
101094
  const recipientManager = new RecipientManager(sopsClient, matrixManager);
100783
101095
  const recipients = await recipientManager.list(manifest, repoRoot, opts2.environment);
101096
+ if (isJsonMode()) {
101097
+ formatter.json(recipients);
101098
+ return;
101099
+ }
100784
101100
  if (recipients.length === 0) {
100785
101101
  const scope2 = opts2.environment ? ` for environment '${opts2.environment}'` : "";
100786
101102
  formatter.info(`No recipients configured${scope2}.`);
@@ -100811,7 +101127,7 @@ function registerRecipientsCommand(program3, deps2) {
100811
101127
  return;
100812
101128
  }
100813
101129
  const normalizedKey = validation.key;
100814
- const success = await executeRecipientAdd(
101130
+ const result = await executeRecipientAdd(
100815
101131
  repoRoot,
100816
101132
  program3,
100817
101133
  deps2,
@@ -100819,11 +101135,21 @@ function registerRecipientsCommand(program3, deps2) {
100819
101135
  opts2.label,
100820
101136
  opts2.environment
100821
101137
  );
100822
- if (success) {
100823
- const label2 = opts2.label || keyPreview(normalizedKey);
100824
- formatter.hint(
100825
- `git add clef.yaml && git add -A && git commit -m "add recipient: ${label2} [${opts2.environment}]"`
100826
- );
101138
+ if (result) {
101139
+ if (isJsonMode()) {
101140
+ formatter.json({
101141
+ action: "added",
101142
+ key: normalizedKey,
101143
+ label: opts2.label || keyPreview(normalizedKey),
101144
+ environment: opts2.environment,
101145
+ reEncryptedFiles: result.reEncryptedFiles.length
101146
+ });
101147
+ } else {
101148
+ const label2 = opts2.label || keyPreview(normalizedKey);
101149
+ formatter.hint(
101150
+ `git add clef.yaml && git add -A && git commit -m "add recipient: ${label2} [${opts2.environment}]"`
101151
+ );
101152
+ }
100827
101153
  }
100828
101154
  } catch (err) {
100829
101155
  handleCommandError(err);
@@ -100919,6 +101245,16 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`
100919
101245
  const relative7 = path45.relative(repoRoot, file);
100920
101246
  formatter.print(` ${sym("success")} ${relative7}`);
100921
101247
  }
101248
+ if (isJsonMode()) {
101249
+ formatter.json({
101250
+ action: "removed",
101251
+ key: trimmedKey,
101252
+ label: label2,
101253
+ environment: opts2.environment ?? null,
101254
+ reEncryptedFiles: result.reEncryptedFiles.length
101255
+ });
101256
+ return;
101257
+ }
100922
101258
  formatter.success(
100923
101259
  `${label2} removed. ${result.reEncryptedFiles.length} files re-encrypted. ${sym("locked")}`
100924
101260
  );
@@ -100983,6 +101319,15 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`
100983
101319
  return;
100984
101320
  }
100985
101321
  upsertRequest(repoRoot, publicKey, label2, opts2.environment);
101322
+ if (isJsonMode()) {
101323
+ formatter.json({
101324
+ action: "requested",
101325
+ label: label2,
101326
+ key: publicKey,
101327
+ environment: opts2.environment ?? null
101328
+ });
101329
+ return;
101330
+ }
100986
101331
  const scope = opts2.environment ? ` for environment '${opts2.environment}'` : "";
100987
101332
  formatter.success(`Access requested as '${label2}'${scope}`);
100988
101333
  formatter.print(` Key: ${keyPreview(publicKey)}`);
@@ -100998,6 +101343,15 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`
100998
101343
  try {
100999
101344
  const repoRoot = program3.opts().dir || process.cwd();
101000
101345
  const requests = loadRequests(repoRoot);
101346
+ if (isJsonMode()) {
101347
+ formatter.json(
101348
+ requests.map((r) => ({
101349
+ ...r,
101350
+ requestedAt: r.requestedAt.toISOString()
101351
+ }))
101352
+ );
101353
+ return;
101354
+ }
101001
101355
  if (requests.length === 0) {
101002
101356
  formatter.info("No pending access requests.");
101003
101357
  return;
@@ -101037,7 +101391,7 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`
101037
101391
  process.exit(2);
101038
101392
  return;
101039
101393
  }
101040
- const success = await executeRecipientAdd(
101394
+ const result = await executeRecipientAdd(
101041
101395
  repoRoot,
101042
101396
  program3,
101043
101397
  deps2,
@@ -101045,11 +101399,21 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`
101045
101399
  request.label,
101046
101400
  environment
101047
101401
  );
101048
- if (success) {
101402
+ if (result) {
101049
101403
  removeRequest(repoRoot, identifier);
101050
- formatter.hint(
101051
- `git add clef.yaml ${REQUESTS_FILENAME} && git add -A && git commit -m "approve recipient: ${request.label} [${environment}]"`
101052
- );
101404
+ if (isJsonMode()) {
101405
+ formatter.json({
101406
+ action: "approved",
101407
+ identifier,
101408
+ label: request.label,
101409
+ environment,
101410
+ reEncryptedFiles: result.reEncryptedFiles.length
101411
+ });
101412
+ } else {
101413
+ formatter.hint(
101414
+ `git add clef.yaml ${REQUESTS_FILENAME} && git add -A && git commit -m "approve recipient: ${request.label} [${environment}]"`
101415
+ );
101416
+ }
101053
101417
  }
101054
101418
  } catch (err) {
101055
101419
  handleCommandError(err);
@@ -101065,7 +101429,7 @@ async function executeRecipientAdd(repoRoot, _program, deps2, key, label2, envir
101065
101429
  `Environment '${environment}' not found. Available: ${manifest.environments.map((e) => e.name).join(", ")}`
101066
101430
  );
101067
101431
  process.exit(2);
101068
- return false;
101432
+ return null;
101069
101433
  }
101070
101434
  const matrixManager = new MatrixManager();
101071
101435
  const sopsClient = await createSopsClient(repoRoot, deps2.runner);
@@ -101074,7 +101438,7 @@ async function executeRecipientAdd(repoRoot, _program, deps2, key, label2, envir
101074
101438
  if (existing.some((r) => r.key === key)) {
101075
101439
  formatter.error(`Recipient '${keyPreview(key)}' is already present.`);
101076
101440
  process.exit(2);
101077
- return false;
101441
+ return null;
101078
101442
  }
101079
101443
  const allCells = matrixManager.resolveMatrix(manifest, repoRoot).filter((c) => c.exists && c.environment === environment);
101080
101444
  const fileCount = allCells.length;
@@ -101091,7 +101455,7 @@ This will re-encrypt ${fileCount} files in the matrix.`);
101091
101455
  const confirmed = await formatter.confirm("Proceed?");
101092
101456
  if (!confirmed) {
101093
101457
  formatter.info("Aborted.");
101094
- return false;
101458
+ return null;
101095
101459
  }
101096
101460
  formatter.print(`
101097
101461
  ${sym("working")} Re-encrypting matrix...`);
@@ -101108,7 +101472,7 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`);
101108
101472
  );
101109
101473
  formatter.print("\nNo changes were applied. Investigate the error above and retry.");
101110
101474
  process.exit(1);
101111
- return false;
101475
+ return null;
101112
101476
  }
101113
101477
  for (const file of result.reEncryptedFiles) {
101114
101478
  const relative7 = path45.relative(repoRoot, file);
@@ -101118,7 +101482,7 @@ ${sym("failure")} Re-encryption failed on ${path45.basename(failedFile)}`);
101118
101482
  formatter.success(
101119
101483
  `${displayLabel} added. ${result.reEncryptedFiles.length} files re-encrypted. ${sym("locked")}`
101120
101484
  );
101121
- return true;
101485
+ return { reEncryptedFiles: result.reEncryptedFiles };
101122
101486
  }
101123
101487
 
101124
101488
  // src/commands/merge-driver.ts
@@ -101281,6 +101645,16 @@ function registerServiceCommand(program3, deps2) {
101281
101645
  repoRoot,
101282
101646
  kmsEnvConfigs
101283
101647
  );
101648
+ if (isJsonMode()) {
101649
+ formatter.json({
101650
+ action: "created",
101651
+ identity: result.identity.name,
101652
+ namespaces: result.identity.namespaces,
101653
+ environments: Object.keys(result.identity.environments),
101654
+ privateKeys: result.privateKeys
101655
+ });
101656
+ return;
101657
+ }
101284
101658
  formatter.success(`Service identity '${name}' created.`);
101285
101659
  formatter.print(`
101286
101660
  Namespaces: ${result.identity.namespaces.join(", ")}`);
@@ -101334,6 +101708,10 @@ function registerServiceCommand(program3, deps2) {
101334
101708
  const sopsClient = await createSopsClient(repoRoot, deps2.runner);
101335
101709
  const manager = new ServiceIdentityManager(sopsClient, matrixManager);
101336
101710
  const identities = manager.list(manifest);
101711
+ if (isJsonMode()) {
101712
+ formatter.json(identities);
101713
+ return;
101714
+ }
101337
101715
  if (identities.length === 0) {
101338
101716
  formatter.info("No service identities configured.");
101339
101717
  return;
@@ -101363,6 +101741,10 @@ function registerServiceCommand(program3, deps2) {
101363
101741
  process.exit(1);
101364
101742
  return;
101365
101743
  }
101744
+ if (isJsonMode()) {
101745
+ formatter.json(identity);
101746
+ return;
101747
+ }
101366
101748
  formatter.print(`
101367
101749
  Service Identity: ${identity.name}`);
101368
101750
  formatter.print(`Description: ${identity.description}`);
@@ -101391,6 +101773,11 @@ Service Identity: ${identity.name}`);
101391
101773
  const sopsClient = await createSopsClient(repoRoot, deps2.runner);
101392
101774
  const manager = new ServiceIdentityManager(sopsClient, matrixManager);
101393
101775
  const issues = await manager.validate(manifest, repoRoot);
101776
+ if (isJsonMode()) {
101777
+ formatter.json({ issues });
101778
+ process.exit(issues.length > 0 ? 1 : 0);
101779
+ return;
101780
+ }
101394
101781
  if (issues.length === 0) {
101395
101782
  formatter.success("All service identities are valid.");
101396
101783
  return;
@@ -101441,6 +101828,17 @@ Service Identity: ${identity.name}`);
101441
101828
  const manager = new ServiceIdentityManager(sopsClient, matrixManager);
101442
101829
  formatter.print(`${sym("working")} Updating service identity '${name}'...`);
101443
101830
  await manager.updateEnvironments(name, kmsEnvConfigs, manifest, repoRoot);
101831
+ if (isJsonMode()) {
101832
+ formatter.json({
101833
+ action: "updated",
101834
+ identity: name,
101835
+ changed: Object.entries(kmsEnvConfigs).map(([env, cfg]) => ({
101836
+ environment: env,
101837
+ provider: cfg.provider
101838
+ }))
101839
+ });
101840
+ return;
101841
+ }
101444
101842
  formatter.success(`Service identity '${name}' updated.`);
101445
101843
  for (const [envName, kmsConfig] of Object.entries(kmsEnvConfigs)) {
101446
101844
  formatter.print(` ${envName}: switched to KMS envelope (${kmsConfig.provider})`);
@@ -101476,6 +101874,10 @@ Service Identity: ${identity.name}`);
101476
101874
  const manager = new ServiceIdentityManager(sopsClient, matrixManager);
101477
101875
  formatter.print(`${sym("working")} Deleting service identity '${name}'...`);
101478
101876
  await manager.delete(name, manifest, repoRoot);
101877
+ if (isJsonMode()) {
101878
+ formatter.json({ action: "deleted", identity: name });
101879
+ return;
101880
+ }
101479
101881
  formatter.success(`Service identity '${name}' deleted.`);
101480
101882
  formatter.hint(
101481
101883
  `git add clef.yaml && git commit -m "chore: delete service identity '${name}'"`
@@ -101512,6 +101914,15 @@ Service Identity: ${identity.name}`);
101512
101914
  }
101513
101915
  formatter.print(`${sym("working")} Rotating key for '${name}'...`);
101514
101916
  const newKeys = await manager.rotateKey(name, manifest, repoRoot, opts2.environment);
101917
+ if (isJsonMode()) {
101918
+ formatter.json({
101919
+ action: "rotated",
101920
+ identity: name,
101921
+ environments: Object.keys(newKeys),
101922
+ privateKeys: newKeys
101923
+ });
101924
+ return;
101925
+ }
101515
101926
  formatter.success(`Key rotated for '${name}'.`);
101516
101927
  const entries = Object.entries(newKeys);
101517
101928
  const block = entries.map(([env, key]) => `${env}: ${key}`).join("\n");
@@ -101652,7 +102063,7 @@ function registerPackCommand(program3, deps2) {
101652
102063
  const envConfig = si?.environments[environment];
101653
102064
  if (envConfig && isKmsEnvelope(envConfig)) {
101654
102065
  const { createKmsProvider } = await Promise.resolve().then(() => __toESM(require_dist3()));
101655
- kmsProvider = createKmsProvider(envConfig.kms.provider, {
102066
+ kmsProvider = await createKmsProvider(envConfig.kms.provider, {
101656
102067
  region: envConfig.kms.region
101657
102068
  });
101658
102069
  }
@@ -101687,6 +102098,18 @@ function registerPackCommand(program3, deps2) {
101687
102098
  const fileOut = new FilePackOutput(outputPath);
101688
102099
  await fileOut.write(memOutput.artifact, memOutput.json);
101689
102100
  }
102101
+ if (isJsonMode()) {
102102
+ formatter.json({
102103
+ identity,
102104
+ environment,
102105
+ keyCount: result.keyCount,
102106
+ namespaceCount: result.namespaceCount,
102107
+ artifactSize: result.artifactSize,
102108
+ revision: result.revision,
102109
+ output: outputPath ?? null
102110
+ });
102111
+ return;
102112
+ }
101690
102113
  formatter.success(
101691
102114
  `Artifact packed: ${result.keyCount} keys from ${result.namespaceCount} namespace(s).`
101692
102115
  );
@@ -101764,6 +102187,15 @@ function registerRevokeCommand(program3, _deps) {
101764
102187
  fs32.mkdirSync(artifactDir, { recursive: true });
101765
102188
  fs32.writeFileSync(artifactPath, JSON.stringify(revoked, null, 2) + "\n", "utf-8");
101766
102189
  const relPath = path49.relative(repoRoot, artifactPath);
102190
+ if (isJsonMode()) {
102191
+ formatter.json({
102192
+ identity,
102193
+ environment,
102194
+ revokedAt: revoked.revokedAt,
102195
+ markerPath: relPath
102196
+ });
102197
+ return;
102198
+ }
101767
102199
  formatter.success(`Artifact revoked: ${relPath}`);
101768
102200
  formatter.print("");
101769
102201
  formatter.print(`${sym("arrow")} If your agent fetches artifacts from git (VCS source):`);
@@ -101792,37 +102224,35 @@ import * as path50 from "path";
101792
102224
  function registerDriftCommand(program3, _deps) {
101793
102225
  program3.command("drift <path>").description(
101794
102226
  "Compare key sets across two local Clef repos without decryption.\n\nReads encrypted YAML files as plaintext (key names are not encrypted)\nand reports keys that exist in some environments but not others.\n\nDoes not require sops to be installed.\n\nExit codes:\n 0 No drift\n 1 Drift found"
101795
- ).option("--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(
101796
- async (remotePath, options) => {
101797
- try {
101798
- const localRoot = program3.opts().dir || process.cwd();
101799
- const remoteRoot = path50.resolve(localRoot, remotePath);
101800
- const detector = new DriftDetector();
101801
- const result = detector.detect(localRoot, remoteRoot, options.namespace);
101802
- if (options.push) {
101803
- const config = resolveTelemetryConfig();
101804
- if (!config) {
101805
- formatter.error("--push requires CLEF_TELEMETRY_URL to be set.");
101806
- process.exit(1);
101807
- return;
101808
- }
101809
- const payload = driftResultToOtlp(result, version2);
101810
- await pushOtlp(payload, config);
101811
- formatter.success("Drift results pushed to telemetry endpoint.");
101812
- }
101813
- if (options.json) {
101814
- formatter.raw(JSON.stringify(result, null, 2) + "\n");
101815
- process.exit(result.issues.length > 0 ? 1 : 0);
102227
+ ).option("--push", "Push results as OTLP to CLEF_TELEMETRY_URL").option("--namespace <name...>", "Scope to specific namespace(s)").action(async (remotePath, options) => {
102228
+ try {
102229
+ const localRoot = program3.opts().dir || process.cwd();
102230
+ const remoteRoot = path50.resolve(localRoot, remotePath);
102231
+ const detector = new DriftDetector();
102232
+ const result = detector.detect(localRoot, remoteRoot, options.namespace);
102233
+ if (options.push) {
102234
+ const config = resolveTelemetryConfig();
102235
+ if (!config) {
102236
+ formatter.error("--push requires CLEF_TELEMETRY_URL to be set.");
102237
+ process.exit(1);
101816
102238
  return;
101817
102239
  }
101818
- formatDriftOutput(result);
102240
+ const payload = driftResultToOtlp(result, version2);
102241
+ await pushOtlp(payload, config);
102242
+ formatter.success("Drift results pushed to telemetry endpoint.");
102243
+ }
102244
+ if (isJsonMode()) {
102245
+ formatter.json(result);
101819
102246
  process.exit(result.issues.length > 0 ? 1 : 0);
101820
- } catch (err) {
101821
- formatter.error(err.message);
101822
- process.exit(1);
102247
+ return;
101823
102248
  }
102249
+ formatDriftOutput(result);
102250
+ process.exit(result.issues.length > 0 ? 1 : 0);
102251
+ } catch (err) {
102252
+ formatter.error(err.message);
102253
+ process.exit(1);
101824
102254
  }
101825
- );
102255
+ });
101826
102256
  }
101827
102257
  function formatDriftOutput(result) {
101828
102258
  if (result.namespacesCompared === 0) {
@@ -101915,7 +102345,7 @@ async function getHeadSha(repoRoot, runner2) {
101915
102345
  function registerReportCommand(program3, deps2) {
101916
102346
  program3.command("report").description(
101917
102347
  "Generate a metadata report for this Clef repository.\n\nIncludes repo identity, matrix status, policy issues, and recipient\nsummaries. Never exposes ciphertext, key names, or decrypted values.\n\nExit codes:\n 0 No errors\n 1 Errors found"
101918
- ).option("--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(
102348
+ ).option("--push", "Push report as OTLP to CLEF_TELEMETRY_URL (with automatic gap-fill)").option("--at <sha>", "Generate report at a specific commit").option("--since <sha>", "Generate reports for all commits since <sha>").option("--namespace <name...>", "Filter to namespace(s)").option("--environment <name...>", "Filter to environment(s)").action(
101919
102349
  async (options) => {
101920
102350
  try {
101921
102351
  const repoRoot = program3.opts().dir || process.cwd();
@@ -101927,7 +102357,7 @@ function registerReportCommand(program3, deps2) {
101927
102357
  deps2.runner
101928
102358
  );
101929
102359
  await maybePush(report, options.push);
101930
- outputReport(report, options);
102360
+ outputReport(report);
101931
102361
  return;
101932
102362
  }
101933
102363
  const sopsClient = await createSopsClient(repoRoot, deps2.runner);
@@ -101945,7 +102375,7 @@ function registerReportCommand(program3, deps2) {
101945
102375
  });
101946
102376
  if (options.push) {
101947
102377
  await pushWithGapFill(repoRoot, headReport, deps2.runner);
101948
- outputReport(headReport, options);
102378
+ outputReport(headReport);
101949
102379
  return;
101950
102380
  }
101951
102381
  if (options.since) {
@@ -101959,8 +102389,8 @@ function registerReportCommand(program3, deps2) {
101959
102389
  reports.push(await generateReportAtCommit(repoRoot, sha, version2, deps2.runner));
101960
102390
  }
101961
102391
  }
101962
- if (options.json) {
101963
- formatter.raw(JSON.stringify(reports, null, 2) + "\n");
102392
+ if (isJsonMode()) {
102393
+ formatter.json(reports);
101964
102394
  } else {
101965
102395
  formatter.print(
101966
102396
  `Generated ${reports.length} report(s) for commits since ${options.since.slice(0, 8)}`
@@ -101975,7 +102405,7 @@ function registerReportCommand(program3, deps2) {
101975
102405
  process.exit(reports.some((r) => r.policy.issueCount.error > 0) ? 1 : 0);
101976
102406
  return;
101977
102407
  }
101978
- outputReport(headReport, options);
102408
+ outputReport(headReport);
101979
102409
  } catch (err) {
101980
102410
  handleCommandError(err);
101981
102411
  }
@@ -102026,9 +102456,9 @@ async function maybePush(report, push) {
102026
102456
  await pushOtlp(payload, config);
102027
102457
  formatter.success("Report pushed to telemetry endpoint.");
102028
102458
  }
102029
- function outputReport(report, opts2) {
102030
- if (opts2.json) {
102031
- formatter.raw(JSON.stringify(report, null, 2) + "\n");
102459
+ function outputReport(report) {
102460
+ if (isJsonMode()) {
102461
+ formatter.json(report);
102032
102462
  process.exit(report.policy.issueCount.error > 0 ? 1 : 0);
102033
102463
  return;
102034
102464
  }
@@ -102197,6 +102627,16 @@ function registerInstallCommand(program3, _deps) {
102197
102627
  }
102198
102628
  const manifestFile = files.find((f2) => f2.name === "broker.yaml");
102199
102629
  const manifest = manifestFile ? (0, import_yaml.parse)(manifestFile.content) : {};
102630
+ if (isJsonMode()) {
102631
+ formatter.json({
102632
+ broker: entry.name,
102633
+ provider: entry.provider,
102634
+ tier: entry.tier,
102635
+ files: files.map((f2) => `brokers/${entry.name}/${f2.name}`)
102636
+ });
102637
+ process.exit(0);
102638
+ return;
102639
+ }
102200
102640
  formatter.print("");
102201
102641
  formatter.print(` ${sym("success")} ${entry.name}`);
102202
102642
  formatter.print("");
@@ -102260,6 +102700,11 @@ function registerSearchCommand(program3, _deps) {
102260
102700
  if (options.tier) {
102261
102701
  results = results.filter((b) => b.tier === Number(options.tier));
102262
102702
  }
102703
+ if (isJsonMode()) {
102704
+ formatter.json(results);
102705
+ process.exit(0);
102706
+ return;
102707
+ }
102263
102708
  if (results.length === 0) {
102264
102709
  formatter.info("No brokers found matching your query.");
102265
102710
  process.exit(0);
@@ -102390,6 +102835,20 @@ ${sym("working")} Backend migration summary:`);
102390
102835
  }
102391
102836
  }
102392
102837
  );
102838
+ if (isJsonMode()) {
102839
+ formatter.json({
102840
+ backend: target.backend,
102841
+ migratedFiles: result.migratedFiles,
102842
+ skippedFiles: result.skippedFiles,
102843
+ verifiedFiles: result.verifiedFiles,
102844
+ warnings: result.warnings,
102845
+ rolledBack: result.rolledBack,
102846
+ error: result.error ?? null,
102847
+ dryRun: opts2.dryRun ?? false
102848
+ });
102849
+ process.exit(result.rolledBack ? 1 : 0);
102850
+ return;
102851
+ }
102393
102852
  if (result.rolledBack) {
102394
102853
  formatter.error(`Migration failed: ${result.error}`);
102395
102854
  formatter.info("All changes have been rolled back.");
@@ -102478,7 +102937,7 @@ function registerServeCommand(program3, deps2) {
102478
102937
  const envConfig = si.environments[opts2.env];
102479
102938
  if (envConfig && isKmsEnvelope(envConfig)) {
102480
102939
  const { createKmsProvider } = await Promise.resolve().then(() => __toESM(require_dist3()));
102481
- kmsProvider = createKmsProvider(envConfig.kms.provider, {
102940
+ kmsProvider = await createKmsProvider(envConfig.kms.provider, {
102482
102941
  region: envConfig.kms.region
102483
102942
  });
102484
102943
  }
@@ -102540,12 +102999,18 @@ var VERSION = package_default.version;
102540
102999
  var program2 = new Command();
102541
103000
  var runner = new NodeSubprocessRunner();
102542
103001
  var deps = { runner };
102543
- program2.name("clef").option("--dir <path>", "Path to a local Clef repository root (default: current directory)").option("--plain", "Plain output, no emoji or colour");
103002
+ program2.name("clef").option("--dir <path>", "Path to a local Clef repository root (default: current directory)").option("--plain", "Plain output, no emoji or colour").option("--json", "Output machine-readable JSON (suppresses human output)").option("--yes", "Auto-confirm destructive operations (required with --json for writes)");
102544
103003
  program2.hook("preAction", async () => {
102545
103004
  const opts2 = program2.opts();
102546
103005
  if (opts2.plain) {
102547
103006
  setPlainMode(true);
102548
103007
  }
103008
+ if (opts2.json) {
103009
+ setJsonMode(true);
103010
+ }
103011
+ if (opts2.yes) {
103012
+ setYesMode(true);
103013
+ }
102549
103014
  });
102550
103015
  program2.addHelpText("beforeAll", () => {
102551
103016
  const clef = isPlainMode() ? "clef" : symbols.clef;
@@ -102644,6 +103109,9 @@ async function main() {
102644
103109
  }
102645
103110
  }
102646
103111
  main().catch((err) => {
103112
+ if (isJsonMode()) {
103113
+ exitJsonError(err.message);
103114
+ }
102647
103115
  formatter.error(err.message);
102648
103116
  process.exit(1);
102649
103117
  });