@dotenvx/dotenvx-ops 0.42.1 → 0.44.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/CHANGELOG.md CHANGED
@@ -2,7 +2,19 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
- [Unreleased](https://github.com/dotenvx/dotenvx-ops/compare/v0.42.1...main)
5
+ [Unreleased](https://github.com/dotenvx/dotenvx-ops/compare/v0.44.0...main)
6
+
7
+ ## [0.44.0](https://github.com/dotenvx/dotenvx-ops/compare/v0.43.0...v0.44.0) (2026-05-07)
8
+
9
+ ### Added
10
+
11
+ * Prompt for team on `armor up` (if member of multiple teams) ([#65](https://github.com/dotenvx/dotenvx-ops/pull/65))
12
+
13
+ ## [0.43.0](https://github.com/dotenvx/dotenvx-ops/compare/v0.42.1...v0.43.0) (2026-05-06)
14
+
15
+ ### Added
16
+
17
+ * Add `armor move` for moving keys between teams ([#64](https://github.com/dotenvx/dotenvx-ops/pull/64))
6
18
 
7
19
  ## [0.42.1](https://github.com/dotenvx/dotenvx-ops/compare/v0.42.0...v0.42.1) (2026-04-26)
8
20
 
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.42.1",
2
+ "version": "0.44.0",
3
3
  "name": "@dotenvx/dotenvx-ops",
4
4
  "description": "Secrets for agents–from the creator of `dotenv` and `dotenvx`",
5
5
  "author": "@motdotla",
@@ -0,0 +1,39 @@
1
+ const { logger } = require('@dotenvx/dotenvx')
2
+ const Session = require('./../../../db/session')
3
+ const ArmorMove = require('./../../../lib/services/armorMove')
4
+ const createSpinner = require('../../../lib/helpers/createSpinner2')
5
+
6
+ async function move () {
7
+ const options = this.opts()
8
+ const spinner = await createSpinner({ ...options, text: 'moving' })
9
+
10
+ logger.debug(`options: ${JSON.stringify(options)}`)
11
+
12
+ const sesh = new Session()
13
+ await sesh.notifyUpdate()
14
+ const hostname = options.hostname || sesh.hostname()
15
+ const token = options.token || sesh.token()
16
+
17
+ try {
18
+ const devicePublicKey = sesh.devicePublicKey()
19
+
20
+ const {
21
+ changed,
22
+ privateKeyName,
23
+ team
24
+ } = await new ArmorMove(hostname, token, devicePublicKey, options.envFile).run()
25
+
26
+ if (spinner) spinner.stop()
27
+ if (changed) {
28
+ logger.success(`◈ moved ${privateKeyName} (${team})`)
29
+ } else {
30
+ logger.info(`○ no change ${privateKeyName} (${team})`)
31
+ }
32
+ } catch (error) {
33
+ if (spinner) spinner.stop()
34
+ logger.error(error.message)
35
+ process.exit(1)
36
+ }
37
+ }
38
+
39
+ module.exports = move
@@ -0,0 +1,25 @@
1
+ const { logger } = require('@dotenvx/dotenvx')
2
+ const Session = require('./../../../db/session')
3
+ const createSpinner = require('../../../lib/helpers/createSpinner2')
4
+
5
+ async function rotate () {
6
+ const options = this.opts()
7
+ const spinner = await createSpinner({ ...options, text: 'rotating' })
8
+
9
+ logger.debug(`options: ${JSON.stringify(options)}`)
10
+
11
+ const sesh = new Session()
12
+ await sesh.notifyUpdate()
13
+
14
+ try {
15
+ if (spinner) spinner.stop()
16
+
17
+ logger.info('◇ coming soon')
18
+ } catch (error) {
19
+ if (spinner) spinner.stop()
20
+ logger.error(error.message)
21
+ process.exit(1)
22
+ }
23
+ }
24
+
25
+ module.exports = rotate
@@ -18,7 +18,6 @@ const Backup = require('./../../lib/services/backup')
18
18
  const spinner = createSpinner('waiting on browser authorization')
19
19
 
20
20
  async function backup () {
21
- // debug opts
22
21
  const options = this.opts()
23
22
 
24
23
  const sesh = new Session()
@@ -44,4 +44,20 @@ armor
44
44
  .option('-f, --env-file <path>', 'path to your env file')
45
45
  .action(pullAction)
46
46
 
47
+ // dotenvx-ops armor rotate
48
+ const rotateAction = require('./../actions/armor/rotate')
49
+ armor
50
+ .command('rotate')
51
+ .description('rotate armored key and re-encrypt .env file')
52
+ .option('-f, --env-file <path>', 'path to your env file')
53
+ .action(rotateAction)
54
+
55
+ // dotenvx-ops armor move
56
+ const moveAction = require('./../actions/armor/move')
57
+ armor
58
+ .command('move')
59
+ .description('move armored key (to other team)')
60
+ .option('-f, --env-file <path>', 'path to your env file')
61
+ .action(moveAction)
62
+
47
63
  module.exports = armor
@@ -0,0 +1,48 @@
1
+ const { http } = require('../../lib/helpers/http')
2
+ const buildApiError = require('../../lib/helpers/buildApiError')
3
+ const packageJson = require('../../lib/helpers/packageJson')
4
+ const normalizeToken = require('../../lib/helpers/normalizeToken')
5
+
6
+ class PostArmorMove {
7
+ constructor (hostname, token, devicePublicKey, publicKey, team) {
8
+ this.hostname = hostname || 'https://ops.dotenvx.com'
9
+ this.token = token
10
+ this.devicePublicKey = devicePublicKey
11
+ this.publicKey = publicKey
12
+ this.team = team
13
+ }
14
+
15
+ async run () {
16
+ const token = normalizeToken(this.token)
17
+ const devicePublicKey = this.devicePublicKey
18
+ const publicKey = this.publicKey
19
+ const team = this.team
20
+ const url = `${this.hostname}/api/armor/move`
21
+
22
+ const body = {
23
+ device_public_key: devicePublicKey,
24
+ cli_version: packageJson.version,
25
+ public_key: publicKey,
26
+ team
27
+ }
28
+
29
+ const resp = await http(url, {
30
+ method: 'POST',
31
+ headers: {
32
+ Authorization: `Bearer ${token}`,
33
+ 'Content-Type': 'application/json'
34
+ },
35
+ body: JSON.stringify(body)
36
+ })
37
+
38
+ const json = await resp.body.json()
39
+
40
+ if (resp.statusCode >= 400) {
41
+ throw buildApiError(resp.statusCode, json)
42
+ }
43
+
44
+ return json
45
+ }
46
+ }
47
+
48
+ module.exports = PostArmorMove
@@ -4,12 +4,13 @@ const packageJson = require('../../lib/helpers/packageJson')
4
4
  const normalizeToken = require('../../lib/helpers/normalizeToken')
5
5
 
6
6
  class PostArmorUp {
7
- constructor (hostname, token, devicePublicKey, publicKey, privateKey) {
7
+ constructor (hostname, token, devicePublicKey, publicKey, privateKey, team) {
8
8
  this.hostname = hostname || 'https://ops.dotenvx.com'
9
9
  this.token = token
10
10
  this.devicePublicKey = devicePublicKey
11
11
  this.publicKey = publicKey
12
12
  this.privateKey = privateKey
13
+ this.team = team
13
14
  }
14
15
 
15
16
  async run () {
@@ -17,13 +18,15 @@ class PostArmorUp {
17
18
  const devicePublicKey = this.devicePublicKey
18
19
  const publicKey = this.publicKey
19
20
  const privateKey = this.privateKey
21
+ const team = this.team
20
22
  const url = `${this.hostname}/api/armor/up`
21
23
 
22
24
  const body = {
23
25
  device_public_key: devicePublicKey,
24
26
  cli_version: packageJson.version,
25
27
  public_key: publicKey,
26
- private_key: privateKey
28
+ private_key: privateKey,
29
+ team
27
30
  }
28
31
 
29
32
  const resp = await http(url, {
@@ -0,0 +1,50 @@
1
+ const dotenvx = require('@dotenvx/dotenvx')
2
+ const prompts = require('@inquirer/prompts')
3
+ const PostArmorMove = require('../api/postArmorMove')
4
+ const GetAccount = require('../api/getAccount')
5
+ const keyNamesForEnvFile = require('../helpers/keyNamesForEnvFile')
6
+
7
+ class ArmorMove {
8
+ constructor (hostname, token, devicePublicKey, envFile = '.env') {
9
+ this.hostname = hostname
10
+ this.token = token
11
+ this.devicePublicKey = devicePublicKey
12
+ this.envFile = envFile
13
+
14
+ this.team = null // implement
15
+ }
16
+
17
+ async run () {
18
+ const hostname = this.hostname
19
+ const token = this.token
20
+ const devicePublicKey = this.devicePublicKey
21
+ const envFile = this.envFile
22
+ let team = this.team
23
+
24
+ const {
25
+ publicKeyName,
26
+ privateKeyName
27
+ } = keyNamesForEnvFile(envFile)
28
+ const publicKey = dotenvx.get(publicKeyName, { path: envFile, strict: true, ignore: ['MISSING_PRIVATE_KEY'], noOps: true })
29
+
30
+ const accountJson = await new GetAccount(hostname, token).run()
31
+ const choices = accountJson.organizations.map(o => ({
32
+ name: o.provider_slug,
33
+ value: o.provider_slug
34
+ }))
35
+ team = await prompts.select({
36
+ message: 'Select team',
37
+ choices
38
+ })
39
+
40
+ const json = await new PostArmorMove(hostname, token, devicePublicKey, publicKey, team).run()
41
+
42
+ return {
43
+ ...json,
44
+ privateKeyName,
45
+ privateKeyValue: json.private_key
46
+ }
47
+ }
48
+ }
49
+
50
+ module.exports = ArmorMove
@@ -1,5 +1,7 @@
1
1
  const dotenvx = require('@dotenvx/dotenvx')
2
+ const prompts = require('@inquirer/prompts')
2
3
  const PostArmorUp = require('../api/postArmorUp')
4
+ const GetAccount = require('../api/getAccount')
3
5
  const keyNamesForEnvFile = require('../helpers/keyNamesForEnvFile')
4
6
  const removeEnvKey = require('../helpers/removeEnvKey')
5
7
 
@@ -24,7 +26,21 @@ class ArmorUp {
24
26
 
25
27
  const publicKey = dotenvx.get(publicKeyName, { path: envFile, strict: true, ignore: ['MISSING_PRIVATE_KEY'], noOps: true })
26
28
  const privateKey = dotenvx.get(privateKeyName, { path: '.env.keys', strict: true, ignore: ['MISSING_KEY'], noOps: true })
27
- const json = await new PostArmorUp(hostname, token, devicePublicKey, publicKey, privateKey).run()
29
+
30
+ const accountJson = await new GetAccount(hostname, token).run()
31
+ const choices = accountJson.organizations.map(o => ({
32
+ name: o.provider_slug,
33
+ value: o.provider_slug
34
+ }))
35
+ let team = choices[0] && choices[0].value
36
+ if (choices.length > 1) {
37
+ team = await prompts.select({
38
+ message: 'Select team',
39
+ choices
40
+ })
41
+ }
42
+
43
+ const json = await new PostArmorUp(hostname, token, devicePublicKey, publicKey, privateKey, team).run()
28
44
  removeEnvKey(privateKeyName)
29
45
 
30
46
  return {