@dotenc/cli 0.5.1 → 0.6.0

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.
Files changed (3) hide show
  1. package/README.md +2 -4
  2. package/dist/cli.js +444 -234
  3. package/package.json +1 -2
package/README.md CHANGED
@@ -112,8 +112,7 @@ After setup, your project will look like:
112
112
  │ └── ...
113
113
  ├── .env.alice.enc
114
114
  ├── .env.production.enc
115
- ├── .env.development.enc
116
- └── dotenc.json
115
+ └── .env.development.enc
117
116
  ```
118
117
 
119
118
  Encrypted files are committed to Git. Public keys are stored inside `.dotenc/`. Each developer gets a personal encrypted environment (e.g., `.env.alice.enc`).
@@ -158,8 +157,7 @@ This will interactively guide you through the setup process:
158
157
  2. Prompting for your username (defaults to your system username);
159
158
  3. Letting you choose which SSH key to use;
160
159
  4. Deriving the public key and storing it in `.dotenc/` (e.g., `.dotenc/alice.pub`);
161
- 5. Creating a `dotenc.json` configuration file in the root of your project;
162
- 6. Creating your personal encrypted environment (e.g., `.env.alice.enc`).
160
+ 5. Creating encrypted `development` and personal environments (e.g., `.env.development.enc`, `.env.alice.enc`).
163
161
 
164
162
  No keys to generate. If you already have an SSH key (and you probably do), you're ready to go.
165
163
 
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ var package_default;
7
7
  var init_package = __esm(() => {
8
8
  package_default = {
9
9
  name: "@dotenc/cli",
10
- version: "0.5.1",
10
+ version: "0.6.0",
11
11
  description: "🔐 Git-native encrypted environments powered by your SSH keys",
12
12
  author: "Ivan Filho <i@ivanfilho.com>",
13
13
  license: "MIT",
@@ -38,7 +38,6 @@ var init_package = __esm(() => {
38
38
  typescript: "^5.8.2"
39
39
  },
40
40
  dependencies: {
41
- "@paralleldrive/cuid2": "^2.2.2",
42
41
  chalk: "^5.4.1",
43
42
  commander: "^13.1.0",
44
43
  eciesjs: "^0.4.15",
@@ -486,9 +485,77 @@ function tryParsePrivateKey(keyContent) {
486
485
  return parseOpenSSHPrivateKey(keyContent);
487
486
  }
488
487
  }
488
+ function describeUnsupportedAlgorithm(keyType) {
489
+ return `unsupported algorithm: ${String(keyType ?? "unknown")}`;
490
+ }
491
+ function readLengthPrefixedBytes(buffer, offset) {
492
+ if (offset + 4 > buffer.length)
493
+ return null;
494
+ const length = buffer.readUInt32BE(offset);
495
+ const start = offset + 4;
496
+ const end = start + length;
497
+ if (end > buffer.length)
498
+ return null;
499
+ return { bytes: buffer.subarray(start, end), nextOffset: end };
500
+ }
501
+ function readLengthPrefixedString(buffer, offset) {
502
+ const bytes = readLengthPrefixedBytes(buffer, offset);
503
+ if (!bytes)
504
+ return null;
505
+ return { value: bytes.bytes.toString("ascii"), nextOffset: bytes.nextOffset };
506
+ }
507
+ function detectUnsupportedOpenSSHAlgorithm(keyContent) {
508
+ if (!keyContent.includes("BEGIN OPENSSH PRIVATE KEY"))
509
+ return null;
510
+ const lines = keyContent.split(`
511
+ `);
512
+ const startIdx = lines.findIndex((line) => line.trim().startsWith("-----BEGIN OPENSSH PRIVATE KEY-----"));
513
+ const endIdx = lines.findIndex((line) => line.trim().startsWith("-----END OPENSSH PRIVATE KEY-----"));
514
+ if (startIdx === -1 || endIdx === -1)
515
+ return null;
516
+ const base64 = lines.slice(startIdx + 1, endIdx).map((line) => line.trim()).join("");
517
+ const buffer = Buffer.from(base64, "base64");
518
+ const MAGIC = "openssh-key-v1\x00";
519
+ if (buffer.length < MAGIC.length)
520
+ return null;
521
+ const magic = buffer.subarray(0, MAGIC.length).toString("ascii");
522
+ if (magic !== MAGIC)
523
+ return null;
524
+ let offset = MAGIC.length;
525
+ const ciphername = readLengthPrefixedString(buffer, offset);
526
+ if (!ciphername)
527
+ return null;
528
+ offset = ciphername.nextOffset;
529
+ const kdfname = readLengthPrefixedString(buffer, offset);
530
+ if (!kdfname)
531
+ return null;
532
+ offset = kdfname.nextOffset;
533
+ const kdfoptions = readLengthPrefixedBytes(buffer, offset);
534
+ if (!kdfoptions)
535
+ return null;
536
+ offset = kdfoptions.nextOffset;
537
+ if (offset + 4 > buffer.length)
538
+ return null;
539
+ const keyCount = buffer.readUInt32BE(offset);
540
+ offset += 4;
541
+ if (keyCount < 1)
542
+ return null;
543
+ const publicBlob = readLengthPrefixedBytes(buffer, offset);
544
+ if (!publicBlob)
545
+ return null;
546
+ const publicBlobType = readLengthPrefixedString(publicBlob.bytes, 0);
547
+ if (!publicBlobType)
548
+ return null;
549
+ if (publicBlobType.value === "ssh-rsa")
550
+ return null;
551
+ if (publicBlobType.value === "ssh-ed25519")
552
+ return null;
553
+ return publicBlobType.value;
554
+ }
489
555
  var SSH_KEY_FILES, getPrivateKeys = async () => {
490
556
  const privateKeys = [];
491
557
  const passphraseProtectedKeys = [];
558
+ const unsupportedKeys = [];
492
559
  if (process.env.DOTENC_PRIVATE_KEY) {
493
560
  let privateKey = null;
494
561
  try {
@@ -512,6 +579,10 @@ var SSH_KEY_FILES, getPrivateKeys = async () => {
512
579
  }
513
580
  privateKeys.push(entry);
514
581
  } else {
582
+ unsupportedKeys.push({
583
+ name: "env.DOTENC_PRIVATE_KEY",
584
+ reason: describeUnsupportedAlgorithm(privateKey.asymmetricKeyType)
585
+ });
515
586
  console.error(`Unsupported key type in DOTENC_PRIVATE_KEY: ${privateKey.asymmetricKeyType}. Only RSA and Ed25519 are supported.`);
516
587
  }
517
588
  } else {
@@ -520,11 +591,15 @@ var SSH_KEY_FILES, getPrivateKeys = async () => {
520
591
  process.exit(1);
521
592
  }
522
593
  console.error("Invalid private key format in DOTENC_PRIVATE_KEY environment variable. Please provide a valid private key (PEM or OpenSSH format).");
594
+ unsupportedKeys.push({
595
+ name: "env.DOTENC_PRIVATE_KEY",
596
+ reason: "invalid private key format"
597
+ });
523
598
  }
524
599
  }
525
600
  const sshDir = path2.join(os.homedir(), ".ssh");
526
601
  if (!existsSync(sshDir)) {
527
- return { keys: privateKeys, passphraseProtectedKeys };
602
+ return { keys: privateKeys, passphraseProtectedKeys, unsupportedKeys };
528
603
  }
529
604
  const files = await fs2.readdir(sshDir);
530
605
  const knownFiles = SSH_KEY_FILES.filter((f) => files.includes(f));
@@ -551,12 +626,27 @@ var SSH_KEY_FILES, getPrivateKeys = async () => {
551
626
  if (!privateKey) {
552
627
  if (isPassphraseProtected(keyContent)) {
553
628
  passphraseProtectedKeys.push(fileName);
629
+ unsupportedKeys.push({
630
+ name: fileName,
631
+ reason: "passphrase-protected"
632
+ });
633
+ } else {
634
+ const unsupportedOpenSSHType = detectUnsupportedOpenSSHAlgorithm(keyContent);
635
+ unsupportedKeys.push({
636
+ name: fileName,
637
+ reason: unsupportedOpenSSHType ? describeUnsupportedAlgorithm(unsupportedOpenSSHType) : "invalid private key format"
638
+ });
554
639
  }
555
640
  continue;
556
641
  }
557
642
  const algorithm = detectAlgorithm(privateKey);
558
- if (!algorithm)
643
+ if (!algorithm) {
644
+ unsupportedKeys.push({
645
+ name: fileName,
646
+ reason: describeUnsupportedAlgorithm(privateKey.asymmetricKeyType)
647
+ });
559
648
  continue;
649
+ }
560
650
  const entry = {
561
651
  name: fileName,
562
652
  privateKey,
@@ -570,7 +660,7 @@ var SSH_KEY_FILES, getPrivateKeys = async () => {
570
660
  }
571
661
  privateKeys.push(entry);
572
662
  }
573
- return { keys: privateKeys, passphraseProtectedKeys };
663
+ return { keys: privateKeys, passphraseProtectedKeys, unsupportedKeys };
574
664
  };
575
665
  var init_getPrivateKeys = __esm(() => {
576
666
  init_getKeyFingerprint();
@@ -880,20 +970,41 @@ var init_chooseEnvironment = __esm(() => {
880
970
 
881
971
  // src/prompts/choosePublicKey.ts
882
972
  import inquirer2 from "inquirer";
883
- var choosePublicKeyPrompt = async (message, multiple) => {
884
- const publicKeys = await getPublicKeys();
885
- const result = await inquirer2.prompt([
973
+ async function _runChoosePublicKeyPrompt(message, multiple, depsOverrides = {}) {
974
+ const deps = {
975
+ ...defaultDeps,
976
+ ...depsOverrides
977
+ };
978
+ const publicKeys = await deps.getPublicKeys();
979
+ const result = await deps.prompt([
886
980
  {
887
- type: multiple ? "list" : "checkbox",
981
+ type: multiple ? "checkbox" : "list",
888
982
  name: "key",
889
983
  message,
890
984
  choices: publicKeys.map((key) => key.name.replace(".pub", ""))
891
985
  }
892
986
  ]);
987
+ if (multiple) {
988
+ return Array.isArray(result.key) ? result.key : [result.key];
989
+ }
990
+ if (Array.isArray(result.key)) {
991
+ return result.key[0] ?? "";
992
+ }
893
993
  return result.key;
894
- };
994
+ }
995
+ async function choosePublicKeyPrompt(message, multiple) {
996
+ if (multiple) {
997
+ return _runChoosePublicKeyPrompt(message, true);
998
+ }
999
+ return _runChoosePublicKeyPrompt(message, false);
1000
+ }
1001
+ var defaultDeps;
895
1002
  var init_choosePublicKey = __esm(() => {
896
1003
  init_getPublicKeys();
1004
+ defaultDeps = {
1005
+ prompt: inquirer2.prompt,
1006
+ getPublicKeys
1007
+ };
897
1008
  });
898
1009
 
899
1010
  // src/commands/auth/grant.ts
@@ -1213,25 +1324,6 @@ var init_getEnvironmentNameSuggestion = __esm(() => {
1213
1324
  init_environmentExists();
1214
1325
  });
1215
1326
 
1216
- // src/helpers/projectConfig.ts
1217
- import { existsSync as existsSync5 } from "node:fs";
1218
- import fs8 from "node:fs/promises";
1219
- import path8 from "node:path";
1220
- import { z as z3 } from "zod";
1221
- var projectConfigSchema, configPath, getProjectConfig = async () => {
1222
- if (existsSync5(configPath)) {
1223
- const config = JSON.parse(await fs8.readFile(configPath, "utf-8"));
1224
- return projectConfigSchema.parse(config);
1225
- }
1226
- return {};
1227
- };
1228
- var init_projectConfig = __esm(() => {
1229
- projectConfigSchema = z3.object({
1230
- projectId: z3.string()
1231
- });
1232
- configPath = path8.join(process.cwd(), "dotenc.json");
1233
- });
1234
-
1235
1327
  // src/prompts/createEnvironment.ts
1236
1328
  import inquirer4 from "inquirer";
1237
1329
  var createEnvironmentPrompt = async (message, defaultValue) => {
@@ -1248,15 +1340,23 @@ var createEnvironmentPrompt = async (message, defaultValue) => {
1248
1340
  var init_createEnvironment = () => {};
1249
1341
 
1250
1342
  // src/commands/env/create.ts
1251
- import fs9 from "node:fs/promises";
1252
- import path9 from "node:path";
1343
+ import fs8 from "node:fs/promises";
1344
+ import path8 from "node:path";
1253
1345
  import chalk9 from "chalk";
1254
- var createCommand = async (environmentNameArg, publicKeyNameArg, initialContent) => {
1255
- const { projectId } = await getProjectConfig();
1256
- if (!projectId) {
1257
- console.error('No project found. Run "dotenc init" to create one.');
1258
- process.exit(1);
1346
+ var _getRunUsageHintForEnvironment = (environmentName) => {
1347
+ if (environmentName === "development") {
1348
+ return chalk9.gray("dotenc dev <command> [args...]");
1349
+ }
1350
+ return `${chalk9.gray(`dotenc run -e ${environmentName} <command> [args...]`)} or ${chalk9.gray(`DOTENC_ENV=${environmentName} dotenc run <command> [args...]`)}`;
1351
+ }, _normalizePublicKeyNamesForCreate = (selection) => {
1352
+ if (Array.isArray(selection)) {
1353
+ return selection;
1354
+ }
1355
+ if (typeof selection === "string" && selection.trim().length > 0) {
1356
+ return [selection];
1259
1357
  }
1358
+ return [];
1359
+ }, createCommand = async (environmentNameArg, publicKeyNameArg, initialContent) => {
1260
1360
  let environmentName = environmentNameArg;
1261
1361
  if (!environmentName) {
1262
1362
  environmentName = await createEnvironmentPrompt("What should the environment be named?", getEnvironmentNameSuggestion());
@@ -1279,7 +1379,8 @@ var createCommand = async (environmentNameArg, publicKeyNameArg, initialContent)
1279
1379
  console.error(`${chalk9.red("Error:")} no public keys found. Please add a public key using ${chalk9.gray("dotenc key add")}.`);
1280
1380
  process.exit(1);
1281
1381
  }
1282
- const publicKeys = publicKeyNameArg ? [publicKeyNameArg] : await choosePublicKeyPrompt("Which public key(s) do you want to grant access for this environment?", true);
1382
+ const publicKeySelection = publicKeyNameArg ? publicKeyNameArg : await choosePublicKeyPrompt("Which public key(s) do you want to grant access for this environment?", true);
1383
+ const publicKeys = _normalizePublicKeyNamesForCreate(publicKeySelection);
1283
1384
  const dataKey = createDataKey();
1284
1385
  const content = initialContent ?? `# ${environmentName} environment
1285
1386
  `;
@@ -1302,16 +1403,14 @@ var createCommand = async (environmentNameArg, publicKeyNameArg, initialContent)
1302
1403
  algorithm: publicKey.algorithm
1303
1404
  });
1304
1405
  }
1305
- await fs9.writeFile(path9.join(process.cwd(), `.env.${environmentName}.enc`), JSON.stringify(environmentJson, null, 2), "utf-8");
1406
+ await fs8.writeFile(path8.join(process.cwd(), `.env.${environmentName}.enc`), JSON.stringify(environmentJson, null, 2), "utf-8");
1306
1407
  console.log(`${chalk9.green("✔")} Environment ${chalk9.cyan(environmentName)} created!`);
1307
1408
  console.log(`
1308
1409
  Some useful tips:`);
1309
1410
  const editCommand = chalk9.gray(`dotenc env edit ${environmentName}`);
1310
1411
  console.log(`
1311
1412
  - To securely edit your environment: ${editCommand}`);
1312
- const runCommand2 = chalk9.gray(`dotenc run -e ${environmentName} <command> [args...]`);
1313
- const runCommandWithEnv = chalk9.gray(`DOTENC_ENV=${environmentName} dotenc run <command> [args...]`);
1314
- console.log(`- To run your application: ${runCommand2} or ${runCommandWithEnv}`);
1413
+ console.log(`- To run your application: ${_getRunUsageHintForEnvironment(environmentName)}`);
1315
1414
  };
1316
1415
  var init_create = __esm(() => {
1317
1416
  init_crypto();
@@ -1319,7 +1418,6 @@ var init_create = __esm(() => {
1319
1418
  init_environmentExists();
1320
1419
  init_getEnvironmentNameSuggestion();
1321
1420
  init_getPublicKeys();
1322
- init_projectConfig();
1323
1421
  init_choosePublicKey();
1324
1422
  init_createEnvironment();
1325
1423
  });
@@ -1361,8 +1459,9 @@ var defaultDecryptCommandDeps, ansiPattern, stripAnsi = (value) => value.replace
1361
1459
  try {
1362
1460
  const environment = await deps.getEnvironmentByName(environmentName);
1363
1461
  const plaintext = await deps.decryptEnvironmentData(environment);
1462
+ const grantedUsers = Array.from(new Set(environment.keys.map((key) => key.name.trim()).filter((name) => name.length > 0)));
1364
1463
  if (options.json) {
1365
- writeJson({ ok: true, content: plaintext }, deps);
1464
+ writeJson({ ok: true, content: plaintext, grantedUsers }, deps);
1366
1465
  } else {
1367
1466
  deps.writeStdout(plaintext);
1368
1467
  }
@@ -1445,10 +1544,10 @@ var init_getDefaultEditor = __esm(() => {
1445
1544
 
1446
1545
  // src/commands/env/edit.ts
1447
1546
  import { spawnSync } from "node:child_process";
1448
- import { existsSync as existsSync6 } from "node:fs";
1449
- import fs10 from "node:fs/promises";
1547
+ import { existsSync as existsSync5 } from "node:fs";
1548
+ import fs9 from "node:fs/promises";
1450
1549
  import os3 from "node:os";
1451
- import path10 from "node:path";
1550
+ import path9 from "node:path";
1452
1551
  import chalk10 from "chalk";
1453
1552
  var editCommand = async (environmentNameArg) => {
1454
1553
  const environmentName = environmentNameArg || await chooseEnvironmentPrompt("What environment do you want to edit?");
@@ -1458,8 +1557,8 @@ var editCommand = async (environmentNameArg) => {
1458
1557
  process.exit(1);
1459
1558
  }
1460
1559
  const environmentFile = `.env.${environmentName}.enc`;
1461
- const environmentFilePath = path10.join(process.cwd(), environmentFile);
1462
- if (!existsSync6(environmentFilePath)) {
1560
+ const environmentFilePath = path9.join(process.cwd(), environmentFile);
1561
+ if (!existsSync5(environmentFilePath)) {
1463
1562
  console.error(`Environment file not found: ${environmentFilePath}`);
1464
1563
  process.exit(1);
1465
1564
  }
@@ -1483,15 +1582,15 @@ ${environment.keys.map((key) => `# - ${key.name}`).join(`
1483
1582
  # Use 'dotenc auth grant' and/or 'dotenc auth revoke' to manage access.
1484
1583
  # Make sure to save your changes before closing the editor.
1485
1584
  ${separator}${content}`;
1486
- const tempDir = await fs10.mkdtemp(path10.join(os3.tmpdir(), "dotenc-"));
1487
- const tempFilePath = path10.join(tempDir, `.env.${environmentName}`);
1488
- await fs10.writeFile(tempFilePath, content, { encoding: "utf-8", mode: 384 });
1585
+ const tempDir = await fs9.mkdtemp(path9.join(os3.tmpdir(), "dotenc-"));
1586
+ const tempFilePath = path9.join(tempDir, `.env.${environmentName}`);
1587
+ await fs9.writeFile(tempFilePath, content, { encoding: "utf-8", mode: 384 });
1489
1588
  const initialHash = createHash(content);
1490
1589
  const cleanup = async () => {
1491
- await fs10.rm(tempDir, { recursive: true, force: true }).catch(() => {});
1590
+ await fs9.rm(tempDir, { recursive: true, force: true }).catch(() => {});
1492
1591
  };
1493
1592
  const onSignal = () => {
1494
- fs10.rm(tempDir, { recursive: true, force: true }).catch(() => {}).finally(() => process.exit(130));
1593
+ fs9.rm(tempDir, { recursive: true, force: true }).catch(() => {}).finally(() => process.exit(130));
1495
1594
  };
1496
1595
  process.on("SIGINT", onSignal);
1497
1596
  process.on("SIGTERM", onSignal);
@@ -1506,7 +1605,7 @@ ${separator}${content}`;
1506
1605
  Editor exited with code ${result.status}`);
1507
1606
  process.exit(1);
1508
1607
  }
1509
- let newContent = await fs10.readFile(tempFilePath, "utf-8");
1608
+ let newContent = await fs9.readFile(tempFilePath, "utf-8");
1510
1609
  const finalHash = createHash(newContent);
1511
1610
  if (initialHash === finalHash) {
1512
1611
  console.log(`
@@ -1696,31 +1795,22 @@ var init_rotate = __esm(() => {
1696
1795
  init_chooseEnvironment();
1697
1796
  });
1698
1797
 
1699
- // src/helpers/createProject.ts
1700
- import { createId } from "@paralleldrive/cuid2";
1701
- var createProject = async () => {
1702
- return {
1703
- projectId: createId()
1704
- };
1705
- };
1706
- var init_createProject = () => {};
1707
-
1708
1798
  // src/helpers/setupGitDiff.ts
1709
1799
  import { spawnSync as spawnSync2 } from "node:child_process";
1710
- import fs11 from "node:fs";
1711
- import path11 from "node:path";
1800
+ import fs10 from "node:fs";
1801
+ import path10 from "node:path";
1712
1802
  var setupGitDiff = () => {
1713
- const gitattributesPath = path11.join(process.cwd(), ".gitattributes");
1803
+ const gitattributesPath = path10.join(process.cwd(), ".gitattributes");
1714
1804
  const marker = "*.enc diff=dotenc";
1715
1805
  let content = "";
1716
- if (fs11.existsSync(gitattributesPath)) {
1717
- content = fs11.readFileSync(gitattributesPath, "utf-8");
1806
+ if (fs10.existsSync(gitattributesPath)) {
1807
+ content = fs10.readFileSync(gitattributesPath, "utf-8");
1718
1808
  }
1719
1809
  if (!content.includes(marker)) {
1720
1810
  const newline = content.length > 0 && !content.endsWith(`
1721
1811
  `) ? `
1722
1812
  ` : "";
1723
- fs11.writeFileSync(gitattributesPath, `${content}${newline}${marker}
1813
+ fs10.writeFileSync(gitattributesPath, `${content}${newline}${marker}
1724
1814
  `);
1725
1815
  }
1726
1816
  spawnSync2("git", ["config", "--local", "diff.dotenc.textconv", "dotenc textconv"], {
@@ -1729,10 +1819,169 @@ var setupGitDiff = () => {
1729
1819
  };
1730
1820
  var init_setupGitDiff = () => {};
1731
1821
 
1732
- // src/prompts/inputName.ts
1822
+ // src/helpers/createEd25519SshKey.ts
1823
+ import { spawnSync as spawnSync3 } from "node:child_process";
1824
+ import { existsSync as existsSync6 } from "node:fs";
1825
+ import fs11 from "node:fs/promises";
1826
+ import os4 from "node:os";
1827
+ import path11 from "node:path";
1828
+ var DEFAULT_DOTENC_KEY_BASENAME = "id_ed25519_dotenc", defaultResolveNewKeyPathDeps, _resolveNewDotencSshKeyPath = (deps = defaultResolveNewKeyPathDeps) => {
1829
+ const sshDir = path11.join(os4.homedir(), ".ssh");
1830
+ const basePath = path11.join(sshDir, DEFAULT_DOTENC_KEY_BASENAME);
1831
+ if (!deps.existsSync(basePath) && !deps.existsSync(`${basePath}.pub`)) {
1832
+ return basePath;
1833
+ }
1834
+ for (let index = 1;index < 1000; index += 1) {
1835
+ const candidatePath = path11.join(sshDir, `${DEFAULT_DOTENC_KEY_BASENAME}_${index}`);
1836
+ if (!deps.existsSync(candidatePath) && !deps.existsSync(`${candidatePath}.pub`)) {
1837
+ return candidatePath;
1838
+ }
1839
+ }
1840
+ throw new Error("Could not determine an available SSH key path in ~/.ssh.");
1841
+ }, defaultCreateEd25519SshKeyDeps, createEd25519SshKey = async (deps = defaultCreateEd25519SshKeyDeps) => {
1842
+ const keyPath = deps.resolveNewSshKeyPath();
1843
+ await deps.mkdir(path11.dirname(keyPath), { recursive: true, mode: 448 });
1844
+ const result = deps.spawnSync("ssh-keygen", ["-t", "ed25519", "-f", keyPath, "-N", "", "-q", "-C", "dotenc"], {
1845
+ stdio: "pipe",
1846
+ encoding: "utf-8"
1847
+ });
1848
+ if (result.error) {
1849
+ throw new Error(`Failed to run ssh-keygen: ${result.error.message || "unknown error"}`);
1850
+ }
1851
+ if (typeof result.status === "number" && result.status !== 0) {
1852
+ const stderr = typeof result.stderr === "string" ? result.stderr.trim() : "";
1853
+ const stdout = typeof result.stdout === "string" ? result.stdout.trim() : "";
1854
+ throw new Error(`ssh-keygen failed (${result.status}): ${stderr || stdout || "unknown error"}`);
1855
+ }
1856
+ return keyPath;
1857
+ };
1858
+ var init_createEd25519SshKey = __esm(() => {
1859
+ defaultResolveNewKeyPathDeps = {
1860
+ existsSync: existsSync6
1861
+ };
1862
+ defaultCreateEd25519SshKeyDeps = {
1863
+ mkdir: fs11.mkdir,
1864
+ spawnSync: spawnSync3,
1865
+ resolveNewSshKeyPath: () => _resolveNewDotencSshKeyPath()
1866
+ };
1867
+ });
1868
+
1869
+ // src/prompts/choosePrivateKey.ts
1870
+ import path12 from "node:path";
1871
+ import chalk12 from "chalk";
1733
1872
  import inquirer5 from "inquirer";
1873
+ function toSupportedChoice(key) {
1874
+ return {
1875
+ name: `${key.name} (${key.algorithm})`,
1876
+ value: key.name
1877
+ };
1878
+ }
1879
+ function toUnsupportedChoice(key, index) {
1880
+ return {
1881
+ name: `${chalk12.gray(key.name)} (${chalk12.yellow(key.reason)})`,
1882
+ value: `__unsupported_${index}__`,
1883
+ disabled: true
1884
+ };
1885
+ }
1886
+ var CREATE_NEW_PRIVATE_KEY_CHOICE = "__dotenc_create_new_private_key__", defaultChoosePrivateKeyPromptDeps, buildPromptChoices = (keys, unsupportedKeys) => {
1887
+ const choices = keys.map(toSupportedChoice);
1888
+ if (unsupportedKeys.length > 0) {
1889
+ if (choices.length > 0) {
1890
+ choices.push({
1891
+ name: chalk12.gray("────────"),
1892
+ value: "__separator_supported_unsupported__",
1893
+ disabled: true
1894
+ });
1895
+ }
1896
+ choices.push({
1897
+ name: chalk12.gray("Unsupported keys (ignored)"),
1898
+ value: "__unsupported_header__",
1899
+ disabled: true
1900
+ });
1901
+ choices.push(...unsupportedKeys.map(toUnsupportedChoice));
1902
+ }
1903
+ if (choices.length > 0) {
1904
+ choices.push({
1905
+ name: chalk12.gray("────────"),
1906
+ value: "__separator_create__",
1907
+ disabled: true
1908
+ });
1909
+ }
1910
+ choices.push({
1911
+ name: "Create a new SSH key (ed25519, recommended)",
1912
+ value: CREATE_NEW_PRIVATE_KEY_CHOICE
1913
+ });
1914
+ return choices;
1915
+ }, _runChoosePrivateKeyPrompt = async (message, deps = defaultChoosePrivateKeyPromptDeps) => {
1916
+ for (;; ) {
1917
+ const {
1918
+ keys,
1919
+ passphraseProtectedKeys,
1920
+ unsupportedKeys = []
1921
+ } = await deps.getPrivateKeys();
1922
+ const privateKeyMap = new Map(keys.map((key) => [key.name, key]));
1923
+ if (!deps.isInteractive()) {
1924
+ if (keys.length > 0) {
1925
+ return keys[0];
1926
+ }
1927
+ if (passphraseProtectedKeys.length > 0) {
1928
+ throw new Error(passphraseProtectedKeyError(passphraseProtectedKeys));
1929
+ }
1930
+ if (unsupportedKeys.length > 0) {
1931
+ const unsupportedList = unsupportedKeys.map((key) => ` - ${key.name}: ${key.reason}`).join(`
1932
+ `);
1933
+ throw new Error(`No supported SSH keys found.
1934
+
1935
+ Unsupported keys:
1936
+ ${unsupportedList}
1937
+
1938
+ Generate a new key with:
1939
+ ssh-keygen -t ed25519 -N ""`);
1940
+ }
1941
+ throw new Error('No SSH keys found in ~/.ssh/. Generate one with: ssh-keygen -t ed25519 -N ""');
1942
+ }
1943
+ const result = await deps.prompt([
1944
+ {
1945
+ type: "list",
1946
+ name: "key",
1947
+ message,
1948
+ choices: buildPromptChoices(keys, unsupportedKeys)
1949
+ }
1950
+ ]);
1951
+ const selected = String(result.key || "");
1952
+ if (selected === CREATE_NEW_PRIVATE_KEY_CHOICE) {
1953
+ try {
1954
+ const createdPath = await deps.createEd25519SshKey();
1955
+ deps.logInfo(`${chalk12.green("✔")} Created ${chalk12.cyan(path12.basename(createdPath))} at ${chalk12.gray(createdPath)}.`);
1956
+ } catch (error) {
1957
+ deps.logWarn(`${chalk12.yellow("Warning:")} failed to create a new SSH key. ${error instanceof Error ? error.message : String(error)}`);
1958
+ }
1959
+ continue;
1960
+ }
1961
+ const selectedKey = privateKeyMap.get(selected);
1962
+ if (selectedKey) {
1963
+ return selectedKey;
1964
+ }
1965
+ }
1966
+ }, choosePrivateKeyPrompt = async (message) => _runChoosePrivateKeyPrompt(message);
1967
+ var init_choosePrivateKey = __esm(() => {
1968
+ init_createEd25519SshKey();
1969
+ init_errors();
1970
+ init_getPrivateKeys();
1971
+ defaultChoosePrivateKeyPromptDeps = {
1972
+ getPrivateKeys,
1973
+ prompt: inquirer5.prompt,
1974
+ createEd25519SshKey,
1975
+ logInfo: console.log,
1976
+ logWarn: console.warn,
1977
+ isInteractive: () => Boolean(process.stdin.isTTY && process.stdout.isTTY)
1978
+ };
1979
+ });
1980
+
1981
+ // src/prompts/inputName.ts
1982
+ import inquirer6 from "inquirer";
1734
1983
  var inputNamePrompt = async (message, defaultValue) => {
1735
- const result = await inquirer5.prompt([
1984
+ const result = await inquirer6.prompt([
1736
1985
  {
1737
1986
  type: "input",
1738
1987
  name: "name",
@@ -1817,9 +2066,9 @@ function validatePublicKey(key) {
1817
2066
  }
1818
2067
 
1819
2068
  // src/prompts/inputKey.ts
1820
- import inquirer6 from "inquirer";
2069
+ import inquirer7 from "inquirer";
1821
2070
  var inputKeyPrompt = async (message, defaultValue) => {
1822
- const result = await inquirer6.prompt([
2071
+ const result = await inquirer7.prompt([
1823
2072
  {
1824
2073
  type: "password",
1825
2074
  name: "key",
@@ -1836,26 +2085,21 @@ var init_inputKey = () => {};
1836
2085
  import crypto10 from "node:crypto";
1837
2086
  import { existsSync as existsSync7 } from "node:fs";
1838
2087
  import fs12 from "node:fs/promises";
1839
- import os4 from "node:os";
1840
- import path12 from "node:path";
1841
- import chalk12 from "chalk";
1842
- import inquirer7 from "inquirer";
2088
+ import os5 from "node:os";
2089
+ import path13 from "node:path";
2090
+ import chalk13 from "chalk";
2091
+ import inquirer8 from "inquirer";
1843
2092
  var keyAddCommand = async (nameArg, options) => {
1844
- const { projectId } = await getProjectConfig();
1845
- if (!projectId) {
1846
- console.error('No project found. Run "dotenc init" to create one.');
1847
- process.exit(1);
1848
- }
1849
2093
  let publicKey;
1850
2094
  if (options?.fromSsh) {
1851
- const sshPath = options.fromSsh.startsWith("~") ? path12.join(os4.homedir(), options.fromSsh.slice(1)) : options.fromSsh;
2095
+ const sshPath = options.fromSsh.startsWith("~") ? path13.join(os5.homedir(), options.fromSsh.slice(1)) : options.fromSsh;
1852
2096
  if (!existsSync7(sshPath)) {
1853
- console.error(`File ${chalk12.cyan(sshPath)} does not exist. Please provide a valid SSH key path.`);
2097
+ console.error(`File ${chalk13.cyan(sshPath)} does not exist. Please provide a valid SSH key path.`);
1854
2098
  process.exit(1);
1855
2099
  }
1856
2100
  const keyContent = await fs12.readFile(sshPath, "utf-8");
1857
2101
  if (isPassphraseProtected(keyContent)) {
1858
- console.error(`${chalk12.red("Error:")} the provided key is passphrase-protected, which is not currently supported by dotenc.`);
2102
+ console.error(`${chalk13.red("Error:")} the provided key is passphrase-protected, which is not currently supported by dotenc.`);
1859
2103
  process.exit(1);
1860
2104
  }
1861
2105
  try {
@@ -1878,12 +2122,12 @@ var keyAddCommand = async (nameArg, options) => {
1878
2122
  }
1879
2123
  if (options?.fromFile) {
1880
2124
  if (!existsSync7(options.fromFile)) {
1881
- console.error(`File ${chalk12.cyan(options.fromFile)} does not exist. Please provide a valid file path.`);
2125
+ console.error(`File ${chalk13.cyan(options.fromFile)} does not exist. Please provide a valid file path.`);
1882
2126
  process.exit(1);
1883
2127
  }
1884
2128
  const keyContent = await fs12.readFile(options.fromFile, "utf-8");
1885
2129
  if (isPassphraseProtected(keyContent)) {
1886
- console.error(`${chalk12.red("Error:")} the provided key is passphrase-protected, which is not currently supported by dotenc.`);
2130
+ console.error(`${chalk13.red("Error:")} the provided key is passphrase-protected, which is not currently supported by dotenc.`);
1887
2131
  process.exit(1);
1888
2132
  }
1889
2133
  try {
@@ -1905,7 +2149,7 @@ var keyAddCommand = async (nameArg, options) => {
1905
2149
  }
1906
2150
  if (options?.fromString) {
1907
2151
  if (isPassphraseProtected(options.fromString)) {
1908
- console.error(`${chalk12.red("Error:")} the provided key is passphrase-protected, which is not currently supported by dotenc.`);
2152
+ console.error(`${chalk13.red("Error:")} the provided key is passphrase-protected, which is not currently supported by dotenc.`);
1909
2153
  process.exit(1);
1910
2154
  }
1911
2155
  try {
@@ -1926,23 +2170,12 @@ var keyAddCommand = async (nameArg, options) => {
1926
2170
  }
1927
2171
  }
1928
2172
  if (!publicKey) {
1929
- const { keys: sshKeys, passphraseProtectedKeys } = await getPrivateKeys();
1930
- if (sshKeys.length === 0 && passphraseProtectedKeys.length > 0) {
1931
- console.warn(`${chalk12.yellow("Warning:")} SSH keys were found but are passphrase-protected (not supported by dotenc):
1932
- ${passphraseProtectedKeys.map((k) => ` - ${k}`).join(`
1933
- `)}
1934
- `);
1935
- }
1936
- const choices = sshKeys.map((key) => ({
1937
- name: `${key.name} (${key.algorithm})`,
1938
- value: key.name
1939
- }));
1940
- const modePrompt = await inquirer7.prompt({
2173
+ const modePrompt = await inquirer8.prompt({
1941
2174
  type: "list",
1942
2175
  name: "mode",
1943
2176
  message: "Would you like to add one of your SSH keys or paste a public key?",
1944
2177
  choices: [
1945
- ...choices.length ? [{ name: "Choose from my SSH keys", value: "choose" }] : [],
2178
+ { name: "Choose or create an SSH key", value: "choose" },
1946
2179
  { name: "Paste a public key (PEM format)", value: "paste" }
1947
2180
  ]
1948
2181
  });
@@ -1960,15 +2193,11 @@ ${passphraseProtectedKeys.map((k) => ` - ${k}`).join(`
1960
2193
  process.exit(1);
1961
2194
  }
1962
2195
  } else {
1963
- const keyPrompt = await inquirer7.prompt({
1964
- type: "list",
1965
- name: "key",
1966
- message: "Which SSH key do you want to add?",
1967
- choices
1968
- });
1969
- const selectedKey = sshKeys.find((k) => k.name === keyPrompt.key);
1970
- if (!selectedKey) {
1971
- console.error("SSH key not found.");
2196
+ let selectedKey;
2197
+ try {
2198
+ selectedKey = await choosePrivateKeyPrompt("Which SSH key do you want to add?");
2199
+ } catch (error) {
2200
+ console.error(error instanceof Error ? error.message : String(error));
1972
2201
  process.exit(1);
1973
2202
  }
1974
2203
  publicKey = crypto10.createPublicKey(selectedKey.privateKey);
@@ -1990,25 +2219,24 @@ ${passphraseProtectedKeys.map((k) => ` - ${k}`).join(`
1990
2219
  type: "spki",
1991
2220
  format: "pem"
1992
2221
  });
1993
- if (!existsSync7(path12.join(process.cwd(), ".dotenc"))) {
1994
- await fs12.mkdir(path12.join(process.cwd(), ".dotenc"));
2222
+ if (!existsSync7(path13.join(process.cwd(), ".dotenc"))) {
2223
+ await fs12.mkdir(path13.join(process.cwd(), ".dotenc"));
1995
2224
  }
1996
2225
  let name = nameArg;
1997
2226
  if (!name) {
1998
2227
  name = await inputNamePrompt("What name do you want to give to the new public key?");
1999
- if (existsSync7(path12.join(process.cwd(), ".dotenc", `${name}.pub`))) {
2000
- console.error(`A public key with name ${chalk12.cyan(name)} already exists. Please choose a different name.`);
2228
+ if (existsSync7(path13.join(process.cwd(), ".dotenc", `${name}.pub`))) {
2229
+ console.error(`A public key with name ${chalk13.cyan(name)} already exists. Please choose a different name.`);
2001
2230
  process.exit(1);
2002
2231
  }
2003
2232
  }
2004
- await fs12.writeFile(path12.join(process.cwd(), ".dotenc", `${name}.pub`), publicKeyOutput, "utf-8");
2233
+ await fs12.writeFile(path13.join(process.cwd(), ".dotenc", `${name}.pub`), publicKeyOutput, "utf-8");
2005
2234
  console.log(`
2006
- Public key ${chalk12.cyan(name)} added successfully!`);
2235
+ Public key ${chalk13.cyan(name)} added successfully!`);
2007
2236
  };
2008
2237
  var init_add = __esm(() => {
2009
- init_getPrivateKeys();
2010
2238
  init_parseOpenSSHKey();
2011
- init_projectConfig();
2239
+ init_choosePrivateKey();
2012
2240
  init_inputKey();
2013
2241
  init_inputName();
2014
2242
  });
@@ -2017,61 +2245,32 @@ var init_add = __esm(() => {
2017
2245
  import crypto11 from "node:crypto";
2018
2246
  import { existsSync as existsSync8 } from "node:fs";
2019
2247
  import fs13 from "node:fs/promises";
2020
- import os5 from "node:os";
2021
- import path13 from "node:path";
2022
- import chalk13 from "chalk";
2023
- import inquirer8 from "inquirer";
2024
- var initCommand = async (options) => {
2025
- const { keys: privateKeys, passphraseProtectedKeys } = await getPrivateKeys();
2026
- if (!privateKeys.length) {
2027
- if (passphraseProtectedKeys.length > 0) {
2028
- console.error(passphraseProtectedKeyError(passphraseProtectedKeys));
2029
- } else {
2030
- console.error(`${chalk13.red("Error:")} no SSH keys found in ~/.ssh/. Please generate one first using ${chalk13.gray("ssh-keygen")}.`);
2031
- }
2032
- process.exit(1);
2248
+ import os6 from "node:os";
2249
+ import path14 from "node:path";
2250
+ import chalk14 from "chalk";
2251
+ var _resolveDocsUrl = () => {
2252
+ if (typeof package_default.homepage === "string" && package_default.homepage.trim().length > 0) {
2253
+ return package_default.homepage;
2033
2254
  }
2034
- const username = options.name || await inputNamePrompt("What's your name?", os5.userInfo().username);
2255
+ const repositoryUrl = typeof package_default.repository === "string" ? package_default.repository : package_default.repository?.url;
2256
+ if (typeof repositoryUrl !== "string" || repositoryUrl.trim().length === 0) {
2257
+ return;
2258
+ }
2259
+ return repositoryUrl.replace(/^git\+/, "").replace(/\.git$/, "");
2260
+ }, initCommand = async (options) => {
2261
+ const username = options.name || await inputNamePrompt("What's your name?", os6.userInfo().username);
2035
2262
  if (!username) {
2036
- console.error(`${chalk13.red("Error:")} no name provided.`);
2263
+ console.error(`${chalk14.red("Error:")} no name provided.`);
2037
2264
  process.exit(1);
2038
2265
  }
2039
- if (!existsSync8(path13.join(process.cwd(), "dotenc.json"))) {
2040
- console.log("No project found. Let's create a new one.");
2041
- try {
2042
- const { projectId } = await createProject();
2043
- await fs13.writeFile(path13.join(process.cwd(), "dotenc.json"), JSON.stringify({ projectId }, null, 2), "utf-8");
2044
- } catch (error) {
2045
- console.error(`${chalk13.red("Error:")} failed to create the project.`);
2046
- console.error(`${chalk13.red("Details:")} ${error instanceof Error ? error.message : error}`);
2047
- process.exit(1);
2048
- }
2049
- }
2050
- let keyToAdd;
2051
- if (privateKeys.length === 1) {
2052
- keyToAdd = privateKeys[0].name;
2053
- } else {
2054
- const result = await inquirer8.prompt([
2055
- {
2056
- type: "list",
2057
- name: "key",
2058
- message: "Which SSH key would you like to use?",
2059
- choices: privateKeys.map((key) => ({
2060
- name: `${key.name} (${key.algorithm})`,
2061
- value: key.name
2062
- }))
2063
- }
2064
- ]);
2065
- keyToAdd = result.key;
2066
- }
2067
- if (!keyToAdd) {
2068
- console.error(`${chalk13.red("Error:")} no SSH key selected. Please select a key.`);
2266
+ let keyEntry;
2267
+ try {
2268
+ keyEntry = await choosePrivateKeyPrompt("Which SSH key would you like to use?");
2269
+ } catch (error) {
2270
+ console.error(error instanceof Error ? error.message : String(error));
2069
2271
  process.exit(1);
2070
2272
  }
2071
- const keyEntry = privateKeys.find((k) => k.name === keyToAdd);
2072
- if (!keyEntry)
2073
- process.exit(1);
2074
- console.log(`Adding key: ${chalk13.cyan(username)} (${keyEntry.algorithm})`);
2273
+ console.log(`Adding key: ${chalk14.cyan(username)} (${keyEntry.algorithm})`);
2075
2274
  const publicKey = crypto11.createPublicKey(keyEntry.privateKey);
2076
2275
  const publicKeyPem = publicKey.export({ type: "spki", format: "pem" }).toString();
2077
2276
  await keyAddCommand(username, {
@@ -2080,36 +2279,47 @@ var initCommand = async (options) => {
2080
2279
  try {
2081
2280
  setupGitDiff();
2082
2281
  } catch (_error) {
2083
- console.warn(`${chalk13.yellow("Warning:")} could not set up git diff driver. You can run ${chalk13.gray("dotenc init")} again inside a git repository.`);
2282
+ console.warn(`${chalk14.yellow("Warning:")} could not set up git diff driver. You can run ${chalk14.gray("dotenc init")} again inside a git repository.`);
2084
2283
  }
2085
2284
  let initialContent;
2086
- const envPath = path13.join(process.cwd(), ".env");
2285
+ const envPath = path14.join(process.cwd(), ".env");
2087
2286
  if (existsSync8(envPath)) {
2088
2287
  initialContent = await fs13.readFile(envPath, "utf-8");
2089
2288
  await fs13.unlink(envPath);
2090
- console.log(`Migrated ${chalk13.gray(".env")} contents to ${chalk13.cyan(username)} environment.`);
2289
+ console.log(`Migrated ${chalk14.gray(".env")} contents to ${chalk14.cyan("development")} environment.`);
2290
+ }
2291
+ await createCommand("development", username, initialContent);
2292
+ if (username !== "development") {
2293
+ await createCommand(username, username);
2091
2294
  }
2092
- await createCommand(username, username, initialContent);
2093
2295
  console.log(`
2094
- ${chalk13.green("✔")} Initialization complete!`);
2296
+ ${chalk14.green("✔")} Initialization complete!`);
2095
2297
  console.log(`
2096
2298
  Some useful tips:`);
2097
- const editCmd = chalk13.gray(`dotenc env edit ${username}`);
2098
- console.log(`- To edit your personal environment: ${editCmd}`);
2099
- const devCmd = chalk13.gray("dotenc dev <command>");
2100
- console.log(`- To run with your encrypted env: ${devCmd}`);
2299
+ const developmentEditCmd = chalk14.gray("dotenc env edit development");
2300
+ const personalEditCmd = chalk14.gray(`dotenc env edit ${username}`);
2301
+ console.log(`- Edit the development environment: ${developmentEditCmd}`);
2302
+ if (username !== "development") {
2303
+ console.log(`- Edit your personal environment: ${personalEditCmd}`);
2304
+ }
2305
+ const devEnvironmentChain = username === "development" ? "development" : `development,${username}`;
2306
+ const devCmd = chalk14.gray("dotenc dev <command>");
2307
+ console.log(`- Run in development mode: ${devCmd} ${chalk14.gray(`(loads ${devEnvironmentChain})`)}`);
2308
+ const docsUrl = _resolveDocsUrl();
2309
+ if (docsUrl) {
2310
+ console.log(`- Full docs: ${chalk14.gray(docsUrl)}`);
2311
+ }
2101
2312
  if (existsSync8(".claude") || existsSync8("CLAUDE.md")) {
2102
- console.log(`- Install the agent skill: ${chalk13.gray("dotenc tools install-agent-skill")}`);
2313
+ console.log(`- Install the agent skill: ${chalk14.gray("dotenc tools install-agent-skill")}`);
2103
2314
  }
2104
2315
  if (existsSync8(".vscode") || existsSync8(".cursor") || existsSync8(".windsurf")) {
2105
- console.log(`- Add the editor extension: ${chalk13.gray("dotenc tools install-vscode-extension")}`);
2316
+ console.log(`- Add the editor extension: ${chalk14.gray("dotenc tools install-vscode-extension")}`);
2106
2317
  }
2107
2318
  };
2108
2319
  var init_init = __esm(() => {
2109
- init_createProject();
2110
- init_errors();
2111
- init_getPrivateKeys();
2320
+ init_package();
2112
2321
  init_setupGitDiff();
2322
+ init_choosePrivateKey();
2113
2323
  init_inputName();
2114
2324
  init_create();
2115
2325
  init_add();
@@ -2147,16 +2357,16 @@ var init_confirm = () => {};
2147
2357
  // src/commands/key/remove.ts
2148
2358
  import { existsSync as existsSync9 } from "node:fs";
2149
2359
  import fs14 from "node:fs/promises";
2150
- import path14 from "node:path";
2151
- import chalk14 from "chalk";
2360
+ import path15 from "node:path";
2361
+ import chalk15 from "chalk";
2152
2362
  var keyRemoveCommand = async (nameArg) => {
2153
2363
  let name = nameArg;
2154
2364
  if (!name) {
2155
2365
  name = await choosePublicKeyPrompt("Which public key do you want to remove?");
2156
2366
  }
2157
- const filePath = path14.join(process.cwd(), ".dotenc", `${name}.pub`);
2367
+ const filePath = path15.join(process.cwd(), ".dotenc", `${name}.pub`);
2158
2368
  if (!existsSync9(filePath)) {
2159
- console.error(`Public key ${chalk14.cyan(name)} not found.`);
2369
+ console.error(`Public key ${chalk15.cyan(name)} not found.`);
2160
2370
  process.exit(1);
2161
2371
  }
2162
2372
  const allEnvironments = await getEnvironments();
@@ -2170,7 +2380,7 @@ var keyRemoveCommand = async (nameArg) => {
2170
2380
  } catch {}
2171
2381
  }
2172
2382
  if (affectedEnvironments.length > 0) {
2173
- console.log(`Key ${chalk14.cyan(name)} has access to the following environments:`);
2383
+ console.log(`Key ${chalk15.cyan(name)} has access to the following environments:`);
2174
2384
  for (const env of affectedEnvironments) {
2175
2385
  console.log(` - ${env}`);
2176
2386
  }
@@ -2183,7 +2393,7 @@ Access will be revoked from these environments automatically.`);
2183
2393
  return;
2184
2394
  }
2185
2395
  await fs14.unlink(filePath);
2186
- console.log(`Public key ${chalk14.cyan(name)} removed successfully.`);
2396
+ console.log(`Public key ${chalk15.cyan(name)} removed successfully.`);
2187
2397
  for (const envName of affectedEnvironments) {
2188
2398
  try {
2189
2399
  const envJson = await getEnvironmentByName(envName);
@@ -2191,9 +2401,9 @@ Access will be revoked from these environments automatically.`);
2191
2401
  await encryptEnvironment(envName, content, {
2192
2402
  revokePublicKeys: [name]
2193
2403
  });
2194
- console.log(`Revoked access from ${chalk14.cyan(envName)} environment.`);
2404
+ console.log(`Revoked access from ${chalk15.cyan(envName)} environment.`);
2195
2405
  } catch {
2196
- console.warn(`${chalk14.yellow("Warning:")} could not revoke access from ${chalk14.cyan(envName)}. You may need to run ${chalk14.gray(`dotenc auth revoke ${envName} ${name}`)} manually or rotate the environment.`);
2406
+ console.warn(`${chalk15.yellow("Warning:")} could not revoke access from ${chalk15.cyan(envName)}. You may need to run ${chalk15.gray(`dotenc auth revoke ${envName} ${name}`)} manually or rotate the environment.`);
2197
2407
  }
2198
2408
  }
2199
2409
  };
@@ -2208,9 +2418,9 @@ var init_remove = __esm(() => {
2208
2418
 
2209
2419
  // src/commands/textconv.ts
2210
2420
  import fs15 from "node:fs/promises";
2211
- import path15 from "node:path";
2421
+ import path16 from "node:path";
2212
2422
  var textconvCommand = async (filePath) => {
2213
- const absolutePath = path15.isAbsolute(filePath) ? filePath : path15.join(process.cwd(), filePath);
2423
+ const absolutePath = path16.isAbsolute(filePath) ? filePath : path16.join(process.cwd(), filePath);
2214
2424
  try {
2215
2425
  const environment = await getEnvironmentByPath(absolutePath);
2216
2426
  const plaintext = await decryptEnvironmentData(environment);
@@ -2227,18 +2437,18 @@ var init_textconv = __esm(() => {
2227
2437
 
2228
2438
  // src/commands/tools/install-agent-skill.ts
2229
2439
  import { spawn as spawn2 } from "node:child_process";
2230
- import chalk15 from "chalk";
2440
+ import chalk16 from "chalk";
2231
2441
  import inquirer10 from "inquirer";
2232
- var SKILL_SOURCE = "ivanfilhoz/dotenc", SKILL_NAME = "dotenc", runNpx = (args) => new Promise((resolve, reject) => {
2233
- const child = spawn2("npx", args, {
2442
+ var SKILL_SOURCE = "ivanfilhoz/dotenc", SKILL_NAME = "dotenc", runNpx = (args, spawnImpl = spawn2) => new Promise((resolve, reject) => {
2443
+ const child = spawnImpl("npx", args, {
2234
2444
  stdio: "inherit",
2235
2445
  shell: process.platform === "win32"
2236
2446
  });
2237
2447
  child.on("error", reject);
2238
2448
  child.on("exit", (code) => resolve(code ?? 1));
2239
- }), defaultDeps, _runInstallAgentSkillCommand = async (options, depsOverrides = {}) => {
2449
+ }), defaultDeps2, _runInstallAgentSkillCommand = async (options, depsOverrides = {}) => {
2240
2450
  const deps = {
2241
- ...defaultDeps,
2451
+ ...defaultDeps2,
2242
2452
  ...depsOverrides
2243
2453
  };
2244
2454
  const { scope } = await deps.prompt([
@@ -2264,21 +2474,21 @@ var SKILL_SOURCE = "ivanfilhoz/dotenc", SKILL_NAME = "dotenc", runNpx = (args) =
2264
2474
  try {
2265
2475
  exitCode = await deps.runNpx(args);
2266
2476
  } catch (error) {
2267
- deps.logError(`${chalk15.red("Error:")} failed to run ${chalk15.gray(npxCommand)}.`);
2268
- deps.logError(`${chalk15.red("Details:")} ${error instanceof Error ? error.message : String(error)}`);
2477
+ deps.logError(`${chalk16.red("Error:")} failed to run ${chalk16.gray(npxCommand)}.`);
2478
+ deps.logError(`${chalk16.red("Details:")} ${error instanceof Error ? error.message : String(error)}`);
2269
2479
  deps.exit(1);
2270
2480
  }
2271
2481
  if (exitCode !== 0) {
2272
- deps.logError(`${chalk15.red("Error:")} skill installation command exited with code ${exitCode}.`);
2482
+ deps.logError(`${chalk16.red("Error:")} skill installation command exited with code ${exitCode}.`);
2273
2483
  deps.exit(exitCode);
2274
2484
  }
2275
- deps.log(`${chalk15.green("✓")} Agent skill installation completed via ${chalk15.gray(npxCommand)}.`);
2276
- deps.log(`Run ${chalk15.gray("/dotenc")} in your agent to use it.`);
2485
+ deps.log(`${chalk16.green("✓")} Agent skill installation completed via ${chalk16.gray(npxCommand)}.`);
2486
+ deps.log(`Run ${chalk16.gray("/dotenc")} in your agent to use it.`);
2277
2487
  }, installAgentSkillCommand = async (options) => {
2278
2488
  await _runInstallAgentSkillCommand(options);
2279
2489
  };
2280
2490
  var init_install_agent_skill = __esm(() => {
2281
- defaultDeps = {
2491
+ defaultDeps2 = {
2282
2492
  prompt: inquirer10.prompt,
2283
2493
  runNpx,
2284
2494
  log: console.log,
@@ -2291,12 +2501,12 @@ var init_install_agent_skill = __esm(() => {
2291
2501
  import { exec, execFile } from "node:child_process";
2292
2502
  import { existsSync as existsSync10 } from "node:fs";
2293
2503
  import fs16 from "node:fs/promises";
2294
- import path16 from "node:path";
2504
+ import path17 from "node:path";
2295
2505
  import { promisify } from "node:util";
2296
- import chalk16 from "chalk";
2506
+ import chalk17 from "chalk";
2297
2507
  import inquirer11 from "inquirer";
2298
2508
  async function addToExtensionsJson() {
2299
- const extensionsJsonPath = path16.join(process.cwd(), ".vscode", "extensions.json");
2509
+ const extensionsJsonPath = path17.join(process.cwd(), ".vscode", "extensions.json");
2300
2510
  let json = {};
2301
2511
  if (existsSync10(extensionsJsonPath)) {
2302
2512
  const content = await fs16.readFile(extensionsJsonPath, "utf-8");
@@ -2306,7 +2516,7 @@ async function addToExtensionsJson() {
2306
2516
  json = {};
2307
2517
  }
2308
2518
  } else {
2309
- await fs16.mkdir(path16.join(process.cwd(), ".vscode"), { recursive: true });
2519
+ await fs16.mkdir(path17.join(process.cwd(), ".vscode"), { recursive: true });
2310
2520
  }
2311
2521
  if (!Array.isArray(json.recommendations)) {
2312
2522
  json.recommendations = [];
@@ -2314,9 +2524,9 @@ async function addToExtensionsJson() {
2314
2524
  if (!json.recommendations.includes(EXTENSION_ID)) {
2315
2525
  json.recommendations.push(EXTENSION_ID);
2316
2526
  await fs16.writeFile(extensionsJsonPath, JSON.stringify(json, null, 2), "utf-8");
2317
- console.log(`${chalk16.green("✓")} Added dotenc to ${chalk16.gray(".vscode/extensions.json")}`);
2527
+ console.log(`${chalk17.green("✓")} Added dotenc to ${chalk17.gray(".vscode/extensions.json")}`);
2318
2528
  } else {
2319
- console.log(`${chalk16.green("✓")} dotenc already in ${chalk16.gray(".vscode/extensions.json")}`);
2529
+ console.log(`${chalk17.green("✓")} dotenc already in ${chalk17.gray(".vscode/extensions.json")}`);
2320
2530
  }
2321
2531
  }
2322
2532
  async function which(bin) {
@@ -2329,11 +2539,11 @@ async function which(bin) {
2329
2539
  }
2330
2540
  async function detectEditors() {
2331
2541
  const detected = [];
2332
- if (existsSync10(path16.join(process.cwd(), ".cursor")))
2542
+ if (existsSync10(path17.join(process.cwd(), ".cursor")))
2333
2543
  detected.push("cursor");
2334
- if (existsSync10(path16.join(process.cwd(), ".windsurf")))
2544
+ if (existsSync10(path17.join(process.cwd(), ".windsurf")))
2335
2545
  detected.push("windsurf");
2336
- if (existsSync10(path16.join(process.cwd(), ".vscode")))
2546
+ if (existsSync10(path17.join(process.cwd(), ".vscode")))
2337
2547
  detected.push("vscode");
2338
2548
  const checks = [
2339
2549
  { key: "cursor", bins: ["cursor"] },
@@ -2380,7 +2590,7 @@ async function _runInstallVscodeExtension(getEditors = detectEditors, _openUrl =
2380
2590
  await addToExtensionsJson();
2381
2591
  if (editors.length === 0) {
2382
2592
  console.log(`
2383
- Install the extension in VS Code: ${chalk16.cyan(EDITOR_PROTOCOL_URLS.vscode)}`);
2593
+ Install the extension in VS Code: ${chalk17.cyan(EDITOR_PROTOCOL_URLS.vscode)}`);
2384
2594
  return;
2385
2595
  }
2386
2596
  if (editors.length === 1) {
@@ -2399,10 +2609,10 @@ Install the extension in VS Code: ${chalk16.cyan(EDITOR_PROTOCOL_URLS.vscode)}`)
2399
2609
  try {
2400
2610
  await _openUrl(url);
2401
2611
  } catch {
2402
- console.log(`Open manually: ${chalk16.cyan(url)}`);
2612
+ console.log(`Open manually: ${chalk17.cyan(url)}`);
2403
2613
  }
2404
2614
  } else {
2405
- console.log(`Install manually: ${chalk16.cyan(url)}`);
2615
+ console.log(`Install manually: ${chalk17.cyan(url)}`);
2406
2616
  }
2407
2617
  return;
2408
2618
  }
@@ -2411,7 +2621,7 @@ Install the extension in your editor:`);
2411
2621
  for (const editor of editors) {
2412
2622
  const name = EDITOR_NAMES[editor] ?? editor;
2413
2623
  const url = EDITOR_PROTOCOL_URLS[editor];
2414
- console.log(` ${name}: ${chalk16.cyan(url)}`);
2624
+ console.log(` ${name}: ${chalk17.cyan(url)}`);
2415
2625
  }
2416
2626
  }
2417
2627
  var execFileAsync, execAsync, EXTENSION_ID = "dotenc.dotenc", EDITOR_PROTOCOL_URLS, EDITOR_NAMES, installVscodeExtensionCommand = async () => {
@@ -2518,31 +2728,31 @@ var init_update = () => {};
2518
2728
 
2519
2729
  // src/commands/update.ts
2520
2730
  import { spawn as spawn3 } from "node:child_process";
2521
- import chalk17 from "chalk";
2522
- var runPackageManagerCommand = (command, args) => new Promise((resolve, reject) => {
2523
- const child = spawn3(command, args, {
2731
+ import chalk18 from "chalk";
2732
+ var runPackageManagerCommand = (command, args, spawnImpl = spawn3) => new Promise((resolve, reject) => {
2733
+ const child = spawnImpl(command, args, {
2524
2734
  stdio: "inherit",
2525
2735
  shell: process.platform === "win32"
2526
2736
  });
2527
2737
  child.on("error", reject);
2528
2738
  child.on("exit", (code) => resolve(code ?? 1));
2529
- }), defaultDeps2, updateCommands, _runUpdateCommand = async (depsOverrides = {}) => {
2739
+ }), defaultDeps3, updateCommands, _runUpdateCommand = async (depsOverrides = {}) => {
2530
2740
  const deps = {
2531
- ...defaultDeps2,
2741
+ ...defaultDeps3,
2532
2742
  ...depsOverrides
2533
2743
  };
2534
2744
  const method = deps.detectInstallMethod();
2535
2745
  if (method === "binary") {
2536
- deps.log(`Standalone binary detected. Download the latest release at ${chalk17.cyan(GITHUB_RELEASES_URL)}.`);
2746
+ deps.log(`Standalone binary detected. Download the latest release at ${chalk18.cyan(GITHUB_RELEASES_URL)}.`);
2537
2747
  return;
2538
2748
  }
2539
2749
  if (method === "unknown") {
2540
2750
  deps.log("Could not determine installation method automatically.");
2541
2751
  deps.log(`Try one of these commands:`);
2542
- deps.log(` ${chalk17.gray("brew upgrade dotenc")}`);
2543
- deps.log(` ${chalk17.gray("scoop update dotenc")}`);
2544
- deps.log(` ${chalk17.gray("npm install -g @dotenc/cli")}`);
2545
- deps.log(`Or download from ${chalk17.cyan(GITHUB_RELEASES_URL)}.`);
2752
+ deps.log(` ${chalk18.gray("brew upgrade dotenc")}`);
2753
+ deps.log(` ${chalk18.gray("scoop update dotenc")}`);
2754
+ deps.log(` ${chalk18.gray("npm install -g @dotenc/cli")}`);
2755
+ deps.log(`Or download from ${chalk18.cyan(GITHUB_RELEASES_URL)}.`);
2546
2756
  return;
2547
2757
  }
2548
2758
  const updater = updateCommands[method];
@@ -2551,12 +2761,12 @@ var runPackageManagerCommand = (command, args) => new Promise((resolve, reject)
2551
2761
  try {
2552
2762
  exitCode = await deps.runPackageManagerCommand(updater.command, updater.args);
2553
2763
  } catch (error) {
2554
- deps.logError(`${chalk17.red("Error:")} failed to run ${chalk17.gray([updater.command, ...updater.args].join(" "))}.`);
2555
- deps.logError(`${chalk17.red("Details:")} ${error instanceof Error ? error.message : String(error)}`);
2764
+ deps.logError(`${chalk18.red("Error:")} failed to run ${chalk18.gray([updater.command, ...updater.args].join(" "))}.`);
2765
+ deps.logError(`${chalk18.red("Details:")} ${error instanceof Error ? error.message : String(error)}`);
2556
2766
  deps.exit(1);
2557
2767
  }
2558
2768
  if (exitCode !== 0) {
2559
- deps.logError(`${chalk17.red("Error:")} update command exited with code ${exitCode}.`);
2769
+ deps.logError(`${chalk18.red("Error:")} update command exited with code ${exitCode}.`);
2560
2770
  deps.exit(exitCode);
2561
2771
  }
2562
2772
  }, updateCommand = async () => {
@@ -2564,7 +2774,7 @@ var runPackageManagerCommand = (command, args) => new Promise((resolve, reject)
2564
2774
  };
2565
2775
  var init_update2 = __esm(() => {
2566
2776
  init_update();
2567
- defaultDeps2 = {
2777
+ defaultDeps3 = {
2568
2778
  detectInstallMethod,
2569
2779
  runPackageManagerCommand,
2570
2780
  log: console.log,
@@ -2643,8 +2853,8 @@ var init_whoami = __esm(() => {
2643
2853
  });
2644
2854
 
2645
2855
  // src/helpers/updateNotifier.ts
2646
- import chalk18 from "chalk";
2647
- var CHECK_INTERVAL_MS, defaultDeps3, shouldSkipCheck = (args, env) => {
2856
+ import chalk19 from "chalk";
2857
+ var CHECK_INTERVAL_MS, defaultDeps4, shouldSkipCheck = (args, env) => {
2648
2858
  if (env.DOTENC_SKIP_UPDATE_CHECK === "1") {
2649
2859
  return true;
2650
2860
  }
@@ -2666,7 +2876,7 @@ var CHECK_INTERVAL_MS, defaultDeps3, shouldSkipCheck = (args, env) => {
2666
2876
  } catch {}
2667
2877
  }, maybeNotifyAboutUpdate = async (depsOverrides = {}) => {
2668
2878
  const deps = {
2669
- ...defaultDeps3,
2879
+ ...defaultDeps4,
2670
2880
  ...depsOverrides
2671
2881
  };
2672
2882
  if (shouldSkipCheck(deps.args, deps.env)) {
@@ -2699,7 +2909,7 @@ var CHECK_INTERVAL_MS, defaultDeps3, shouldSkipCheck = (args, env) => {
2699
2909
  if (updateState.notifiedVersion === latestVersion) {
2700
2910
  return;
2701
2911
  }
2702
- deps.log(`${chalk18.yellow("Update available:")} ${chalk18.gray(`dotenc ${deps.currentVersion}`)} -> ${chalk18.cyan(`dotenc ${latestVersion}`)}. Run ${chalk18.gray("dotenc update")}.`);
2912
+ deps.log(`${chalk19.yellow("Update available:")} ${chalk19.gray(`dotenc ${deps.currentVersion}`)} -> ${chalk19.cyan(`dotenc ${latestVersion}`)}. Run ${chalk19.gray("dotenc update")}.`);
2703
2913
  updateState = {
2704
2914
  ...updateState,
2705
2915
  notifiedVersion: latestVersion
@@ -2711,7 +2921,7 @@ var init_updateNotifier = __esm(() => {
2711
2921
  init_homeConfig();
2712
2922
  init_update();
2713
2923
  CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000;
2714
- defaultDeps3 = {
2924
+ defaultDeps4 = {
2715
2925
  getHomeConfig,
2716
2926
  setHomeConfig,
2717
2927
  fetchLatestVersion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotenc/cli",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "🔐 Git-native encrypted environments powered by your SSH keys",
5
5
  "author": "Ivan Filho <i@ivanfilho.com>",
6
6
  "license": "MIT",
@@ -31,7 +31,6 @@
31
31
  "typescript": "^5.8.2"
32
32
  },
33
33
  "dependencies": {
34
- "@paralleldrive/cuid2": "^2.2.2",
35
34
  "chalk": "^5.4.1",
36
35
  "commander": "^13.1.0",
37
36
  "eciesjs": "^0.4.15",