Wispr Flow 1.6.1102 — how it works on Windows
Static analysis of the installed app files, 2026-10-11. Target: C:\Users\<user>\AppData\Local\WisprFlow\app-1.6.1102\. Background: wisprflow.ai home page and wisprflow.ai/privacy. No part of the app was run.
PROVEN I saw it in the shipped files. INFERRED The names or structure point to it, but I did not see the code do it. HIDDEN The answer is on Wispr's servers or in parts I could not open.
Secrets: the app bundle contains an API key literal for the inference host, a Supabase anonymous token, a PostHog key, and a Sentry DSN. This report names them but does not copy their values.
1. Where the app is and what I inspected
Wispr Flow is not in your Desktop folder. Your Desktop (C:\Users\<user>\Desktop, plus the OneDrive Desktop) has no Wispr file or shortcut. The app is installed by Squirrel at C:\Users\<user>\AppData\Local\WisprFlow\, with three versions: app-1.4.303, app-1.4.338, app-1.6.1102. I analyzed 1.6.1102, the newest. User data is in C:\Users\<user>\AppData\Roaming\Wispr Flow\ (not opened). The project folder holds only a README.txt with this prompt and a link to the video “How To Use REA To Clone Any Software In Minutes” by Eric Tech; I did not watch the video.
| Part | What it is | Size |
|---|---|---|
Wispr Flow.exe | Electron shell | 235,430,936 B |
resources\app.asar | All JavaScript: .webpack/main/index.js (9,771,276 B main process) and renderer windows hub, status, overlay, scratchpad, meeting_recorder and others. 902 entries, no node_modules folder (everything is bundled). | 133,651,466 B |
resources\Release\Wispr Flow Helper.exe | Native Windows helper. .NET compiled ahead of time (NativeAOT): it has System.Private.CoreLib and Wispr Flow Helper.dll strings but no CLR header. Signed by CN="Wispr AI, Inc.", San Francisco. | 20,228,632 B |
resources\migrations\*.js | 169 SQLite schema migrations for the local flow.sqlite history database | small |
resources\ax-inspect*.mjs | Developer tools that render saved accessibility (AX) context of past dictations from flow.sqlite into HTML | ~104 KB |
app.asar.unpacked\...\jabra-device-connector, node_sqlite3.node, crypt32-x64.node | Headset buttons (Jabra), local database, Windows certificate store | — |
How REA was used, and where it failed
Both REA MCP calls failed. analyze_javascript_application on the 133 MB ASAR sent no progress for 3,385 s and inspect_managed_artifact on the helper sent none for 3,666 s; Claude Code then stopped them on its MCP idle timeout. This is a harness limit (the MCP idle timeout), not a model error. inspect_managed_artifact was also the wrong tool: the helper has no CLR header, so it is not a managed PE.
The results below come from four sources: (1) I extracted the ASAR with a small Node script and read the bundles directly; (2) I scanned the helper's ASCII and UTF-16 strings; (3) I ran the REA CLI on the 22 extracted main-process bundles (result in section 6); (4) I ran REA with the Ghidra 12.1.4 engine on the helper and read its paste code as pseudocode (section 7).
Which REA engine did what: the JavaScript part needs no disassembler. REA reads it with its own analyzer, analyze_javascript_application. Only the helper is machine code. In the first pass, no disassembler was set up: rea doctor reported Ghidra, IDA and Hopper as missing_analysis_engine. I then installed Ghidra 12.1.4 and used it for section 7. IDA and Hopper are still not set up.
2. The pipeline, step by step
UpdateShortcuts, DictationStart, DictationStop to the helper; the helper contains SetWindowsHookExW and LowLevelKeyboardProc (a low-level keyboard hook) and sends KeypressEvent{eventType,key,index,inputType} back. Feature flags windows-default-shortcut and windows-caps-lock-shortcut exist. HIDDEN the default key combination is not a constant I found.hub renderer window calls navigator.mediaDevices.getUserMedia, builds an AudioContext at sampleRate: 16000 (hub@744855), and loads dist/recorderWorklet.js. That AudioWorklet buffers 640 samples per message (40 ms at 16 kHz); handleRecorderWorkletMessage (hub@747338) receives them.AudioEncoder, config {codec:"opus", sampleRate:16000, numberOfChannels:1, bitrate:64000}, 20 ms packets. If Opus is not supported, it sends WAV (float → Int16 PCM). Each packet goes to the main process over Electron IPC channel dictation:audioChunk; the end goes over dictation:recordingDone. Silent chunks before the first real audio are dropped.contents, before_text, selected_text, after_text) through the helper's GetTextBoxInfo; words read from the screen through accessibility (screenContentAx) and through OCR (screenContentOcr); VS Code variable names and file names; nearby text and an accessibility HTML snapshot (contentHtml). The helper contains IUIAutomation, IUIAutomationTextPattern, IUIAutomationValuePattern, IUIAutomationLegacyIAccessiblePattern. HIDDEN which OCR engine runs on Windows (no Windows.Media.Ocr string found).flow_api.v1.TranscriptionService/TranscribeStream (protobuf-ts over a grpc-node client, TLS; the bundle does not name the client library, but it contains makeBidiStreamRequest and grpc-node.retry_max_attempts_limit). Each Request carries some of: init (metadata + preferences), context, payload (Opus or WAV packets, max 18,000 per message), commit. Headers: authorization: Bearer <user token>, baseten-authorization: Api-Key <embedded key>, baseten-model-id, x-baseten-environment, disable-formatting. Keepalive pings and a heartbeat timeout run during the stream.State messages (raw_text, formatted_text, progress of transcriber and formatter, candidate_beams, audio_snr, audio_volume) and a final Result (output.plaintext, output.html, languages, confidence, response_time, transcript_origin, format_skip_reason). If the heartbeat times out or gRPC fails, main sends the whole recording in one unary gRPC call Transcribe (the code calls it the "Baseten HTTP fallback") and uses whichever answer arrives first.clipboard.readText()) and saves it, then sends PasteText{text, htmlText?, pasteId, captureKeystrokeTrace?} to the helper. The helper replies PasteOutcome{pasteId, success, timeElapsedMs} or PasteBlocked{reason}, and later DictatedTextVerification{pasteId, verdict, reason, focusedElementContents}. The helper contains PasteViaClipboard, ClipboardSnapshot, ClipboardRestore, ClipboardRestorePolicy, SetClipboardData, HTML Format, SendInput, ShiftInsert. PROVEN by Ghidra (section 7): the helper writes the text (and HTML Format when present) to the clipboard, presses Ctrl+V, or Shift+Insert in VS Code, Cursor and Windsurf when a flag is on, then restores the old clipboard after 500 ms, 3,000 ms or 5,000 ms, depending on the app.UpdateEditedText, contentObservationEndLastKeystroke) and the app stores edit distance and correction counts in flow.sqlite (migrations 20260409120000-add-correction-counts-to-history.js, 20260717120000-add-history-edit-distance.js).Servers named in the code
| Host | Role | Status |
|---|---|---|
inference.wisprflow.com | Default gRPC dictation target | PROVEN |
model-v31pl413.grpc.api.baseten.co | Same service hosted on Baseten (model id v31pl413) | PROVEN in the allowlist |
wispr-ai-prod--prod-grpc-server-flowgrpcserver.us-east.modal.direct | Same service hosted on Modal | PROVEN in the allowlist |
https://inference-info.wisprflow.com/dictation_url.txt | Remote switch: the app fetches it and changes target only to one of the three hosts above | PROVEN |
api.wisprflow.ai, api-east.wisprflow.ai (https and wss) | Account and app API; WebSocket constants exist | INFERRED not traced into dictation |
dodjkfqhwrzqjwkfnthl.supabase.co, us.i.posthog.com, ph.wisprflow.ai, Sentry | Backend data, product analytics, crash reports | PROVEN constants |
3. Proven, inferred, hidden
PROVEN from the app files
- Speech recognition does not run on your PC. All 902 ASAR entries contain no model file: 0 matches for
onnx|ggml|gguf|tflite|.pt|.bin|whisper. The only "model" match isGNAudio.DeviceApis.ModelBase.dll(Jabra headset library). The website agrees: "Transcription always happens in the cloud." - Formatting (punctuation, filler removal, style) is server side. The response has separate
raw_textandformatted_text, and timingsformat,external_asr_time,external_llm_time. The client only sends style settings:WritingStyle(FORMAL, CASUAL, VERY_CASUAL, EXCITED, GENZ),EditingStrength(VERBATIM, LIGHT, MEDIUM, HEAVY), dictionary, replacements, snippets. - The server can use more than one recognizer. The
Originenum lists MIXTURE, WHISPER, SCRIBE, VOXTRAL, QWEN;transcript_originacceptsinternal,external,canto_1.0,canto_1.0.1,canto_1.1. - Screen context goes to the server: text-box text before and after the cursor, selected text, accessibility words, OCR words, VS Code names, nearby text, AX HTML. The website calls this "Reads limited text from the app you're dictating into."
- The
Context.screenshotfield exists in the protocol, butgetContext()sets it to an emptyUint8Arrayin this build. So 1.6.1102 does not send screenshot pixels in this field. - Your first name, last name and email go in every
Init.preferences.user. - Local history
flow.sqlitecan keepaxTextandaxHtml(the accessibility text of the target app) per dictation (migrations20250113230038-add-axtext.js,20250218194609-add-ax-html.js). Theax-inspect-server.mjscomment says its pages "render local Slack/email content".
INFERRED from names and structure
- "SCRIBE" may be ElevenLabs Scribe and "VOXTRAL" may be Mistral Voxtral. Only the names support this.
- "canto" is the in-house model name (the code also links
https://wisprflow.ai/canto). - The labels of the helper functions in section 7 (for example "SimulatePaste"). NativeAOT removed the function names; I named each function from the log strings inside it.
- The meaning of the window messages
0x305and0x306in the helper (WM_RENDERFORMAT,WM_RENDERALLFORMATS). The numbers are in the code; the names are from my knowledge of Win32. - The helper string
rustcsuggests a Rust component inside the helper. I did not confirm it.
HIDDEN (on the server or not opened)
- Which ASR models and LLMs run, how they are chosen, the prompts, and how context is used.
- What the server stores. The privacy page says audio "is deleted as soon as it's transcribed, unless you've turned on Dictation cloud storage." The code cannot prove this.
- The live values of the helper's feature flags (
shift-insert,windows-key-up-simulation,disable-clipboard-restore). Ghidra shows both branches; the server sets the flags. - The helper's keyboard-hook procedure, the UIPI check, and how it reads text through UI Automation. I did not decompile these.
- The default hotkey and the OCR engine on Windows.
4. A small version you can build
This copies the proven mechanism in its smallest form: one Python file, one hotkey, one recognizer, the clipboard, and a Ctrl+V keystroke. It skips context, streaming and verification.
| Wispr part | Small version |
|---|---|
| Helper keyboard hook | keyboard package (hold F9 to talk) |
| getUserMedia 16 kHz mono | sounddevice, 16 kHz, mono, int16 |
| gRPC stream to cloud ASR | Local: faster-whisper on your CUDA GPU (you already use it in the intake pipeline). Cloud option: any speech-to-text API that takes a WAV file. |
| Server formatter | Optional: one LLM call with "clean up this dictation, keep meaning". Off by default. |
| Helper PasteText | pyperclip save → set text → keyboard.send("ctrl+v") → wait → restore. The current flowlite/flowlite.py adds the helper rules from section 7: the key combination for each app, the 50 ms key timing, the restore delay for each app, and no restore when the clipboard changed. |
# flowlite.py - hold F9, speak, release: text is pasted into the focused app.
# pip install faster-whisper sounddevice numpy keyboard pyperclip
import time, numpy as np, sounddevice as sd, keyboard, pyperclip
from faster_whisper import WhisperModel
RATE, KEY = 16000, "f9"
model = WhisperModel("base.en", device="cuda", compute_type="float16") # ponytail: "cpu"/"int8" if no GPU
def record():
chunks = []
with sd.InputStream(samplerate=RATE, channels=1, dtype="float32",
callback=lambda d, *_: chunks.append(d.copy())):
while keyboard.is_pressed(KEY):
time.sleep(0.02)
return np.concatenate(chunks)[:, 0] if chunks else np.zeros(0, np.float32)
def paste(text):
old = pyperclip.paste()
pyperclip.copy(text)
keyboard.send("ctrl+v")
time.sleep(0.15) # ponytail: fixed delay; slow apps may paste the old clipboard
pyperclip.copy(old) # ponytail: text only; images/HTML on the clipboard are lost
while True:
keyboard.wait(KEY)
audio = record()
if len(audio) < RATE // 4:
continue # under 0.25 s: ignore taps
segs, _ = model.transcribe(audio, vad_filter=True)
text = " ".join(s.text.strip() for s in segs).strip()
if text:
paste(text + " ")
Services it needs
- Local route: none. Your NVIDIA GPU with CUDA and the
base.enmodel (~150 MB download on first run). - Cloud route: one speech-to-text API key. Replace the
model.transcribeline with an HTTP POST of the WAV. - Optional cleanup: one LLM API key and a short prompt.
Known limits of this version
- Clipboard race: if the target app reads the clipboard after 150 ms, it pastes your old clipboard. Wispr verifies the field with UI Automation; this version does not.
- Restore keeps only text. Wispr snapshots all formats.
- No streaming: you wait for the full recording to transcribe. I did not measure latency on this machine.
- No context (no dictionary, no screen words), so names and jargon are less accurate.
- Elevated (admin) windows ignore keystrokes from a normal-level process.
Next steps, in order: (1) add a dictionary as faster-whisper initial_prompt; (2) add the LLM cleanup; (3) read the focused text box with uiautomation to verify the paste.
5. Not done
- Helper decompile: done for the paste path only (section 7). Not done for the keyboard hook, UI Automation reading, or the UIPI check.
- Renderer bundles: I scanned
hubfor audio code but did not run REA on the renderer windows. - Runtime check: I did not run the app or capture its network traffic, so nothing here is observed at runtime.
- The YouTube video in
README.txt: not watched.
6. REA CLI result
Command: npx -y rea-agents@6.3.0 analyze-javascript-application <22 extracted main bundles + package.json> --json. It finished with exit 0 in about 7 minutes and wrote a 505,348,651-byte Evidence file.
- Evidence ID:
ev_caa9799415d6a1d4bb802cb454813ff0c86184024019966bd09b79f7a4fe718b - Coverage: partial (graph and semantic graph). 21 JavaScript files parsed, 0 parse failures, 3,497,416 AST nodes visited, 819 findings.
- Electron summary: 11
BrowserWindowconstructions, 3 utility processes (0 entrypoints resolved), 0 preload entrypoints, 0 native addon bindings resolved. - IPC: 166 main-process handlers, 155 with literal channels, 11 with dynamic channel expressions. Renderer side: 0, because I gave REA only the main bundles.
- Semantic relations: 91,213 kept, 178,512 unknowns. Families: call-flow 4,651, data-flow 36,410, closure 44,551, event 111, timer 25, child-process 0, request 0.
Limit: the graph does not keep string values. The strings dictation:audioChunk, Wispr Flow Helper.exe and inference.wisprflow.com each have 0 matches in the Evidence file. REA also found 0 child-process and 0 request relations, but its own limitations say "Graph coverage is incomplete; unavailable facts remain explicit and must not be read as absence." So the helper launch (spawn) and the gRPC hosts in sections 2–3 come from my direct reading of the bundle text, not from REA.
7. The helper's paste code, read with Ghidra
The full account, with the reasons Ghidra did not run at the start and the consequences of this pass, is on the Ghidra pass page.
Setup. REA 6.3.0 with the Ghidra 12.1.4 engine (JDK 23.0.2) on Wispr Flow Helper.exe, 20,228,632 B, SHA-256 c58c61b106c83f7b77efc921b0e849bb7210154898504215f17306c1a6d6abe0. Ghidra found 56,803 functions and 14,969 strings (Evidence ev_63ff9ab0f8afa4dfd337adbc0aea57e776623c5a8e10cf35790ae26bea871bab). Its analysis took 1,310 s. REA's default Ghidra time limit is 330,000 ms, so the first run failed with provider_timeout. I raised REA_GHIDRA_STARTUP_TIMEOUT_MS to 10,800,000 and kept one REA session open with tools/rea_session.mjs.
Method. NativeAOT removes function names. REA's find_xrefs_to_name returned name_not_found for SendInput, SetClipboardData and SetWindowsHookExW, because the helper does not import them by name in its import table. The helper keeps its log messages as .NET string objects, and the code points at the object, which starts 12 bytes (0xC) before the characters. So I asked REA for references to string address − 12. That found the function behind each log message. Then I read each function as Ghidra pseudocode. tools/strs.py writes the string text next to each reference.
PROVEN paste rules (from pseudocode, constants and log strings)
- Two paste paths. First the helper tries a delayed-render clipboard: it registers the text without copying it, then presses the paste keys. When the target app reads the clipboard, Windows sends the helper message
0x305. Then the helper supplies the text as format13, format1or the HTML format, and logs"Clipboard read detected! Rendering delayed data...". This is how the helper knows that the paste reached the app. If that path fails, or the text has{{TAB}}/{{BREAK}}markers, it uses the regular path: save the clipboard, write the text, press the keys, restore. - Formats written.
Text, and alsoHTML FormatwhenhtmlTextis not empty. - Key combination. Ctrl+V is the key array
0x560011(VK0x11Ctrl, VK0x56V). Shift+Insert is0x2d0010(VK0x10Shift, VK0x2DInsert). The helper uses Shift+Insert only when the flagshift-insertis on and the focused process isCursor,WindsurforCode. Otherwise it uses Ctrl+V. - Key timing. It sends all key-downs in one
SendInputcall, waits 50 ms (Sleep(0x32)), then sends all key-ups. It retries a failed key-up once. - Waits. 50 ms after the keys (
"Pasted text in window, now sleeping for 50 ms for paste to complete"). In marked text: 150 ms before a Tab key, 50 ms between sections. On the regular path: 500 ms, then restore. - Restore delay per app (delayed path). The helper compares the start of the process name with comparison mode
5(in .NET that isOrdinalIgnoreCase, so case does not matter; the mode name is from my knowledge of .NET):- 5,000 ms:
vncviewer,tvnviewer,mstsc,msrdc,wfica32,vmware-view,rdcman,anydesk,teamviewer(remote-desktop apps). - 3,000 ms:
OUTLOOK,olk,WINWORD,WhatsApp. - 500 ms: all other apps.
"Clipboard not read within timeout -- paste likely failed"and reports a failure. - 5,000 ms:
- No restore when the clipboard changed after the paste (
"Clipboard changed since paste, skipping restoration") or when the flagdisable-clipboard-restoreis on. - What is saved and restored. The helper keeps two lists of clipboard formats. FormatsToRestore:
Text,UnicodeText,HTML Format, the bitmap and image formats, and the Windows clipboard-history markers (CanIncludeInClipboardHistory,CanUploadToCloudClipboard,ExcludeClipboardContentFromMonitorProcessing,Clipboard Viewer Ignore). FormatsToIgnore:FileDrop,Rich Text Format,Portable Document Format,WaveAudio,Csvand others. Any other format is logged as"Found unknown clipboard format". - Safety checks. If a modifier key is held, the helper logs failure cause 2. When the flag
windows-key-up-simulationis on, it releases a held Alt (VK0x12,0xA4,0xA5) or Windows key (VK0x5B,0x5C) with a "masking sequence" that uses VK0x11(Ctrl). If the target window runs at a higher privilege level, the paste fails with cause 4,UIPI_RESTRICTIONS. If the keys were already sent, it does not paste again on the regular path, so the text cannot appear twice. - Threading. The regular path runs on an STA thread. If the caller is not on one, the helper starts a new STA thread and waits for it.
INFERRED
- The function labels in the table below. Each label comes from the log strings inside that function, not from a symbol.
0x305/0x306=WM_RENDERFORMAT/WM_RENDERALLFORMATS, and formats13/1=CF_UNICODETEXT/CF_TEXT. These are standard Win32 values from my knowledge; the names are not in the binary.- The purpose of a third list in the format setup function: the apps
Cursor,slack,GitHubDesktop,Discord,Spotify,chrome,brave,Arc,msedge,Opera,MozillaFirefox,Notepad,Windsurf, and the siteschatgpt.com,github.com,google.com,claude.ai,gemini.google.com,replit.com,v0.dev. I did not trace where these lists are used.
STILL HIDDEN
- The live flag values. The code has both branches; the server sends the flags.
- The keyboard-hook procedure. Only
"Failed to create keyboard hook"andMultiKeybindServicewere found. Not decompiled. - The UIPI check (
FUN_140322010), the HTML conversion (FUN_1403274e0) and the UI Automation text reading. Not decompiled. - Second references to the clipboard-history markers, at
0x140329848and0x140329884. Not read.
Functions read (pseudocode in ghidra/pseudocode/)
| Label (inferred) | Ghidra function | REA Evidence ID |
|---|---|---|
| PasteText | FUN_140325210 | ev_dcadbc2da87cdcb9accca51a40f30008d8d196253852aa77184a1638cccaa035 |
| PasteTextInternal | FUN_140325ec0 | ev_5c80d4f5a8f7c23b962cff65491a5e63fb84f67b399c62c0720dee817050a0f8 |
| PasteRegular | FUN_1403259d0 | ev_3107c101e2232fe2554dfcc240e802a5558dd60f3fea6860fb11c20c9e2487af |
| SimulatePaste | FUN_140324cc0 | ev_1e82ad9e36f4d70d5a77d3d00242672b8fbf172a0052f21f82ca4582d4d60620 |
| SendMultipleKeyEvents | FUN_140322420 | ev_e4cf49c4216da88aec83106d51c761f9adf73d1e071f9e55c2eb2a2eabccbe23 |
| SendMaskingSequence | FUN_140323170 | ev_0b7f777d818d6fe2903427fc9f6443018cd48532f5ee8e191b008ddb6fa75d70 |
| SimulateTab | FUN_140325720 | ev_fa8232b713df8387028f5ca726685cdb5e1d1f876825cd623a26474b0259991f |
| Sleep | FUN_14016a3e0 | ev_a6c8afed1703f254b9ac1f108d1612d31b0bbea3614d471ff39f8ba8f9b4c19c |
| CopyWithFormatting | FUN_140326e60 | ev_8dc21b58ac38790f87852027f56292372a67e0787af469efc26e6cb35c2c6395 |
| RestoreAfterPaste | FUN_140326b76 | ev_d010683ff6d1d581c03156211d5146324e007f004bfeac7bb7dd8dbf8ed81311 |
| RestoreDelayMs | FUN_140318380 | ev_0a153dd50f3f4cdf8c2a0a51144ffa243c9091b31399482590a78bb3e1207250 |
| FailureTimeoutMs | FUN_1403183f0 | ev_9caad8a7122a8f59c1e9718e8fc9db0387bf82c6e5ed019983639c6bfd572b2d |
| ProcessPrefixMatch | FUN_140318320 | ev_db2a095a44aa4e31510f8a32a5da2e0f271ffb1094e32c6cb7e7a8ce5e071893 |
| RestoreDelayAppLists | FUN_140318450 | ev_953a27683c90b4a9fa3f3796ac1966cbea44565e888a45efead90f64fc84ca01 |
| ForegroundAppInit | FUN_140409240 | ev_62474b466f60f64b3f6de80945d4f4bc7ac6681ebda6c61d262fa9dad7b55600 |
| CapturePasteTarget | FUN_1403196b0 | ev_2e36ae139aa1308f38ee98ac2eb7b38e5ccdb8aa4dceff278c8a3dfa210f2e30 |
| LoadDelayedClipboard | FUN_1403186e0 | ev_d0d08a755fa52ab537fdde28e664d4cfeff4d52b61ce876f16b8ad5c18893605 |
| DelayedRenderWndProc | FUN_140319890 | ev_485e2d7d7c324d5656a3de994bd8d7f1e2c7e2303b167b8d827e1a5150bf1198 |
| ScheduleRestore | FUN_140319cf0 | ev_0d0ff5eb9ff59fc4ce85c2e039072540d5ff7d2949101ee95163098ed30f76d3 |
| ClipboardReadTimeout | FUN_140411990 | ev_f382f1ce023ca79de30a20a4f5905b4c72e3ccc5932d5f581d63a3ef559b6545 |
| ClipboardFormatLists | FUN_140327960 | ev_7ccd18a16a303acd80a19274017a1575d560de2869a3ac246a297bf4c2fa9aaf |
Evidence locations
Offsets are character positions in the extracted files under scratchpad\asar_out\.webpack\.
| Claim | File @ offset |
|---|---|
| Recorder worklet, 640-sample chunks | renderer/dist/recorderWorklet.js |
| 16 kHz, Opus 64 kbps config | renderer/hub/index.js@665196 |
| Dictation audio graph | renderer/hub/index.js@744855 |
Opus encoder → dictation:audioChunk | renderer/hub/index.js@758524, channel name main/index.js@2692782 |
| Endpoint constants | main/index.js@2632015 |
Dictation host allowlist, dictation_url.txt | main/index.js@3589039 |
TranscriptionService definition | main/index.js@3692633 |
| gRPC client, headers, context, fallback | main/index.js 3,694,000–3,740,000 |
| Helper spawn (stdio pipes, fd 3 IPC) | main/index.js@4435889 |
Clipboard save + PasteText | main/index.js@4465164 |
Helper JSON schema (PasteTextPayload, TextBoxInfoPayload) | main/index.js ~2,492,000 |