@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 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 → GPT performs the necessary cleanup and process reconstruction inside the notes-generation stage → produce smart notes.
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 (Codex device-code flow, for the notes backend; no pi TUI needed)
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: setup wizard (identity / Volcano keys / proxy) ChatGPT sign-in (no terminal on the device; uses `vn login`'s browser-callback flow)
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 Codex agent) |
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 fill in identity + your own Volcano ASR/TOS keys + proxy (BYOK) save click "Sign in to ChatGPT" in the Status panel (one-time browser authorization). The GUI then installs and loads the background LaunchAgent (pointing at the bundled engine); plug in the recorder and transcription + notes happen automatically.
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(Codex 设备码流,纪要后端用;无需开 pi TUI)
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.2",
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.2'
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
- const out = spawnSync(inv.bin, inv.args, { encoding: 'utf8', timeout: PI_AUTH_PROBE_MS, windowsHide: true })
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 || 'gpt-5.5'
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 on the pi-codex backend. The agent's working dir IS the knowledge
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
- // Configured vs effective, plus per-provider status: "why did my chain shrink"
2757
- // and "why is my misspelled provider still listed" are the same question.
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-codex notes)')
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]