From 187264ff550de0818c2c42cdc2bb8db7fd5abd1e Mon Sep 17 00:00:00 2001 From: WiseGuru <42100212+WiseGuru@users.noreply.github.com> Date: Mon, 15 Jun 2026 21:26:48 -0700 Subject: [PATCH] Rename plugin id to satisfy Obsidian manifest rules The id rewrite-plugin is rejected on submission because an id may not end in plugin. Rename to rewrite-voice-notes (lowercase + hyphens, no obsidian, does not end in plugin). Pre-release, no installed users. - manifest.json: id -> rewrite-voice-notes - package.json / package-lock.json: name -> rewrite-voice-notes - README.md, CLAUDE.md, docs/DEVCONFLICTS.md: update install-folder paths, command-id examples, and the id-locked note - src/ needs no change (uses this.manifest.id dynamically) Co-Authored-By: Claude Opus 4.8 --- CLAUDE.md | 18 +++++++++--------- README.md | 24 ++++++++++++------------ docs/DEVCONFLICTS.md | 2 +- manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 225a3bb..6dbda1c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -144,7 +144,7 @@ Saving flow strips secrets out of `data.json` and writes them to `secrets.json.n [src/secrets.ts](src/secrets.ts) supports two encryption modes for API keys, file-wide (not per-key). There is no unencrypted at-rest option. -- **`secretStorage`** — Obsidian's first-party `app.secretStorage` (GA 1.11.4): a shared, cross-plugin, OS-encrypted store (Keychain/DPAPI/libsecret under the hood, the same backend Electron `safeStorage` used). **Default/preferred when available.** Values live in Obsidian's store, NOT in `secrets.json.nosync` (the on-disk envelope in this mode is just `{ version, mode: 'secretStorage', keys: {} }` to record the mode). Keys are namespaced with `manifest.id` (`rewrite-plugin:profile:desktop:transcription`) because the store is shared across plugins. The API is reached via a narrow `SecretStorageLike` interface + cast (the installed typings predate it) that normalizes `getSecret`/`get`, `setSecret`/`set`, `listSecrets`/`list`, and `deleteSecret`/`removeSecret`/`delete` (falling back to `setSecret(id, '')` when no delete exists); each method may be sync or async, so callers `await`. **Availability** is a round-trip self-test (`setSecret` a sentinel, `getSecret`, compare, clean up) cached for the session — a device with no working OS secret store (e.g. Linux without a keyring) reads unavailable and falls back to passphrase. `warmSecretStorage(plugin)` runs the probe once in `onload` BEFORE `getEncryptionStatus`, so the synchronous `defaultEnvelope()` can prefer `secretStorage` on first run. **Sync caveat:** because the store can sync across devices via Obsidian Sync, keys in this mode are not bound to one device (unlike the `.nosync` file); this is surfaced in the settings mode description. +- **`secretStorage`** — Obsidian's first-party `app.secretStorage` (GA 1.11.4): a shared, cross-plugin, OS-encrypted store (Keychain/DPAPI/libsecret under the hood, the same backend Electron `safeStorage` used). **Default/preferred when available.** Values live in Obsidian's store, NOT in `secrets.json.nosync` (the on-disk envelope in this mode is just `{ version, mode: 'secretStorage', keys: {} }` to record the mode). Keys are namespaced with `manifest.id` (`rewrite-voice-notes:profile:desktop:transcription`) because the store is shared across plugins. The API is reached via a narrow `SecretStorageLike` interface + cast (the installed typings predate it) that normalizes `getSecret`/`get`, `setSecret`/`set`, `listSecrets`/`list`, and `deleteSecret`/`removeSecret`/`delete` (falling back to `setSecret(id, '')` when no delete exists); each method may be sync or async, so callers `await`. **Availability** is a round-trip self-test (`setSecret` a sentinel, `getSecret`, compare, clean up) cached for the session — a device with no working OS secret store (e.g. Linux without a keyring) reads unavailable and falls back to passphrase. `warmSecretStorage(plugin)` runs the probe once in `onload` BEFORE `getEncryptionStatus`, so the synchronous `defaultEnvelope()` can prefer `secretStorage` on first run. **Sync caveat:** because the store can sync across devices via Obsidian Sync, keys in this mode are not bound to one device (unlike the `.nosync` file); this is surfaced in the settings mode description. - **`passphrase`** — WebCrypto AES-GCM-256 with a key derived from a user-supplied passphrase. The KDF is **Argon2id** by default (via `hash-wasm`, `m = 32 MiB`, `t = 3`, `p = 1`, 32-byte output), with **PBKDF2-SHA256** (600,000 iterations) as the fallback when a device can't run Argon2id. The chosen algorithm + params live in the envelope `kdf` (see below). Each value is stored as `.` (12-byte random IV per value) in `secrets.json.nosync`. A `verifier` field stores an encryption of `VERIFIER_PLAINTEXT` so unlock can validate the passphrase without decrypting user keys. Works on every platform including mobile and Linux-without-keyring; the always-available fallback when `secretStorage` is not. **Entropy gate:** `changeEncryptionMode`/`changePassphrase` reject a passphrase scoring below `MIN_PASSPHRASE_SCORE` (3 of 4) via [src/passphrase-strength.ts](src/passphrase-strength.ts) (zxcvbn-ts wrapper); this is the hard, non-bypassable gate. The passphrase modal ([src/ui/passphrase-modal.ts](src/ui/passphrase-modal.ts)) surfaces a live strength meter and a **Generate** button (6-word EFF-wordlist diceware via [src/diceware.ts](src/diceware.ts) + [src/eff-large-wordlist.ts](src/eff-large-wordlist.ts)) on create/change flows (`enforceStrength: true`). **zxcvbn-ts is lazy-loaded:** its dictionaries are ~1.6 MB / ~140 ms to build, so [src/passphrase-strength.ts](src/passphrase-strength.ts) pulls the packages via dynamic `import()` (not a static top-level import), keeping them out of plugin-startup evaluation; `evaluatePassphrase`/`isPassphraseAcceptable` are therefore **async**, and the modal calls `warmPassphraseStrength()` on open so the first keystroke does not pay the build cost. The bytes stay in `main.js` (esbuild has no code-splitting here); only the parse/construct work is deferred. History note: an earlier hand-rolled `safeStorage` mode (direct Electron keychain) was REMOVED and superseded by `secretStorage`, which uses the same OS backend but is Obsidian-managed and cross-platform. No users had it (unpublished), so it was dropped outright with no migration; a stale `safeStorage` envelope parses as invalid and resets to default. @@ -258,12 +258,12 @@ UI surfaces, both a collapsed `
` so the frictionless path is untouched: Registered in [src/main.ts](src/main.ts): -- **`rewrite-plugin:open-modal`** ("Open"): opens the main modal with the last-used template selected. -- **`rewrite-plugin:quick-record`** ("Quick record (last used)"): starts a recording immediately with a floating mini-UI (no modal). Second press toggles to Stop. Template = `pickQuickRecordTemplate` (`lastUsedTemplateId` → `defaultTemplateId` → `templates[0]`). On unconfigured profile or capture-API unavailability, opens the modal instead. On post-capture pipeline error, opens the modal so the user can retry (the persisted audio file is the recovery path; the pipeline no longer copies the transcript to the clipboard). -- **`rewrite-plugin:quick-record-fixed`** ("Quick record (set template)"): same flow, but records with the template chosen in `GlobalSettings.quickRecordTemplateId` (Settings dropdown beside Default template). If that id is unset or no longer resolves, it shows a "choose a Quick record template in settings" `Notice` and does not start (no `templates[0]` fallback, unlike the last-used command). Both commands share `toggleQuickRecord(opts?)` and the single `activeQuickRecord` ref, so either one stops an in-flight recording. The floating UI shows a stop-hotkey hint ("Press or click Stop") read live from the command's binding; see the Gotcha on `hotkeyManager`. -- **`rewrite-plugin:process-text`** ("Process text with template"): runs a template over the active editor's selection (or the whole note body if there's no selection). Opens a template quick-picker, then runs the pipeline in the background with progress shown via `Notice`. Gates on LLM-only configuration; opens the main modal's setup card when not configured. Bails with a Notice when no Markdown editor is active. -- **`rewrite-plugin:reprocess-audio`** ("Reprocess audio file with template"): reruns the pipeline over an audio file already in the vault. Opens an `AudioFilePickerModal` (`FuzzySuggestModal` filtered to `AUDIO_EXTENSIONS` from [src/audio-persist.ts](src/audio-persist.ts)) then the template quick-picker, then calls `runAudioFilePipeline` in [src/ui/audio-source.ts](src/ui/audio-source.ts). The pipeline skips its `persist-audio` stage because the `audio` source variant carries a `sourcePath` (the existing vault path is reused for the `![[]]\n\n` prepend). Gates on the full voice profile (`isProfileConfigured`). -- **`rewrite-plugin:start-whisper-host`** / **`rewrite-plugin:stop-whisper-host`**: start or stop the local whisper.cpp server. Both use `checkCallback` so the palette only shows them on desktop, when the active profile's transcription provider is `whisper-local` (start) or when the host is currently `running` / `starting` (stop). Errors surface via `Notice`. Same code paths as the settings-tab Start/Stop button. +- **`rewrite-voice-notes:open-modal`** ("Open"): opens the main modal with the last-used template selected. +- **`rewrite-voice-notes:quick-record`** ("Quick record (last used)"): starts a recording immediately with a floating mini-UI (no modal). Second press toggles to Stop. Template = `pickQuickRecordTemplate` (`lastUsedTemplateId` → `defaultTemplateId` → `templates[0]`). On unconfigured profile or capture-API unavailability, opens the modal instead. On post-capture pipeline error, opens the modal so the user can retry (the persisted audio file is the recovery path; the pipeline no longer copies the transcript to the clipboard). +- **`rewrite-voice-notes:quick-record-fixed`** ("Quick record (set template)"): same flow, but records with the template chosen in `GlobalSettings.quickRecordTemplateId` (Settings dropdown beside Default template). If that id is unset or no longer resolves, it shows a "choose a Quick record template in settings" `Notice` and does not start (no `templates[0]` fallback, unlike the last-used command). Both commands share `toggleQuickRecord(opts?)` and the single `activeQuickRecord` ref, so either one stops an in-flight recording. The floating UI shows a stop-hotkey hint ("Press or click Stop") read live from the command's binding; see the Gotcha on `hotkeyManager`. +- **`rewrite-voice-notes:process-text`** ("Process text with template"): runs a template over the active editor's selection (or the whole note body if there's no selection). Opens a template quick-picker, then runs the pipeline in the background with progress shown via `Notice`. Gates on LLM-only configuration; opens the main modal's setup card when not configured. Bails with a Notice when no Markdown editor is active. +- **`rewrite-voice-notes:reprocess-audio`** ("Reprocess audio file with template"): reruns the pipeline over an audio file already in the vault. Opens an `AudioFilePickerModal` (`FuzzySuggestModal` filtered to `AUDIO_EXTENSIONS` from [src/audio-persist.ts](src/audio-persist.ts)) then the template quick-picker, then calls `runAudioFilePipeline` in [src/ui/audio-source.ts](src/ui/audio-source.ts). The pipeline skips its `persist-audio` stage because the `audio` source variant carries a `sourcePath` (the existing vault path is reused for the `![[]]\n\n` prepend). Gates on the full voice profile (`isProfileConfigured`). +- **`rewrite-voice-notes:start-whisper-host`** / **`rewrite-voice-notes:stop-whisper-host`**: start or stop the local whisper.cpp server. Both use `checkCallback` so the palette only shows them on desktop, when the active profile's transcription provider is `whisper-local` (start) or when the host is currently `running` / `starting` (stop). Errors surface via `Notice`. Same code paths as the settings-tab Start/Stop button. Plus an editor-menu item "ReWrite with template..." registered via `workspace.on('editor-menu', ...)` (and a second "Reprocess audio with template..." item that appears only when the cursor sits inside an `![[