@fastagent-sh/voicenote 0.22.2 → 1.0.0
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 +12 -394
- package/install.mjs +68 -0
- package/package.json +8 -41
- package/LICENSE +0 -21
- package/README.zh-CN.md +0 -396
- package/src/cli.ts +0 -2800
- package/src/jobs.ts +0 -460
- package/src/runLock.ts +0 -20
- package/src/tos.ts +0 -18
package/README.md
CHANGED
|
@@ -1,401 +1,19 @@
|
|
|
1
|
-
# voicenote
|
|
1
|
+
# @fastagent-sh/voicenote
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
-
|
|
15
|
+
Everything the app needs ships inside the bundle — no Node, no Bun, no
|
|
16
|
+
separate transcription tooling.
|
|
400
17
|
|
|
401
|
-
|
|
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,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastagent-sh/voicenote",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.0",
|
|
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"
|
|
11
|
+
"url": "git+https://github.com/fastagent-sh/voicenote.git",
|
|
12
|
+
"directory": "installer"
|
|
12
13
|
},
|
|
13
|
-
"
|
|
14
|
-
"url": "https://github.com/fastagent-sh/voicenote/issues"
|
|
15
|
-
},
|
|
16
|
-
"keywords": [
|
|
17
|
-
"voice",
|
|
18
|
-
"voicenote",
|
|
19
|
-
"transcription",
|
|
20
|
-
"diarization",
|
|
21
|
-
"meeting-notes",
|
|
22
|
-
"philips",
|
|
23
|
-
"cli",
|
|
24
|
-
"bun"
|
|
25
|
-
],
|
|
14
|
+
"keywords": ["voicenote", "installer", "macos", "transcription", "meeting-notes"],
|
|
26
15
|
"bin": {
|
|
27
|
-
"
|
|
28
|
-
},
|
|
29
|
-
"files": [
|
|
30
|
-
"src/cli.ts",
|
|
31
|
-
"src/jobs.ts",
|
|
32
|
-
"src/runLock.ts",
|
|
33
|
-
"src/tos.ts",
|
|
34
|
-
"README.md",
|
|
35
|
-
"LICENSE"
|
|
36
|
-
],
|
|
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"
|
|
16
|
+
"voicenote": "install.mjs"
|
|
43
17
|
},
|
|
18
|
+
"files": ["install.mjs", "README.md"],
|
|
44
19
|
"publishConfig": {
|
|
45
20
|
"access": "public"
|
|
46
21
|
},
|
|
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
22
|
"engines": {
|
|
56
|
-
"
|
|
23
|
+
"node": ">=20"
|
|
57
24
|
}
|
|
58
25
|
}
|
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.
|