@holdyourvoice/hyv 2.9.14 → 2.9.16
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 +11 -0
- package/README.md +1 -1
- package/agents/chatgpt.md +12 -1
- package/agents/cursor.md +3 -1
- package/agents/generic.md +10 -1
- package/dist/index.js +902 -414
- package/package.json +1 -1
- package/scripts/install.ps1 +13 -1
- package/scripts/postinstall-lib.js +375 -37
- package/scripts/postinstall.js +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holdyourvoice/hyv",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.16",
|
|
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": {
|
package/scripts/install.ps1
CHANGED
|
@@ -68,7 +68,16 @@ function Install-Hyv {
|
|
|
68
68
|
throw 'npm not found after node install'
|
|
69
69
|
}
|
|
70
70
|
Write-Log 'installing hold your voice...'
|
|
71
|
-
|
|
71
|
+
try {
|
|
72
|
+
npm i -g $HyvPkg
|
|
73
|
+
} catch {
|
|
74
|
+
if ($_.Exception.Message -match 'ExecutionPolicy|scripts is disabled') {
|
|
75
|
+
Write-Log 'PowerShell execution policy blocked npm — retrying with Bypass...'
|
|
76
|
+
powershell -ExecutionPolicy Bypass -Command "npm i -g $HyvPkg"
|
|
77
|
+
} else {
|
|
78
|
+
throw
|
|
79
|
+
}
|
|
80
|
+
}
|
|
72
81
|
Refresh-Path
|
|
73
82
|
if (Get-Command hyv -ErrorAction SilentlyContinue) {
|
|
74
83
|
Write-Log 'running hyv welcome...'
|
|
@@ -81,6 +90,9 @@ function Install-Hyv {
|
|
|
81
90
|
Write-Host ''
|
|
82
91
|
Write-Host 'hold your voice — installer'
|
|
83
92
|
Write-Host ''
|
|
93
|
+
Write-Host ' Note: If you see an "execution policy" error, re-run using:'
|
|
94
|
+
Write-Host ' powershell -ExecutionPolicy Bypass -c "irm https://holdyourvoice.com/install.ps1 | iex"'
|
|
95
|
+
Write-Host ''
|
|
84
96
|
Ensure-Node
|
|
85
97
|
if ($EnsureNodeOnly) { exit 0 }
|
|
86
98
|
Install-Hyv
|
|
@@ -89,6 +89,18 @@ function resolveHyvMcpCommandArray(pkgDir) {
|
|
|
89
89
|
return [command, ...args];
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
function mcpJsonEntriesMatch(existing, desired) {
|
|
93
|
+
if (!existing || typeof existing !== 'object') return false;
|
|
94
|
+
const a = existing.args || [];
|
|
95
|
+
const b = desired.args || [];
|
|
96
|
+
return existing.command === desired.command && JSON.stringify(a) === JSON.stringify(b);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function mcpCommandArrayMatches(existing, desired) {
|
|
100
|
+
if (!Array.isArray(existing) || !Array.isArray(desired)) return false;
|
|
101
|
+
return JSON.stringify(existing) === JSON.stringify(desired);
|
|
102
|
+
}
|
|
103
|
+
|
|
92
104
|
function parseJsonc(text) {
|
|
93
105
|
const noBlock = text.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
94
106
|
const noLine = noBlock.replace(/^\s*\/\/.*$/gm, '');
|
|
@@ -151,6 +163,226 @@ function chatgptHelperDir(home) {
|
|
|
151
163
|
return path.join(home, '.chatgpt');
|
|
152
164
|
}
|
|
153
165
|
|
|
166
|
+
function macAppInstalled(appName, home) {
|
|
167
|
+
if (process.platform !== 'darwin') return false;
|
|
168
|
+
const roots = ['/Applications', path.join(home, 'Applications')];
|
|
169
|
+
return roots.some((root) => fs.existsSync(path.join(root, `${appName}.app`)));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function dirExists(dir) {
|
|
173
|
+
try {
|
|
174
|
+
return fs.existsSync(dir) && fs.statSync(dir).isDirectory();
|
|
175
|
+
} catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function fileExists(file) {
|
|
181
|
+
try {
|
|
182
|
+
return fs.existsSync(file);
|
|
183
|
+
} catch {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function commandOnPath(cmd) {
|
|
189
|
+
const pathVar = process.env.PATH || '';
|
|
190
|
+
const exts = process.platform === 'win32' ? (process.env.PATHEXT || '.EXE;.CMD;.BAT').split(';') : [''];
|
|
191
|
+
for (const entry of pathVar.split(path.delimiter)) {
|
|
192
|
+
if (!entry) continue;
|
|
193
|
+
for (const ext of exts) {
|
|
194
|
+
const candidate = path.join(entry, process.platform === 'win32' ? `${cmd}${ext}` : cmd);
|
|
195
|
+
try {
|
|
196
|
+
if (fs.existsSync(candidate)) return true;
|
|
197
|
+
} catch {
|
|
198
|
+
// ignore
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Detect locally installed AI apps that hyv can integrate with.
|
|
207
|
+
* @returns {{ id: string, label: string, reason: string, integration: 'mcp'|'rules'|'manual' }[]}
|
|
208
|
+
*/
|
|
209
|
+
function detectInstalledAgents(home = require('os').homedir(), isWin = process.platform === 'win32') {
|
|
210
|
+
const detected = [];
|
|
211
|
+
|
|
212
|
+
const claudeDir = claudeDesktopDir(home, isWin);
|
|
213
|
+
if (
|
|
214
|
+
dirExists(claudeDir) ||
|
|
215
|
+
fileExists(path.join(claudeDir, 'claude_desktop_config.json')) ||
|
|
216
|
+
macAppInstalled('Claude', home)
|
|
217
|
+
) {
|
|
218
|
+
detected.push({
|
|
219
|
+
id: 'claude-desktop',
|
|
220
|
+
label: 'claude desktop',
|
|
221
|
+
reason: dirExists(claudeDir) ? 'config directory found' : 'claude desktop app found',
|
|
222
|
+
integration: 'mcp',
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const cursorDir = path.join(home, '.cursor');
|
|
227
|
+
if (dirExists(cursorDir) || macAppInstalled('Cursor', home)) {
|
|
228
|
+
detected.push({
|
|
229
|
+
id: 'cursor',
|
|
230
|
+
label: 'cursor',
|
|
231
|
+
reason: dirExists(cursorDir) ? '~/.cursor found' : 'cursor app found',
|
|
232
|
+
integration: 'mcp',
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const claudeCodeDir = path.join(home, '.claude');
|
|
237
|
+
if (dirExists(claudeCodeDir) || commandOnPath('claude')) {
|
|
238
|
+
detected.push({
|
|
239
|
+
id: 'claude-code',
|
|
240
|
+
label: 'claude code',
|
|
241
|
+
reason: dirExists(claudeCodeDir) ? '~/.claude found' : 'claude cli on path',
|
|
242
|
+
integration: 'rules',
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const windsurfDirs = [
|
|
247
|
+
path.join(home, '.windsurf'),
|
|
248
|
+
isWin ? path.join(home, 'AppData', 'Roaming', 'Windsurf') : path.join(home, 'Library', 'Application Support', 'Windsurf'),
|
|
249
|
+
].filter(Boolean);
|
|
250
|
+
if (windsurfDirs.some(dirExists) || macAppInstalled('Windsurf', home)) {
|
|
251
|
+
detected.push({
|
|
252
|
+
id: 'windsurf',
|
|
253
|
+
label: 'windsurf',
|
|
254
|
+
reason: windsurfDirs.some(dirExists) ? 'windsurf config found' : 'windsurf app found',
|
|
255
|
+
integration: 'rules',
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (dirExists(path.join(home, '.codex')) || commandOnPath('codex')) {
|
|
260
|
+
detected.push({
|
|
261
|
+
id: 'codex',
|
|
262
|
+
label: 'codex',
|
|
263
|
+
reason: dirExists(path.join(home, '.codex')) ? '~/.codex found' : 'codex cli on path',
|
|
264
|
+
integration: 'rules',
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (dirExists(path.join(home, '.commandcode'))) {
|
|
269
|
+
detected.push({
|
|
270
|
+
id: 'command-code',
|
|
271
|
+
label: 'command code',
|
|
272
|
+
reason: '~/.commandcode found',
|
|
273
|
+
integration: 'rules',
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const agDir = path.dirname(antigravityMcpConfigPath(home, isWin));
|
|
278
|
+
if (dirExists(agDir) || macAppInstalled('Antigravity', home)) {
|
|
279
|
+
detected.push({
|
|
280
|
+
id: 'antigravity',
|
|
281
|
+
label: 'antigravity',
|
|
282
|
+
reason: dirExists(agDir) ? '~/.gemini/config found' : 'antigravity app found',
|
|
283
|
+
integration: 'mcp',
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const ocDir = opencodeConfigDir(home, isWin);
|
|
288
|
+
if (dirExists(ocDir) || commandOnPath('opencode')) {
|
|
289
|
+
detected.push({
|
|
290
|
+
id: 'opencode',
|
|
291
|
+
label: 'opencode',
|
|
292
|
+
reason: dirExists(ocDir) ? 'opencode config found' : 'opencode cli on path',
|
|
293
|
+
integration: 'mcp',
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const chatgptDir = chatgptHelperDir(home);
|
|
298
|
+
if (dirExists(chatgptDir) || macAppInstalled('ChatGPT', home)) {
|
|
299
|
+
detected.push({
|
|
300
|
+
id: 'chatgpt',
|
|
301
|
+
label: 'chatgpt desktop',
|
|
302
|
+
reason: dirExists(chatgptDir) ? '~/.chatgpt found' : 'chatgpt app found',
|
|
303
|
+
integration: 'manual',
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return detected;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function agentIdForConfiguredLabel(label) {
|
|
311
|
+
const map = {
|
|
312
|
+
'claude desktop': 'claude-desktop',
|
|
313
|
+
'claude desktop mcp': 'claude-desktop',
|
|
314
|
+
cursor: 'cursor',
|
|
315
|
+
'cursor mcp': 'cursor',
|
|
316
|
+
'claude code': 'claude-code',
|
|
317
|
+
'claude code skill': 'claude-code',
|
|
318
|
+
windsurf: 'windsurf',
|
|
319
|
+
codex: 'codex',
|
|
320
|
+
'command code': 'command-code',
|
|
321
|
+
'antigravity mcp': 'antigravity',
|
|
322
|
+
'opencode mcp': 'opencode',
|
|
323
|
+
'opencode agents': 'opencode',
|
|
324
|
+
'chatgpt connector guide': 'chatgpt',
|
|
325
|
+
};
|
|
326
|
+
return map[label] || null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function isAgentIdEnabled(agentId, onlyDetected, detectedIds) {
|
|
330
|
+
if (!onlyDetected) return true;
|
|
331
|
+
return detectedIds.includes(agentId);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function readMcpHyvEntry(configFile) {
|
|
335
|
+
if (!fileExists(configFile)) return null;
|
|
336
|
+
try {
|
|
337
|
+
const cfg = JSON.parse(fs.readFileSync(configFile, 'utf-8'));
|
|
338
|
+
return cfg?.mcpServers?.hyv || null;
|
|
339
|
+
} catch {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function isAgentIntegrated(agentId, home = require('os').homedir(), isWin = process.platform === 'win32') {
|
|
345
|
+
switch (agentId) {
|
|
346
|
+
case 'claude-desktop':
|
|
347
|
+
return Boolean(readMcpHyvEntry(path.join(claudeDesktopDir(home, isWin), 'claude_desktop_config.json')));
|
|
348
|
+
case 'cursor':
|
|
349
|
+
return Boolean(readMcpHyvEntry(path.join(home, '.cursor', 'mcp.json')))
|
|
350
|
+
&& fileExists(path.join(home, '.cursor', 'rules', 'hyv.mdc'));
|
|
351
|
+
case 'claude-code':
|
|
352
|
+
return fileExists(path.join(home, '.claude', 'commands', 'hyv.md'))
|
|
353
|
+
&& fileExists(path.join(home, '.claude', 'skills', 'hold-your-voice', 'SKILL.md'));
|
|
354
|
+
case 'windsurf': {
|
|
355
|
+
const dirs = [
|
|
356
|
+
path.join(home, '.windsurf'),
|
|
357
|
+
isWin ? path.join(home, 'AppData', 'Roaming', 'Windsurf') : path.join(home, 'Library', 'Application Support', 'Windsurf'),
|
|
358
|
+
].filter(Boolean);
|
|
359
|
+
return dirs.some((dir) => fileExists(path.join(dir, 'rules', 'hyv.md')));
|
|
360
|
+
}
|
|
361
|
+
case 'codex': {
|
|
362
|
+
const agents = path.join(home, '.codex', 'AGENTS.md');
|
|
363
|
+
return fileExists(agents) && fs.readFileSync(agents, 'utf-8').includes('hyv');
|
|
364
|
+
}
|
|
365
|
+
case 'command-code':
|
|
366
|
+
return fileExists(path.join(home, '.commandcode', 'skills', 'hyv', 'SKILL.md'));
|
|
367
|
+
case 'antigravity':
|
|
368
|
+
return Boolean(readMcpHyvEntry(antigravityMcpConfigPath(home, isWin)));
|
|
369
|
+
case 'opencode': {
|
|
370
|
+
const ocFile = path.join(opencodeConfigDir(home, isWin), 'opencode.jsonc');
|
|
371
|
+
if (!fileExists(ocFile)) return false;
|
|
372
|
+
try {
|
|
373
|
+
const cfg = parseJsonc(fs.readFileSync(ocFile, 'utf-8'));
|
|
374
|
+
return Boolean(cfg.mcp?.hyv);
|
|
375
|
+
} catch {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
case 'chatgpt':
|
|
380
|
+
return fileExists(path.join(chatgptHelperDir(home), 'hyv-mcp-connector.txt'));
|
|
381
|
+
default:
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
154
386
|
function mergeJsonConfigFile(configFile, mutator, { backup = true } = {}) {
|
|
155
387
|
fs.mkdirSync(path.dirname(configFile), { recursive: true });
|
|
156
388
|
const read = readJsonObjectFromFile(configFile, (text) => JSON.parse(text));
|
|
@@ -207,20 +439,35 @@ function claudeDesktopDir(home, isWin) {
|
|
|
207
439
|
|
|
208
440
|
/**
|
|
209
441
|
* Configure MCP + agent instructions for detected IDEs.
|
|
210
|
-
* @returns {{ configured: string[], warnings: string[], pkgVersion: string }}
|
|
442
|
+
* @returns {{ configured: string[], warnings: string[], pkgVersion: string, detected: object[], skipped: string[] }}
|
|
211
443
|
*/
|
|
212
|
-
function setupAgents({
|
|
444
|
+
function setupAgents({
|
|
445
|
+
pkgDir,
|
|
446
|
+
home = require('os').homedir(),
|
|
447
|
+
quiet = false,
|
|
448
|
+
onlyDetected = false,
|
|
449
|
+
force = false,
|
|
450
|
+
} = {}) {
|
|
213
451
|
const configured = [];
|
|
214
452
|
const warnings = [];
|
|
453
|
+
const skipped = [];
|
|
215
454
|
const isWin = process.platform === 'win32';
|
|
216
455
|
const hyvDir = path.join(home, '.hyv');
|
|
217
456
|
const agentsMarker = path.join(hyvDir, 'agents-version.json');
|
|
218
457
|
const pkgVersion = readPkgVersion(pkgDir);
|
|
219
458
|
const mcpCmd = resolveHyvMcpCommand(pkgDir);
|
|
220
459
|
const autoConfigure = process.env.HYV_AUTO_CONFIGURE_AGENTS !== '0';
|
|
460
|
+
const detected = detectInstalledAgents(home, isWin);
|
|
461
|
+
const detectedIds = detected.map((entry) => entry.id);
|
|
221
462
|
|
|
222
463
|
if (!autoConfigure) {
|
|
223
|
-
return {
|
|
464
|
+
return {
|
|
465
|
+
configured,
|
|
466
|
+
warnings: ['HYV_AUTO_CONFIGURE_AGENTS=0 — skipped agent setup'],
|
|
467
|
+
pkgVersion,
|
|
468
|
+
detected,
|
|
469
|
+
skipped: detectedIds,
|
|
470
|
+
};
|
|
224
471
|
}
|
|
225
472
|
|
|
226
473
|
function installAgent(src, dest, transform) {
|
|
@@ -229,11 +476,16 @@ function setupAgents({ pkgDir, home = require('os').homedir(), quiet = false })
|
|
|
229
476
|
}
|
|
230
477
|
|
|
231
478
|
function setupMcpJson(configFile, label) {
|
|
479
|
+
const desired = { command: mcpCmd.command, args: mcpCmd.args };
|
|
232
480
|
const result = mergeJsonConfigFile(configFile, (config) => {
|
|
233
481
|
if (!config.mcpServers) config.mcpServers = {};
|
|
234
|
-
|
|
235
|
-
|
|
482
|
+
const existing = config.mcpServers.hyv;
|
|
483
|
+
if (!existing) {
|
|
484
|
+
config.mcpServers.hyv = desired;
|
|
236
485
|
configured.push(`${label} mcp`);
|
|
486
|
+
} else if (force || !mcpJsonEntriesMatch(existing, desired)) {
|
|
487
|
+
config.mcpServers.hyv = desired;
|
|
488
|
+
configured.push(`${label} mcp (updated)`);
|
|
237
489
|
}
|
|
238
490
|
return config;
|
|
239
491
|
});
|
|
@@ -242,18 +494,22 @@ function setupAgents({ pkgDir, home = require('os').homedir(), quiet = false })
|
|
|
242
494
|
}
|
|
243
495
|
|
|
244
496
|
// Claude Desktop MCP
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
497
|
+
if (isAgentIdEnabled('claude-desktop', onlyDetected, detectedIds)) {
|
|
498
|
+
try {
|
|
499
|
+
const claudeDir = claudeDesktopDir(home, isWin);
|
|
500
|
+
const configFile = path.join(claudeDir, 'claude_desktop_config.json');
|
|
249
501
|
setupMcpJson(configFile, 'claude desktop');
|
|
502
|
+
} catch (err) {
|
|
503
|
+
warnings.push(`claude desktop: ${err.message}`);
|
|
250
504
|
}
|
|
251
|
-
}
|
|
252
|
-
|
|
505
|
+
} else if (onlyDetected) {
|
|
506
|
+
skipped.push('claude-desktop');
|
|
253
507
|
}
|
|
254
508
|
|
|
255
509
|
// Cursor — global MCP + always-on rule (.mdc)
|
|
256
|
-
|
|
510
|
+
if (!isAgentIdEnabled('cursor', onlyDetected, detectedIds)) {
|
|
511
|
+
if (onlyDetected) skipped.push('cursor');
|
|
512
|
+
} else try {
|
|
257
513
|
const cursorDir = path.join(home, '.cursor');
|
|
258
514
|
fs.mkdirSync(cursorDir, { recursive: true });
|
|
259
515
|
const mcpFile = path.join(cursorDir, 'mcp.json');
|
|
@@ -271,7 +527,9 @@ function setupAgents({ pkgDir, home = require('os').homedir(), quiet = false })
|
|
|
271
527
|
}
|
|
272
528
|
|
|
273
529
|
// Claude Code — slash command + skill
|
|
274
|
-
|
|
530
|
+
if (!isAgentIdEnabled('claude-code', onlyDetected, detectedIds)) {
|
|
531
|
+
if (onlyDetected) skipped.push('claude-code');
|
|
532
|
+
} else try {
|
|
275
533
|
const cmdDir = path.join(home, '.claude', 'commands');
|
|
276
534
|
const skillDir = path.join(home, '.claude', 'skills', 'hold-your-voice');
|
|
277
535
|
fs.mkdirSync(cmdDir, { recursive: true });
|
|
@@ -287,7 +545,9 @@ function setupAgents({ pkgDir, home = require('os').homedir(), quiet = false })
|
|
|
287
545
|
}
|
|
288
546
|
|
|
289
547
|
// Windsurf — rules with frontmatter
|
|
290
|
-
|
|
548
|
+
if (!isAgentIdEnabled('windsurf', onlyDetected, detectedIds)) {
|
|
549
|
+
if (onlyDetected) skipped.push('windsurf');
|
|
550
|
+
} else try {
|
|
291
551
|
const wsDirs = [
|
|
292
552
|
isWin ? path.join(home, 'AppData', 'Roaming', 'Windsurf') : path.join(home, '.windsurf'),
|
|
293
553
|
isWin ? null : path.join(home, 'Library', 'Application Support', 'Windsurf'),
|
|
@@ -306,7 +566,9 @@ function setupAgents({ pkgDir, home = require('os').homedir(), quiet = false })
|
|
|
306
566
|
}
|
|
307
567
|
|
|
308
568
|
// Codex — ~/.codex/AGENTS.md
|
|
309
|
-
|
|
569
|
+
if (!isAgentIdEnabled('codex', onlyDetected, detectedIds)) {
|
|
570
|
+
if (onlyDetected) skipped.push('codex');
|
|
571
|
+
} else try {
|
|
310
572
|
const codexDir = path.join(home, '.codex');
|
|
311
573
|
fs.mkdirSync(codexDir, { recursive: true });
|
|
312
574
|
const agentsFile = path.join(codexDir, 'AGENTS.md');
|
|
@@ -327,7 +589,9 @@ function setupAgents({ pkgDir, home = require('os').homedir(), quiet = false })
|
|
|
327
589
|
}
|
|
328
590
|
|
|
329
591
|
// Command Code skill
|
|
330
|
-
|
|
592
|
+
if (!isAgentIdEnabled('command-code', onlyDetected, detectedIds)) {
|
|
593
|
+
if (onlyDetected) skipped.push('command-code');
|
|
594
|
+
} else try {
|
|
331
595
|
const ccSkillDir = path.join(home, '.commandcode', 'skills', 'hyv');
|
|
332
596
|
fs.mkdirSync(ccSkillDir, { recursive: true });
|
|
333
597
|
const skillSrc = path.join(pkgDir, 'skills', 'hold-your-voice', 'SKILL.md');
|
|
@@ -338,14 +602,21 @@ function setupAgents({ pkgDir, home = require('os').homedir(), quiet = false })
|
|
|
338
602
|
}
|
|
339
603
|
|
|
340
604
|
// Antigravity — ~/.gemini/config/mcp_config.json (absolute paths for GUI launch)
|
|
341
|
-
|
|
605
|
+
if (!isAgentIdEnabled('antigravity', onlyDetected, detectedIds)) {
|
|
606
|
+
if (onlyDetected) skipped.push('antigravity');
|
|
607
|
+
} else try {
|
|
342
608
|
const agFile = antigravityMcpConfigPath(home, isWin);
|
|
343
609
|
const mcpCmd = resolveHyvMcpCommand(pkgDir);
|
|
610
|
+
const desired = { command: mcpCmd.command, args: mcpCmd.args };
|
|
344
611
|
const result = mergeJsonConfigFile(agFile, (config) => {
|
|
345
612
|
if (!config.mcpServers) config.mcpServers = {};
|
|
346
|
-
|
|
347
|
-
|
|
613
|
+
const existing = config.mcpServers.hyv;
|
|
614
|
+
if (!existing) {
|
|
615
|
+
config.mcpServers.hyv = desired;
|
|
348
616
|
configured.push('antigravity mcp');
|
|
617
|
+
} else if (force || !mcpJsonEntriesMatch(existing, desired)) {
|
|
618
|
+
config.mcpServers.hyv = desired;
|
|
619
|
+
configured.push('antigravity mcp (updated)');
|
|
349
620
|
}
|
|
350
621
|
return config;
|
|
351
622
|
});
|
|
@@ -355,15 +626,22 @@ function setupAgents({ pkgDir, home = require('os').homedir(), quiet = false })
|
|
|
355
626
|
}
|
|
356
627
|
|
|
357
628
|
// OpenCode — ~/.config/opencode/opencode.jsonc + AGENTS.md
|
|
358
|
-
|
|
629
|
+
if (!isAgentIdEnabled('opencode', onlyDetected, detectedIds)) {
|
|
630
|
+
if (onlyDetected) skipped.push('opencode');
|
|
631
|
+
} else try {
|
|
359
632
|
const ocDir = opencodeConfigDir(home, isWin);
|
|
360
633
|
const ocFile = path.join(ocDir, 'opencode.jsonc');
|
|
361
634
|
const cmdArr = resolveHyvMcpCommandArray(pkgDir);
|
|
362
635
|
const ocResult = mergeJsoncConfigFile(ocFile, (config) => {
|
|
363
636
|
if (!config.mcp) config.mcp = {};
|
|
364
|
-
|
|
365
|
-
|
|
637
|
+
const existing = config.mcp.hyv;
|
|
638
|
+
const desired = { type: 'local', command: cmdArr, enabled: true };
|
|
639
|
+
if (!existing) {
|
|
640
|
+
config.mcp.hyv = desired;
|
|
366
641
|
configured.push('opencode mcp');
|
|
642
|
+
} else if (force || !mcpCommandArrayMatches(existing.command, cmdArr)) {
|
|
643
|
+
config.mcp.hyv = { ...existing, ...desired, command: cmdArr };
|
|
644
|
+
configured.push('opencode mcp (updated)');
|
|
367
645
|
}
|
|
368
646
|
return config;
|
|
369
647
|
});
|
|
@@ -389,28 +667,60 @@ function setupAgents({ pkgDir, home = require('os').homedir(), quiet = false })
|
|
|
389
667
|
}
|
|
390
668
|
|
|
391
669
|
// ChatGPT — connector helper (UI has no config file; write exact values for manual add)
|
|
392
|
-
|
|
670
|
+
if (!isAgentIdEnabled('chatgpt', onlyDetected, detectedIds)) {
|
|
671
|
+
if (onlyDetected) skipped.push('chatgpt');
|
|
672
|
+
} else try {
|
|
393
673
|
const cgDir = chatgptHelperDir(home);
|
|
394
674
|
fs.mkdirSync(cgDir, { recursive: true });
|
|
395
675
|
const mcpCmd = resolveHyvMcpCommand(pkgDir);
|
|
396
676
|
const connectorGuide = [
|
|
397
|
-
'hold your voice — chatgpt desktop
|
|
677
|
+
'hold your voice — chatgpt desktop setup (two paths)',
|
|
678
|
+
'',
|
|
679
|
+
'=== which setup do i have? ===',
|
|
680
|
+
'',
|
|
681
|
+
' If you see "New App" with a Server URL field:',
|
|
682
|
+
' → Developer mode is ON → use PATH A (remote)',
|
|
683
|
+
' If you see "Connectors" with Command/Arguments fields:',
|
|
684
|
+
' → use PATH B (local)',
|
|
685
|
+
'',
|
|
686
|
+
'=== PATH A — remote mcp (Developer mode → New App) ===',
|
|
687
|
+
' Requires: paid HYV plan + ChatGPT Desktop',
|
|
688
|
+
'',
|
|
689
|
+
' 1. In ChatGPT: Settings → turn Developer mode ON',
|
|
690
|
+
' (Accept the elevated risk warning — expected, not a HYV bug)',
|
|
691
|
+
' 2. Settings → Apps → New App',
|
|
692
|
+
' 3. Fill in:',
|
|
693
|
+
' Name: hold your voice',
|
|
694
|
+
' Description: Scans writing for AI patterns and voice drift',
|
|
695
|
+
' Connection: Server URL → https://holdyourvoice.com/mcp',
|
|
696
|
+
' Authentication: OAuth',
|
|
697
|
+
' Checkbox: "I understand and want to continue"',
|
|
698
|
+
' 4. Save → complete OAuth sign-in to holdyourvoice.com',
|
|
699
|
+
' 5. Test: "scan this with hold your voice"',
|
|
700
|
+
'',
|
|
701
|
+
' Cloud app setup: https://holdyourvoice.com/app/api/mcp',
|
|
702
|
+
' Wiki guide: https://holdyourvoice.com/wiki/use-hyv-in-chatgpt',
|
|
398
703
|
'',
|
|
399
|
-
'
|
|
704
|
+
'=== PATH B — local connector (Connectors → Command/Arguments) ===',
|
|
705
|
+
' Requires: Node.js 18+ (free offline scan)',
|
|
400
706
|
'',
|
|
401
|
-
'1.
|
|
402
|
-
'2.
|
|
403
|
-
'3.
|
|
404
|
-
|
|
405
|
-
|
|
707
|
+
' 1. Install hyv: npm i -g @holdyourvoice/hyv@latest && hyv welcome',
|
|
708
|
+
' 2. In ChatGPT: Settings → Connectors (or https://chatgpt.com/#settings/Connectors)',
|
|
709
|
+
' 3. Add connector:',
|
|
710
|
+
` Name: hold your voice`,
|
|
711
|
+
` Command: ${mcpCmd.command}`,
|
|
712
|
+
` Arguments: ${mcpCmd.args.join(' ')}`,
|
|
713
|
+
' 4. Save, restart ChatGPT Desktop',
|
|
714
|
+
' 5. Test: "scan this with hold your voice"',
|
|
406
715
|
'',
|
|
407
|
-
'
|
|
408
|
-
` command: ${mcpCmd.command}`,
|
|
409
|
-
` arguments: ${mcpCmd.args.join(' ')}`,
|
|
716
|
+
' If connector fails to start, use the absolute paths above.',
|
|
410
717
|
'',
|
|
411
|
-
'
|
|
718
|
+
'=== not seeing either UI? ===',
|
|
719
|
+
' • Turn Developer mode ON in ChatGPT settings for New App',
|
|
720
|
+
' • Connectors may require ChatGPT Plus',
|
|
721
|
+
' • Browser ChatGPT cannot use either path — desktop app only',
|
|
412
722
|
'',
|
|
413
|
-
'
|
|
723
|
+
'More help: hyv mcp --setup-chatgpt',
|
|
414
724
|
'',
|
|
415
725
|
].join('\n');
|
|
416
726
|
const guideFile = path.join(cgDir, 'hyv-mcp-connector.txt');
|
|
@@ -424,7 +734,7 @@ function setupAgents({ pkgDir, home = require('os').homedir(), quiet = false })
|
|
|
424
734
|
warnings.push(`chatgpt: ${err.message}`);
|
|
425
735
|
}
|
|
426
736
|
|
|
427
|
-
// Generic reference copy
|
|
737
|
+
// Generic reference copy (always when auto-configure runs)
|
|
428
738
|
try {
|
|
429
739
|
const genericSrc = path.join(pkgDir, 'agents', 'generic.md');
|
|
430
740
|
const genericDest = path.join(hyvDir, 'agents', 'generic.md');
|
|
@@ -437,7 +747,31 @@ function setupAgents({ pkgDir, home = require('os').homedir(), quiet = false })
|
|
|
437
747
|
if (!quiet && warnings.length) {
|
|
438
748
|
for (const w of warnings) process.stderr.write(`[hyv postinstall] warning: ${w}\n`);
|
|
439
749
|
}
|
|
440
|
-
return {
|
|
750
|
+
return {
|
|
751
|
+
configured: [...new Set(configured)],
|
|
752
|
+
warnings,
|
|
753
|
+
pkgVersion,
|
|
754
|
+
detected,
|
|
755
|
+
skipped: [...new Set(skipped)],
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Detect installed apps and configure hyv MCP/rules for each one.
|
|
761
|
+
* @returns {{ detected: object[], configured: string[], warnings: string[], skipped: string[] }}
|
|
762
|
+
*/
|
|
763
|
+
function integrateDetectedAgents({ pkgDir, home = require('os').homedir(), quiet = false, force = false } = {}) {
|
|
764
|
+
const detected = detectInstalledAgents(home);
|
|
765
|
+
if (detected.length === 0) {
|
|
766
|
+
return { detected, configured: [], warnings: [], skipped: [] };
|
|
767
|
+
}
|
|
768
|
+
const result = setupAgents({ pkgDir, home, quiet, onlyDetected: true, force });
|
|
769
|
+
return {
|
|
770
|
+
detected: result.detected || detected,
|
|
771
|
+
configured: result.configured || [],
|
|
772
|
+
warnings: result.warnings || [],
|
|
773
|
+
skipped: result.skipped || [],
|
|
774
|
+
};
|
|
441
775
|
}
|
|
442
776
|
|
|
443
777
|
module.exports = {
|
|
@@ -457,6 +791,10 @@ module.exports = {
|
|
|
457
791
|
toCursorMdc,
|
|
458
792
|
toWindsurfRule,
|
|
459
793
|
setupAgents,
|
|
794
|
+
detectInstalledAgents,
|
|
795
|
+
integrateDetectedAgents,
|
|
796
|
+
agentIdForConfiguredLabel,
|
|
797
|
+
isAgentIntegrated,
|
|
460
798
|
claudeDesktopDir,
|
|
461
799
|
antigravityMcpConfigPath,
|
|
462
800
|
opencodeConfigDir,
|
package/scripts/postinstall.js
CHANGED
|
@@ -21,6 +21,12 @@ print(' ✓ hold your voice installed — free local scan ready');
|
|
|
21
21
|
print(' next: hyv welcome | hyv scan draft.md | hyv mcp --setup');
|
|
22
22
|
print(' no node yet? curl -fsSL https://holdyourvoice.com/install.sh | bash');
|
|
23
23
|
print('');
|
|
24
|
+
if (process.platform === 'win32') {
|
|
25
|
+
print(' Windows: if you get "scripts is disabled", run in PowerShell:');
|
|
26
|
+
print(' Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser');
|
|
27
|
+
print(' Or use: powershell -ExecutionPolicy Bypass -Command "hyv ..."');
|
|
28
|
+
print('');
|
|
29
|
+
}
|
|
24
30
|
|
|
25
31
|
const { configured, warnings } = setupAgents({ pkgDir, quiet });
|
|
26
32
|
|