envhub-cli 0.1.1

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 (78) hide show
  1. package/README.md +60 -0
  2. package/dist/cli.d.ts +6 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +92 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/commands/cat.d.ts +6 -0
  7. package/dist/commands/cat.d.ts.map +1 -0
  8. package/dist/commands/cat.js +55 -0
  9. package/dist/commands/cat.js.map +1 -0
  10. package/dist/commands/delete.d.ts +10 -0
  11. package/dist/commands/delete.d.ts.map +1 -0
  12. package/dist/commands/delete.js +46 -0
  13. package/dist/commands/delete.js.map +1 -0
  14. package/dist/commands/grant.d.ts +6 -0
  15. package/dist/commands/grant.d.ts.map +1 -0
  16. package/dist/commands/grant.js +25 -0
  17. package/dist/commands/grant.js.map +1 -0
  18. package/dist/commands/init.d.ts +6 -0
  19. package/dist/commands/init.d.ts.map +1 -0
  20. package/dist/commands/init.js +275 -0
  21. package/dist/commands/init.js.map +1 -0
  22. package/dist/commands/list.d.ts +6 -0
  23. package/dist/commands/list.d.ts.map +1 -0
  24. package/dist/commands/list.js +46 -0
  25. package/dist/commands/list.js.map +1 -0
  26. package/dist/commands/pull.d.ts +6 -0
  27. package/dist/commands/pull.d.ts.map +1 -0
  28. package/dist/commands/pull.js +35 -0
  29. package/dist/commands/pull.js.map +1 -0
  30. package/dist/commands/push.d.ts +11 -0
  31. package/dist/commands/push.d.ts.map +1 -0
  32. package/dist/commands/push.js +126 -0
  33. package/dist/commands/push.js.map +1 -0
  34. package/dist/commands/revoke.d.ts +6 -0
  35. package/dist/commands/revoke.d.ts.map +1 -0
  36. package/dist/commands/revoke.js +25 -0
  37. package/dist/commands/revoke.js.map +1 -0
  38. package/dist/config/config.d.ts +52 -0
  39. package/dist/config/config.d.ts.map +1 -0
  40. package/dist/config/config.js +136 -0
  41. package/dist/config/config.js.map +1 -0
  42. package/dist/config/config.schema.d.ts +66 -0
  43. package/dist/config/config.schema.d.ts.map +1 -0
  44. package/dist/config/config.schema.js +33 -0
  45. package/dist/config/config.schema.js.map +1 -0
  46. package/dist/index.d.ts +3 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +14 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/providers/aws/aws-secrets.provider.d.ts +44 -0
  51. package/dist/providers/aws/aws-secrets.provider.d.ts.map +1 -0
  52. package/dist/providers/aws/aws-secrets.provider.js +306 -0
  53. package/dist/providers/aws/aws-secrets.provider.js.map +1 -0
  54. package/dist/providers/provider.factory.d.ts +24 -0
  55. package/dist/providers/provider.factory.d.ts.map +1 -0
  56. package/dist/providers/provider.factory.js +40 -0
  57. package/dist/providers/provider.factory.js.map +1 -0
  58. package/dist/providers/provider.interface.d.ts +96 -0
  59. package/dist/providers/provider.interface.d.ts.map +1 -0
  60. package/dist/providers/provider.interface.js +2 -0
  61. package/dist/providers/provider.interface.js.map +1 -0
  62. package/dist/utils/diff.d.ts +27 -0
  63. package/dist/utils/diff.d.ts.map +1 -0
  64. package/dist/utils/diff.js +105 -0
  65. package/dist/utils/diff.js.map +1 -0
  66. package/dist/utils/env-parser.d.ts +48 -0
  67. package/dist/utils/env-parser.d.ts.map +1 -0
  68. package/dist/utils/env-parser.js +84 -0
  69. package/dist/utils/env-parser.js.map +1 -0
  70. package/dist/utils/logger.d.ts +57 -0
  71. package/dist/utils/logger.d.ts.map +1 -0
  72. package/dist/utils/logger.js +82 -0
  73. package/dist/utils/logger.js.map +1 -0
  74. package/dist/versioning/version-control.d.ts +44 -0
  75. package/dist/versioning/version-control.d.ts.map +1 -0
  76. package/dist/versioning/version-control.js +81 -0
  77. package/dist/versioning/version-control.js.map +1 -0
  78. package/package.json +58 -0
package/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # envhub Documentation
2
+
3
+ **envhub** is a CLI tool that makes sharing `.env` files between developers easy and secure. Instead of sending secrets over chat messages, envhub stores them in a cloud secrets manager and lets your team push and pull environment configurations safely.
4
+
5
+ ## Why envhub?
6
+
7
+ - No more sending API keys over Teams or Slack
8
+ - Built-in version control prevents accidental overwrites
9
+ - Easy interactive setup — no manual config files needed
10
+ - Extensible provider architecture (AWS today, Azure & GCP coming soon)
11
+
12
+ ## Table of Contents
13
+
14
+ ### Getting Started
15
+
16
+ 1. [Installation](docs/getting-started/installation.md)
17
+ 2. [Setup (envhub init)](docs/getting-started/setup.md)
18
+ 3. [Your First Secret](docs/getting-started/first-secret.md)
19
+ 4. [Version Control](docs/getting-started/version-control.md)
20
+
21
+ ### Commands
22
+
23
+ | Command | Description |
24
+ | --- | --- |
25
+ | [push](docs/commands/push.md) | Push a local .env file to the cloud |
26
+ | [pull](docs/commands/pull.md) | Pull the latest .env file from the cloud |
27
+ | [cat](docs/commands/cat.md) | Display the contents of a secret |
28
+ | [list](docs/commands/list.md) | List all managed secrets |
29
+ | [delete](docs/commands/delete.md) | Delete a secret |
30
+ | [grant](docs/commands/grant.md) | Grant a user access to a secret |
31
+ | [revoke](docs/commands/revoke.md) | Revoke a user's access to a secret |
32
+
33
+ ### Architecture
34
+
35
+ - [Configuration (.envhubrc.json)](docs/architecture/configuration.md)
36
+ - [Provider Architecture](docs/architecture/providers.md)
37
+
38
+ ## Quick Example
39
+
40
+ ```bash
41
+ # 1. Set up your project
42
+ npx envhub init
43
+
44
+ # 2. Push your .env file
45
+ npx envhub push my-app-dev ./.env -m "Initial setup"
46
+
47
+ # 3. Your teammate pulls it
48
+ npx envhub pull my-app-dev ./.env
49
+
50
+ # 4. Grant access to another developer
51
+ npx envhub grant my-app-dev jane.doe
52
+ ```
53
+
54
+ ## Supported Providers
55
+
56
+ | Provider | Status |
57
+ | --- | --- |
58
+ | AWS Secrets Manager | Available |
59
+ | Azure Key Vault | Planned |
60
+ | GCP Secret Manager | Planned |
package/dist/cli.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { Command } from "commander";
2
+ /**
3
+ * Create and configure the CLI program with all commands.
4
+ */
5
+ export declare function createProgram(): Command;
6
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC;;GAEG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAkGvC"}
package/dist/cli.js ADDED
@@ -0,0 +1,92 @@
1
+ import { Command } from "commander";
2
+ import { initCommand } from "./commands/init.js";
3
+ import { pushCommand } from "./commands/push.js";
4
+ import { pullCommand } from "./commands/pull.js";
5
+ import { catCommand } from "./commands/cat.js";
6
+ import { listCommand } from "./commands/list.js";
7
+ import { deleteCommand } from "./commands/delete.js";
8
+ import { grantCommand } from "./commands/grant.js";
9
+ import { revokeCommand } from "./commands/revoke.js";
10
+ /**
11
+ * Create and configure the CLI program with all commands.
12
+ */
13
+ export function createProgram() {
14
+ const program = new Command();
15
+ program
16
+ .name("envhub")
17
+ .description("Securely share .env files between developers using cloud providers.")
18
+ .version("0.1.0");
19
+ // ── init ────────────────────────────────────────────────────────
20
+ program
21
+ .command("init")
22
+ .description("Set up envhub for your project (interactive wizard)")
23
+ .action(async () => {
24
+ await initCommand();
25
+ });
26
+ // ── push ────────────────────────────────────────────────────────
27
+ program
28
+ .command("push")
29
+ .description("Push a local .env file to the cloud provider")
30
+ .argument("<name>", "Name for the secret")
31
+ .argument("<file>", "Path to the .env file")
32
+ .option("-m, --message <message>", "A message describing this version")
33
+ .option("-f, --force", "Bypass version conflict checking", false)
34
+ .action(async (name, file, options) => {
35
+ await pushCommand(name, file, options);
36
+ });
37
+ // ── pull ────────────────────────────────────────────────────────
38
+ program
39
+ .command("pull")
40
+ .description("Pull the latest .env file from the cloud provider")
41
+ .argument("<name>", "Name of the secret to pull")
42
+ .argument("<file>", "Path to write the .env file to")
43
+ .action(async (name, file) => {
44
+ await pullCommand(name, file);
45
+ });
46
+ // ── cat ─────────────────────────────────────────────────────────
47
+ program
48
+ .command("cat")
49
+ .description("Display the contents of a secret")
50
+ .argument("<name>", "Name of the secret to display")
51
+ .action(async (name) => {
52
+ await catCommand(name);
53
+ });
54
+ // ── list ────────────────────────────────────────────────────────
55
+ program
56
+ .command("list")
57
+ .alias("ls")
58
+ .description("List all secrets managed by envhub")
59
+ .action(async () => {
60
+ await listCommand();
61
+ });
62
+ // ── delete ──────────────────────────────────────────────────────
63
+ program
64
+ .command("delete")
65
+ .alias("rm")
66
+ .description("Delete a secret from the cloud provider")
67
+ .argument("<name>", "Name of the secret to delete")
68
+ .option("-f, --force", "Force immediate deletion", false)
69
+ .action(async (name, options) => {
70
+ await deleteCommand(name, options);
71
+ });
72
+ // ── grant ───────────────────────────────────────────────────────
73
+ program
74
+ .command("grant")
75
+ .description("Grant another user access to a secret")
76
+ .argument("<name>", "Name of the secret")
77
+ .argument("<user>", "IAM username or ARN of the user to grant access")
78
+ .action(async (name, user) => {
79
+ await grantCommand(name, user);
80
+ });
81
+ // ── revoke ──────────────────────────────────────────────────────
82
+ program
83
+ .command("revoke")
84
+ .description("Revoke a user's access to a secret")
85
+ .argument("<name>", "Name of the secret")
86
+ .argument("<user>", "IAM username or ARN of the user to revoke access")
87
+ .action(async (name, user) => {
88
+ await revokeCommand(name, user);
89
+ });
90
+ return program;
91
+ }
92
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The `envhub cat` command.
3
+ * Outputs the contents of a secret without writing to disk.
4
+ */
5
+ export declare function catCommand(secretName: string): Promise<void>;
6
+ //# sourceMappingURL=cat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cat.d.ts","sourceRoot":"","sources":["../../src/commands/cat.ts"],"names":[],"mappings":"AAqCA;;;GAGG;AACH,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBlE"}
@@ -0,0 +1,55 @@
1
+ import chalk from "chalk";
2
+ import { configManager } from "../config/config.js";
3
+ import { ProviderFactory } from "../providers/provider.factory.js";
4
+ import { parseEnvContent } from "../utils/env-parser.js";
5
+ import { logger } from "../utils/logger.js";
6
+ /**
7
+ * Format .env content into a styled, readable table.
8
+ */
9
+ function formatEnvTable(content) {
10
+ const entries = parseEnvContent(content);
11
+ if (entries.size === 0) {
12
+ return chalk.dim(" (empty)");
13
+ }
14
+ // Find the longest key for alignment
15
+ let maxKeyLen = 0;
16
+ for (const key of entries.keys()) {
17
+ if (key.length > maxKeyLen)
18
+ maxKeyLen = key.length;
19
+ }
20
+ const lines = [];
21
+ const separator = chalk.dim("─".repeat(maxKeyLen + 40));
22
+ lines.push(separator);
23
+ for (const [key, value] of entries) {
24
+ const paddedKey = key.padEnd(maxKeyLen);
25
+ lines.push(` ${chalk.bold.cyan(paddedKey)} ${chalk.dim("=")} ${value}`);
26
+ }
27
+ lines.push(separator);
28
+ return lines.join("\n");
29
+ }
30
+ /**
31
+ * The `envhub cat` command.
32
+ * Outputs the contents of a secret without writing to disk.
33
+ */
34
+ export async function catCommand(secretName) {
35
+ // Load config and create provider
36
+ const config = await configManager.load();
37
+ const provider = ProviderFactory.createProvider(config);
38
+ const spinner = logger.spinner(`Reading '${secretName}'...`);
39
+ try {
40
+ const content = await provider.cat(secretName);
41
+ const entries = parseEnvContent(content);
42
+ spinner.succeed(`${chalk.bold(secretName)} ${chalk.dim(`(${entries.size} keys)`)}`);
43
+ logger.newline();
44
+ logger.log(formatEnvTable(content));
45
+ logger.newline();
46
+ }
47
+ catch (error) {
48
+ spinner.fail(`Failed to read '${secretName}'.`);
49
+ if (error instanceof Error) {
50
+ logger.error(error.message);
51
+ }
52
+ process.exit(1);
53
+ }
54
+ }
55
+ //# sourceMappingURL=cat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cat.js","sourceRoot":"","sources":["../../src/commands/cat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C;;GAEG;AACH,SAAS,cAAc,CAAC,OAAe;IACrC,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IAEzC,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAChC,CAAC;IAED,qCAAqC;IACrC,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QACjC,IAAI,GAAG,CAAC,MAAM,GAAG,SAAS;YAAE,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC;IACrD,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,GAAG,EAAE,CAAC,CAAC,CAAC;IAExD,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAEtB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;QACnC,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACxC,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAEtB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAAkB;IACjD,kCAAkC;IAClC,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,CAAC;IAC1C,MAAM,QAAQ,GAAG,eAAe,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IAExD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,YAAY,UAAU,MAAM,CAAC,CAAC;IAE7D,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC/C,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;QACzC,OAAO,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,IAAI,QAAQ,CAAC,EAAE,CAAC,CAAC;QAEpF,MAAM,CAAC,OAAO,EAAE,CAAC;QACjB,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;QACpC,MAAM,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,mBAAmB,UAAU,IAAI,CAAC,CAAC;QAChD,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1,10 @@
1
+ interface DeleteCommandOptions {
2
+ force?: boolean;
3
+ }
4
+ /**
5
+ * The `envhub delete` command.
6
+ * Deletes a secret from the cloud provider.
7
+ */
8
+ export declare function deleteCommand(secretName: string, options: DeleteCommandOptions): Promise<void>;
9
+ export {};
10
+ //# sourceMappingURL=delete.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delete.d.ts","sourceRoot":"","sources":["../../src/commands/delete.ts"],"names":[],"mappings":"AAKA,UAAU,oBAAoB;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC,CA4Cf"}
@@ -0,0 +1,46 @@
1
+ import { confirm } from "@inquirer/prompts";
2
+ import { configManager } from "../config/config.js";
3
+ import { ProviderFactory } from "../providers/provider.factory.js";
4
+ import { logger } from "../utils/logger.js";
5
+ /**
6
+ * The `envhub delete` command.
7
+ * Deletes a secret from the cloud provider.
8
+ */
9
+ export async function deleteCommand(secretName, options) {
10
+ // Load config and create provider
11
+ const config = await configManager.load();
12
+ const provider = ProviderFactory.createProvider(config);
13
+ // Confirm deletion
14
+ if (!options.force) {
15
+ const confirmed = await confirm({
16
+ message: `Are you sure you want to delete '${secretName}'? This action cannot be undone.`,
17
+ default: false,
18
+ });
19
+ if (!confirmed) {
20
+ logger.info("Deletion cancelled.");
21
+ return;
22
+ }
23
+ }
24
+ const spinner = logger.spinner(`Deleting '${secretName}'...`);
25
+ try {
26
+ await provider.delete(secretName, { force: options.force });
27
+ // Remove from local tracking
28
+ const cfg = configManager.getConfig();
29
+ if (cfg.secrets[secretName]) {
30
+ delete cfg.secrets[secretName];
31
+ await configManager.save(cfg);
32
+ }
33
+ spinner.succeed(`Deleted '${secretName}'.`);
34
+ if (!options.force) {
35
+ logger.dim(" Note: The secret is scheduled for deletion. Use --force for immediate deletion.");
36
+ }
37
+ }
38
+ catch (error) {
39
+ spinner.fail(`Failed to delete '${secretName}'.`);
40
+ if (error instanceof Error) {
41
+ logger.error(error.message);
42
+ }
43
+ process.exit(1);
44
+ }
45
+ }
46
+ //# sourceMappingURL=delete.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delete.js","sourceRoot":"","sources":["../../src/commands/delete.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAM5C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,UAAkB,EAClB,OAA6B;IAE7B,kCAAkC;IAClC,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,CAAC;IAC1C,MAAM,QAAQ,GAAG,eAAe,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IAExD,mBAAmB;IACnB,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACnB,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC;YAC9B,OAAO,EAAE,oCAAoC,UAAU,kCAAkC;YACzF,OAAO,EAAE,KAAK;SACf,CAAC,CAAC;QAEH,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;YACnC,OAAO;QACT,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,aAAa,UAAU,MAAM,CAAC,CAAC;IAE9D,IAAI,CAAC;QACH,MAAM,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QAE5D,6BAA6B;QAC7B,MAAM,GAAG,GAAG,aAAa,CAAC,SAAS,EAAE,CAAC;QACtC,IAAI,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5B,OAAO,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;YAC/B,MAAM,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;QAED,OAAO,CAAC,OAAO,CAAC,YAAY,UAAU,IAAI,CAAC,CAAC;QAE5C,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACnB,MAAM,CAAC,GAAG,CACR,mFAAmF,CACpF,CAAC;QACJ,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,qBAAqB,UAAU,IAAI,CAAC,CAAC;QAClD,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The `envhub grant` command.
3
+ * Grants another user access to a secret.
4
+ */
5
+ export declare function grantCommand(secretName: string, userIdentifier: string): Promise<void>;
6
+ //# sourceMappingURL=grant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grant.d.ts","sourceRoot":"","sources":["../../src/commands/grant.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,wBAAsB,YAAY,CAChC,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,CAqBf"}
@@ -0,0 +1,25 @@
1
+ import { configManager } from "../config/config.js";
2
+ import { ProviderFactory } from "../providers/provider.factory.js";
3
+ import { logger } from "../utils/logger.js";
4
+ /**
5
+ * The `envhub grant` command.
6
+ * Grants another user access to a secret.
7
+ */
8
+ export async function grantCommand(secretName, userIdentifier) {
9
+ // Load config and create provider
10
+ const config = await configManager.load();
11
+ const provider = ProviderFactory.createProvider(config);
12
+ const spinner = logger.spinner(`Granting access to '${secretName}' for '${userIdentifier}'...`);
13
+ try {
14
+ await provider.grant(secretName, userIdentifier);
15
+ spinner.succeed(`Granted '${userIdentifier}' access to '${secretName}'.`);
16
+ }
17
+ catch (error) {
18
+ spinner.fail(`Failed to grant access.`);
19
+ if (error instanceof Error) {
20
+ logger.error(error.message);
21
+ }
22
+ process.exit(1);
23
+ }
24
+ }
25
+ //# sourceMappingURL=grant.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grant.js","sourceRoot":"","sources":["../../src/commands/grant.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,UAAkB,EAClB,cAAsB;IAEtB,kCAAkC;IAClC,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,CAAC;IAC1C,MAAM,QAAQ,GAAG,eAAe,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IAExD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAC5B,uBAAuB,UAAU,UAAU,cAAc,MAAM,CAChE,CAAC;IAEF,IAAI,CAAC;QACH,MAAM,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;QACjD,OAAO,CAAC,OAAO,CACb,YAAY,cAAc,gBAAgB,UAAU,IAAI,CACzD,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACxC,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The `envhub init` command.
3
+ * Interactive wizard that creates the project configuration.
4
+ */
5
+ export declare function initCommand(): Promise<void>;
6
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AA6HA;;;GAGG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAsLjD"}
@@ -0,0 +1,275 @@
1
+ import { select, input, confirm } from "@inquirer/prompts";
2
+ import chalk from "chalk";
3
+ import * as fs from "node:fs/promises";
4
+ import * as path from "node:path";
5
+ import { logger } from "../utils/logger.js";
6
+ import { ConfigManager } from "../config/config.js";
7
+ import { ProviderFactory } from "../providers/provider.factory.js";
8
+ /**
9
+ * Read available AWS profiles from ~/.aws/credentials and ~/.aws/config.
10
+ * Also extracts the region for each profile from ~/.aws/config.
11
+ */
12
+ async function getAWSProfiles() {
13
+ const profileNames = new Set();
14
+ const profileRegions = new Map();
15
+ const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? "";
16
+ // Parse credentials file (only profile names)
17
+ const credentialsPath = path.join(homeDir, ".aws", "credentials");
18
+ try {
19
+ const content = await fs.readFile(credentialsPath, "utf-8");
20
+ const profileRegex = /\[([^\]]+)\]/g;
21
+ let match;
22
+ while ((match = profileRegex.exec(content)) !== null) {
23
+ profileNames.add(match[1].trim());
24
+ }
25
+ }
26
+ catch {
27
+ // File doesn't exist, skip
28
+ }
29
+ // Parse config file (profile names + regions)
30
+ const configPath = path.join(homeDir, ".aws", "config");
31
+ try {
32
+ const content = await fs.readFile(configPath, "utf-8");
33
+ const lines = content.split("\n");
34
+ let currentProfile = null;
35
+ for (const line of lines) {
36
+ const trimmed = line.trim();
37
+ // Match profile header: [profile name] or [default]
38
+ const headerMatch = trimmed.match(/^\[(?:profile\s+)?([^\]]+)\]$/);
39
+ if (headerMatch) {
40
+ currentProfile = headerMatch[1].trim();
41
+ profileNames.add(currentProfile);
42
+ continue;
43
+ }
44
+ // Match region key under current profile
45
+ if (currentProfile) {
46
+ const regionMatch = trimmed.match(/^region\s*=\s*(.+)$/);
47
+ if (regionMatch) {
48
+ profileRegions.set(currentProfile, regionMatch[1].trim());
49
+ }
50
+ }
51
+ }
52
+ }
53
+ catch {
54
+ // File doesn't exist, skip
55
+ }
56
+ return Array.from(profileNames)
57
+ .sort()
58
+ .map((name) => ({
59
+ name,
60
+ region: profileRegions.get(name),
61
+ }));
62
+ }
63
+ /**
64
+ * Common AWS regions for the selection prompt.
65
+ */
66
+ const AWS_REGIONS = [
67
+ { name: "EU (Frankfurt) - eu-central-1", value: "eu-central-1" },
68
+ { name: "EU (Ireland) - eu-west-1", value: "eu-west-1" },
69
+ { name: "EU (London) - eu-west-2", value: "eu-west-2" },
70
+ { name: "EU (Paris) - eu-west-3", value: "eu-west-3" },
71
+ { name: "EU (Stockholm) - eu-north-1", value: "eu-north-1" },
72
+ { name: "US East (N. Virginia) - us-east-1", value: "us-east-1" },
73
+ { name: "US East (Ohio) - us-east-2", value: "us-east-2" },
74
+ { name: "US West (Oregon) - us-west-2", value: "us-west-2" },
75
+ { name: "Asia Pacific (Tokyo) - ap-northeast-1", value: "ap-northeast-1" },
76
+ { name: "Asia Pacific (Singapore) - ap-southeast-1", value: "ap-southeast-1" },
77
+ ];
78
+ /**
79
+ * Update .gitignore to include .envhubrc.json if not already present.
80
+ */
81
+ async function updateGitignore(dir) {
82
+ const gitignorePath = path.join(dir, ".gitignore");
83
+ const entry = ".envhubrc.json";
84
+ try {
85
+ let content = "";
86
+ try {
87
+ content = await fs.readFile(gitignorePath, "utf-8");
88
+ }
89
+ catch {
90
+ // .gitignore doesn't exist, we'll create it
91
+ }
92
+ if (content.includes(entry)) {
93
+ return false; // Already in .gitignore
94
+ }
95
+ const newContent = content
96
+ ? content.trimEnd() + "\n\n# envhub config (contains AWS profile info)\n" + entry + "\n"
97
+ : "# envhub config (contains AWS profile info)\n" + entry + "\n";
98
+ await fs.writeFile(gitignorePath, newContent, "utf-8");
99
+ return true;
100
+ }
101
+ catch {
102
+ return false;
103
+ }
104
+ }
105
+ /**
106
+ * The `envhub init` command.
107
+ * Interactive wizard that creates the project configuration.
108
+ */
109
+ export async function initCommand() {
110
+ logger.log("");
111
+ logger.log(chalk.cyan(" ███████╗███╗ ██╗██╗ ██╗██╗ ██╗██╗ ██╗██████╗ "));
112
+ logger.log(chalk.cyan(" ██╔════╝████╗ ██║██║ ██║██║ ██║██║ ██║██╔══██╗"));
113
+ logger.log(chalk.cyan(" █████╗ ██╔██╗ ██║██║ ██║███████║██║ ██║██████╔╝"));
114
+ logger.log(chalk.cyan(" ██╔══╝ ██║╚██╗██║╚██╗ ██╔╝██╔══██║██║ ██║██╔══██╗"));
115
+ logger.log(chalk.cyan(" ███████╗██║ ╚████║ ╚████╔╝ ██║ ██║╚██████╔╝██████╔╝"));
116
+ logger.log(chalk.cyan(" ╚══════╝╚═╝ ╚═══╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ "));
117
+ logger.log("");
118
+ logger.log(chalk.dim(" Securely share .env files between developers."));
119
+ logger.log("");
120
+ // Check if config already exists
121
+ const exists = await ConfigManager.exists();
122
+ if (exists) {
123
+ const overwrite = await confirm({
124
+ message: "An .envhubrc.json already exists. Overwrite?",
125
+ default: false,
126
+ });
127
+ if (!overwrite) {
128
+ logger.info("Setup cancelled.");
129
+ return;
130
+ }
131
+ }
132
+ // Step 1: Select provider
133
+ const providers = ProviderFactory.getAvailableProviders();
134
+ const provider = await select({
135
+ message: "Which cloud provider would you like to use?",
136
+ choices: providers.map((p) => ({
137
+ name: p.available ? p.label : `${p.label} (coming soon)`,
138
+ value: p.type,
139
+ disabled: !p.available,
140
+ })),
141
+ });
142
+ // Step 2: Provider-specific configuration
143
+ const config = {
144
+ provider,
145
+ prefix: "envhub-",
146
+ secrets: {},
147
+ };
148
+ if (provider === "aws") {
149
+ // Detect available AWS profiles
150
+ const profiles = await getAWSProfiles();
151
+ let profileName;
152
+ let detectedRegion;
153
+ if (profiles.length > 0) {
154
+ profileName = await select({
155
+ message: "Select your AWS profile:",
156
+ choices: [
157
+ ...profiles.map((p) => ({
158
+ name: p.region ? `${p.name} (${p.region})` : p.name,
159
+ value: p.name,
160
+ })),
161
+ { name: "Enter a different profile name...", value: "__custom__" },
162
+ ],
163
+ });
164
+ if (profileName === "__custom__") {
165
+ profileName = await input({
166
+ message: "Enter your AWS profile name:",
167
+ validate: (val) => (val.trim() ? true : "Profile name is required."),
168
+ });
169
+ }
170
+ else {
171
+ // Get the region from the selected profile
172
+ detectedRegion = profiles.find((p) => p.name === profileName)?.region;
173
+ }
174
+ }
175
+ else {
176
+ logger.warn("No AWS profiles found in ~/.aws/credentials or ~/.aws/config.");
177
+ logger.dim(" Create one first: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html");
178
+ logger.log("");
179
+ profileName = await input({
180
+ message: "Enter your AWS profile name:",
181
+ validate: (val) => (val.trim() ? true : "Profile name is required."),
182
+ });
183
+ }
184
+ // Select region — use detected region from profile if available
185
+ let finalRegion;
186
+ if (detectedRegion) {
187
+ const useDetected = await confirm({
188
+ message: `Use region '${detectedRegion}' from your AWS profile?`,
189
+ default: true,
190
+ });
191
+ if (useDetected) {
192
+ finalRegion = detectedRegion;
193
+ }
194
+ else {
195
+ finalRegion = await select({
196
+ message: "Select a different AWS region:",
197
+ choices: [
198
+ ...AWS_REGIONS,
199
+ { name: "Enter a custom region...", value: "__custom__" },
200
+ ],
201
+ });
202
+ if (finalRegion === "__custom__") {
203
+ finalRegion = await input({
204
+ message: "Enter the AWS region (e.g. eu-central-1):",
205
+ validate: (val) => (val.trim() ? true : "Region is required."),
206
+ });
207
+ }
208
+ }
209
+ }
210
+ else {
211
+ finalRegion = await select({
212
+ message: "Select your AWS region:",
213
+ choices: [
214
+ ...AWS_REGIONS,
215
+ { name: "Enter a custom region...", value: "__custom__" },
216
+ ],
217
+ default: "eu-central-1",
218
+ });
219
+ if (finalRegion === "__custom__") {
220
+ finalRegion = await input({
221
+ message: "Enter the AWS region (e.g. eu-central-1):",
222
+ validate: (val) => (val.trim() ? true : "Region is required."),
223
+ });
224
+ }
225
+ }
226
+ config.aws = {
227
+ profile: profileName,
228
+ region: finalRegion,
229
+ };
230
+ }
231
+ // Step 3: Configure prefix
232
+ const customPrefix = await confirm({
233
+ message: `Use default secret prefix "${config.prefix}"?`,
234
+ default: true,
235
+ });
236
+ if (!customPrefix) {
237
+ config.prefix = await input({
238
+ message: "Enter a custom prefix for your secrets:",
239
+ default: "envhub-",
240
+ validate: (val) => (val.trim() ? true : "Prefix is required."),
241
+ });
242
+ }
243
+ // Step 4: Save configuration
244
+ const spinner = logger.spinner("Creating configuration...");
245
+ try {
246
+ const configManager = new ConfigManager();
247
+ const filePath = await configManager.create(config);
248
+ spinner.succeed("Configuration created.");
249
+ // Step 5: Update .gitignore
250
+ const gitignoreUpdated = await updateGitignore(process.cwd());
251
+ if (gitignoreUpdated) {
252
+ logger.success("Added .envhubrc.json to .gitignore");
253
+ }
254
+ logger.newline();
255
+ logger.success("envhub is ready to use!");
256
+ logger.newline();
257
+ logger.dim(` Config file: ${filePath}`);
258
+ logger.dim(` Provider: ${config.provider}`);
259
+ if (config.aws) {
260
+ logger.dim(` AWS Profile: ${config.aws.profile}`);
261
+ logger.dim(` AWS Region: ${config.aws.region}`);
262
+ }
263
+ logger.newline();
264
+ logger.log(" Next steps:");
265
+ logger.log(" Push a secret: envhub push <name> <file>");
266
+ logger.log(" Pull a secret: envhub pull <name> <file>");
267
+ logger.log(" List secrets: envhub list");
268
+ logger.newline();
269
+ }
270
+ catch (error) {
271
+ spinner.fail("Failed to create configuration.");
272
+ throw error;
273
+ }
274
+ }
275
+ //# sourceMappingURL=init.js.map