@dotenvx/dotenvx-ops 0.37.9 → 0.38.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.
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.37.9...main)
5
+ [Unreleased](https://github.com/dotenvx/dotenvx-ops/compare/v0.38.1...main)
6
+
7
+ ## [0.38.1](https://github.com/dotenvx/dotenvx-ops/compare/v0.38.0...v0.38.1) (2026-04-20)
8
+
9
+ ### Changed
10
+
11
+ * Patch `rotate`
12
+
13
+ ## [0.38.0](https://github.com/dotenvx/dotenvx-ops/compare/v0.37.9...v0.38.0) (2026-04-20)
14
+
15
+ ### Added
16
+
17
+ * Add update notification `⛆ update available (patch)` when update is available - once every 24 hours ([#38](https://github.com/dotenvx/dotenvx-ops/pull/38))
6
18
 
7
19
  ## [0.37.9](https://github.com/dotenvx/dotenvx-ops/compare/v0.37.8...v0.37.9) (2026-04-17)
8
20
 
package/README.md CHANGED
@@ -1,7 +1,11 @@
1
- [![dotenvx-ops](https://dotenvx.com/dotenvx-ops-banner.png?v=3)](https://dotenvx.com/ops)
1
+ [![dotenvx-ops](https://dotenvx.com/dotenvx-ops-banner.png?v=6)](https://dotenvx.com/ops)
2
2
 
3
- > ARMORED KEYS by dotenvx
3
+ ```
4
+ ⛨ ARMORED KEYS: Harden your private keys.
5
+ ⮕ install [curl -sfS https://dotenvx.sh/ops | sh]
6
+ ⮕ then run [dotenvx-ops login]
7
+ ```
4
8
 
5
- [Learn more](https://dotenvx.com/docs/ops)
9
+ [Learn more](https://dotenvx.com/ops)
6
10
 
7
11
   
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.37.9",
2
+ "version": "0.38.1",
3
3
  "name": "@dotenvx/dotenvx-ops",
4
4
  "description": "Secrets for agents–from the creator of `dotenv` and `dotenvx`",
5
5
  "author": "@motdotla",
@@ -54,6 +54,7 @@
54
54
  "express": "^5.2.1",
55
55
  "open": "^8.4.2",
56
56
  "playwright": "^1.57.0",
57
+ "semver": "^7.7.4",
57
58
  "systeminformation": "^5.22.11",
58
59
  "undici": "^7.11.0",
59
60
  "yocto-spinner": "^1.1.0"
@@ -1,6 +1,7 @@
1
1
  const { logger } = require('@dotenvx/dotenvx')
2
2
  const Session = require('./../../db/session')
3
3
  const createSpinner = require('../../lib/helpers/createSpinner2')
4
+ const PostKeypair = require('../../lib/api/postKeypair')
4
5
 
5
6
  async function keypair (publicKey) {
6
7
  // debug opts
@@ -13,14 +14,17 @@ async function keypair (publicKey) {
13
14
  }
14
15
 
15
16
  const sesh = new Session()
17
+ await sesh.notifyUpdate()
16
18
  const hostname = options.hostname || sesh.hostname()
17
- const token = options.token
19
+ const token = options.token || sesh.token()
18
20
  try {
19
- const main = require('./../../lib/main')
20
- const kp = await main.keypair(publicKey, { hostname, token })
21
+ const devicePublicKey = sesh.devicePublicKey()
22
+
23
+ const json = await new PostKeypair(hostname, token, devicePublicKey, publicKey).run()
24
+
21
25
  const output = {
22
- public_key: kp.publicKey,
23
- private_key: kp.privateKey
26
+ public_key: json.public_key,
27
+ private_key: json.private_key
24
28
  }
25
29
 
26
30
  let space = 0
@@ -63,6 +63,7 @@ async function login () {
63
63
  logger.debug(`options: ${JSON.stringify(options)}`)
64
64
 
65
65
  const sesh = new Session()
66
+ await sesh.notifyUpdate()
66
67
  const hostname = options.hostname || sesh.hostname()
67
68
  let cleanupOpenKeyListener = () => {}
68
69
 
@@ -1,29 +1,31 @@
1
1
  const { logger } = require('@dotenvx/dotenvx')
2
2
  const Session = require('./../../db/session')
3
3
  const Rotate = require('./../../lib/services/rotate')
4
- const { createSpinner } = require('./../../lib/helpers/createSpinner')
5
-
6
- const spinner = createSpinner('rotating..')
4
+ const createSpinner2 = require('./../../lib/helpers/createSpinner2')
7
5
 
8
6
  async function rotate (uri) {
9
7
  const options = this.opts()
8
+ const spinner = await createSpinner2({ ...options, text: 'rotating' })
9
+
10
10
  logger.debug(`options: ${JSON.stringify(options)}`)
11
+ if (uri) {
12
+ logger.debug(`uri: ${uri}`)
13
+ }
11
14
 
12
15
  try {
13
- spinner.start()
14
-
15
16
  const sesh = new Session()
17
+ await sesh.notifyUpdate()
16
18
  const hostname = options.hostname || sesh.hostname()
17
19
  const token = options.token || sesh.token()
18
20
 
19
21
  const { url, rotUid } = await new Rotate(hostname, token, uri).run()
20
22
 
21
- spinner.stop()
23
+ if (spinner) spinner.stop()
22
24
 
23
- logger.success(`✔ rotated [${url}]`)
25
+ logger.success(`⟳ rotated [${url}]`)
24
26
  logger.help(`⮕ next run [dotenvx-ops get dotenvx://${rotUid}]`)
25
27
  } catch (error) {
26
- spinner.stop()
28
+ if (spinner) spinner.stop()
27
29
  if (error.message) {
28
30
  logger.error(error.message)
29
31
  } else {
@@ -7,6 +7,8 @@ const program = new Command()
7
7
  const { setLogLevel } = require('@dotenvx/dotenvx')
8
8
 
9
9
  const packageJson = require('./../lib/helpers/packageJson')
10
+ const argv = process.argv.slice(2)
11
+ const firstArg = argv[0]
10
12
 
11
13
  // global log levels
12
14
  program
@@ -15,7 +17,7 @@ program
15
17
  .option('-q, --quiet', 'sets log level to error')
16
18
  .option('-v, --verbose', 'sets log level to verbose')
17
19
  .option('-d, --debug', 'sets log level to debug')
18
- .hook('preAction', (thisCommand, actionCommand) => {
20
+ .hook('preAction', async (thisCommand, actionCommand) => {
19
21
  const options = thisCommand.opts()
20
22
 
21
23
  setLogLevel(options)
@@ -90,8 +92,6 @@ program
90
92
  .action(setAction)
91
93
 
92
94
  // dotenvx-ops rotate
93
- const argv = process.argv.slice(2)
94
- const firstArg = argv[0]
95
95
  const shouldLoadRotateCommand = firstArg === 'rotate' || firstArg === 'help' || firstArg === '--help' || firstArg === '-h'
96
96
  if (shouldLoadRotateCommand) {
97
97
  program.addCommand(require('./commands/rotate'))
package/src/db/session.js CHANGED
@@ -1,9 +1,15 @@
1
1
  const Conf = require('conf')
2
2
  const dotenv = require('dotenv')
3
3
  const si = require('systeminformation')
4
+ const semver = require('semver')
5
+ const { logger } = require('@dotenvx/dotenvx')
4
6
 
5
7
  const Device = require('./device')
6
8
  const jsonToEnv = require('./../lib/helpers/jsonToEnv')
9
+ const packageJson = require('./../lib/helpers/packageJson')
10
+ const GetVersion = require('./../lib/api/getVersion')
11
+
12
+ const HOURS_24 = 60 * 60 * 24 * 1000 // 24hrs in ms
7
13
 
8
14
  class Session {
9
15
  constructor () {
@@ -74,6 +80,41 @@ class Session {
74
80
  }
75
81
  }
76
82
 
83
+ //
84
+ // Notify Update
85
+ //
86
+ async notifyUpdate () {
87
+ try {
88
+ const lastCheck = Number(this.store.get('DOTENVX_OPS_VERSION_LAST_CHECK') || 0)
89
+ const now = Date.now()
90
+
91
+ // 24 hours have passed
92
+ if ((lastCheck + HOURS_24) < now) {
93
+ const local = packageJson.version
94
+ let remote = local // in case of http fetch error
95
+
96
+ try {
97
+ const VERSION = await new GetVersion().run()
98
+ remote = VERSION
99
+ this.store.set('DOTENVX_OPS_VERSION', VERSION) // remote version
100
+ } catch (err) {
101
+ // noop
102
+ logger.debug(err.message)
103
+ }
104
+
105
+ this.store.set('DOTENVX_OPS_VERSION_LAST_CHECK', now) // record check
106
+
107
+ if (semver.gt(remote, local)) {
108
+ const diff = semver.diff(local, remote)
109
+ console.error(`⛆ update available (${diff})`)
110
+ }
111
+ }
112
+ } catch (err) {
113
+ // noop
114
+ logger.debug(err.message)
115
+ }
116
+ }
117
+
77
118
  //
78
119
  // Set/Delete
79
120
  //
@@ -121,6 +162,8 @@ class Session {
121
162
  this.store.delete('DOTENVX_OPS_TOKEN')
122
163
  this.store.delete('DOTENVX_OPS_HOSTNAME')
123
164
  this.store.delete('DOTENVX_OPS_ON')
165
+ this.store.delete('DOTENVX_OPS_VERSION')
166
+ this.store.delete('DOTENVX_OPS_VERSION_LAST_CHECK')
124
167
  return true
125
168
  }
126
169
 
@@ -0,0 +1,24 @@
1
+ const { http } = require('../../lib/helpers/http')
2
+
3
+ class GetVersion {
4
+ constructor () {
5
+ this.hostname = 'https://dotenvx.sh'
6
+ }
7
+
8
+ async run () {
9
+ const url = `${this.hostname}/ops/VERSION`
10
+
11
+ const resp = await http(url, { method: 'GET' })
12
+ const VERSION = await resp.body.text()
13
+
14
+ if (resp.statusCode >= 400) {
15
+ const error = new Error('[VERSION_FETCH_FAILED] version fetch failed')
16
+ error.code = 'VERSION_FETCH_FAILED'
17
+ throw error
18
+ }
19
+
20
+ return VERSION
21
+ }
22
+ }
23
+
24
+ module.exports = GetVersion
package/src/lib/main.js CHANGED
@@ -14,18 +14,18 @@ const dotenvxProjectId = require('./helpers/dotenvxProjectId')
14
14
  const observe = async function (encoded, options = {}) {
15
15
  const sesh = new Session() // TODO: handle scenario where constructor fails
16
16
 
17
- let hostname = process.env.DOTENVX_OPS_HOSTNAME || options.hostname
17
+ let hostname = options.hostname
18
18
  if (!hostname) {
19
19
  hostname = sesh.hostname()
20
20
  }
21
21
 
22
- let token = process.env.DOTENVX_OPS_TOKEN || options.token
22
+ let token = options.token
23
23
  if (!token) {
24
24
  token = sesh.token()
25
25
  }
26
26
 
27
27
  const _pwd = process.cwd()
28
- const _dotenvxProjectId = process.env.DOTENVX_PROJECT_ID || options.dotenvxProjectId || dotenvxProjectId(_pwd, false)
28
+ const _dotenvxProjectId = options.dotenvxProjectId || dotenvxProjectId(_pwd, false)
29
29
 
30
30
  const _gitUrl = gitUrl()
31
31
  const _gitBranch = gitBranch()
@@ -45,12 +45,12 @@ const observe = async function (encoded, options = {}) {
45
45
  const get = async function (uri, options = {}) {
46
46
  const sesh = new Session() // TODO: handle scenario where constructor fails
47
47
 
48
- let hostname = process.env.DOTENVX_OPS_HOSTNAME || options.hostname
48
+ let hostname = options.hostname
49
49
  if (!hostname) {
50
50
  hostname = sesh.hostname()
51
51
  }
52
52
 
53
- let token = process.env.DOTENVX_OPS_TOKEN || options.token
53
+ let token = options.token
54
54
  if (!token) {
55
55
  token = sesh.token()
56
56
  }
@@ -62,12 +62,12 @@ const get = async function (uri, options = {}) {
62
62
  const set = async function (uri, value, options = {}) {
63
63
  const sesh = new Session() // TODO: handle scenario where constructor fails
64
64
 
65
- let hostname = process.env.DOTENVX_OPS_HOSTNAME || options.hostname
65
+ let hostname = options.hostname
66
66
  if (!hostname) {
67
67
  hostname = sesh.hostname()
68
68
  }
69
69
 
70
- let token = process.env.DOTENVX_OPS_TOKEN || options.token
70
+ let token = options.token
71
71
  if (!token) {
72
72
  token = sesh.token()
73
73
  }
@@ -80,12 +80,12 @@ const set = async function (uri, value, options = {}) {
80
80
  const keypair = async function (publicKey, options = {}) {
81
81
  const sesh = new Session() // TODO: handle scenario where constructor fails
82
82
 
83
- let hostname = process.env.DOTENVX_OPS_HOSTNAME || options.hostname
83
+ let hostname = options.hostname
84
84
  if (!hostname) {
85
85
  hostname = sesh.hostname()
86
86
  }
87
87
 
88
- let token = process.env.DOTENVX_OPS_TOKEN || options.token
88
+ let token = options.token
89
89
  if (!token) {
90
90
  token = sesh.token()
91
91
  }