@fastagent-sh/voicenote 0.22.2 → 1.0.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/README.md CHANGED
@@ -1,401 +1,19 @@
1
- # voicenote
1
+ # @fastagent-sh/voicenote
2
2
 
3
- Voice recordings → diarized transcripts → integrated semantic Markdown notes.
4
-
5
- CLI command: `vn`
6
-
7
- [中文文档 / Chinese documentation](README.zh-CN.md)
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 → the selected summary model performs cleanup and process reconstruction → produce smart notes.
10
-
11
- **Two ways to use it:**
12
-
13
- - 🖥️ **Desktop app (GUI)** — for non-terminal users, a self-contained `.app`, one-line install:
14
- ```bash
15
- curl -fsSL https://raw.githubusercontent.com/fastagent-sh/voicenote/main/scripts/install-app.sh | bash
16
- ```
17
- See [Desktop app](#desktop-app-gui-app) below.
18
- - ⌨️ **CLI (`vn`)** — for terminal users / developers, see "Install (CLI)" below.
19
-
20
- ## Install (CLI)
21
-
22
- > The CLI installs from the npm package `@fastagent-sh/voicenote`. The install script / `bun add -g` below require the package to be **published to npm** (see "Development" at the end for the release flow).
23
-
24
- Recommended: the install script (macOS):
25
-
26
- ```bash
27
- curl -fsSL https://raw.githubusercontent.com/fastagent-sh/voicenote/main/scripts/install.sh | bash
28
- ```
29
-
30
- The install script does **no interactive configuration** by default: it installs/checks `ffmpeg`, Bun, Node/npm, pi, and `vn`, then writes an editable `config.json` template. After installation, open the config file and fill in your keys and name:
31
-
32
- ```bash
33
- open ~/.config/voicenote/config.json
34
- # or open the directory
35
- vn open config
36
- ```
37
-
38
- Advanced users can preseed the template with environment variables:
39
-
40
- ```bash
41
- VOICENOTE_NAME="Jane Doe" \
42
- VOICENOTE_ALIAS="jane" \
43
- VOICENOTE_WORKSPACE="$HOME/Documents/meetings" \
44
- VOLCANO_ASR_KEY="..." \
45
- VOLCANO_TOS_BUCKET="..." \
46
- VOLCANO_TOS_ACCESS_KEY="..." \
47
- VOLCANO_TOS_SECRET_KEY="..." \
48
- bash <(curl -fsSL https://raw.githubusercontent.com/fastagent-sh/voicenote/main/scripts/install.sh)
49
- ```
50
-
51
- The first install creates `~/.config/voicenote/config.json`. Once configured, run `vn doctor` to check it, and `vn install-launch-agent` if you want background monitoring.
52
-
53
- Manual install:
54
-
55
- ```bash
56
- bun remove -g @kid7st/voicenote 2>/dev/null || true # drop the pre-rebrand package if present (safe no-op otherwise)
57
- bun add -g @fastagent-sh/voicenote
58
- mkdir -p ~/.local/bin
59
- ln -sf ~/.bun/bin/vn ~/.local/bin/vn
60
- ```
61
-
62
- An older `git+…#main` install is replaced in place by `bun add -g @fastagent-sh/voicenote`. The one exception is the **pre-rebrand `@kid7st/voicenote`** package: it ships the same `vn` bin, so the `bun remove -g` line above clears it first (the one-line `install.sh` does this automatically).
63
-
64
- ### Windows (CLI)
65
-
66
- The CLI is cross-platform. Prerequisites: Bun, ffmpeg (provides `ffprobe.exe`), Node + pi.
67
-
68
- ```powershell
69
- bun remove -g @kid7st/voicenote 2>$null # drop the pre-rebrand package if present (safe no-op otherwise)
70
- bun add -g @fastagent-sh/voicenote
71
- # Windows has no /Volumes mount points; set the recorder drive explicitly
72
- '{"env":{"VOICENOTE_RECORD_DIR":"E:\\RECORD"}}' | vn config set
73
- ```
74
-
75
- - Config: `%APPDATA%\voicenote\config.json`; logs/locks: `%LOCALAPPDATA%\voicenote\`
76
- - Background automation uses the **Windows Task Scheduler**: `vn install-launch-agent` to register / `vn status` to inspect / `vn uninstall-launch-agent` to remove (same command names as macOS; dispatched per platform internally)
77
-
78
- ## Dependencies
79
-
80
- - **Bun >= 1.3 (required at runtime)** — the code uses `Bun.Glob` / `Bun.file`; plain Node cannot run it
81
- - Node / npm — only used to install the pi CLI (the notes backend)
82
- - ffmpeg / ffprobe (audio duration detection):
83
-
84
- ```bash
85
- brew install ffmpeg
86
- ```
87
-
88
- The install script only writes `vn` / Bun / Homebrew PATH entries to your shell config; app configuration lives in `~/.config/voicenote/config.json`. A manual setup needs at least:
89
-
90
- ```json
91
- {
92
- "VOICENOTE_WORKSPACE": "/Users/you/Documents/meetings",
93
- "VOLCANO_ASR_KEY": "...",
94
- "VOLCANO_ASR_RESOURCE_ID": "volc.seedasr.auc",
95
- "VOLCANO_TOS_REGION": "cn-guangzhou",
96
- "VOLCANO_TOS_ENDPOINT": "tos-s3-cn-guangzhou.volces.com",
97
- "VOLCANO_TOS_BUCKET": "...",
98
- "VOLCANO_TOS_ACCESS_KEY": "...",
99
- "VOLCANO_TOS_SECRET_KEY": "...",
100
- "VOLCANO_TOS_KEEP": "0",
101
- "speakers": {
102
- "self": { "name": "Your name", "aliases": ["nickname", "alias"] },
103
- "known": []
104
- }
105
- }
106
- ```
107
-
108
- An empty or missing key means "use the built-in default" — those defaults live in `src/cli.ts` and nowhere else, so the installer and the GUI leave such fields blank.
109
-
110
- Optional settings:
111
-
112
- ```json
113
- {
114
- "VOICENOTE_DEVICE_VOLUME": "VTR6500",
115
- "VOICENOTE_RECORD_DIR": "/Volumes/VTR6500/RECORD",
116
- "VOICENOTE_MAX_AGE_HOURS": "48",
117
- "VOICENOTE_PI_BIN": "pi",
118
- "VOICENOTE_PI_MODEL": "openai-codex/gpt-5.6-sol",
119
- "PI_CODING_AGENT_DIR": "$HOME/.config/voicenote/pi-agent",
120
- "VOICENOTE_PI_THINKING": "high",
121
- "VOICENOTE_PI_SUMMARY_TOOLS": "read,grep",
122
- "VOICENOTE_CONTEXT_DIR": "/Users/you/vault"
123
- }
124
- ```
125
-
126
- ### Which model writes the notes
127
-
128
- `VOICENOTE_PI_MODEL` is passed straight to pi as `--model`. pi accepts
129
- `provider/id`, so one value pins both (`openai-codex/gpt-5.6-sol`). Leave it unset
130
- and pi's own configured default model is used.
131
-
132
- Credentials always belong to pi (`pi` → `/login <provider>`, or a provider API key
133
- in the environment); voicenote never picks a provider and never falls back to a
134
- second one. If pi fails, the transcript is kept and the summary can be retried
135
- with `vn run --latest`.
136
-
137
- ### Credentials of their own
138
-
139
- `PI_CODING_AGENT_DIR` relocates pi's config directory, which is where it keeps
140
- `auth.json`. Point it at a voicenote-owned directory and `vn login` writes there,
141
- pi refreshes the tokens there, and an interactive pi session cannot clobber
142
- them — it rewrites its own `auth.json` wholesale on exit, which has silently
143
- dropped providers before:
144
-
145
- ```bash
146
- echo '{"env":{"PI_CODING_AGENT_DIR":"$HOME/.config/voicenote/pi-agent"}}' | vn config set
147
- vn login # signs in and stores credentials in that directory
148
- ```
149
-
150
- `vn doctor` prints the path it will read (`pi.auth=...`).
151
-
152
- `DEEPSEEK_API_KEY` and `OPENAI_API_KEY` in the config are only forwarded to pi's
153
- environment for providers that read them.
154
-
155
- Transient failures (dropped socket, 5xx, 429) are retried on the same provider up
156
- to `VOICENOTE_PI_RETRIES` times (default 3). Quota and auth errors are not
157
- retried.
158
-
159
- ## Usage
160
-
161
- ```bash
162
- vn doctor # check environment and config
163
- vn run # default: Volcano ASR + pi notes
164
- vn run --mode transcript # transcript only, skip semantic notes
165
- vn run --latest # process only the latest valid recording
166
- vn run --latest --force # re-run the latest one
167
- vn run --pdf # additionally render a PDF after notes
168
- vn run --dry-run # print the plan only
169
- vn run /path/to/audio.m4a # process one file by path (skips the scan, ignores age/size/duration filters)
170
- vn list # list this month's notes
171
- vn list --month 2026-05 # specific month
172
- vn last # print the latest processing summary
173
- vn open # open the notes directory in Finder
174
- vn open config # open ~/.config/voicenote/
175
- vn open logs # open the logs directory
176
- vn open <slug> # open a note by filename fragment
177
- vn forget <id|filename> # let a recording be processed again
178
- vn log # print today's log tail (--lines N / -f follow / --err include launchd.err / --date YYYY-MM-DD)
179
- vn errors # print recent ERROR logs
180
- vn import /path/to/audio.mp3 # copy one local recording into the durable manual-import queue
181
- vn login # sign in to ChatGPT for the notes backend (browser callback; `--device-code` for headless machines). No pi TUI needed
182
- vn upgrade # reinstall latest npm package
183
- vn install-launch-agent
184
- vn status
185
- vn uninstall-launch-agent
186
- ```
187
-
188
- ## Configuration file
189
-
190
- The install script writes an editable template:
191
-
192
- ```text
193
- ~/.config/voicenote/config.json # workspace, Volcano ASR/TOS, summary backend, your name/aliases, etc.
194
- ```
195
-
196
- `speakers` maps Speaker A/B/C back to real names; `known` lists known contacts:
197
-
198
- ```json
199
- {
200
- "speakers": {
201
- "self": { "name": "Your name", "aliases": ["nickname", "alias"] },
202
- "known": []
203
- }
204
- }
205
- ```
206
-
207
- Changes take effect on the next `vn run`. `~`, `$HOME`, and `${HOME}` are accepted at the start of path settings. Environment variables override the file for the current CLI process; background runs use `config.json`, not shell startup files.
208
-
209
- ## Workflow
210
-
211
- 1. Scan recordings under `/Volumes/VTR6500/RECORD/`
212
- 2. Filter: ignore `._*`, small files (<100KB), short recordings (<60s), and already-processed recordings; if a previous run failed only at the summary stage and the transcript is saved, it is not considered done — processing resumes from there
213
- 3. Copy the original audio into `${VOICENOTE_WORKSPACE}/_audio/YYYY-MM/`
214
- 4. Transcribe with the Volcano Doubao large-model audio-file recognition API: upload local audio to TOS, submit the job, poll for results, and delete the TOS object by default when done
215
- 5. Persist the raw transcript immediately after transcription (no lossy cleanup), so a later-stage failure never wastes the ASR spend
216
- 6. The summary model (default: pi codex via ChatGPT Plus) reads the raw transcript directly, performing necessary cleanup, speaker restoration, and reconstruction of views/debates/consensus inside the notes-generation stage; if the summary fails, the next `vn run` / `vn run --latest` reuses the saved transcript and retries only the notes generation — no `vn forget` needed
217
- 7. Write notes / metadata; the system makes no archiving decisions — files stay in the configured workspace
218
-
219
- The history range defaults to 48 hours. In the GUI, choose 7 days, 30 days, or all recordings under **Settings → Recording history to process**. Expanding it re-evaluates recordings previously filtered as `too_old`; the dashboard's filtered summary links directly to this setting.
220
-
221
- To process a local file immediately, drop one supported audio file onto the GUI. It is copied atomically to `${VOICENOTE_WORKSPACE}/_inbox`, queued even if another run is active or the recorder is disconnected, and processed before automatic recorder items without the automatic age/size/duration filters. The temporary inbox copy is removed after success and retained after failure for Retry. Imports are content-addressed, so dropping the same audio again opens the existing note instead of paying for ASR twice when a matching completed job is known.
222
-
223
- A failing recording is retried on later runs, but at most **3 times** (whether it fails in transcription or in summarisation, and a run killed mid-job counts too). After that it is marked `Gave up` and left alone, so one broken file can't burn ASR/LLM budget on every scheduler tick. Use **Retry** on its GUI row to reset the budget, preserve saved outputs, and run it again; `vn forget <name>` is the CLI escape hatch that drops the record and re-queues it. Either path reuses a saved transcript instead of paying for ASR again. Both take the run lock, so retry after the active run finishes if the state file is busy.
224
-
225
- Records whose source file is no longer on the recorder are forgotten on the next scan (and the removal is logged), *unless* they already produced notes or a transcript — that history is kept. This is why swapping recorders, or deleting files from the device, no longer leaves permanent "failed" rows behind.
226
-
227
- ## Output locations
228
-
229
- `VOICENOTE_WORKSPACE` defaults to `~/Documents/meetings`.
230
-
231
- - Notes entry point: `${VOICENOTE_WORKSPACE}/YYYY-MM/`
232
- - Original audio: `${VOICENOTE_WORKSPACE}/_audio/YYYY-MM/`
233
- - Full transcripts: `${VOICENOTE_WORKSPACE}/_transcripts/YYYY-MM/`
234
- - Metadata: `${VOICENOTE_WORKSPACE}/_metadata/YYYY-MM/`
235
- - Pending manual imports: `${VOICENOTE_WORKSPACE}/_inbox/` (removed after success)
236
- - State: `${VOICENOTE_WORKSPACE}/_state/jobs.json` — one record per recording, holding its `state` — where it is in its lifecycle (`queued`, `running`, `done`, `filtered`, `error`, or `gave_up` once retries are spent) — plus a `code` saying why (`summary_failed`, `transcribe_failed`, `interrupted`, `too_small`, …), its attempt count and its output paths. `vn run` writes lifecycle updates; only explicit retry/forget actions mutate it otherwise. `vn jobs` and passive GUI refreshes are pure reads, so what you see is what will run. A pre-0.18 `processed.json` is converted automatically on the first run and kept as `processed.json.v1.bak`.
237
- - Index: `${VOICENOTE_WORKSPACE}/_index/notes.jsonl`
238
-
239
- ## Automation
240
-
241
- The install script can set this up automatically. Manual setup:
242
-
243
- ```bash
244
- vn install-launch-agent
245
- launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/sh.fastagent.voicenote.plist 2>/dev/null || true
246
- launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/sh.fastagent.voicenote.plist
247
- launchctl enable gui/$(id -u)/sh.fastagent.voicenote
248
- vn status
249
- ```
250
-
251
- The LaunchAgent invokes `vn run` every 60 seconds. Without a recorder it still processes queued local imports; once the VTR6500 is connected, new recorder items are processed automatically too.
252
-
253
- > `config.json` changes are picked up by the background agent on its next run. The plist stores only a fixed PATH and executable paths: **after changing `VOICENOTE_PI_BIN`, re-run `vn install-launch-agent --load`** (`vn upgrade` does this automatically). Shell-only settings are deliberately not copied into the scheduler; persist them with `vn config set`. If pi or ASR is not configured, the agent skips before spending ASR.
254
-
255
- Logs:
256
-
257
- ```text
258
- ~/.local/state/voicenote/logs/launchd.out.log
259
- ~/.local/state/voicenote/logs/launchd.err.log
260
- ```
261
-
262
- ## Development
263
-
264
- ```bash
265
- git clone https://github.com/fastagent-sh/voicenote.git
266
- cd voicenote
267
- bun install
268
- bun run typecheck
269
- bun src/cli.ts doctor
270
- ```
271
-
272
- Distribution: vn ships as **source** with no build step — it only runs on bun (shebang + `bun:ffi` + `engines.bun`), and bun runs TypeScript natively, so `bin` points straight at `src/cli.ts` and the npm tarball only contains `src/{cli,jobs,runLock,tos}.ts`. The install script / `vn upgrade` install from the published npm package (`bun add -g @fastagent-sh/voicenote`); a `git+https` install also works directly (the git tree carries the source; no build or install script needed).
273
-
274
- Routine release (tag triggers CI):
275
-
276
- ```bash
277
- npm version patch
278
- git push --follow-tags
279
- ```
280
-
281
- `package.json` is the CLI version source; `vn --version` reads it directly and CI rejects a mismatched `v*` tag.
282
-
283
- The workflow lives at `.github/workflows/release.yml`: CI explicitly runs typecheck, tests, and an entry-point smoke test, then `npm publish --ignore-scripts` (deterministic publishing, no lifecycle dependence). Publishing uses **npm trusted publishing (OIDC)**: no long-lived token (`id-token: write` + a Trusted Publisher configured on npmjs.com), with provenance attached automatically. A bare local `npm publish` is still guarded by `prepublishOnly` (typecheck + tests).
284
-
285
- > Both are already done for this package (Trusted Publisher configured, CI publishing since 0.18.0 with provenance), so a routine release needs nothing but the tag. Kept for forks: npm has no pending-publisher, so trusted publishing cannot publish a package's *very first* version — publish once manually with `npm login` + `npm publish --ignore-scripts`, then add a Trusted Publisher on the package settings page at npmjs.com (repo, workflow `release.yml`); CI takes over afterwards (the npm account needs 2FA).
286
-
287
- ## Desktop app (GUI, `app/`)
288
-
289
- A self-contained macOS `.app` (Tauri v2) for **non-terminal users**: the target machine needs no pre-installed bun / pi / ffprobe / global `vn`.
290
-
291
- **Positioning**: the GUI is a status dashboard with quick access to output, drag-to-import, and manual Sync/Retry controls. The full pipeline still runs autonomously every 60s via the background LaunchAgent using the bundled CLI (it keeps running with the GUI closed).
292
-
293
- - First run: settings (identity / Volcano keys / proxy). The notes model comes from pi; ChatGPT users can sign in from the Status panel (`vn login`'s browser-callback flow).
294
- - After that: the main view shows agent activity and recent notes, opens outputs, retries failed recordings, and accepts one local audio file dropped anywhere on the window.
295
-
296
- ### What's bundled
297
-
298
- `bun build --compile` compiles the `vn` CLI (bun runtime + pi-ai included) into a single-file sidecar; pi cannot be compiled (it reads data files from disk at runtime), so the whole package ships alongside and runs with a bundled `bun`:
299
-
300
- | Component | Form | Purpose |
301
- |------|------|------|
302
- | `vn` (compiled) | externalBin | pipeline + ChatGPT sign-in |
303
- | `bun` | externalBin | runs pi |
304
- | `ffprobe` (native universal on macOS) | externalBin | audio duration (pi only needs ffprobe, not all of ffmpeg) |
305
- | `pi` + node_modules | resource | notes backend (ChatGPT, OpenAI API, or DeepSeek) |
306
-
307
- At runtime, Rust invokes the bundled `vn` directly and injects `VOICENOTE_PI_BIN`, `VOICENOTE_PI_CLI`, and `VOICENOTE_FFPROBE_BIN`; `vn` then runs `<bundled bun> <bundled pi/cli.js>` without a wrapper script. Release builds are **universal** (x86_64 + arm64; vn/bun/ffprobe each merged with `lipo`; pi is JS and needs none).
308
-
309
- ### Build
310
-
311
- Prerequisites: Rust + cargo, bun, node/npm, Xcode CLT, and **pi installed globally on the build machine** (`npm i -g @earendil-works/pi-coding-agent`; the build script stages pi from there).
312
-
313
- ```bash
314
- cd app
315
- bun install
316
- bun run tauri build
317
- # Output: src-tauri/target/release/bundle/macos/VoiceNote.app
318
- ```
319
-
320
- **Windows** (build on Windows with Rust + MSVC C++ build tools; WebView2 is preinstalled on Win10/11, NSIS is downloaded by Tauri automatically):
321
-
322
- ```powershell
323
- cd app
324
- bun install
325
- bun run tauri build --config src-tauri/tauri.windows.conf.json
326
- # Output: app\src-tauri\target\release\bundle\nsis\VoiceNote_<version>_x64-setup.exe
327
- ```
328
-
329
- Windows uses `scripts/build-vn-sidecar.ps1` to stage `vn.exe` (`--windows-hide-console`, no console window) / `bun.exe` / `ffprobe.exe` + pi; `tauri.windows.conf.json` produces the NSIS installer (currentUser, no admin).
330
-
331
- `beforeBuildCommand` first runs `scripts/build-vn-sidecar.sh` to stage vn/bun/ffprobe/pi (`binaries/` and `resources/` are gitignored; pi/ffprobe copying is idempotent). For development use `bun run tauri dev` (dev mode runs `../src/cli.ts` directly, no bundling, no background agent install).
332
-
333
- ### How users install (one line, recommended)
334
-
335
- > This is separate from the CLI `install.sh` above: the CLI script targets developers (installs bun/pi/vn); this one targets **non-technical users** (download .app → /Applications).
336
-
337
- ```bash
338
- curl -fsSL https://raw.githubusercontent.com/fastagent-sh/voicenote/main/scripts/install-app.sh | bash
339
- ```
340
-
341
- **Windows** (one line, no admin):
342
-
343
- ```powershell
344
- irm https://raw.githubusercontent.com/fastagent-sh/voicenote/main/scripts/install-app.ps1 | iex
345
- ```
346
-
347
- `install-app.ps1` downloads the NSIS installer from the GitHub Release (self-contained vn/bun/ffprobe/pi) → silent install into `%LOCALAPPDATA%` (no admin) → launches it.
348
-
349
- `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).
350
-
351
- **First launch**: the app lands on Settings. Fill in identity, your Volcano ASR/TOS keys, and proxy as needed. Notes are written by pi with pi's own provider and model; for ChatGPT, click "Sign in to ChatGPT" in the Status panel. Saving installs and loads the background LaunchAgent using the bundled CLI. Once credentials are configured, plug in the recorder for automatic transcription and notes.
352
-
353
- > 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.
354
-
355
- **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.
356
-
357
- > **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.
358
-
359
- ### Maintainers: packaging + release
360
-
361
- **Automatic (recommended)**: push an `app-v*` tag to trigger `.github/workflows/release-app.yml`:
362
-
363
- ```bash
364
- git tag app-v0.1.0 && git push --tags
365
- ```
366
-
367
- **One `app-v*` tag = one Release covering mac + Windows.** `release-app.yml` is a single workflow: mac (universal + ad-hoc deep signing) and Windows (NSIS) build in parallel, then the `release` job publishes. Each Release carries:
368
-
369
- - **First-install packages** `VoiceNote.zip` (mac) / `VoiceNote-setup.exe` (win) — fetched by `install-app.*` from `releases/latest/download/...`;
370
- - **Updater artifacts** `VoiceNote.app.tar.gz` + `latest.json` — used by the in-app Tauri updater (Settings → Software update).
371
-
372
- > The updater needs signing secrets `TAURI_SIGNING_PRIVATE_KEY` / `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` (generated with `tauri signer generate`; the public key goes in `tauri.conf.json`); the `preflight` job blocks the release while the pubkey is still a placeholder.
373
-
374
- > Use `app-v*` (distinct from the CLI's `v*` npm release tags). Artifacts are **universal** (x86_64 + arm64), working on both Intel and Apple Silicon.
375
-
376
- **Manual**:
3
+ Installs the **VoiceNote desktop app** on macOS:
377
4
 
378
5
  ```bash
379
- cd app
380
- bash scripts/package.sh # → app/release/VoiceNote-<version>.zip (~110MB)
381
- gh release create app-v0.1.0 app/release/VoiceNote-<version>.zip#VoiceNote.zip -t "VoiceNote 0.1.0" -n "Desktop app"
6
+ npx @fastagent-sh/voicenote
382
7
  ```
383
8
 
384
- The asset name must be **`VoiceNote.zip`** (`install-app.sh` fetches `releases/latest/download/VoiceNote.zip`). For local testing bypass the Release with: `VOICENOTE_APP_URL=file:///path/to/VoiceNote.zip bash scripts/install-app.sh`.
385
-
386
- ### Signing / notarization (no `xattr`, double-click to run)
387
-
388
- JIT entitlements are in place (`src-tauri/entitlements.plist`: `allow-jit` etc. for bun/vn; referenced from `tauri.conf.json`). `scripts/sign-macos.sh` performs inside-out deep signing (hardened runtime + entitlements):
389
-
390
- ```bash
391
- # Internal ad-hoc (JIT verified to survive under hardened runtime)
392
- bash scripts/sign-macos.sh /Applications/VoiceNote.app
393
-
394
- # Official distribution (requires a Developer ID certificate, Apple Developer Program $99/yr)
395
- bash scripts/sign-macos.sh VoiceNote.app "Developer ID Application: NAME (TEAMID)"
396
- xcrun notarytool submit ... && xcrun stapler staple VoiceNote.app
397
- ```
9
+ It downloads the latest release from
10
+ [GitHub](https://github.com/fastagent-sh/voicenote/releases), replaces any
11
+ existing copy, and puts `VoiceNote.app` in `/Applications`. The app updates
12
+ itself after that, so this command is only needed once (or to repair an
13
+ install).
398
14
 
399
- ## License
15
+ Everything the app needs ships inside the bundle — no Node, no Bun, no
16
+ separate transcription tooling.
400
17
 
401
- MIT
18
+ Looking for the command line tool? That is
19
+ [`@fastagent-sh/vn`](https://www.npmjs.com/package/@fastagent-sh/vn).
package/install.mjs ADDED
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ // Installs the VoiceNote desktop app from its GitHub release.
3
+ //
4
+ // The app itself is a signed .app bundle, not something npm can hold; this
5
+ // package exists so `npx @fastagent-sh/voicenote` is a one-line install (and
6
+ // re-install, and repair) without hunting for a download page. Everything the
7
+ // app needs is inside the bundle, so there is nothing else to set up.
8
+ //
9
+ // A file downloaded by this script is not quarantined the way a browser
10
+ // download is, so the app opens on the first double-click — no right-click →
11
+ // Open dance, even though the signature is self-signed.
12
+ import { execFileSync } from 'node:child_process'
13
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises'
14
+ import { existsSync } from 'node:fs'
15
+ import { tmpdir } from 'node:os'
16
+ import { join } from 'node:path'
17
+
18
+ const REPO = 'fastagent-sh/voicenote'
19
+ const APPS = '/Applications'
20
+
21
+ function fail(message) {
22
+ console.error(`\nVoiceNote 安装失败:${message}\n`)
23
+ process.exit(1)
24
+ }
25
+
26
+ if (process.platform !== 'darwin') {
27
+ fail(`目前只有 macOS 版本(检测到 ${process.platform})。`)
28
+ }
29
+
30
+ const release = await fetch(`https://api.github.com/repos/${REPO}/releases/latest`, {
31
+ headers: { accept: 'application/vnd.github+json' },
32
+ }).then(r => r.ok ? r.json() : fail(`读取发布信息失败(HTTP ${r.status})。`))
33
+
34
+ // The zip is what ships the .app; the dmg is for people who download by hand.
35
+ const wanted = process.arch === 'arm64' ? 'arm64' : 'x64'
36
+ const asset = (release.assets ?? []).find(a => a.name.endsWith('-mac.zip') && a.name.includes(wanted))
37
+ if (!asset) fail(`这个版本没有 ${wanted} 的 macOS 包(${release.tag_name})。`)
38
+
39
+ console.log(`下载 VoiceNote ${release.tag_name.replace(/^app-v/, '')}(${(asset.size / 1e6).toFixed(0)} MB)…`)
40
+ const work = await mkdtemp(join(tmpdir(), 'voicenote-install-'))
41
+ const zipPath = join(work, asset.name)
42
+ try {
43
+ const download = await fetch(asset.browser_download_url)
44
+ if (!download.ok) fail(`下载失败(HTTP ${download.status})。`)
45
+ await writeFile(zipPath, Buffer.from(await download.arrayBuffer()))
46
+
47
+ const target = join(APPS, 'VoiceNote.app')
48
+ if (existsSync(target)) {
49
+ console.log('替换已安装的版本…')
50
+ // Quit it first: replacing a running bundle leaves the old process alive
51
+ // with files that no longer exist.
52
+ try { execFileSync('osascript', ['-e', 'tell application "VoiceNote" to quit'], { stdio: 'ignore' }) } catch { /* not running */ }
53
+ await rm(target, { recursive: true, force: true })
54
+ }
55
+ execFileSync('ditto', ['-x', '-k', zipPath, APPS], { stdio: 'inherit' })
56
+ if (!existsSync(target)) fail('解压后没有找到 VoiceNote.app。')
57
+
58
+ console.log(`\n✓ 已安装到 ${target}`)
59
+ console.log(' 打开方式:访达 → 应用程序 → VoiceNote,或执行 open -a VoiceNote')
60
+ console.log(' 应用会自己检查更新,之后不需要再跑这个命令。\n')
61
+ } catch (error) {
62
+ if (error?.code === 'EACCES' || /Permission denied/i.test(String(error?.message))) {
63
+ fail(`没有写入 ${APPS} 的权限。用 sudo 重试:sudo npx @fastagent-sh/voicenote`)
64
+ }
65
+ fail(String(error?.message ?? error))
66
+ } finally {
67
+ await rm(work, { recursive: true, force: true })
68
+ }
package/package.json CHANGED
@@ -1,58 +1,34 @@
1
1
  {
2
2
  "name": "@fastagent-sh/voicenote",
3
- "version": "0.22.2",
4
- "description": "Voice recordings → diarized transcripts → integrated semantic Markdown notes. Currently optimized for the PHILIPS VTR6500 recorder, but the workflow is generic.",
3
+ "version": "1.0.1",
4
+ "description": "Installs the VoiceNote desktop app (macOS). The command line tool is @fastagent-sh/vn.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "fastagent-sh",
8
8
  "homepage": "https://github.com/fastagent-sh/voicenote",
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "git+https://github.com/fastagent-sh/voicenote.git"
12
- },
13
- "bugs": {
14
- "url": "https://github.com/fastagent-sh/voicenote/issues"
11
+ "url": "git+https://github.com/fastagent-sh/voicenote.git",
12
+ "directory": "installer"
15
13
  },
16
14
  "keywords": [
17
- "voice",
18
15
  "voicenote",
16
+ "installer",
17
+ "macos",
19
18
  "transcription",
20
- "diarization",
21
- "meeting-notes",
22
- "philips",
23
- "cli",
24
- "bun"
19
+ "meeting-notes"
25
20
  ],
26
21
  "bin": {
27
- "vn": "src/cli.ts"
22
+ "voicenote": "install.mjs"
28
23
  },
29
24
  "files": [
30
- "src/cli.ts",
31
- "src/jobs.ts",
32
- "src/runLock.ts",
33
- "src/tos.ts",
34
- "README.md",
35
- "LICENSE"
25
+ "install.mjs",
26
+ "README.md"
36
27
  ],
37
- "scripts": {
38
- "dev": "bun src/cli.ts",
39
- "typecheck": "tsc --noEmit",
40
- "test": "bun test",
41
- "doctor": "bun src/cli.ts doctor",
42
- "prepublishOnly": "bun run typecheck && bun test ./src"
43
- },
44
28
  "publishConfig": {
45
29
  "access": "public"
46
30
  },
47
- "dependencies": {
48
- "@earendil-works/pi-ai": "^0.79.3",
49
- "cac": "latest"
50
- },
51
- "devDependencies": {
52
- "@types/bun": "latest",
53
- "typescript": "latest"
54
- },
55
31
  "engines": {
56
- "bun": ">=1.3.0"
32
+ "node": ">=20"
57
33
  }
58
34
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 fastagent-sh
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.