agentreceipt-voice 0.1.0 → 0.2.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.
Files changed (4) hide show
  1. package/README.md +153 -20
  2. package/bin/cli.js +3 -1
  3. package/package.json +35 -9
  4. package/src/cli.js +162 -5
package/README.md CHANGED
@@ -1,39 +1,172 @@
1
1
  # agentreceipt-voice
2
2
 
3
- A small toolkit for people who dictate a lot. One install, four tools, each also available as its own package.
3
+ **One package for people who dictate.** It sets up your voice-dictation app with ready-made shortcuts, checks your import files before the app silently skips rows, and builds a custom dictionary from your own code.
4
4
 
5
5
  ```sh
6
- npx agentreceipt-voice packs export developer # ready-made snippets and dictionary words
7
- npx agentreceipt-voice check my-dictionary.csv # see what the app would skip, before you import
8
- npx agentreceipt-voice vocab ./my-repo # build a dictionary from the names in your code
9
- npx agentreceipt-voice snippets init # keep snippets as files in git
6
+ npx agentreceipt-voice
10
7
  ```
11
8
 
12
- | Command | What it does | Also available as |
9
+ That is the whole install. It opens a short menu, asks what you want, and does it for you.
10
+
11
+ > **Unofficial.** This is not made by, or affiliated with, any dictation app. Its files follow the bulk-import format that Wispr Flow's help centre describes (a dictionary CSV and a snippets JSON). It has not been tested inside the app itself, so look at the app's own import preview before you confirm.
12
+
13
+ ---
14
+
15
+ ## Which package do I install?
16
+
17
+ You will see five packages on npm with similar names. **You only need this one.**
18
+
19
+ | Package | What it is | Do I install it? |
13
20
  |---|---|---|
14
- | `check <file>` | Checks a dictionary CSV or a snippets JSON and lists every row the app would silently skip | [agentreceipt-voice-check](https://www.npmjs.com/package/agentreceipt-voice-check) |
15
- | `packs list` / `packs export` | Writes ready-made snippet and dictionary packs for developers, founders, support and sales | [agentreceipt-voice-packs](https://www.npmjs.com/package/agentreceipt-voice-packs) |
16
- | `snippets init` / `snippets build` | Keeps your snippets and dictionary as plain files in a repo, and builds the import files | [agentreceipt-voice-snippets](https://www.npmjs.com/package/agentreceipt-voice-snippets) |
17
- | `vocab [folder]` | Scans a repo for package names and identifiers dictation gets wrong, and writes a dictionary CSV | [agentreceipt-voice-vocab](https://www.npmjs.com/package/agentreceipt-voice-vocab) |
21
+ | **`agentreceipt-voice`** (this one) | Everything, in one place, with a guided menu | **Yes. This is the only one.** |
22
+ | `agentreceipt-voice-check` | Just the file checker | No. Already included here. |
23
+ | `agentreceipt-voice-packs` | Just the ready-made packs | No. Already included here. |
24
+ | `agentreceipt-voice-snippets` | Just "snippets as code" | No. Already included here. |
25
+ | `agentreceipt-voice-vocab` | Just the dictionary builder | No. Already included here. |
18
26
 
19
- Everything runs on your computer. Nothing is uploaded.
27
+ The four smaller packages exist for developers who want to use one tool inside their own code without the rest. When you install `agentreceipt-voice`, npm brings the other four with it automatically. You never type their names.
20
28
 
21
- ## Good to know
29
+ ---
30
+
31
+ ## Start here (about a minute)
32
+
33
+ Open a terminal (needs [Node.js](https://nodejs.org) 18 or newer) and run:
34
+
35
+ ```sh
36
+ npx agentreceipt-voice
37
+ ```
38
+
39
+ You will see:
40
+
41
+ ```
42
+ What do you want to do?
43
+
44
+ 1 Get ready-made voice shortcuts (packs)
45
+ 2 Check a file before I import it
46
+ 3 Build a dictionary from my code
47
+ 4 Keep my shortcuts as files in git
48
+ q Quit
49
+ ```
50
+
51
+ Type a number and press Enter. It asks one or two simple questions (press Enter to accept the suggested answer), does the job, checks the result, and tells you the next click to make in your dictation app.
52
+
53
+ **Install it for good (optional).** If you use it often:
54
+
55
+ ```sh
56
+ npm install -g agentreceipt-voice
57
+ voice-kit
58
+ ```
59
+
60
+ `voice-kit` is a short name for the same command. (With `npx` you type the full `agentreceipt-voice`; the short name works once it is installed.)
61
+
62
+ ---
63
+
64
+ ## What each option does, step by step
65
+
66
+ ### 1. Ready-made voice shortcuts
67
+
68
+ Shortcuts let you say a short phrase and get a whole block of text. Four packs are included:
69
+
70
+ | Pack | Shortcuts | Examples |
71
+ |---|---|---|
72
+ | `developer` | 8, plus 43 dictionary words | `insert pr checklist`, `insert bug report`, `insert daily standup`, `insert ai change review` |
73
+ | `founder` | 8 | `insert investor update`, `insert warm intro`, `insert polite decline` |
74
+ | `support` | 7 | `insert need more info`, `insert refund processed`, `insert apology` |
75
+ | `sales` | 8 | `insert discovery questions`, `insert demo follow up`, `insert pricing reply` |
22
76
 
23
- - **Unofficial.** This is not made by, or affiliated with, any dictation app. The files follow the bulk-import format that Wispr Flow's help centre describes: a dictionary CSV with no header row (one column, or `misspelling,correction`), and a snippets JSON array of `{ "name", "text" }`, with at most 1,000 entries and 3 MB per file. It has not been tested inside the app itself, so run `check` and look at the app's own import preview before you confirm.
24
- - **Triggers start with "insert".** Dictation apps match triggers as whole words, so a plain phrase like "bug report" would expand in the middle of normal speech. "insert bug report" will not.
25
- - If the app changes its import rules, `check` will need to change too.
77
+ 1. Run `npx agentreceipt-voice` and choose **1**.
78
+ 2. Pick a pack (a number, a name, or `a` for all of them) and a folder to write the files in.
79
+ 3. It writes the files, checks them, and prints what to do next.
80
+ 4. In your dictation app on a **Mac or Windows** computer:
81
+ - open **Snippets**, click **Import** (top right, next to *Add new*), choose the `.json` file, and confirm;
82
+ - open **Dictionary**, click **Import**, and choose each `.csv` file (the developer pack has two).
83
+ 5. Click into any text box and say, for example, "insert pr checklist".
84
+
85
+ Every shortcut starts with the word **insert**. Dictation apps match shortcuts as whole words, so a plain phrase like "bug report" would expand in the middle of ordinary speech. "insert bug report" will not. The text has `[brackets]` where you fill in details after it appears.
86
+
87
+ ### 2. Check a file before you import it
88
+
89
+ Dictation apps skip bad rows without telling you. This tells you first.
90
+
91
+ 1. Choose **2** and give it the path to your file (a `.csv` dictionary or a `.json` snippets file; you can drag the file into the terminal).
92
+ 2. It lists every problem, for example:
93
+
94
+ ```
95
+ my-words.csv: 41 dictionary entries will import
96
+ SKIPPED line 7: more than two non-empty columns (quote fields that contain commas)
97
+ REPEAT line 19: "Vercel" already appears on line 3 (only the first is kept)
98
+ NOTE line 1 looks like a header row. The app imports it as an entry, so remove it.
99
+ 1 skipped. Fix these before importing.
100
+ ```
101
+
102
+ 3. Fix the file and run it again until it says **OK: nothing will be skipped**.
103
+
104
+ What it checks: no header row, one or two columns, quoting, 60 characters for a word or trigger, 4,000 for the text, repeats ignoring capitals, at most 1,000 entries and 3 MB per file.
105
+
106
+ ### 3. Build a dictionary from your code
107
+
108
+ If your app keeps mishearing names such as `useAuthSession`, `drizzle-orm` or `make_receipt_id`, this teaches it.
109
+
110
+ 1. Choose **3**, then pick the code folder to scan and where to save the list (default `words.csv`).
111
+ 2. It reads your code **on your computer only. Nothing is uploaded.** It collects package names and identifiers written in camelCase, PascalCase or snake_case, most used first, and skips `node_modules`, build folders and hidden folders.
112
+ 3. Open `words.csv` and delete any names you do not want, then in your app go to **Dictionary → Import** and choose it.
113
+
114
+ ### 4. Keep your shortcuts as files in git (for teams)
115
+
116
+ Good when a team shares shortcuts and wants changes reviewed like code.
117
+
118
+ 1. Choose **4**. The first time, it creates a starter folder (`voice-snippets`) with an example shortcut and a `dictionary.txt`.
119
+ 2. Edit it: one `.md` file per shortcut (the file name is the trigger, or write `trigger: insert my thing` at the top), and one word per line in `dictionary.txt`, or `what the app wrote => what you meant`.
120
+ 3. Run the menu again and choose **4** to build the import files. It **refuses to write anything the app would partly skip**, and tells you why.
121
+ 4. Import `snippets.json` and the `.csv` files as above.
122
+
123
+ ---
124
+
125
+ ## Prefer typing commands?
126
+
127
+ Everything in the menu is also a command, which is useful in scripts:
128
+
129
+ ```sh
130
+ npx agentreceipt-voice packs list
131
+ npx agentreceipt-voice packs export developer -o voice-packs
132
+ npx agentreceipt-voice check my-words.csv voice-packs/developer.snippets.json
133
+ npx agentreceipt-voice vocab ./my-repo -o words.csv --max 300
134
+ npx agentreceipt-voice snippets init voice-snippets
135
+ npx agentreceipt-voice snippets build voice-snippets -o voice-out
136
+ npx agentreceipt-voice --help
137
+ ```
138
+
139
+ `check` exits with `0` when everything is fine, `1` when something would be skipped, and `2` when it could not read a file, so it can fail a build.
140
+
141
+ ---
142
+
143
+ ## If something goes wrong
144
+
145
+ | What you see | What to do |
146
+ |---|---|
147
+ | `command not found: voice-kit` | Use `npx agentreceipt-voice` instead, or install it first with `npm install -g agentreceipt-voice`. |
148
+ | `npx` says the package is not found | Check your internet connection, and that Node.js is version 18 or newer (`node --version`). |
149
+ | No **Import** button in your dictation app | Import is on the Mac and Windows apps only, not on phones. Update the app, then look again. |
150
+ | The app imported fewer entries than the checker said | Entries that already exist in your app are skipped as duplicates. Look at the app's import preview. |
151
+ | Triggers expand while you talk normally | Use the `insert ...` triggers from the packs, or add `insert` before your own triggers. |
152
+ | The menu does not open when you run it from a script | It only opens for a person at a keyboard. In scripts, give a command (`check`, `packs`, `snippets`, `vocab`). |
153
+
154
+ ---
155
+
156
+ ## Good to know
26
157
 
27
- ## Use it from code
158
+ - **Local only.** Nothing you scan, generate or check is sent anywhere.
159
+ - **If your app changes its import rules,** the checker needs updating too.
160
+ - **Use it from code:**
28
161
 
29
162
  ```js
30
163
  import { check, packs } from 'agentreceipt-voice';
31
164
 
32
- const result = check.checkDictionary('Supabase\nsuper base,Supabase\n');
33
- console.log(result.ok, result.valid.length);
165
+ const r = check.checkDictionary('Supabase\nsuper base,Supabase\n');
166
+ console.log(r.ok, r.valid.length);
34
167
  console.log(packs.listPacks());
35
168
  ```
36
169
 
37
- ## Licence
170
+ ## Licence and feedback
38
171
 
39
- MIT. Made by the team behind [AgentReceipt](https://www.agentreceipt.online), which records what AI coding agents change in your code.
172
+ MIT. Made by the team behind [AgentReceipt](https://www.agentreceipt.online), which records what AI coding agents change in your code. Questions or bugs: founders@agentreceipt.online.
package/bin/cli.js CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { run } from '../src/cli.js';
3
3
 
4
- process.exitCode = await run(process.argv.slice(2));
4
+ // With no command, a person at a keyboard gets the guided menu; a script gets the help text.
5
+ const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
6
+ process.exitCode = await run(process.argv.slice(2), undefined, { interactive });
package/package.json CHANGED
@@ -1,27 +1,53 @@
1
1
  {
2
2
  "name": "agentreceipt-voice",
3
- "version": "0.1.0",
4
- "description": "A toolkit for voice dictation power users: check import files, keep snippets as code, install ready-made snippet packs, and build a dictionary from your repo. One command, four tools. Unofficial; works with the Wispr Flow import format.",
5
- "keywords": ["dictation", "voice", "snippets", "dictionary", "wispr-flow", "speech-to-text", "voice-typing", "developer-tools", "toolkit"],
3
+ "version": "0.2.0",
4
+ "description": "One package for voice dictation power users: a guided setup, ready-made snippet packs, an import-file checker, a dictionary builder for your code, and snippets as code. Includes everything; nothing else to install. Unofficial; works with the Wispr Flow import format.",
5
+ "keywords": [
6
+ "dictation",
7
+ "voice",
8
+ "snippets",
9
+ "dictionary",
10
+ "wispr-flow",
11
+ "speech-to-text",
12
+ "voice-typing",
13
+ "developer-tools",
14
+ "toolkit"
15
+ ],
6
16
  "license": "MIT",
7
17
  "author": "Ayush Pund <founders@agentreceipt.online> (https://www.agentreceipt.online)",
8
18
  "homepage": "https://www.agentreceipt.online",
9
- "bugs": { "email": "founders@agentreceipt.online" },
19
+ "bugs": {
20
+ "email": "founders@agentreceipt.online"
21
+ },
10
22
  "type": "module",
11
23
  "main": "src/index.js",
12
24
  "exports": {
13
25
  ".": "./src/index.js",
14
26
  "./package.json": "./package.json"
15
27
  },
16
- "bin": { "agentreceipt-voice": "bin/cli.js" },
17
- "files": ["bin", "src", "README.md", "LICENSE"],
28
+ "bin": {
29
+ "agentreceipt-voice": "bin/cli.js",
30
+ "voice-kit": "bin/cli.js"
31
+ },
32
+ "files": [
33
+ "bin",
34
+ "src",
35
+ "README.md",
36
+ "LICENSE"
37
+ ],
18
38
  "dependencies": {
19
39
  "agentreceipt-voice-check": "^0.1.0",
20
40
  "agentreceipt-voice-packs": "^0.1.0",
21
41
  "agentreceipt-voice-snippets": "^0.1.0",
22
42
  "agentreceipt-voice-vocab": "^0.1.0"
23
43
  },
24
- "engines": { "node": ">=18" },
25
- "publishConfig": { "access": "public" },
26
- "scripts": { "test": "node --test" }
44
+ "engines": {
45
+ "node": ">=18"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "scripts": {
51
+ "test": "node --test"
52
+ }
27
53
  }
package/src/cli.js CHANGED
@@ -1,29 +1,186 @@
1
- import { readFileSync } from 'node:fs';
1
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { createInterface } from 'node:readline/promises';
2
4
  import { run as check } from 'agentreceipt-voice-check/cli';
5
+ import { getPack, listPacks } from 'agentreceipt-voice-packs';
3
6
  import { run as packs } from 'agentreceipt-voice-packs/cli';
4
7
  import { run as snippets } from 'agentreceipt-voice-snippets/cli';
5
8
  import { run as vocab } from 'agentreceipt-voice-vocab/cli';
6
9
 
7
- const USAGE = `agentreceipt-voice <command> [options]
10
+ const USAGE = `agentreceipt-voice [command] [options]
11
+
12
+ (no command) a guided menu that asks what you want and does it for you
13
+ setup the same guided menu, started on purpose
8
14
 
9
15
  check <file>... check a dictionary CSV or snippets JSON before you import it
10
16
  packs list | export ready-made snippet and dictionary packs (developer, founder, support, sales)
11
17
  snippets init | build keep snippets as files in git and build the import files
12
18
  vocab [folder] build a dictionary CSV from the names in a code repository
13
19
 
14
- Each command is also its own package: agentreceipt-voice-check, -packs, -snippets and -vocab.
20
+ Short name: voice-kit does the same as agentreceipt-voice.
21
+ Each command is also its own package (agentreceipt-voice-check, -packs, -snippets, -vocab), but you do
22
+ not need them: this one package already includes all four.
15
23
  Run a command with --help for its options. Unofficial: not made by or affiliated with any dictation app.`;
16
24
 
17
25
  const COMMANDS = { check, packs, snippets, vocab };
26
+ const defaultIo = { out: (s) => process.stdout.write(s), err: (s) => process.stderr.write(s) };
27
+
28
+ const answer = async (ask, question, fallback) => {
29
+ const a = (await ask(question)).trim();
30
+ return a === '' ? fallback : a;
31
+ };
32
+ const unquote = (s) => s.trim().replace(/^["']|["']$/g, '');
33
+
34
+ /** Files in a folder that belong to the given pack (or all packs). */
35
+ function packFilesIn(dir, chosen) {
36
+ return readdirSync(dir)
37
+ .filter((f) => /\.(json|csv)$/.test(f) && (chosen === 'all' || f.startsWith(`${chosen}.`)))
38
+ .sort()
39
+ .map((f) => join(dir, f));
40
+ }
41
+
42
+ /** The guided menu. `ask` takes a question and gives back what the person typed. */
43
+ export async function guided(io, ask) {
44
+ io.out(`\nWhat do you want to do?
45
+
46
+ 1 Get ready-made voice shortcuts (packs)
47
+ 2 Check a file before I import it
48
+ 3 Build a dictionary from my code
49
+ 4 Keep my shortcuts as files in git
50
+ q Quit
51
+
52
+ `);
53
+ const pick = (await ask('Type 1, 2, 3, 4 or q, then press Enter: ')).trim().toLowerCase();
54
+ if (pick === 'q' || pick === 'quit') return 0;
55
+
56
+ if (pick === '1') {
57
+ const list = listPacks();
58
+ io.out(`\nWhich pack?\n\n${list.map((p, i) => ` ${i + 1} ${p.id.padEnd(10)} ${p.description}`).join('\n')}\n a all of them\n\n`);
59
+ const raw = (await answer(ask, 'Type a number, a name, or a (default: 1): ', '1')).toLowerCase();
60
+ const chosen = raw === 'a' || raw === 'all' ? 'all' : (list[Number(raw) - 1]?.id ?? raw);
61
+ const out = unquote(await answer(ask, 'Folder to write the files in (default: voice-packs): ', 'voice-packs'));
62
+ const code = await packs(['export', chosen, '-o', out], io);
63
+ if (code !== 0) return code;
64
+ io.out('\nChecking the files the way your app would...\n');
65
+ const result = await check(packFilesIn(out, chosen), io);
66
+ let example = 'insert pr checklist';
67
+ try {
68
+ example = getPack(chosen === 'all' ? 'developer' : chosen).snippets[0].name;
69
+ } catch {
70
+ /* keep the default */
71
+ }
72
+ io.out(`
73
+ Next, open your dictation app on a Mac or Windows computer:
74
+ Snippets -> Import -> choose the .json file(s) in ${out}
75
+ Dictionary -> Import -> choose the .csv file(s) in ${out} (the developer pack has two)
76
+ Then say "${example}" in any text box to try it.
77
+ `);
78
+ return result;
79
+ }
80
+
81
+ if (pick === '2') {
82
+ const file = unquote(await ask('Which file? (a .csv dictionary or a .json snippets file): '));
83
+ if (!file) {
84
+ io.err('No file given.\n');
85
+ return 2;
86
+ }
87
+ return check([file], io);
88
+ }
89
+
90
+ if (pick === '3') {
91
+ const dir = unquote(await answer(ask, 'Which code folder should I scan? (default: this folder): ', '.'));
92
+ const out = unquote(await answer(ask, 'Where should the dictionary file go? (default: words.csv): ', 'words.csv'));
93
+ const code = await vocab([dir, '-o', out], io);
94
+ if (code !== 0) return code;
95
+ io.out('\nChecking it the way your app would...\n');
96
+ const result = await check([out], io);
97
+ io.out(`\nNext: open your dictation app (Mac or Windows) -> Dictionary -> Import -> choose ${out}.\nOpen the file first and delete any names you do not want.\n`);
98
+ return result;
99
+ }
100
+
101
+ if (pick === '4') {
102
+ const dir = unquote(await answer(ask, 'Folder for your snippets (default: voice-snippets): ', 'voice-snippets'));
103
+ if (!existsSync(dir)) {
104
+ io.out(`\nThat folder does not exist yet, so I will create a starter one.\n`);
105
+ const code = await snippets(['init', dir], io);
106
+ io.out(`\nEdit the files in ${dir} (one .md file per shortcut), then run this again and choose 4 to build the import files.\n`);
107
+ return code;
108
+ }
109
+ const out = unquote(await answer(ask, 'Folder for the import files (default: voice-out): ', 'voice-out'));
110
+ return snippets(['build', dir, '-o', out], io);
111
+ }
112
+
113
+ io.err('Please type 1, 2, 3, 4 or q.\n');
114
+ return 2;
115
+ }
116
+
117
+ class EndOfInput extends Error {}
118
+
119
+ /**
120
+ * Reads typed answers one line at a time. Lines that arrive before a question is asked (pasted or piped
121
+ * input) are kept, not lost, and when input ends (Ctrl+D, or a script with too few answers) the pending
122
+ * question gets `null` instead of waiting forever.
123
+ */
124
+ function lineReader() {
125
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
126
+ const queue = [];
127
+ const waiters = [];
128
+ let closed = false;
129
+ rl.on('line', (line) => {
130
+ const w = waiters.shift();
131
+ if (w) w(line);
132
+ else queue.push(line);
133
+ });
134
+ rl.on('close', () => {
135
+ closed = true;
136
+ while (waiters.length) waiters.shift()(null);
137
+ });
138
+ return {
139
+ ask(question) {
140
+ process.stdout.write(question);
141
+ if (queue.length) return Promise.resolve(queue.shift());
142
+ if (closed) return Promise.resolve(null);
143
+ return new Promise((resolve) => waiters.push(resolve));
144
+ },
145
+ close: () => rl.close(),
146
+ };
147
+ }
148
+
149
+ async function withAsk(env, io, fn) {
150
+ if (env.ask) return fn(env.ask);
151
+ const reader = lineReader();
152
+ const ask = async (question) => {
153
+ const a = await reader.ask(question);
154
+ if (a === null) throw new EndOfInput();
155
+ return a;
156
+ };
157
+ try {
158
+ return await fn(ask);
159
+ } catch (e) {
160
+ if (e instanceof EndOfInput) {
161
+ io.err('\nNo answer was received, so I stopped. Run it again in a normal terminal window.\n');
162
+ return 1;
163
+ }
164
+ throw e;
165
+ } finally {
166
+ reader.close();
167
+ }
168
+ }
18
169
 
19
- export async function run(argv, io = { out: (s) => process.stdout.write(s), err: (s) => process.stderr.write(s) }) {
170
+ /**
171
+ * `env.interactive`: the command was started by a person at a keyboard, so with no command the guided menu opens.
172
+ * `env.ask`: supplies the answers instead of the keyboard (used by the tests).
173
+ */
174
+ export async function run(argv, io = defaultIo, env = {}) {
20
175
  const [cmd, ...rest] = argv;
21
176
  if (cmd === '--version' || cmd === '-v') {
22
177
  const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
23
178
  io.out(`${pkg.version}\n`);
24
179
  return 0;
25
180
  }
26
- if (!cmd || cmd === '--help' || cmd === '-h') return (cmd ? io.out : io.err)(`${USAGE}\n`), cmd ? 0 : 2;
181
+ if (cmd === '--help' || cmd === '-h') return io.out(`${USAGE}\n`), 0;
182
+ if (!cmd && !env.interactive) return io.err(`${USAGE}\n`), 2;
183
+ if (!cmd || cmd === 'setup' || cmd === 'start' || cmd === 'guide') return withAsk(env, io, (ask) => guided(io, ask));
27
184
  const fn = COMMANDS[cmd];
28
185
  if (!fn) return io.err(`Unknown command "${cmd}".\n${USAGE}\n`), 2;
29
186
  return fn(rest, io);