@holdyourvoice/hyv 2.8.6 → 2.8.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holdyourvoice/hyv",
3
- "version": "2.8.6",
3
+ "version": "2.8.8",
4
4
  "description": "Free local AI writing scan for cursor & claude. MCP server, 220+ pattern detection, voice profiles. npx @holdyourvoice/hyv welcome",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * postinstall-lib.js — testable agent copy helpers (used by postinstall.js)
2
+ * postinstall-lib.js — agent setup helpers (postinstall + doctor --fix-agents)
3
3
  */
4
4
  const fs = require('fs');
5
5
  const path = require('path');
@@ -31,22 +31,25 @@ function shouldUpgradeAgent(destFile, markerPath, pkgVersion) {
31
31
  }
32
32
  }
33
33
 
34
- function copyAgent(src, dest, markerPath, pkgVersion) {
34
+ function copyAgent(src, dest, markerPath, pkgVersion, transform) {
35
35
  if (!fs.existsSync(src)) return { copied: false, reason: 'missing-src' };
36
36
  if (!shouldUpgradeAgent(dest, markerPath, pkgVersion) && fs.existsSync(dest)) {
37
37
  return { copied: false, reason: 'up-to-date' };
38
38
  }
39
39
  fs.mkdirSync(path.dirname(dest), { recursive: true });
40
- fs.copyFileSync(src, dest);
40
+ const raw = fs.readFileSync(src, 'utf-8');
41
+ const content = typeof transform === 'function' ? transform(raw) : raw;
42
+ fs.writeFileSync(dest, content);
41
43
  return { copied: true, reason: fs.existsSync(dest) ? 'upgraded' : 'created' };
42
44
  }
43
45
 
44
46
  function writeAgentsMarker(hyvDir, pkgVersion) {
45
47
  const markerPath = path.join(hyvDir, 'agents-version.json');
46
- if (!fs.existsSync(hyvDir)) fs.mkdirSync(hyvDir, { recursive: true });
48
+ if (!fs.existsSync(hyvDir)) fs.mkdirSync(hyvDir, { recursive: true, mode: 0o700 });
47
49
  fs.writeFileSync(
48
50
  markerPath,
49
- JSON.stringify({ version: pkgVersion, updated_at: new Date().toISOString() }, null, 2)
51
+ JSON.stringify({ version: pkgVersion, updated_at: new Date().toISOString() }, null, 2),
52
+ { mode: 0o600 }
50
53
  );
51
54
  return markerPath;
52
55
  }
@@ -64,13 +67,229 @@ function hasCompletedOnboarding(hyvDir) {
64
67
  }
65
68
 
66
69
  function markOnboardingComplete(hyvDir, pkgVersion) {
67
- if (!fs.existsSync(hyvDir)) fs.mkdirSync(hyvDir, { recursive: true });
70
+ if (!fs.existsSync(hyvDir)) fs.mkdirSync(hyvDir, { recursive: true, mode: 0o700 });
68
71
  fs.writeFileSync(
69
72
  onboardingPath(hyvDir),
70
- JSON.stringify({ version: pkgVersion, completed_at: new Date().toISOString() }, null, 2)
73
+ JSON.stringify({ version: pkgVersion, completed_at: new Date().toISOString() }, null, 2),
74
+ { mode: 0o600 }
71
75
  );
72
76
  }
73
77
 
78
+ function resolveHyvMcpCommand(pkgDir) {
79
+ const entry = path.join(pkgDir, 'dist', 'index.js');
80
+ if (fs.existsSync(entry)) {
81
+ return { command: process.execPath, args: [entry, 'mcp'] };
82
+ }
83
+ return { command: 'hyv', args: ['mcp'] };
84
+ }
85
+
86
+ function mergeJsonConfigFile(configFile, mutator, { backup = true } = {}) {
87
+ fs.mkdirSync(path.dirname(configFile), { recursive: true });
88
+ let config = {};
89
+ if (fs.existsSync(configFile)) {
90
+ try {
91
+ config = JSON.parse(fs.readFileSync(configFile, 'utf-8'));
92
+ } catch (err) {
93
+ return { ok: false, reason: 'parse-error', error: err.message };
94
+ }
95
+ }
96
+ const next = mutator(config) || config;
97
+ if (backup && fs.existsSync(configFile)) {
98
+ try {
99
+ fs.copyFileSync(configFile, `${configFile}.hyv.bak`);
100
+ } catch {
101
+ // non-fatal
102
+ }
103
+ }
104
+ fs.writeFileSync(configFile, JSON.stringify(next, null, 2));
105
+ return { ok: true };
106
+ }
107
+
108
+ function toCursorMdc(content) {
109
+ if (content.startsWith('---')) return content;
110
+ return [
111
+ '---',
112
+ 'description: Hold Your Voice — scan and rewrite drafts for brand voice consistency',
113
+ 'alwaysApply: true',
114
+ '---',
115
+ '',
116
+ content.trim(),
117
+ '',
118
+ ].join('\n');
119
+ }
120
+
121
+ function toWindsurfRule(content) {
122
+ if (content.startsWith('---')) return content;
123
+ return ['---', 'trigger: always_on', '---', '', content.trim(), ''].join('\n');
124
+ }
125
+
126
+ function mergeAgentsMd(existing, addition, title) {
127
+ const marker = `<!-- hyv:${title} -->`;
128
+ if (existing.includes(marker)) {
129
+ const start = existing.indexOf(marker);
130
+ const end = existing.indexOf('<!-- /hyv -->', start);
131
+ if (end !== -1) {
132
+ return existing.slice(0, start) + marker + '\n' + addition.trim() + '\n<!-- /hyv -->\n' + existing.slice(end + '<!-- /hyv -->'.length);
133
+ }
134
+ }
135
+ const block = `${marker}\n${addition.trim()}\n<!-- /hyv -->\n`;
136
+ return existing.trim() ? `${existing.trim()}\n\n${block}` : block;
137
+ }
138
+
139
+ function claudeDesktopDir(home, isWin) {
140
+ if (isWin) return path.join(home, 'AppData', 'Roaming', 'Claude');
141
+ if (process.platform === 'linux') return path.join(home, '.config', 'Claude');
142
+ return path.join(home, 'Library', 'Application Support', 'Claude');
143
+ }
144
+
145
+ /**
146
+ * Configure MCP + agent instructions for detected IDEs.
147
+ * @returns {{ configured: string[], warnings: string[], pkgVersion: string }}
148
+ */
149
+ function setupAgents({ pkgDir, home = require('os').homedir(), quiet = false }) {
150
+ const configured = [];
151
+ const warnings = [];
152
+ const isWin = process.platform === 'win32';
153
+ const hyvDir = path.join(home, '.hyv');
154
+ const agentsMarker = path.join(hyvDir, 'agents-version.json');
155
+ const pkgVersion = readPkgVersion(pkgDir);
156
+ const mcpCmd = resolveHyvMcpCommand(pkgDir);
157
+ const autoConfigure = process.env.HYV_AUTO_CONFIGURE_AGENTS !== '0';
158
+
159
+ if (!autoConfigure) {
160
+ return { configured, warnings: ['HYV_AUTO_CONFIGURE_AGENTS=0 — skipped agent setup'], pkgVersion };
161
+ }
162
+
163
+ function installAgent(src, dest, transform) {
164
+ const result = copyAgent(src, dest, agentsMarker, pkgVersion, transform);
165
+ return result.copied;
166
+ }
167
+
168
+ function setupMcpJson(configFile, label) {
169
+ const result = mergeJsonConfigFile(configFile, (config) => {
170
+ if (!config.mcpServers) config.mcpServers = {};
171
+ if (!config.mcpServers.hyv) {
172
+ config.mcpServers.hyv = { command: mcpCmd.command, args: mcpCmd.args };
173
+ configured.push(`${label} mcp`);
174
+ }
175
+ return config;
176
+ });
177
+ if (!result.ok) warnings.push(`${label}: could not update MCP config (${result.reason})`);
178
+ return result.ok;
179
+ }
180
+
181
+ // Claude Desktop MCP
182
+ try {
183
+ const claudeDir = claudeDesktopDir(home, isWin);
184
+ const configFile = path.join(claudeDir, 'claude_desktop_config.json');
185
+ if (fs.existsSync(claudeDir) || autoConfigure) {
186
+ setupMcpJson(configFile, 'claude desktop');
187
+ }
188
+ } catch (err) {
189
+ warnings.push(`claude desktop: ${err.message}`);
190
+ }
191
+
192
+ // Cursor — global MCP + always-on rule (.mdc)
193
+ try {
194
+ const cursorDir = path.join(home, '.cursor');
195
+ fs.mkdirSync(cursorDir, { recursive: true });
196
+ const mcpFile = path.join(cursorDir, 'mcp.json');
197
+ setupMcpJson(mcpFile, 'cursor');
198
+ const rulesFile = path.join(cursorDir, 'rules', 'hyv.mdc');
199
+ const src = path.join(pkgDir, 'agents', 'cursor.md');
200
+ if (installAgent(src, rulesFile, toCursorMdc)) configured.push('cursor');
201
+ // legacy .md (upgrade path)
202
+ const legacy = path.join(cursorDir, 'rules', 'hyv.md');
203
+ if (fs.existsSync(legacy)) {
204
+ try { fs.unlinkSync(legacy); } catch { /* ignore */ }
205
+ }
206
+ } catch (err) {
207
+ warnings.push(`cursor: ${err.message}`);
208
+ }
209
+
210
+ // Claude Code — slash command + skill
211
+ try {
212
+ const cmdDir = path.join(home, '.claude', 'commands');
213
+ const skillDir = path.join(home, '.claude', 'skills', 'hold-your-voice');
214
+ fs.mkdirSync(cmdDir, { recursive: true });
215
+ fs.mkdirSync(skillDir, { recursive: true });
216
+ const cmdFile = path.join(cmdDir, 'hyv.md');
217
+ const skillFile = path.join(skillDir, 'SKILL.md');
218
+ const cmdSrc = path.join(pkgDir, 'agents', 'claude-code.md');
219
+ const skillSrc = path.join(pkgDir, 'skills', 'hold-your-voice', 'SKILL.md');
220
+ if (installAgent(cmdSrc, cmdFile)) configured.push('claude code');
221
+ if (fs.existsSync(skillSrc) && installAgent(skillSrc, skillFile)) configured.push('claude code skill');
222
+ } catch (err) {
223
+ warnings.push(`claude code: ${err.message}`);
224
+ }
225
+
226
+ // Windsurf — rules with frontmatter
227
+ try {
228
+ const wsDirs = [
229
+ isWin ? path.join(home, 'AppData', 'Roaming', 'Windsurf') : path.join(home, '.windsurf'),
230
+ isWin ? null : path.join(home, 'Library', 'Application Support', 'Windsurf'),
231
+ ].filter(Boolean);
232
+ const src = path.join(pkgDir, 'agents', 'windsurf.md');
233
+ for (const wsDir of wsDirs) {
234
+ fs.mkdirSync(wsDir, { recursive: true });
235
+ const rulesFile = path.join(wsDir, 'rules', 'hyv.md');
236
+ if (installAgent(src, rulesFile, toWindsurfRule)) {
237
+ configured.push('windsurf');
238
+ break;
239
+ }
240
+ }
241
+ } catch (err) {
242
+ warnings.push(`windsurf: ${err.message}`);
243
+ }
244
+
245
+ // Codex — ~/.codex/AGENTS.md
246
+ try {
247
+ const codexDir = path.join(home, '.codex');
248
+ fs.mkdirSync(codexDir, { recursive: true });
249
+ const agentsFile = path.join(codexDir, 'AGENTS.md');
250
+ const src = path.join(pkgDir, 'agents', 'codex.md');
251
+ let existing = '';
252
+ if (fs.existsSync(agentsFile)) existing = fs.readFileSync(agentsFile, 'utf-8');
253
+ const addition = fs.existsSync(src) ? fs.readFileSync(src, 'utf-8') : '';
254
+ if (addition && shouldUpgradeAgent(agentsFile, agentsMarker, pkgVersion)) {
255
+ const merged = mergeAgentsMd(existing, addition, 'hold-your-voice');
256
+ fs.writeFileSync(agentsFile, merged);
257
+ configured.push('codex');
258
+ } else if (!fs.existsSync(agentsFile) && fs.existsSync(src)) {
259
+ fs.writeFileSync(agentsFile, addition.trim() + '\n');
260
+ configured.push('codex');
261
+ }
262
+ } catch (err) {
263
+ warnings.push(`codex: ${err.message}`);
264
+ }
265
+
266
+ // Command Code skill
267
+ try {
268
+ const ccSkillDir = path.join(home, '.commandcode', 'skills', 'hyv');
269
+ fs.mkdirSync(ccSkillDir, { recursive: true });
270
+ const skillSrc = path.join(pkgDir, 'skills', 'hold-your-voice', 'SKILL.md');
271
+ const skillDest = path.join(ccSkillDir, 'SKILL.md');
272
+ if (fs.existsSync(skillSrc) && installAgent(skillSrc, skillDest)) configured.push('command code');
273
+ } catch (err) {
274
+ warnings.push(`command code: ${err.message}`);
275
+ }
276
+
277
+ // Generic reference copy
278
+ try {
279
+ const genericSrc = path.join(pkgDir, 'agents', 'generic.md');
280
+ const genericDest = path.join(hyvDir, 'agents', 'generic.md');
281
+ if (fs.existsSync(genericSrc)) installAgent(genericSrc, genericDest);
282
+ } catch {
283
+ // non-fatal
284
+ }
285
+
286
+ writeAgentsMarker(hyvDir, pkgVersion);
287
+ if (!quiet && warnings.length) {
288
+ for (const w of warnings) process.stderr.write(`[hyv postinstall] warning: ${w}\n`);
289
+ }
290
+ return { configured: [...new Set(configured)], warnings, pkgVersion };
291
+ }
292
+
74
293
  module.exports = {
75
294
  readPkgVersion,
76
295
  readAgentsMarker,
@@ -80,4 +299,10 @@ module.exports = {
80
299
  onboardingPath,
81
300
  hasCompletedOnboarding,
82
301
  markOnboardingComplete,
302
+ resolveHyvMcpCommand,
303
+ mergeJsonConfigFile,
304
+ toCursorMdc,
305
+ toWindsurfRule,
306
+ setupAgents,
307
+ claudeDesktopDir,
83
308
  };
@@ -2,30 +2,16 @@
2
2
  /**
3
3
  * postinstall.js — auto-configure MCP + agent instructions after npm install.
4
4
  */
5
- const fs = require('fs');
6
5
  const path = require('path');
7
- const os = require('os');
8
- const home = os.homedir();
9
- const isWin = process.platform === 'win32';
10
6
  const pkgDir = path.resolve(__dirname, '..');
11
7
  const quiet = process.env.HYV_POSTINSTALL_QUIET === '1' || process.argv.includes('--quiet');
12
- const hyvDir = path.join(home, '.hyv');
13
- const agentsMarker = path.join(hyvDir, 'agents-version.json');
8
+ const hyvDir = require('path').join(require('os').homedir(), '.hyv');
14
9
  const {
15
- readPkgVersion,
16
- copyAgent,
17
- writeAgentsMarker,
10
+ setupAgents,
18
11
  hasCompletedOnboarding,
19
12
  markOnboardingComplete,
20
13
  } = require('./postinstall-lib');
21
14
 
22
- const pkgVersion = readPkgVersion(pkgDir);
23
-
24
- function installAgent(src, dest) {
25
- const result = copyAgent(src, dest, agentsMarker, pkgVersion);
26
- return result.copied;
27
- }
28
-
29
15
  function print(msg) {
30
16
  if (!quiet) console.log(msg);
31
17
  }
@@ -35,87 +21,17 @@ print(' ✓ hold your voice installed — free local scan ready');
35
21
  print(' next: hyv scan draft.md | hyv init | hyv mcp --setup');
36
22
  print('');
37
23
 
38
- const configured = [];
39
-
40
- // ── Claude Desktop ─────────────────────────────────────────────────────────
41
- try {
42
- const claudeDir = isWin
43
- ? path.join(home, 'AppData', 'Roaming', 'Claude')
44
- : path.join(home, 'Library', 'Application Support', 'Claude');
45
- const configFile = path.join(claudeDir, 'claude_desktop_config.json');
46
-
47
- if (fs.existsSync(claudeDir)) {
48
- let config = {};
49
- if (fs.existsSync(configFile)) {
50
- try { config = JSON.parse(fs.readFileSync(configFile, 'utf-8')); } catch {}
51
- }
52
- if (!config.mcpServers) config.mcpServers = {};
53
- if (!config.mcpServers.hyv) {
54
- config.mcpServers.hyv = { command: 'hyv', args: ['mcp'] };
55
- fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
56
- configured.push('claude desktop');
57
- }
58
- }
59
- } catch {}
60
-
61
- // ── Claude Code ──────────────────────────────────────────────────────────────
62
- try {
63
- const cmdDir = path.join(home, '.claude', 'commands');
64
- if (fs.existsSync(path.dirname(cmdDir))) {
65
- fs.mkdirSync(cmdDir, { recursive: true });
66
- const cmdFile = path.join(cmdDir, 'hyv.md');
67
- const src = path.join(pkgDir, 'agents', 'claude-code.md');
68
- if (installAgent(src, cmdFile)) configured.push('claude code');
69
- }
70
- } catch {}
71
-
72
- // ── Cursor ─────────────────────────────────────────────────────────────────
73
- try {
74
- const cursorDir = path.join(home, '.cursor');
75
- if (fs.existsSync(cursorDir)) {
76
- const rulesFile = path.join(cursorDir, 'rules', 'hyv.md');
77
- fs.mkdirSync(path.dirname(rulesFile), { recursive: true });
78
- const src = path.join(pkgDir, 'agents', 'cursor.md');
79
- if (installAgent(src, rulesFile)) configured.push('cursor');
80
- }
81
- } catch {}
82
-
83
- // ── Windsurf ───────────────────────────────────────────────────────────────
84
- try {
85
- const wsDirs = [
86
- isWin ? path.join(home, 'AppData', 'Roaming', 'Windsurf') : path.join(home, '.windsurf'),
87
- isWin ? null : path.join(home, 'Library', 'Application Support', 'Windsurf'),
88
- ].filter(Boolean);
89
- for (const wsDir of wsDirs) {
90
- if (fs.existsSync(wsDir)) {
91
- const rulesFile = path.join(wsDir, 'rules', 'hyv.md');
92
- fs.mkdirSync(path.dirname(rulesFile), { recursive: true });
93
- const src = path.join(pkgDir, 'agents', 'windsurf.md');
94
- if (installAgent(src, rulesFile)) {
95
- configured.push('windsurf');
96
- break;
97
- }
98
- }
99
- }
100
- } catch {}
101
-
102
- // ── ChatGPT ──────────────────────────────────────────────────────────────────
103
- try {
104
- const chatgptDir = path.join(home, '.chatgpt');
105
- if (!fs.existsSync(chatgptDir)) fs.mkdirSync(chatgptDir, { recursive: true });
106
- const instrFile = path.join(chatgptDir, 'hyv-instructions.txt');
107
- const src = path.join(pkgDir, 'agents', 'chatgpt.md');
108
- if (installAgent(src, instrFile)) configured.push('chatgpt');
109
- } catch {}
110
-
111
- writeAgentsMarker(hyvDir, pkgVersion);
24
+ const { configured, warnings } = setupAgents({ pkgDir, quiet });
112
25
 
113
26
  if (configured.length > 0) {
114
27
  print(' ✓ auto-configured: ' + configured.join(', '));
115
28
  print('');
116
29
  }
30
+ if (warnings.length > 0 && !quiet) {
31
+ print(' ! setup notes: ' + warnings.join('; '));
32
+ print('');
33
+ }
117
34
 
118
- // First install → onboarding during install when npm shows script output; else first `hyv` runs it
119
35
  const canShowOnboarding =
120
36
  process.stdout.isTTY || process.env.npm_config_foreground_scripts === 'true';
121
37
 
@@ -129,7 +45,7 @@ if (!quiet && !hasCompletedOnboarding(hyvDir) && canShowOnboarding) {
129
45
  stdio: 'inherit',
130
46
  env: { ...process.env, HYV_POSTINSTALL_ONBOARDING: '1' },
131
47
  });
132
- markOnboardingComplete(hyvDir, pkgVersion);
48
+ markOnboardingComplete(hyvDir, require('./postinstall-lib').readPkgVersion(pkgDir));
133
49
  } catch {
134
50
  print(' finish setup: hyv welcome');
135
51
  print('');
@@ -1,63 +0,0 @@
1
- ---
2
- name: ai-writing-eliminator
3
- description: Use when the user wants to remove AI writing patterns, humanize a draft, make prose less generic, scan for AI cadence, or rewrite only the lines that sound synthetic.
4
- ---
5
-
6
- # AI Writing Eliminator (v2)
7
-
8
- This skill removes machine-shaped writing without destroying the draft. The
9
- algorithm now detects 220+ named AI writing patterns across 31 composite regex
10
- rules, plus 9 structural/rhythmic signals (burstiness, paragraph uniformity,
11
- contraction density, formal hedging, intensifier overuse, fragment ratio,
12
- staccato detection, over-structured lists, uniform sentence rhythm).
13
-
14
- ## Non-Negotiables
15
-
16
- - Fix flagged lines only unless the user asks for a full rewrite.
17
- - Preserve the original argument and local meaning.
18
- - Do not add praise, summaries, preambles, CTAs, or extra sections.
19
- - Do not replace specific roughness with smooth generic prose.
20
- - After editing, rescan the result.
21
-
22
- ## Scan
23
-
24
- - Fix flagged lines only unless the user asks for a full rewrite.
25
- - Preserve the original argument and local meaning.
26
- - Do not add praise, summaries, preambles, CTAs, or extra sections.
27
- - Do not replace specific roughness with smooth generic prose.
28
- - After editing, rescan the result.
29
-
30
- ## Scan
31
-
32
- Use the helper script when a draft is in a file:
33
-
34
- ```bash
35
- hyv scan <draft path>
36
- ```
37
-
38
- For pasted text, apply the same rules manually from
39
- `assets/ai-eliminator-rules.md`.
40
-
41
- ## Repair Prompt
42
-
43
- When a model rewrite is needed, generate a line-level prompt:
44
-
45
- ```bash
46
- hyv rewrite-prompt \
47
- --profile .hold-your-voice/voice-profile.json \
48
- <draft path>
49
- ```
50
-
51
- If there is no profile, still repair the AI patterns, but do not claim the
52
- result is voice-matched.
53
-
54
- ## Bad Fixes
55
-
56
- Reject fixes that:
57
-
58
- - turn the draft into a tidy founder post
59
- - make every paragraph land as a lesson
60
- - replace a concrete scene with an abstract principle
61
- - use dramatic line breaks to fake rhythm
62
- - make the writer sound more professional but less specific
63
-
@@ -1,24 +0,0 @@
1
- ## Hold Your Voice — automated voice rewriting
2
-
3
- when the user asks to "write in my voice", "rewrite this in my voice", or "make this sound like me":
4
-
5
- 1. read their voice profile at `~/.hyv/voice.md`
6
- 2. run `hyv rewrite --text "<their text>"` to generate a structured rewrite prompt
7
- 3. execute the prompt — rewrite the text yourself using your own llm
8
- 4. present the rewritten text
9
- 5. when the user accepts it, run `hyv reinforce original.md edited.md` to teach the system
10
-
11
- ### available commands
12
-
13
- - `hyv scan <file>` — check a draft for voice drift and ai patterns
14
- - `hyv rewrite <file>` — generate a rewrite prompt for your llm
15
- - `hyv validate <file>` — verify a rewrite matches the voice profile
16
- - `hyv reinforce <original> <edited>` — teach the system what was accepted
17
- - `hyv sync` — refresh cached profiles and rules
18
- - `hyv status` — show auth and plan info
19
-
20
- ### important
21
-
22
- - hyv never calls an llm. it generates prompts you execute yourself.
23
- - voice profiles live at `~/.hyv/voice.md`
24
- - always call `hyv validate` after rewriting to verify the result
@@ -1,12 +0,0 @@
1
- # Hold Your Voice — Cursor Rules
2
-
3
- when writing or rewriting content, match the voice profile below. do not use AI-slop words (delve, leverage, tapestry, in today's fast-paced, not just X but Y, etc.). match rhythm, vocabulary, and sentence patterns from the profile.
4
-
5
- your voice profile is at `~/.hyv/voice.md`. load it and use it as your writing guide.
6
-
7
- when the user says "rewrite in my voice", "make this sound like me", or similar:
8
-
9
- 1. run: `hyv rewrite --text "<text>"`
10
- 2. the cli generates a structured rewrite prompt
11
- 3. execute the prompt yourself — rewrite the text matching the voice profile
12
- 4. present the result