@fastagent-sh/voicenote 0.18.4 → 0.18.5
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/README.md +4 -2
- package/package.json +1 -1
- package/src/cli.ts +8 -13
- package/src/envConfig.ts +33 -14
package/README.md
CHANGED
|
@@ -194,7 +194,9 @@ The install script writes an editable template:
|
|
|
194
194
|
}
|
|
195
195
|
```
|
|
196
196
|
|
|
197
|
-
Changes take effect on the next `vn run`.
|
|
197
|
+
Changes take effect on the next `vn run`. Config values and unquoted/double-quoted `.zshrc` exports support simple `$VAR` / `${VAR}` references to other settings and `$HOME`. Single-quoted shell values stay literal. Shell commands are never executed. Runtime references honor inherited environment values; scheduler comparisons resolve from files alone.
|
|
198
|
+
|
|
199
|
+
A legacy `~/.config/voicenote/speakers.json` is still read as a compatibility fallback.
|
|
198
200
|
|
|
199
201
|
## Workflow
|
|
200
202
|
|
|
@@ -237,7 +239,7 @@ The LaunchAgent invokes `vn run` every 60 seconds. It skips safely when no recor
|
|
|
237
239
|
|
|
238
240
|
> Config changes (`config.json` or `~/.zshrc`) are picked up automatically by the background agent on its next run — no reinstall needed. The plist only snapshots real environment variables and pi's absolute path: **after changing `VOICENOTE_PI_BIN`, re-run `vn install-launch-agent` and reload** (`vn upgrade` regenerates the plist automatically). If pi is not signed in or ASR is not configured, the agent skips processing instead of burning ASR spend.
|
|
239
241
|
>
|
|
240
|
-
>
|
|
242
|
+
> Proxy values that match the file configuration, including expanded variable references, are not embedded and produce no override warning. Values supplied only by the shell, or differing from the files, are embedded as explicit overrides. To clear an unwanted override, update or unset the shell variable, then run `vn install-launch-agent --load`. Prefer `LOCAL_PROXY_HOST`/`LOCAL_PROXY_PORT` in `config.json` for proxy configuration.
|
|
241
243
|
|
|
242
244
|
Logs:
|
|
243
245
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastagent-sh/voicenote",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.5",
|
|
4
4
|
"description": "Voice recordings → diarized transcripts → integrated semantic Markdown notes. Currently optimized for the PHILIPS VTR6500 recorder, but the workflow is generic.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/cli.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
|
13
13
|
import { spawn, spawnSync } from 'node:child_process'
|
|
14
14
|
import os from 'node:os'
|
|
15
15
|
|
|
16
|
-
const VERSION = '0.18.
|
|
16
|
+
const VERSION = '0.18.5'
|
|
17
17
|
const LAUNCH_AGENT_LABEL = 'sh.fastagent.voicenote'
|
|
18
18
|
const LAUNCH_AGENT_LABEL_LEGACY = 'com.kid7st.voicenote' // pre-fastagent installs; cleaned up on install
|
|
19
19
|
const TASK_NAME = 'VoiceNote' // Windows Task Scheduler name (mac uses LAUNCH_AGENT_LABEL)
|
|
@@ -209,20 +209,21 @@ function applyDerivedProxy(): void {
|
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
//
|
|
213
|
-
//
|
|
212
|
+
// File values for each ENV_KEY. Hydration passes the current environment for
|
|
213
|
+
// variable references; scheduler comparison uses files alone. Primary source is
|
|
214
|
+
// ~/.config/voicenote/config.json
|
|
214
215
|
// (ENV-style runtime keys at the top level; identity under `speakers`); the
|
|
215
216
|
// legacy fallback is `export KEY=...` lines in ~/.zshrc, for CLI installs
|
|
216
217
|
// that predate config.json. Precedence/expansion logic lives in envConfig.ts
|
|
217
218
|
// (pure + tested). Two consumers: loadEnvConfig() hydrates these into
|
|
218
219
|
// process.env for keys the real environment doesn't set, and launchAgentEnv()
|
|
219
220
|
// uses them to decide which values are recoverable at run time.
|
|
220
|
-
function fileProvidedEnv(): Record<string, string> {
|
|
221
|
+
function fileProvidedEnv(environment: Record<string, string | undefined> = {}): Record<string, string> {
|
|
221
222
|
const data = loadJsonSync<Record<string, unknown>>(CONFIG_ENV_PATH, {})
|
|
222
223
|
let zshrc: string | null = null
|
|
223
224
|
const zshrcPath = join(os.homedir(), '.zshrc')
|
|
224
225
|
if (existsSync(zshrcPath)) { try { zshrc = readFileSync(zshrcPath, 'utf8') } catch { zshrc = null } }
|
|
225
|
-
return parseFileEnv(ENV_KEYS, data, zshrc, os.homedir())
|
|
226
|
+
return parseFileEnv(ENV_KEYS, data, zshrc, os.homedir(), environment)
|
|
226
227
|
}
|
|
227
228
|
|
|
228
229
|
let envConfigLoaded = false
|
|
@@ -231,7 +232,7 @@ function loadEnvConfig(): void {
|
|
|
231
232
|
envConfigLoaded = true
|
|
232
233
|
// Precedence: process.env > config.json (GUI) > ~/.zshrc (legacy); an
|
|
233
234
|
// explicit empty string in the environment is never overridden.
|
|
234
|
-
const toApply = hydrateFromFileEnv(ENV_KEYS, process.env, fileProvidedEnv())
|
|
235
|
+
const toApply = hydrateFromFileEnv(ENV_KEYS, process.env, fileProvidedEnv(process.env))
|
|
235
236
|
for (const [key, v] of Object.entries(toApply)) { process.env[key] = v; hydratedEnvKeys.add(key) }
|
|
236
237
|
// Derive http_proxy etc. from LOCAL_PROXY_HOST/PORT regardless of source, and
|
|
237
238
|
// always keep Volcano hosts on NO_PROXY. (Runs even with no config files.)
|
|
@@ -2250,12 +2251,6 @@ async function launchAgentEnv(): Promise<Record<string, string>> {
|
|
|
2250
2251
|
// about: it may equally be a stale shell session, and it will keep
|
|
2251
2252
|
// overriding config edits until the scheduler is reinstalled.
|
|
2252
2253
|
//
|
|
2253
|
-
// Known blind spot: the matrix compares each key against its OWN file value,
|
|
2254
|
-
// so it can't see cross-key derivations. A real-env http_proxy is embedded
|
|
2255
|
-
// as-is and will shadow a GUI edit to LOCAL_PROXY_HOST (different key name)
|
|
2256
|
-
// until reinstall. Only no_proxy is special-cased (originals below) because
|
|
2257
|
-
// WE synthesize it; http_proxy from a user's shell is left as a real value.
|
|
2258
|
-
//
|
|
2259
2254
|
// no_proxy/NO_PROXY carry a volcano-hosts merge we added; substitute the
|
|
2260
2255
|
// pre-merge real-env original (or drop it entirely if we synthesized the
|
|
2261
2256
|
// whole value) so the scheduler never freezes our merge over config edits.
|
|
@@ -2265,7 +2260,7 @@ async function launchAgentEnv(): Promise<Record<string, string>> {
|
|
|
2265
2260
|
const { embed, frozenOverrides } = envKeysToEmbed(ENV_KEYS, embedEnv, hydratedEnvKeys, fileEnv)
|
|
2266
2261
|
Object.assign(env, embed)
|
|
2267
2262
|
for (const k of frozenOverrides) {
|
|
2268
|
-
console.error(`Warning: environment ${k}
|
|
2263
|
+
console.error(`Warning: environment ${k} overrides the config file. Update or unset it, then re-run \`vn install-launch-agent --load\` to apply the intended value.`)
|
|
2269
2264
|
}
|
|
2270
2265
|
// Embed pi's ABSOLUTE path so launchd resolves it regardless of the fixed plist
|
|
2271
2266
|
// PATH (npm global bin can live outside it under nvm / custom prefixes). Resolve
|
package/src/envConfig.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// Pure logic behind cli.ts's env-config provenance. Extracted (no fs, no
|
|
2
2
|
// process.env) so its invariants are testable — see envConfig.test.ts:
|
|
3
3
|
//
|
|
4
|
-
// 1. File precedence: config.json (GUI) wins over ~/.zshrc (legacy CLI)
|
|
5
|
-
// $
|
|
4
|
+
// 1. File precedence: config.json (GUI) wins over ~/.zshrc (legacy CLI).
|
|
5
|
+
// Simple $VAR/${VAR} references resolve from these values without running
|
|
6
|
+
// shell code; runtime hydration may also use inherited environment values.
|
|
6
7
|
// 2. Hydration: only keys the real environment does NOT set are filled from
|
|
7
8
|
// files — an explicit empty string in the environment (e.g.
|
|
8
9
|
// VOICENOTE_PI_SUMMARY_TOOLS="") counts as set and is never overridden.
|
|
@@ -17,28 +18,46 @@
|
|
|
17
18
|
// warn: it may equally be a stale shell session shadowing a fresh config
|
|
18
19
|
// edit, and it will keep overriding until the scheduler is reinstalled.
|
|
19
20
|
|
|
20
|
-
/**
|
|
21
|
+
/** Omit `environment` when checking which values files can reproduce on their own. */
|
|
21
22
|
export function parseFileEnv(
|
|
22
23
|
keys: readonly string[],
|
|
23
24
|
configData: Record<string, unknown>,
|
|
24
25
|
zshrcContent: string | null,
|
|
25
26
|
home: string,
|
|
27
|
+
environment: Record<string, string | undefined> = {},
|
|
26
28
|
): Record<string, string> {
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
+
const raw = new Map<string, string>()
|
|
30
|
+
const literal = new Set<string>()
|
|
29
31
|
for (const key of keys) {
|
|
30
32
|
const v = configData[key]
|
|
31
|
-
if (typeof v === 'string')
|
|
33
|
+
if (typeof v === 'string') { raw.set(key, v); continue }
|
|
34
|
+
const pattern = new RegExp(`(?:^|\\n)\\s*export\\s+${key}=(?:"([^"]*)"|'([^']*)'|([^\\s"'#]+))`)
|
|
35
|
+
const match = zshrcContent?.match(pattern)
|
|
36
|
+
const value = match?.slice(1).find(v => v !== undefined)
|
|
37
|
+
if (value !== undefined) raw.set(key, value)
|
|
38
|
+
if (match?.[2] !== undefined) literal.add(key)
|
|
32
39
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
const resolved = new Map<string, string>()
|
|
41
|
+
const visiting = new Set<string>()
|
|
42
|
+
const resolve = (key: string): string => {
|
|
43
|
+
const cached = resolved.get(key)
|
|
44
|
+
if (cached !== undefined) return cached
|
|
45
|
+
if (visiting.has(key)) throw new Error(`Circular config variable reference: ${key}`)
|
|
46
|
+
visiting.add(key)
|
|
47
|
+
const value = raw.get(key)!
|
|
48
|
+
const expanded = literal.has(key) ? value : value.replace(/\\(\$)|\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))/g, (token, escaped, braced, bare) => {
|
|
49
|
+
if (escaped) return escaped
|
|
50
|
+
const ref = braced || bare
|
|
51
|
+
if (ref === 'HOME') return home
|
|
52
|
+
// Unknown references stay literal, so they cannot look recoverable from files.
|
|
53
|
+
if (Object.hasOwn(environment, ref) && environment[ref] !== undefined) return environment[ref]!
|
|
54
|
+
return raw.has(ref) ? resolve(ref) : token
|
|
55
|
+
})
|
|
56
|
+
visiting.delete(key)
|
|
57
|
+
resolved.set(key, expanded)
|
|
58
|
+
return expanded
|
|
40
59
|
}
|
|
41
|
-
return
|
|
60
|
+
return Object.fromEntries(Array.from(raw.keys(), key => [key, resolve(key)]))
|
|
42
61
|
}
|
|
43
62
|
|
|
44
63
|
/** Which keys to copy from fileEnv into an environment (invariant 2). */
|