@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/CHANGELOG.md +20 -0
- package/README.md +1 -1
- package/agents/cursor.md +1 -1
- package/dist/index.js +644 -304
- package/package.json +1 -1
- package/scripts/postinstall-lib.js +232 -7
- package/scripts/postinstall.js +8 -92
- package/assets/ai-eliminator-skill.md +0 -63
- package/assets/claude-code-skill.md +0 -24
- package/assets/cursor-rules.md +0 -12
- package/assets/hold-your-voice-skill.md +0 -174
- package/assets/voice-matcher-skill.md +0 -57
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* postinstall-lib.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.
|
|
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
|
};
|
package/scripts/postinstall.js
CHANGED
|
@@ -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(
|
|
13
|
-
const agentsMarker = path.join(hyvDir, 'agents-version.json');
|
|
8
|
+
const hyvDir = require('path').join(require('os').homedir(), '.hyv');
|
|
14
9
|
const {
|
|
15
|
-
|
|
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,
|
|
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
|
package/assets/cursor-rules.md
DELETED
|
@@ -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
|