@dotenc/cli 0.5.2 → 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 +442 -233
  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.2",
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
  });
@@ -1446,10 +1544,10 @@ var init_getDefaultEditor = __esm(() => {
1446
1544
 
1447
1545
  // src/commands/env/edit.ts
1448
1546
  import { spawnSync } from "node:child_process";
1449
- import { existsSync as existsSync6 } from "node:fs";
1450
- import fs10 from "node:fs/promises";
1547
+ import { existsSync as existsSync5 } from "node:fs";
1548
+ import fs9 from "node:fs/promises";
1451
1549
  import os3 from "node:os";
1452
- import path10 from "node:path";
1550
+ import path9 from "node:path";
1453
1551
  import chalk10 from "chalk";
1454
1552
  var editCommand = async (environmentNameArg) => {
1455
1553
  const environmentName = environmentNameArg || await chooseEnvironmentPrompt("What environment do you want to edit?");
@@ -1459,8 +1557,8 @@ var editCommand = async (environmentNameArg) => {
1459
1557
  process.exit(1);
1460
1558
  }
1461
1559
  const environmentFile = `.env.${environmentName}.enc`;
1462
- const environmentFilePath = path10.join(process.cwd(), environmentFile);
1463
- if (!existsSync6(environmentFilePath)) {
1560
+ const environmentFilePath = path9.join(process.cwd(), environmentFile);
1561
+ if (!existsSync5(environmentFilePath)) {
1464
1562
  console.error(`Environment file not found: ${environmentFilePath}`);
1465
1563
  process.exit(1);
1466
1564
  }
@@ -1484,15 +1582,15 @@ ${environment.keys.map((key) => `# - ${key.name}`).join(`
1484
1582
  # Use 'dotenc auth grant' and/or 'dotenc auth revoke' to manage access.
1485
1583
  # Make sure to save your changes before closing the editor.
1486
1584
  ${separator}${content}`;
1487
- const tempDir = await fs10.mkdtemp(path10.join(os3.tmpdir(), "dotenc-"));
1488
- const tempFilePath = path10.join(tempDir, `.env.${environmentName}`);
1489
- 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 });
1490
1588
  const initialHash = createHash(content);
1491
1589
  const cleanup = async () => {
1492
- await fs10.rm(tempDir, { recursive: true, force: true }).catch(() => {});
1590
+ await fs9.rm(tempDir, { recursive: true, force: true }).catch(() => {});
1493
1591
  };
1494
1592
  const onSignal = () => {
1495
- 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));
1496
1594
  };
1497
1595
  process.on("SIGINT", onSignal);
1498
1596
  process.on("SIGTERM", onSignal);
@@ -1507,7 +1605,7 @@ ${separator}${content}`;
1507
1605
  Editor exited with code ${result.status}`);
1508
1606
  process.exit(1);
1509
1607
  }
1510
- let newContent = await fs10.readFile(tempFilePath, "utf-8");
1608
+ let newContent = await fs9.readFile(tempFilePath, "utf-8");
1511
1609
  const finalHash = createHash(newContent);
1512
1610
  if (initialHash === finalHash) {
1513
1611
  console.log(`
@@ -1697,31 +1795,22 @@ var init_rotate = __esm(() => {
1697
1795
  init_chooseEnvironment();
1698
1796
  });
1699
1797
 
1700
- // src/helpers/createProject.ts
1701
- import { createId } from "@paralleldrive/cuid2";
1702
- var createProject = async () => {
1703
- return {
1704
- projectId: createId()
1705
- };
1706
- };
1707
- var init_createProject = () => {};
1708
-
1709
1798
  // src/helpers/setupGitDiff.ts
1710
1799
  import { spawnSync as spawnSync2 } from "node:child_process";
1711
- import fs11 from "node:fs";
1712
- import path11 from "node:path";
1800
+ import fs10 from "node:fs";
1801
+ import path10 from "node:path";
1713
1802
  var setupGitDiff = () => {
1714
- const gitattributesPath = path11.join(process.cwd(), ".gitattributes");
1803
+ const gitattributesPath = path10.join(process.cwd(), ".gitattributes");
1715
1804
  const marker = "*.enc diff=dotenc";
1716
1805
  let content = "";
1717
- if (fs11.existsSync(gitattributesPath)) {
1718
- content = fs11.readFileSync(gitattributesPath, "utf-8");
1806
+ if (fs10.existsSync(gitattributesPath)) {
1807
+ content = fs10.readFileSync(gitattributesPath, "utf-8");
1719
1808
  }
1720
1809
  if (!content.includes(marker)) {
1721
1810
  const newline = content.length > 0 && !content.endsWith(`
1722
1811
  `) ? `
1723
1812
  ` : "";
1724
- fs11.writeFileSync(gitattributesPath, `${content}${newline}${marker}
1813
+ fs10.writeFileSync(gitattributesPath, `${content}${newline}${marker}
1725
1814
  `);
1726
1815
  }
1727
1816
  spawnSync2("git", ["config", "--local", "diff.dotenc.textconv", "dotenc textconv"], {
@@ -1730,10 +1819,169 @@ var setupGitDiff = () => {
1730
1819
  };
1731
1820
  var init_setupGitDiff = () => {};
1732
1821
 
1733
- // 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";
1734
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";
1735
1983
  var inputNamePrompt = async (message, defaultValue) => {
1736
- const result = await inquirer5.prompt([
1984
+ const result = await inquirer6.prompt([
1737
1985
  {
1738
1986
  type: "input",
1739
1987
  name: "name",
@@ -1818,9 +2066,9 @@ function validatePublicKey(key) {
1818
2066
  }
1819
2067
 
1820
2068
  // src/prompts/inputKey.ts
1821
- import inquirer6 from "inquirer";
2069
+ import inquirer7 from "inquirer";
1822
2070
  var inputKeyPrompt = async (message, defaultValue) => {
1823
- const result = await inquirer6.prompt([
2071
+ const result = await inquirer7.prompt([
1824
2072
  {
1825
2073
  type: "password",
1826
2074
  name: "key",
@@ -1837,26 +2085,21 @@ var init_inputKey = () => {};
1837
2085
  import crypto10 from "node:crypto";
1838
2086
  import { existsSync as existsSync7 } from "node:fs";
1839
2087
  import fs12 from "node:fs/promises";
1840
- import os4 from "node:os";
1841
- import path12 from "node:path";
1842
- import chalk12 from "chalk";
1843
- 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";
1844
2092
  var keyAddCommand = async (nameArg, options) => {
1845
- const { projectId } = await getProjectConfig();
1846
- if (!projectId) {
1847
- console.error('No project found. Run "dotenc init" to create one.');
1848
- process.exit(1);
1849
- }
1850
2093
  let publicKey;
1851
2094
  if (options?.fromSsh) {
1852
- 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;
1853
2096
  if (!existsSync7(sshPath)) {
1854
- 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.`);
1855
2098
  process.exit(1);
1856
2099
  }
1857
2100
  const keyContent = await fs12.readFile(sshPath, "utf-8");
1858
2101
  if (isPassphraseProtected(keyContent)) {
1859
- 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.`);
1860
2103
  process.exit(1);
1861
2104
  }
1862
2105
  try {
@@ -1879,12 +2122,12 @@ var keyAddCommand = async (nameArg, options) => {
1879
2122
  }
1880
2123
  if (options?.fromFile) {
1881
2124
  if (!existsSync7(options.fromFile)) {
1882
- 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.`);
1883
2126
  process.exit(1);
1884
2127
  }
1885
2128
  const keyContent = await fs12.readFile(options.fromFile, "utf-8");
1886
2129
  if (isPassphraseProtected(keyContent)) {
1887
- 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.`);
1888
2131
  process.exit(1);
1889
2132
  }
1890
2133
  try {
@@ -1906,7 +2149,7 @@ var keyAddCommand = async (nameArg, options) => {
1906
2149
  }
1907
2150
  if (options?.fromString) {
1908
2151
  if (isPassphraseProtected(options.fromString)) {
1909
- 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.`);
1910
2153
  process.exit(1);
1911
2154
  }
1912
2155
  try {
@@ -1927,23 +2170,12 @@ var keyAddCommand = async (nameArg, options) => {
1927
2170
  }
1928
2171
  }
1929
2172
  if (!publicKey) {
1930
- const { keys: sshKeys, passphraseProtectedKeys } = await getPrivateKeys();
1931
- if (sshKeys.length === 0 && passphraseProtectedKeys.length > 0) {
1932
- console.warn(`${chalk12.yellow("Warning:")} SSH keys were found but are passphrase-protected (not supported by dotenc):
1933
- ${passphraseProtectedKeys.map((k) => ` - ${k}`).join(`
1934
- `)}
1935
- `);
1936
- }
1937
- const choices = sshKeys.map((key) => ({
1938
- name: `${key.name} (${key.algorithm})`,
1939
- value: key.name
1940
- }));
1941
- const modePrompt = await inquirer7.prompt({
2173
+ const modePrompt = await inquirer8.prompt({
1942
2174
  type: "list",
1943
2175
  name: "mode",
1944
2176
  message: "Would you like to add one of your SSH keys or paste a public key?",
1945
2177
  choices: [
1946
- ...choices.length ? [{ name: "Choose from my SSH keys", value: "choose" }] : [],
2178
+ { name: "Choose or create an SSH key", value: "choose" },
1947
2179
  { name: "Paste a public key (PEM format)", value: "paste" }
1948
2180
  ]
1949
2181
  });
@@ -1961,15 +2193,11 @@ ${passphraseProtectedKeys.map((k) => ` - ${k}`).join(`
1961
2193
  process.exit(1);
1962
2194
  }
1963
2195
  } else {
1964
- const keyPrompt = await inquirer7.prompt({
1965
- type: "list",
1966
- name: "key",
1967
- message: "Which SSH key do you want to add?",
1968
- choices
1969
- });
1970
- const selectedKey = sshKeys.find((k) => k.name === keyPrompt.key);
1971
- if (!selectedKey) {
1972
- 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));
1973
2201
  process.exit(1);
1974
2202
  }
1975
2203
  publicKey = crypto10.createPublicKey(selectedKey.privateKey);
@@ -1991,25 +2219,24 @@ ${passphraseProtectedKeys.map((k) => ` - ${k}`).join(`
1991
2219
  type: "spki",
1992
2220
  format: "pem"
1993
2221
  });
1994
- if (!existsSync7(path12.join(process.cwd(), ".dotenc"))) {
1995
- 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"));
1996
2224
  }
1997
2225
  let name = nameArg;
1998
2226
  if (!name) {
1999
2227
  name = await inputNamePrompt("What name do you want to give to the new public key?");
2000
- if (existsSync7(path12.join(process.cwd(), ".dotenc", `${name}.pub`))) {
2001
- 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.`);
2002
2230
  process.exit(1);
2003
2231
  }
2004
2232
  }
2005
- 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");
2006
2234
  console.log(`
2007
- Public key ${chalk12.cyan(name)} added successfully!`);
2235
+ Public key ${chalk13.cyan(name)} added successfully!`);
2008
2236
  };
2009
2237
  var init_add = __esm(() => {
2010
- init_getPrivateKeys();
2011
2238
  init_parseOpenSSHKey();
2012
- init_projectConfig();
2239
+ init_choosePrivateKey();
2013
2240
  init_inputKey();
2014
2241
  init_inputName();
2015
2242
  });
@@ -2018,61 +2245,32 @@ var init_add = __esm(() => {
2018
2245
  import crypto11 from "node:crypto";
2019
2246
  import { existsSync as existsSync8 } from "node:fs";
2020
2247
  import fs13 from "node:fs/promises";
2021
- import os5 from "node:os";
2022
- import path13 from "node:path";
2023
- import chalk13 from "chalk";
2024
- import inquirer8 from "inquirer";
2025
- var initCommand = async (options) => {
2026
- const { keys: privateKeys, passphraseProtectedKeys } = await getPrivateKeys();
2027
- if (!privateKeys.length) {
2028
- if (passphraseProtectedKeys.length > 0) {
2029
- console.error(passphraseProtectedKeyError(passphraseProtectedKeys));
2030
- } else {
2031
- console.error(`${chalk13.red("Error:")} no SSH keys found in ~/.ssh/. Please generate one first using ${chalk13.gray("ssh-keygen")}.`);
2032
- }
2033
- 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;
2034
2254
  }
2035
- 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);
2036
2262
  if (!username) {
2037
- console.error(`${chalk13.red("Error:")} no name provided.`);
2263
+ console.error(`${chalk14.red("Error:")} no name provided.`);
2038
2264
  process.exit(1);
2039
2265
  }
2040
- if (!existsSync8(path13.join(process.cwd(), "dotenc.json"))) {
2041
- console.log("No project found. Let's create a new one.");
2042
- try {
2043
- const { projectId } = await createProject();
2044
- await fs13.writeFile(path13.join(process.cwd(), "dotenc.json"), JSON.stringify({ projectId }, null, 2), "utf-8");
2045
- } catch (error) {
2046
- console.error(`${chalk13.red("Error:")} failed to create the project.`);
2047
- console.error(`${chalk13.red("Details:")} ${error instanceof Error ? error.message : error}`);
2048
- process.exit(1);
2049
- }
2050
- }
2051
- let keyToAdd;
2052
- if (privateKeys.length === 1) {
2053
- keyToAdd = privateKeys[0].name;
2054
- } else {
2055
- const result = await inquirer8.prompt([
2056
- {
2057
- type: "list",
2058
- name: "key",
2059
- message: "Which SSH key would you like to use?",
2060
- choices: privateKeys.map((key) => ({
2061
- name: `${key.name} (${key.algorithm})`,
2062
- value: key.name
2063
- }))
2064
- }
2065
- ]);
2066
- keyToAdd = result.key;
2067
- }
2068
- if (!keyToAdd) {
2069
- 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));
2070
2271
  process.exit(1);
2071
2272
  }
2072
- const keyEntry = privateKeys.find((k) => k.name === keyToAdd);
2073
- if (!keyEntry)
2074
- process.exit(1);
2075
- console.log(`Adding key: ${chalk13.cyan(username)} (${keyEntry.algorithm})`);
2273
+ console.log(`Adding key: ${chalk14.cyan(username)} (${keyEntry.algorithm})`);
2076
2274
  const publicKey = crypto11.createPublicKey(keyEntry.privateKey);
2077
2275
  const publicKeyPem = publicKey.export({ type: "spki", format: "pem" }).toString();
2078
2276
  await keyAddCommand(username, {
@@ -2081,36 +2279,47 @@ var initCommand = async (options) => {
2081
2279
  try {
2082
2280
  setupGitDiff();
2083
2281
  } catch (_error) {
2084
- 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.`);
2085
2283
  }
2086
2284
  let initialContent;
2087
- const envPath = path13.join(process.cwd(), ".env");
2285
+ const envPath = path14.join(process.cwd(), ".env");
2088
2286
  if (existsSync8(envPath)) {
2089
2287
  initialContent = await fs13.readFile(envPath, "utf-8");
2090
2288
  await fs13.unlink(envPath);
2091
- 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);
2092
2294
  }
2093
- await createCommand(username, username, initialContent);
2094
2295
  console.log(`
2095
- ${chalk13.green("✔")} Initialization complete!`);
2296
+ ${chalk14.green("✔")} Initialization complete!`);
2096
2297
  console.log(`
2097
2298
  Some useful tips:`);
2098
- const editCmd = chalk13.gray(`dotenc env edit ${username}`);
2099
- console.log(`- To edit your personal environment: ${editCmd}`);
2100
- const devCmd = chalk13.gray("dotenc dev <command>");
2101
- 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
+ }
2102
2312
  if (existsSync8(".claude") || existsSync8("CLAUDE.md")) {
2103
- 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")}`);
2104
2314
  }
2105
2315
  if (existsSync8(".vscode") || existsSync8(".cursor") || existsSync8(".windsurf")) {
2106
- 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")}`);
2107
2317
  }
2108
2318
  };
2109
2319
  var init_init = __esm(() => {
2110
- init_createProject();
2111
- init_errors();
2112
- init_getPrivateKeys();
2320
+ init_package();
2113
2321
  init_setupGitDiff();
2322
+ init_choosePrivateKey();
2114
2323
  init_inputName();
2115
2324
  init_create();
2116
2325
  init_add();
@@ -2148,16 +2357,16 @@ var init_confirm = () => {};
2148
2357
  // src/commands/key/remove.ts
2149
2358
  import { existsSync as existsSync9 } from "node:fs";
2150
2359
  import fs14 from "node:fs/promises";
2151
- import path14 from "node:path";
2152
- import chalk14 from "chalk";
2360
+ import path15 from "node:path";
2361
+ import chalk15 from "chalk";
2153
2362
  var keyRemoveCommand = async (nameArg) => {
2154
2363
  let name = nameArg;
2155
2364
  if (!name) {
2156
2365
  name = await choosePublicKeyPrompt("Which public key do you want to remove?");
2157
2366
  }
2158
- const filePath = path14.join(process.cwd(), ".dotenc", `${name}.pub`);
2367
+ const filePath = path15.join(process.cwd(), ".dotenc", `${name}.pub`);
2159
2368
  if (!existsSync9(filePath)) {
2160
- console.error(`Public key ${chalk14.cyan(name)} not found.`);
2369
+ console.error(`Public key ${chalk15.cyan(name)} not found.`);
2161
2370
  process.exit(1);
2162
2371
  }
2163
2372
  const allEnvironments = await getEnvironments();
@@ -2171,7 +2380,7 @@ var keyRemoveCommand = async (nameArg) => {
2171
2380
  } catch {}
2172
2381
  }
2173
2382
  if (affectedEnvironments.length > 0) {
2174
- 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:`);
2175
2384
  for (const env of affectedEnvironments) {
2176
2385
  console.log(` - ${env}`);
2177
2386
  }
@@ -2184,7 +2393,7 @@ Access will be revoked from these environments automatically.`);
2184
2393
  return;
2185
2394
  }
2186
2395
  await fs14.unlink(filePath);
2187
- console.log(`Public key ${chalk14.cyan(name)} removed successfully.`);
2396
+ console.log(`Public key ${chalk15.cyan(name)} removed successfully.`);
2188
2397
  for (const envName of affectedEnvironments) {
2189
2398
  try {
2190
2399
  const envJson = await getEnvironmentByName(envName);
@@ -2192,9 +2401,9 @@ Access will be revoked from these environments automatically.`);
2192
2401
  await encryptEnvironment(envName, content, {
2193
2402
  revokePublicKeys: [name]
2194
2403
  });
2195
- console.log(`Revoked access from ${chalk14.cyan(envName)} environment.`);
2404
+ console.log(`Revoked access from ${chalk15.cyan(envName)} environment.`);
2196
2405
  } catch {
2197
- 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.`);
2198
2407
  }
2199
2408
  }
2200
2409
  };
@@ -2209,9 +2418,9 @@ var init_remove = __esm(() => {
2209
2418
 
2210
2419
  // src/commands/textconv.ts
2211
2420
  import fs15 from "node:fs/promises";
2212
- import path15 from "node:path";
2421
+ import path16 from "node:path";
2213
2422
  var textconvCommand = async (filePath) => {
2214
- const absolutePath = path15.isAbsolute(filePath) ? filePath : path15.join(process.cwd(), filePath);
2423
+ const absolutePath = path16.isAbsolute(filePath) ? filePath : path16.join(process.cwd(), filePath);
2215
2424
  try {
2216
2425
  const environment = await getEnvironmentByPath(absolutePath);
2217
2426
  const plaintext = await decryptEnvironmentData(environment);
@@ -2228,18 +2437,18 @@ var init_textconv = __esm(() => {
2228
2437
 
2229
2438
  // src/commands/tools/install-agent-skill.ts
2230
2439
  import { spawn as spawn2 } from "node:child_process";
2231
- import chalk15 from "chalk";
2440
+ import chalk16 from "chalk";
2232
2441
  import inquirer10 from "inquirer";
2233
- var SKILL_SOURCE = "ivanfilhoz/dotenc", SKILL_NAME = "dotenc", runNpx = (args) => new Promise((resolve, reject) => {
2234
- 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, {
2235
2444
  stdio: "inherit",
2236
2445
  shell: process.platform === "win32"
2237
2446
  });
2238
2447
  child.on("error", reject);
2239
2448
  child.on("exit", (code) => resolve(code ?? 1));
2240
- }), defaultDeps, _runInstallAgentSkillCommand = async (options, depsOverrides = {}) => {
2449
+ }), defaultDeps2, _runInstallAgentSkillCommand = async (options, depsOverrides = {}) => {
2241
2450
  const deps = {
2242
- ...defaultDeps,
2451
+ ...defaultDeps2,
2243
2452
  ...depsOverrides
2244
2453
  };
2245
2454
  const { scope } = await deps.prompt([
@@ -2265,21 +2474,21 @@ var SKILL_SOURCE = "ivanfilhoz/dotenc", SKILL_NAME = "dotenc", runNpx = (args) =
2265
2474
  try {
2266
2475
  exitCode = await deps.runNpx(args);
2267
2476
  } catch (error) {
2268
- deps.logError(`${chalk15.red("Error:")} failed to run ${chalk15.gray(npxCommand)}.`);
2269
- 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)}`);
2270
2479
  deps.exit(1);
2271
2480
  }
2272
2481
  if (exitCode !== 0) {
2273
- 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}.`);
2274
2483
  deps.exit(exitCode);
2275
2484
  }
2276
- deps.log(`${chalk15.green("✓")} Agent skill installation completed via ${chalk15.gray(npxCommand)}.`);
2277
- 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.`);
2278
2487
  }, installAgentSkillCommand = async (options) => {
2279
2488
  await _runInstallAgentSkillCommand(options);
2280
2489
  };
2281
2490
  var init_install_agent_skill = __esm(() => {
2282
- defaultDeps = {
2491
+ defaultDeps2 = {
2283
2492
  prompt: inquirer10.prompt,
2284
2493
  runNpx,
2285
2494
  log: console.log,
@@ -2292,12 +2501,12 @@ var init_install_agent_skill = __esm(() => {
2292
2501
  import { exec, execFile } from "node:child_process";
2293
2502
  import { existsSync as existsSync10 } from "node:fs";
2294
2503
  import fs16 from "node:fs/promises";
2295
- import path16 from "node:path";
2504
+ import path17 from "node:path";
2296
2505
  import { promisify } from "node:util";
2297
- import chalk16 from "chalk";
2506
+ import chalk17 from "chalk";
2298
2507
  import inquirer11 from "inquirer";
2299
2508
  async function addToExtensionsJson() {
2300
- const extensionsJsonPath = path16.join(process.cwd(), ".vscode", "extensions.json");
2509
+ const extensionsJsonPath = path17.join(process.cwd(), ".vscode", "extensions.json");
2301
2510
  let json = {};
2302
2511
  if (existsSync10(extensionsJsonPath)) {
2303
2512
  const content = await fs16.readFile(extensionsJsonPath, "utf-8");
@@ -2307,7 +2516,7 @@ async function addToExtensionsJson() {
2307
2516
  json = {};
2308
2517
  }
2309
2518
  } else {
2310
- await fs16.mkdir(path16.join(process.cwd(), ".vscode"), { recursive: true });
2519
+ await fs16.mkdir(path17.join(process.cwd(), ".vscode"), { recursive: true });
2311
2520
  }
2312
2521
  if (!Array.isArray(json.recommendations)) {
2313
2522
  json.recommendations = [];
@@ -2315,9 +2524,9 @@ async function addToExtensionsJson() {
2315
2524
  if (!json.recommendations.includes(EXTENSION_ID)) {
2316
2525
  json.recommendations.push(EXTENSION_ID);
2317
2526
  await fs16.writeFile(extensionsJsonPath, JSON.stringify(json, null, 2), "utf-8");
2318
- 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")}`);
2319
2528
  } else {
2320
- 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")}`);
2321
2530
  }
2322
2531
  }
2323
2532
  async function which(bin) {
@@ -2330,11 +2539,11 @@ async function which(bin) {
2330
2539
  }
2331
2540
  async function detectEditors() {
2332
2541
  const detected = [];
2333
- if (existsSync10(path16.join(process.cwd(), ".cursor")))
2542
+ if (existsSync10(path17.join(process.cwd(), ".cursor")))
2334
2543
  detected.push("cursor");
2335
- if (existsSync10(path16.join(process.cwd(), ".windsurf")))
2544
+ if (existsSync10(path17.join(process.cwd(), ".windsurf")))
2336
2545
  detected.push("windsurf");
2337
- if (existsSync10(path16.join(process.cwd(), ".vscode")))
2546
+ if (existsSync10(path17.join(process.cwd(), ".vscode")))
2338
2547
  detected.push("vscode");
2339
2548
  const checks = [
2340
2549
  { key: "cursor", bins: ["cursor"] },
@@ -2381,7 +2590,7 @@ async function _runInstallVscodeExtension(getEditors = detectEditors, _openUrl =
2381
2590
  await addToExtensionsJson();
2382
2591
  if (editors.length === 0) {
2383
2592
  console.log(`
2384
- 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)}`);
2385
2594
  return;
2386
2595
  }
2387
2596
  if (editors.length === 1) {
@@ -2400,10 +2609,10 @@ Install the extension in VS Code: ${chalk16.cyan(EDITOR_PROTOCOL_URLS.vscode)}`)
2400
2609
  try {
2401
2610
  await _openUrl(url);
2402
2611
  } catch {
2403
- console.log(`Open manually: ${chalk16.cyan(url)}`);
2612
+ console.log(`Open manually: ${chalk17.cyan(url)}`);
2404
2613
  }
2405
2614
  } else {
2406
- console.log(`Install manually: ${chalk16.cyan(url)}`);
2615
+ console.log(`Install manually: ${chalk17.cyan(url)}`);
2407
2616
  }
2408
2617
  return;
2409
2618
  }
@@ -2412,7 +2621,7 @@ Install the extension in your editor:`);
2412
2621
  for (const editor of editors) {
2413
2622
  const name = EDITOR_NAMES[editor] ?? editor;
2414
2623
  const url = EDITOR_PROTOCOL_URLS[editor];
2415
- console.log(` ${name}: ${chalk16.cyan(url)}`);
2624
+ console.log(` ${name}: ${chalk17.cyan(url)}`);
2416
2625
  }
2417
2626
  }
2418
2627
  var execFileAsync, execAsync, EXTENSION_ID = "dotenc.dotenc", EDITOR_PROTOCOL_URLS, EDITOR_NAMES, installVscodeExtensionCommand = async () => {
@@ -2519,31 +2728,31 @@ var init_update = () => {};
2519
2728
 
2520
2729
  // src/commands/update.ts
2521
2730
  import { spawn as spawn3 } from "node:child_process";
2522
- import chalk17 from "chalk";
2523
- var runPackageManagerCommand = (command, args) => new Promise((resolve, reject) => {
2524
- 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, {
2525
2734
  stdio: "inherit",
2526
2735
  shell: process.platform === "win32"
2527
2736
  });
2528
2737
  child.on("error", reject);
2529
2738
  child.on("exit", (code) => resolve(code ?? 1));
2530
- }), defaultDeps2, updateCommands, _runUpdateCommand = async (depsOverrides = {}) => {
2739
+ }), defaultDeps3, updateCommands, _runUpdateCommand = async (depsOverrides = {}) => {
2531
2740
  const deps = {
2532
- ...defaultDeps2,
2741
+ ...defaultDeps3,
2533
2742
  ...depsOverrides
2534
2743
  };
2535
2744
  const method = deps.detectInstallMethod();
2536
2745
  if (method === "binary") {
2537
- 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)}.`);
2538
2747
  return;
2539
2748
  }
2540
2749
  if (method === "unknown") {
2541
2750
  deps.log("Could not determine installation method automatically.");
2542
2751
  deps.log(`Try one of these commands:`);
2543
- deps.log(` ${chalk17.gray("brew upgrade dotenc")}`);
2544
- deps.log(` ${chalk17.gray("scoop update dotenc")}`);
2545
- deps.log(` ${chalk17.gray("npm install -g @dotenc/cli")}`);
2546
- 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)}.`);
2547
2756
  return;
2548
2757
  }
2549
2758
  const updater = updateCommands[method];
@@ -2552,12 +2761,12 @@ var runPackageManagerCommand = (command, args) => new Promise((resolve, reject)
2552
2761
  try {
2553
2762
  exitCode = await deps.runPackageManagerCommand(updater.command, updater.args);
2554
2763
  } catch (error) {
2555
- deps.logError(`${chalk17.red("Error:")} failed to run ${chalk17.gray([updater.command, ...updater.args].join(" "))}.`);
2556
- 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)}`);
2557
2766
  deps.exit(1);
2558
2767
  }
2559
2768
  if (exitCode !== 0) {
2560
- deps.logError(`${chalk17.red("Error:")} update command exited with code ${exitCode}.`);
2769
+ deps.logError(`${chalk18.red("Error:")} update command exited with code ${exitCode}.`);
2561
2770
  deps.exit(exitCode);
2562
2771
  }
2563
2772
  }, updateCommand = async () => {
@@ -2565,7 +2774,7 @@ var runPackageManagerCommand = (command, args) => new Promise((resolve, reject)
2565
2774
  };
2566
2775
  var init_update2 = __esm(() => {
2567
2776
  init_update();
2568
- defaultDeps2 = {
2777
+ defaultDeps3 = {
2569
2778
  detectInstallMethod,
2570
2779
  runPackageManagerCommand,
2571
2780
  log: console.log,
@@ -2644,8 +2853,8 @@ var init_whoami = __esm(() => {
2644
2853
  });
2645
2854
 
2646
2855
  // src/helpers/updateNotifier.ts
2647
- import chalk18 from "chalk";
2648
- var CHECK_INTERVAL_MS, defaultDeps3, shouldSkipCheck = (args, env) => {
2856
+ import chalk19 from "chalk";
2857
+ var CHECK_INTERVAL_MS, defaultDeps4, shouldSkipCheck = (args, env) => {
2649
2858
  if (env.DOTENC_SKIP_UPDATE_CHECK === "1") {
2650
2859
  return true;
2651
2860
  }
@@ -2667,7 +2876,7 @@ var CHECK_INTERVAL_MS, defaultDeps3, shouldSkipCheck = (args, env) => {
2667
2876
  } catch {}
2668
2877
  }, maybeNotifyAboutUpdate = async (depsOverrides = {}) => {
2669
2878
  const deps = {
2670
- ...defaultDeps3,
2879
+ ...defaultDeps4,
2671
2880
  ...depsOverrides
2672
2881
  };
2673
2882
  if (shouldSkipCheck(deps.args, deps.env)) {
@@ -2700,7 +2909,7 @@ var CHECK_INTERVAL_MS, defaultDeps3, shouldSkipCheck = (args, env) => {
2700
2909
  if (updateState.notifiedVersion === latestVersion) {
2701
2910
  return;
2702
2911
  }
2703
- 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")}.`);
2704
2913
  updateState = {
2705
2914
  ...updateState,
2706
2915
  notifiedVersion: latestVersion
@@ -2712,7 +2921,7 @@ var init_updateNotifier = __esm(() => {
2712
2921
  init_homeConfig();
2713
2922
  init_update();
2714
2923
  CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000;
2715
- defaultDeps3 = {
2924
+ defaultDeps4 = {
2716
2925
  getHomeConfig,
2717
2926
  setHomeConfig,
2718
2927
  fetchLatestVersion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotenc/cli",
3
- "version": "0.5.2",
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",