@fastagent-sh/voicenote 0.18.2 → 0.18.4
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 +25 -5
- package/README.zh-CN.md +3 -1
- package/package.json +1 -1
- package/src/cli.ts +10 -8
- package/src/piProvider.ts +4 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ CLI command: `vn`
|
|
|
6
6
|
|
|
7
7
|
[中文文档 / Chinese documentation](README.zh-CN.md)
|
|
8
8
|
|
|
9
|
-
Currently tuned for the PHILIPS VTR6500 voice recorder, but the workflow is generic: scan recordings under a mount point → transcribe with speaker diarization →
|
|
9
|
+
Currently tuned for the PHILIPS VTR6500 voice recorder, but the workflow is generic: scan recordings under a mount point → transcribe with speaker diarization → the selected summary model performs cleanup and process reconstruction → produce smart notes.
|
|
10
10
|
|
|
11
11
|
**Two ways to use it:**
|
|
12
12
|
|
|
@@ -130,6 +130,24 @@ provider that actually failed. If that empties the chain, `vn run --mode notes`
|
|
|
130
130
|
skips instead of paying for a transcript whose summary cannot happen.
|
|
131
131
|
`vn doctor` prints the effective chain and the status of anything not ready.
|
|
132
132
|
|
|
133
|
+
### DeepSeek notes
|
|
134
|
+
|
|
135
|
+
In the desktop app, open **Settings → Notes generation**, select **DeepSeek API**, enter your API key, and save. The model defaults to `deepseek-v4-flash`; enter `deepseek-v4-pro` to use Pro. ChatGPT sign-in is only shown for configurations that use ChatGPT. Audio transcription continues to use Volcano.
|
|
136
|
+
|
|
137
|
+
For the CLI, merge these values into `~/.config/voicenote/config.json`:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"VOICENOTE_PI_PROVIDER": "deepseek",
|
|
142
|
+
"VOICENOTE_PI_MODEL_SUMMARY": "deepseek-v4-flash",
|
|
143
|
+
"DEEPSEEK_API_KEY": "..."
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The notes-specific model setting takes precedence over `VOICENOTE_PI_MODEL`. When switching providers in the GUI, the model resets to that provider's default. API keys can also come from pi's authentication file or the environment; pi's authentication file takes priority over API-key environment variables.
|
|
148
|
+
|
|
149
|
+
Save changes, then check `vn doctor` or the app's Status panel. Credential checks confirm configuration, not API connectivity or account balance. Each provider in a fallback chain receives the same model ID, so configure DeepSeek on its own rather than mixing it with OpenAI.
|
|
150
|
+
|
|
133
151
|
## Usage
|
|
134
152
|
|
|
135
153
|
```bash
|
|
@@ -150,7 +168,7 @@ vn open <slug> # open a note by filename fragment
|
|
|
150
168
|
vn forget <id|filename> # let a recording be processed again
|
|
151
169
|
vn log # print today's log tail (--lines N / -f follow / --err include launchd.err / --date YYYY-MM-DD)
|
|
152
170
|
vn errors # print recent ERROR logs
|
|
153
|
-
vn login # sign in to ChatGPT (
|
|
171
|
+
vn login # sign in to ChatGPT for the notes backend (browser callback; `--device-code` for headless machines). No pi TUI needed
|
|
154
172
|
vn upgrade # reinstall latest npm package
|
|
155
173
|
vn install-launch-agent
|
|
156
174
|
vn status
|
|
@@ -259,7 +277,7 @@ A self-contained macOS `.app` (Tauri v2) for **non-terminal users**: the target
|
|
|
259
277
|
|
|
260
278
|
**Positioning**: the GUI is only a "status dashboard + quick access to output" — it does **not** drive processing. The full pipeline runs autonomously every 60s via the background LaunchAgent using the bundled engine (it keeps running with the GUI closed).
|
|
261
279
|
|
|
262
|
-
- First run:
|
|
280
|
+
- First run: settings (identity / Volcano keys / notes provider / proxy). Choose DeepSeek with an API key, or ChatGPT with browser sign-in (`vn login`'s browser-callback flow).
|
|
263
281
|
- After that: the main view shows agent activity + recent notes (open note / open folder)
|
|
264
282
|
|
|
265
283
|
### What's bundled
|
|
@@ -271,7 +289,7 @@ A self-contained macOS `.app` (Tauri v2) for **non-terminal users**: the target
|
|
|
271
289
|
| `vn` (compiled) | externalBin | pipeline + ChatGPT sign-in |
|
|
272
290
|
| `bun` | externalBin | runs pi |
|
|
273
291
|
| `ffprobe` (native arm64 static) | externalBin | audio duration (pi only needs ffprobe, not all of ffmpeg) |
|
|
274
|
-
| `pi` + node_modules | resource | notes backend (ChatGPT
|
|
292
|
+
| `pi` + node_modules | resource | notes backend (ChatGPT, OpenAI API, or DeepSeek) |
|
|
275
293
|
|
|
276
294
|
At runtime, Rust generates a wrapper (`exec <bundled bun> <bundled pi/cli.js> "$@"`) and injects `VOICENOTE_PI_BIN` / `VOICENOTE_FFPROBE_BIN` into `vn`. Release builds are **universal** (x86_64 + arm64; vn/bun/ffprobe each merged with `lipo`; pi is JS and needs none).
|
|
277
295
|
|
|
@@ -317,12 +335,14 @@ irm https://raw.githubusercontent.com/fastagent-sh/voicenote/main/scripts/instal
|
|
|
317
335
|
|
|
318
336
|
`install-app.sh` downloads the packaged `.app` from GitHub Releases → installs to `/Applications` → **removes the quarantine flag for the user** (Gatekeeper bypass for un-notarized builds) → opens it. The target machine needs no bun/pi/ffprobe/global vn (all bundled).
|
|
319
337
|
|
|
320
|
-
**First launch**: the app lands on Settings
|
|
338
|
+
**First launch**: the app lands on Settings. Fill in identity, your Volcano ASR/TOS keys, notes provider/model, and proxy as needed. For DeepSeek or OpenAI API, enter the corresponding API key; for ChatGPT, save and click "Sign in to ChatGPT" in the Status panel. Saving installs and loads the background LaunchAgent using the bundled engine. Once credentials are configured, plug in the recorder for automatic transcription and notes.
|
|
321
339
|
|
|
322
340
|
> The background agent label is `sh.fastagent.voicenote` (same as the CLI version; only one exists per machine). If the `.app` is moved, open it once to recalibrate the plist.
|
|
323
341
|
|
|
324
342
|
**Upgrades**: since 0.1.9 the app has a built-in updater — open the app → "Settings → Software update" → "Check for updates"; when a new version appears, click "Download & install"; the app restarts automatically with config/notes preserved. For first installs, or upgrades from 0.1.8 and earlier (which had no updater), re-run the one-line install script above.
|
|
325
343
|
|
|
344
|
+
> **Windows, from 0.1.11 or earlier**: those builds point their updater at the pre-rebrand repo, which still exists and stops at 0.1.11 — "Check for updates" therefore always reports "up to date". The bundle identifier changed in the same rebrand, so re-running the installer does *not* replace them; both copies stay installed under the same name. Uninstall the old VoiceNote (Settings → Apps) first, then run the one-line install. Config and notes are untouched by the uninstall.
|
|
345
|
+
|
|
326
346
|
### Maintainers: packaging + release
|
|
327
347
|
|
|
328
348
|
**Automatic (recommended)**: push an `app-v*` tag to trigger `.github/workflows/release-app.yml`:
|
package/README.zh-CN.md
CHANGED
|
@@ -148,7 +148,7 @@ vn open <slug> # 按文件名片段打开纪要
|
|
|
148
148
|
vn forget <id|filename> # 让某条录音重新被处理
|
|
149
149
|
vn log # 打印今天日志末尾(--lines N / -f 跟随 / --err 含 launchd.err / --date YYYY-MM-DD)
|
|
150
150
|
vn errors # 打印最近 ERROR 日志
|
|
151
|
-
vn login # 登录 ChatGPT(
|
|
151
|
+
vn login # 登录 ChatGPT, 纪要后端用(默认浏览器回调; 无头机器用 `--device-code`)。无需开 pi TUI
|
|
152
152
|
vn upgrade # reinstall latest npm package
|
|
153
153
|
vn install-launch-agent
|
|
154
154
|
vn status
|
|
@@ -321,6 +321,8 @@ irm https://raw.githubusercontent.com/fastagent-sh/voicenote/main/scripts/instal
|
|
|
321
321
|
|
|
322
322
|
**升级**:0.1.9 起内置自动更新 —— 打开 app →「设置 → 软件更新」→「检查更新」,有新版点「下载并安装」,装好自动重启,配置/纪要均保留。首次安装、或从 0.1.8 及更早版本升级(它们还没有更新器),重跑上面的一键安装脚本即可。
|
|
323
323
|
|
|
324
|
+
> **Windows 从 0.1.11 及更早版本升级**:这些构建的更新地址指向改名前的旧仓库,而旧仓库至今存在、最新版停在 0.1.11,所以「检查更新」永远显示已是最新。同一次改名还换了 bundle identifier,重跑安装脚本**不会**覆盖旧版,两份会同名共存。先在「设置 → 应用」里卸载旧的 VoiceNote,再跑一键安装。卸载不影响配置和纪要。
|
|
325
|
+
|
|
324
326
|
### 维护者:打包 + 发布
|
|
325
327
|
|
|
326
328
|
**自动(推荐)**:打 `app-v*` tag 触发 `.github/workflows/release-app.yml`:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastagent-sh/voicenote",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.4",
|
|
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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { cac } from 'cac'
|
|
3
3
|
import { deriveNoProxy, envKeysToEmbed, hydrateFromFileEnv, parseFileEnv } from './envConfig'
|
|
4
4
|
import { parseLockOwner } from './runLock'
|
|
5
|
-
import { parsePiAuthStatus, parseProviderChain, usableChain, type PiAuthStatus } from './piProvider'
|
|
5
|
+
import { defaultPiModel, parsePiAuthStatus, parseProviderChain, usableChain, type PiAuthStatus } from './piProvider'
|
|
6
6
|
import { applyOutcome, buildJobsView, classify, emptyState, localIso, MAX_ATTEMPTS, migrateLegacyState, ownsOutput, parseJobsLimit, parseStateFile, parseStrictJson, patchJob, pruneUnseen, reconcileInterrupted, startAttempt, SUMMARY_FAILED_STATUS, type CurrentJob, type JobRecord, type StateFile } from './jobs'
|
|
7
7
|
import { createHash, createHmac, randomUUID } from 'node:crypto'
|
|
8
8
|
import { appendFile, chmod, mkdir, readFile, writeFile, copyFile, rename, unlink, stat, readdir, rm } from 'node:fs/promises'
|
|
@@ -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.4'
|
|
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)
|
|
@@ -137,6 +137,7 @@ const ENV_KEYS = [
|
|
|
137
137
|
'HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'NO_PROXY',
|
|
138
138
|
'LOCAL_PROXY_HOST', 'LOCAL_PROXY_PORT', 'LOCAL_NO_PROXY',
|
|
139
139
|
'OPENAI_API_KEY',
|
|
140
|
+
'DEEPSEEK_API_KEY',
|
|
140
141
|
]
|
|
141
142
|
|
|
142
143
|
// Volcano endpoints (TOS object storage + openspeech ASR) should NEVER go through
|
|
@@ -1493,7 +1494,8 @@ function piProviderAuthStatus(provider: string): PiAuthStatus {
|
|
|
1493
1494
|
const cached = piAuthStatusCache.get(provider)
|
|
1494
1495
|
if (cached && Date.now() - cached.at < PI_AUTH_TTL_MS) return cached.status
|
|
1495
1496
|
const inv = piInvocation(['auth', 'check', '--provider', provider, '--json'])
|
|
1496
|
-
|
|
1497
|
+
// Bun's spawnSync needs an explicit env to inherit keys loaded from config.json.
|
|
1498
|
+
const out = spawnSync(inv.bin, inv.args, { encoding: 'utf8', timeout: PI_AUTH_PROBE_MS, windowsHide: true, env: process.env })
|
|
1497
1499
|
// No pi binary is deterministic evidence in its own right — next run gets the
|
|
1498
1500
|
// same answer — so it belongs with 'unusable', not with a probe that timed out.
|
|
1499
1501
|
const status = (out.error as NodeJS.ErrnoException | undefined)?.code === 'ENOENT'
|
|
@@ -1524,7 +1526,7 @@ function piProviderFor(): string {
|
|
|
1524
1526
|
}
|
|
1525
1527
|
|
|
1526
1528
|
function piCodexModelFor(): string {
|
|
1527
|
-
return process.env.VOICENOTE_PI_MODEL_SUMMARY || process.env.VOICENOTE_PI_MODEL ||
|
|
1529
|
+
return process.env.VOICENOTE_PI_MODEL_SUMMARY || process.env.VOICENOTE_PI_MODEL || defaultPiModel(piConfiguredProviders()[0]!)
|
|
1528
1530
|
}
|
|
1529
1531
|
|
|
1530
1532
|
function stripJsonFences(text: string): string {
|
|
@@ -1661,7 +1663,7 @@ function piSummaryToolsHint(contextDir: string): string {
|
|
|
1661
1663
|
return `Before writing the notes you have two read-only tools: read and grep. Your current working directory (cwd) is \`${contextDir}\` (the configured notes/reference directory); use relative paths for grep/read.\n\nGoal: use existing context to align names, speakers, client/project names, product names, and domain terms in this note; do not maintain or assume a separate glossary.\n\nSuggested flow:\n- First extract the most likely client/project/product keywords from the title, filename, and transcript.\n- If a clear topic matches, prefer grep/read on related index pages, project docs, status records, or the 3-5 most recent related notes in the same directory; use them to identify Speaker B/C/F etc., common aliases, product names, and term spellings.\n- If no clear topic matches, grep the current directory with keywords and read only the few most relevant files.\n- Before output, do one names/terms lint pass: eliminate leftover Speaker A/B/C, obviously misheard names, product-name variants, and outdated names; when context is insufficient, keep the uncertainty — never guess.\n\nConstraints:\n- At most 10 tool calls total; if the transcript alone is sufficient, make none.\n- Read only within \`${contextDir}\`; skip directories that clearly involve personal privacy/credentials/finance (e.g. identity / credentials / finance).\n- Found information is only for consistency and background calibration; never write content absent from this transcript into the notes as new meeting facts.\n- Do not attempt to write files or call bash (those tools are not enabled).`
|
|
1662
1664
|
}
|
|
1663
1665
|
|
|
1664
|
-
// Summary runs
|
|
1666
|
+
// Summary runs through pi with the configured provider. The agent's working dir IS the knowledge
|
|
1665
1667
|
// base, so read/grep/find operate there directly. If a configured context dir is
|
|
1666
1668
|
// missing, say so loudly and run without tools rather than searching the wrong
|
|
1667
1669
|
// tree (tools, the cwd hint, and the spawn cwd move together).
|
|
@@ -2753,8 +2755,8 @@ async function collectDoctor() {
|
|
|
2753
2755
|
const ff = await runCommand(ffprobeBin(), ['-version'], 5000)
|
|
2754
2756
|
const v = config.volcano
|
|
2755
2757
|
const tools = piSummaryTools()
|
|
2756
|
-
//
|
|
2757
|
-
|
|
2758
|
+
// An explicit status refresh must see newly saved keys and OAuth logins.
|
|
2759
|
+
piAuthStatusCache.clear()
|
|
2758
2760
|
const configuredProviders = piConfiguredProviders()
|
|
2759
2761
|
const effectiveProviders = piProviderCandidates()
|
|
2760
2762
|
const providerStatus = Object.fromEntries(configuredProviders.map(p => [p, piProviderAuthStatus(p)]))
|
|
@@ -2989,7 +2991,7 @@ async function serve(): Promise<void> {
|
|
|
2989
2991
|
|
|
2990
2992
|
const cli = cac('vn')
|
|
2991
2993
|
|
|
2992
|
-
cli.command('run', 'Scan recorder and process recordings (Volcano ASR + pi
|
|
2994
|
+
cli.command('run', 'Scan recorder and process recordings (Volcano ASR + pi notes)')
|
|
2993
2995
|
.option('--mode <mode>', 'Output mode: notes (default) | transcript', { default: 'notes' })
|
|
2994
2996
|
.option('--latest', 'Only process newest eligible recording')
|
|
2995
2997
|
.option('--force', 'Reprocess already processed recordings')
|
package/src/piProvider.ts
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
export const DEFAULT_PI_PROVIDERS = ['openai-codex', 'openai']
|
|
5
5
|
|
|
6
|
+
export function defaultPiModel(provider: string): string {
|
|
7
|
+
return provider === 'deepseek' ? 'deepseek-v4-flash' : 'gpt-5.5'
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
export function parseProviderChain(raw: string | undefined): string[] {
|
|
7
11
|
const parsed = Array.from(new Set((raw ?? '').split(',').map(s => s.trim()).filter(Boolean)))
|
|
8
12
|
return parsed.length ? parsed : [...DEFAULT_PI_PROVIDERS]
|