@dinanathdash/envault 1.0.2 → 1.1.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.
package/.env ADDED
@@ -0,0 +1,20 @@
1
+ # Environment Variables Example
2
+
3
+ Copy this file to `.env.local` and fill in your values.
4
+
5
+ ## Supabase Configuration
6
+ NEXT_PUBLIC_SUPABASE_URL=your_supabase_url_here
7
+ NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key_here
8
+
9
+ ## Encryption Key (Required)
10
+ # Generate a new key with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
11
+ # IMPORTANT: Keep this secret and never commit it to version control
12
+ # If you lose this key, all encrypted data becomes unrecoverable
13
+ ENCRYPTION_KEY=your_64_character_hex_encryption_key_here
14
+
15
+ ## Supabase Service Role Key (Required)
16
+ SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key_here
17
+
18
+ # Redis (Upstash)
19
+ UPSTASH_REDIS_REST_URL=your_upstash_redis_rest_url_here
20
+ UPSTASH_REDIS_REST_TOKEN=your_upstash_redis_rest_token_here
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [1.1.0](https://github.com/DinanathDash/Envault/compare/v1.0.2...v1.1.0) (2026-02-01)
2
+
3
+
4
+ ### Features
5
+
6
+ * Implement CLI device authentication, add new CLI API routes for projects and secrets, and enhance CLI commands with improved warnings. ([e4871c4](https://github.com/DinanathDash/Envault/commit/e4871c453d7e3974e94505e1f65014350a15a53a))
7
+
1
8
  ## [1.0.2](https://github.com/DinanathDash/Envault/compare/v1.0.1...v1.0.2) (2026-02-01)
2
9
 
3
10
 
package/README.md CHANGED
@@ -1,9 +1,5 @@
1
1
  # Envault CLI
2
2
 
3
- <p align="center">
4
- <img src="https://envault.tech/logo.png" alt="Envault Logo" width="100" />
5
- </p>
6
-
7
3
  <p align="center">
8
4
  <b>Secure Environment Variable Management for Modern Teams</b>
9
5
  </p>
package/bin/envault.js CHANGED
@@ -7,6 +7,10 @@ import { login } from '../src/commands/login.js';
7
7
  import { init } from '../src/commands/init.js';
8
8
  import { deploy } from '../src/commands/deploy.js';
9
9
  import { pull } from '../src/commands/pull.js';
10
+ import { createRequire } from 'module';
11
+
12
+ const require = createRequire(import.meta.url);
13
+ const pkg = require('../package.json');
10
14
 
11
15
  const program = new Command();
12
16
 
@@ -26,7 +30,7 @@ const showLogo = () => {
26
30
  program
27
31
  .name('envault')
28
32
  .description('Cliff-side security for your environment variables')
29
- .version('1.0.0')
33
+ .version(pkg.version)
30
34
  .hook('preAction', (thisCommand) => {
31
35
  // Show logo on all commands? Maybe too noisy.
32
36
  // Let's show it only on help or specific ones.
@@ -50,6 +54,7 @@ program.command('deploy')
50
54
  .description('Deploy local .env to Envault (Encrypt & Push)')
51
55
  .option('-p, --project <id>', 'Project ID')
52
56
  .option('--dry-run', 'Show what would change without pushing')
57
+ .option('-f, --force', 'Skip confirmation prompts')
53
58
  .action(async (options) => {
54
59
  await deploy(options);
55
60
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dinanathdash/envault",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Envault CLI - Securely manage your environment variables",
5
5
  "repository": {
6
6
  "type": "git",
@@ -37,5 +37,8 @@
37
37
  "@semantic-release/exec": "^7.1.0",
38
38
  "@semantic-release/git": "^10.0.1",
39
39
  "semantic-release": "^25.0.3"
40
+ },
41
+ "overrides": {
42
+ "tar": "^7.5.7"
40
43
  }
41
44
  }
package/release.config.js CHANGED
@@ -13,7 +13,7 @@ const config = {
13
13
  [
14
14
  '@semantic-release/git',
15
15
  {
16
- assets: ['package.json', 'CHANGELOG.md', 'README.md'],
16
+ assets: ['package.json', 'package-lock.json', 'CHANGELOG.md', 'README.md'],
17
17
  message: 'chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}',
18
18
  },
19
19
  ],
@@ -1,3 +1,5 @@
1
+ import boxen from 'boxen';
2
+ import inquirer from 'inquirer';
1
3
 
2
4
  import fs from 'fs';
3
5
  import dotenv from 'dotenv';
@@ -41,6 +43,57 @@ export async function deploy(options) {
41
43
  return;
42
44
  }
43
45
 
46
+ if (!options.force) {
47
+ let projectName = 'Envault';
48
+ try {
49
+ // Attempt to fetch project name. If fails, fallback to 'Envault'
50
+ // We use the list endpoint as we know it exists from init.js
51
+ const { data } = await api.get('/projects');
52
+ const projects = data.projects || data.data || [];
53
+ const project = projects.find(p => p.id === projectId);
54
+ if (project) {
55
+ projectName = project.name;
56
+ }
57
+ } catch (e) {
58
+ // Ignore error and use default
59
+ }
60
+
61
+ const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'https://envault.tech';
62
+
63
+ console.log(boxen(
64
+ chalk.red.bold('WARNING: OVERWRITING REMOTE SECRETS') +
65
+ '\n\n' +
66
+ chalk.white('You are about to ') + chalk.red.bold('DEPLOY') + chalk.white(' local variables to your project:') +
67
+ '\n' +
68
+ chalk.cyan.bold(projectName) +
69
+ '\n\n' +
70
+ chalk.white('Existing secrets in the project will be ') + chalk.red.bold('OVERWRITTEN') + chalk.white(' by values in your .env.') +
71
+ '\n\n' +
72
+ chalk.dim('We recommend checking the dashboard for differences:') +
73
+ '\n' +
74
+ chalk.cyan(`${appUrl}/projects/${projectId}`),
75
+ {
76
+ padding: 1,
77
+ margin: 1,
78
+ borderStyle: 'double',
79
+ borderColor: 'red',
80
+ title: 'Deploy Warning',
81
+ titleAlignment: 'center'
82
+ }
83
+ ));
84
+
85
+ const { confirm } = await inquirer.prompt([{
86
+ type: 'confirm',
87
+ name: 'confirm',
88
+ message: `Are you sure you want to deploy ${secrets.length} secrets to the project?`,
89
+ default: false
90
+ }]);
91
+ if (!confirm) {
92
+ console.log(chalk.yellow('Operation cancelled.'));
93
+ return;
94
+ }
95
+ }
96
+
44
97
  const spinner = ora('Encrypting and deploying secrets...').start();
45
98
 
46
99
  try {
@@ -43,17 +43,46 @@ export async function init() {
43
43
  }
44
44
 
45
45
  if (projects.length === 0) {
46
- console.log(chalk.yellow('No projects found. Create one in the dashboard first.'));
47
- return;
46
+ console.log(chalk.yellow('No existing projects found.'));
48
47
  }
49
48
 
50
- const { projectId } = await inquirer.prompt([{
49
+ const { selectedProjectId } = await inquirer.prompt([{
51
50
  type: 'list',
52
- name: 'projectId',
51
+ name: 'selectedProjectId',
53
52
  message: 'Select the project to link:',
54
- choices: projects.map(p => ({ name: p.name, value: p.id }))
53
+ choices: [
54
+ new inquirer.Separator(),
55
+ { name: '+ Create New Project', value: 'CREATE_NEW' },
56
+ new inquirer.Separator(),
57
+ ...projects.map(p => ({ name: p.name, value: p.id }))
58
+ ]
55
59
  }]);
56
60
 
57
- fs.writeFileSync('envault.json', JSON.stringify({ projectId }, null, 2));
58
- console.log(chalk.green(`\n✔ Project linked! (ID: ${projectId})`));
61
+ let projectId = selectedProjectId;
62
+
63
+ if (selectedProjectId === 'CREATE_NEW') {
64
+ const { newProjectName } = await inquirer.prompt([{
65
+ type: 'input',
66
+ name: 'newProjectName',
67
+ message: 'Enter name for the new project:',
68
+ validate: input => input.trim().length > 0 ? true : 'Project name cannot be empty'
69
+ }]);
70
+
71
+ const createSpinner = ora('Creating project...').start();
72
+ try {
73
+ const { data } = await api.post('/projects', { name: newProjectName });
74
+ projectId = data.project.id;
75
+ createSpinner.succeed(chalk.green(`Project "${data.project.name}" created!`));
76
+ } catch (error) {
77
+ createSpinner.fail('Failed to create project.');
78
+ console.error(chalk.red(handleApiError(error)));
79
+ return; // Exit if creation fails
80
+ }
81
+ }
82
+
83
+ // Only write config if we have a valid projectId
84
+ if (projectId) {
85
+ fs.writeFileSync('envault.json', JSON.stringify({ projectId }, null, 2));
86
+ console.log(chalk.green(`\n✔ Project linked! (ID: ${projectId})`));
87
+ }
59
88
  }
@@ -5,6 +5,7 @@ import ora from 'ora';
5
5
  import open from 'open';
6
6
  import { api, handleApiError } from '../lib/api.js';
7
7
  import { setToken } from '../lib/config.js';
8
+ import os from 'os';
8
9
 
9
10
  export async function login() {
10
11
  console.log(chalk.blue(' Starting Device Authentication Flow...\n'));
@@ -13,8 +14,16 @@ export async function login() {
13
14
  const spinner = ora('Contacting Envault servers...').start();
14
15
  let deviceCode, userCode, verificationUri, interval;
15
16
 
17
+ const deviceInfo = {
18
+ hostname: os.hostname(),
19
+ platform: os.platform(),
20
+ release: os.release(),
21
+ type: os.type(),
22
+ arch: os.arch()
23
+ };
24
+
16
25
  try {
17
- const { data } = await api.post('/auth/device/code');
26
+ const { data } = await api.post('/auth/device/code', { device_info: deviceInfo });
18
27
  deviceCode = data.device_code;
19
28
  userCode = data.user_code; // 8-char
20
29
  verificationUri = data.verification_uri;
@@ -2,6 +2,8 @@
2
2
  import fs from 'fs';
3
3
  import chalk from 'chalk';
4
4
  import ora from 'ora';
5
+ import boxen from 'boxen';
6
+
5
7
  import inquirer from 'inquirer';
6
8
  import { api, handleApiError } from '../lib/api.js';
7
9
 
@@ -22,13 +24,50 @@ export async function pull(options) {
22
24
  }
23
25
 
24
26
  if (fs.existsSync('.env') && !options.force) {
27
+ let projectName = 'Envault';
28
+ try {
29
+ const { data } = await api.get('/projects');
30
+ const projects = data.projects || data.data || [];
31
+ const project = projects.find(p => p.id === projectId);
32
+ if (project) {
33
+ projectName = project.name;
34
+ }
35
+ } catch (e) {
36
+ // Ignore
37
+ }
38
+
39
+ const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'https://envault.tech';
40
+
41
+ console.log(boxen(
42
+ chalk.red.bold('WARNING: POTENTIAL DATA LOSS') +
43
+ '\n\n' +
44
+ chalk.white('You are about to ') + chalk.red.bold('OVERWRITE') + chalk.white(' your local ') + chalk.yellow('.env') + chalk.white(' file.') +
45
+ '\n\n' +
46
+ chalk.white('Any local changes not synced to ') + chalk.cyan.bold(projectName) + chalk.white(' will be ') + chalk.red.bold('PERMANENTLY LOST.') +
47
+ '\n\n' +
48
+ chalk.dim('We recommend checking the dashboard for differences:') +
49
+ '\n' +
50
+ chalk.cyan(`${appUrl}/projects/${projectId}`),
51
+ {
52
+ padding: 1,
53
+ margin: 1,
54
+ borderStyle: 'double',
55
+ borderColor: 'red',
56
+ title: 'Sync Warning',
57
+ titleAlignment: 'center'
58
+ }
59
+ ));
60
+
25
61
  const { confirm } = await inquirer.prompt([{
26
62
  type: 'confirm',
27
63
  name: 'confirm',
28
- message: 'This will overwrite your current .env file. Continue?',
64
+ message: 'Are you sure you want to overwrite your local .env file?',
29
65
  default: false
30
66
  }]);
31
- if (!confirm) return;
67
+ if (!confirm) {
68
+ console.log(chalk.yellow('Operation cancelled.'));
69
+ return;
70
+ }
32
71
  }
33
72
 
34
73
  const spinner = ora('Fetching secrets...').start();
package/src/lib/api.js CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  import axios from 'axios';
3
- import { getToken, getApiUrl } from './config.js';
3
+ import { getToken, getApiUrl, clearToken } from './config.js';
4
4
 
5
5
  export const api = axios.create({
6
6
  baseURL: getApiUrl(),
@@ -17,6 +17,28 @@ api.interceptors.request.use((config) => {
17
17
  return config;
18
18
  });
19
19
 
20
+ // Handle token expiration
21
+ api.interceptors.response.use(
22
+ (response) => response,
23
+ (error) => {
24
+ if (error.response?.status === 401) {
25
+ const errorMsg = error.response?.data?.error;
26
+ if (errorMsg === 'token_expired') {
27
+ console.error('\n❌ Your session has expired (tokens are valid for 3 days).');
28
+ console.error('Please run "envault login" to authenticate again.\n');
29
+ clearToken();
30
+ process.exit(1);
31
+ } else if (errorMsg === 'Invalid token' || errorMsg === 'Missing or invalid authorization header') {
32
+ console.error('\n❌ Authentication required.');
33
+ console.error('Please run "envault login" to authenticate.\n');
34
+ clearToken();
35
+ process.exit(1);
36
+ }
37
+ }
38
+ return Promise.reject(error);
39
+ }
40
+ );
41
+
20
42
  export function handleApiError(error) {
21
43
  if (error.response) {
22
44
  return error.response.data.error || error.response.statusText;