Resolve Hermes dotenv references in provider summaries

Share native dotenv expansion between provider display and credential redaction. Checks: 88 Hermes configuration and ACP tests; desktop pack.
This commit is contained in:
Ryder Sun committed 2026-11-07 22:22:37 +08:00
1 parent 953ba18bfa
commit 2cc146a2a4
4 files changed
+33 -17

No files matched your search

@@ -2,6 +2,7 @@ const fs = require('node:fs')
const path = require('node:path')
const yaml = require('js-yaml')
const { nativeIdentityDirectory } = require('../native-config-directory.cjs')
const { hermesDotenvAssignments } = require('../hermes-dotenv.cjs')
const { configSecrets, nativeSecretEnvKeys } = require('./native-secret-environment.cjs')
function hermesConfigSecrets({ env, home, platform, declaredKeys }) {
@@ -20,19 +21,7 @@ function hermesConfigSecrets({ env, home, platform, declaredKeys }) {
const secrets = new Set(configSecrets(configs))
const declared = new Set(nativeSecretEnvKeys(declaredKeys))
try {
const values = { ...env }
for (const line of read('.env').split(/\r?\n/)) {
const match = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/)
if (!match) continue
const [, key, raw] = match
const quoted = raw.match(/^(['"])(.*?)\1\s*(?:#.*)?$/)
if (/^['"]/.test(raw) && !quoted) continue
let value = quoted ? quoted[2] : raw.replace(/\s+#.*$/, '').trim()
if (quoted?.[1] === '"') value = value.replace(/\\([nr"\\])/g, (_, c) => ({ n: '\n', r: '\r', '"': '"', '\\': '\\' })[c])
if (quoted?.[1] === "'") value = value.replace(/\\(['\\])/g, '$1')
value = value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g,
(_, name, fallback) => Object.hasOwn(values, name) ? values[name] : fallback || '')
values[key] = value
for (const [key, value] of hermesDotenvAssignments(read('.env'), env)) {
if ((declared.has(key) || /(?:API[_-]?KEY|ACCESS[_-]?KEY|TOKEN|SECRET|PASSWORD|AUTHORIZATION)/i.test(key))
&& value && value.length <= 8192) secrets.add(value)
}
+3 -4
View File
@@ -3,6 +3,7 @@ const os = require('node:os')
const path = require('node:path')
const yaml = require('js-yaml')
const { redactSecrets } = require('../security/secret-redaction.cjs')
const { hermesDotenvAssignments } = require('./hermes-dotenv.cjs')
function hermesProviderFromConfig(config, env = {}) {
const model = config?.model && typeof config.model === 'object' && !Array.isArray(config.model) ? config.model : {}
@@ -24,10 +25,8 @@ function hermesProviderEnvironment(root, env = {}) {
const stat = fs.statSync(file)
if (stat.isFile() && stat.size <= 1024 * 1024) {
let provider
for (const line of fs.readFileSync(file, 'utf8').split(/\r?\n/)) {
if (!/^\s*(?:export\s+)?HERMES_INFERENCE_PROVIDER\s*=/.test(line)) continue
const match = line.match(/^\s*(?:export\s+)?HERMES_INFERENCE_PROVIDER\s*=\s*(?:"([A-Za-z0-9._:-]*)"|'([A-Za-z0-9._:-]*)'|([A-Za-z0-9._:-]*))\s*(?:#.*)?$/)
provider = match ? match[1] ?? match[2] ?? match[3] : ''
for (const [key, value] of hermesDotenvAssignments(fs.readFileSync(file, 'utf8'), env)) {
if (key === 'HERMES_INFERENCE_PROVIDER') provider = value ?? ''
}
if (provider !== undefined) return { ...env, HERMES_INFERENCE_PROVIDER: provider }
}
+24
View File
@@ -0,0 +1,24 @@
function hermesDotenvAssignments(content, env = {}) {
const values = { ...env }
const assignments = []
for (const line of content.split(/\r?\n/)) {
const match = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/)
if (!match) continue
const [, key, raw] = match
const quoted = raw.match(/^(['"])(.*?)\1\s*(?:#.*)?$/)
if (/^['"]/.test(raw) && !quoted) {
assignments.push([key, null])
continue
}
let value = quoted ? quoted[2] : raw.replace(/\s+#.*$/, '').trim()
if (quoted?.[1] === '"') value = value.replace(/\\([nr"\\])/g, (_, c) => ({ n: '\n', r: '\r', '"': '"', '\\': '\\' })[c])
if (quoted?.[1] === "'") value = value.replace(/\\(['\\])/g, '$1')
value = value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g,
(_, name, fallback) => Object.hasOwn(values, name) ? values[name] : fallback || '')
values[key] = value
assignments.push([key, value])
}
return assignments
}
module.exports = { hermesDotenvAssignments }
@@ -39,6 +39,10 @@ test('Hermes summary reads the selected native environment provider without expo
assert.equal(hermesConfiguredProvider({ home, env: { HERMES_HOME: home, HERMES_INFERENCE_PROVIDER: 'custom' } }), '')
fs.writeFileSync(path.join(home, '.env'), 'HERMES_INFERENCE_PROVIDER=${OTHER_PROVIDER}\n')
assert.equal(hermesConfiguredProvider({ home, env: { HERMES_HOME: home, HERMES_INFERENCE_PROVIDER: 'custom' } }), '')
fs.writeFileSync(path.join(home, '.env'), 'SELECTED_PROVIDER=openrouter\nHERMES_INFERENCE_PROVIDER=${SELECTED_PROVIDER}\n')
assert.equal(hermesConfiguredProvider({ home, env: { HERMES_HOME: home, SELECTED_PROVIDER: 'stale' } }), 'openrouter')
fs.writeFileSync(path.join(home, '.env'), 'HERMES_INFERENCE_PROVIDER=${UNSET_MELDWORK_PROVIDER:-custom:work}\n')
assert.equal(hermesConfiguredProvider({ home, env: { HERMES_HOME: home } }), 'custom:work')
fs.writeFileSync(path.join(home, 'config.yaml'), 'model:\n provider: anthropic\n')
assert.equal(hermesConfiguredProvider({ home, env: { HERMES_HOME: home } }), 'anthropic')
})