@holdyourvoice/hyv 2.9.28 → 3.0.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.
- package/LICENSE +17 -22
- package/Readme.md +247 -0
- package/dist/ai-editor.js +9 -0
- package/dist/ai-editor.test.js +61 -0
- package/dist/cli.js +106 -0
- package/dist/cli.test.js +119 -0
- package/dist/contracts.js +1 -0
- package/dist/pipeline.js +64 -0
- package/dist/pipeline.test.js +36 -0
- package/dist/release-audit.test.js +29 -0
- package/dist/text.js +42 -0
- package/dist/text.test.js +16 -0
- package/dist/voice-dna.js +77 -0
- package/dist/voice-dna.test.js +32 -0
- package/package.json +14 -74
- package/CHANGELOG.md +0 -241
- package/README.md +0 -218
- package/agents/AGENTS.md +0 -82
- package/agents/README.md +0 -20
- package/agents/chatgpt.md +0 -47
- package/agents/claude-code.md +0 -45
- package/agents/codex.md +0 -31
- package/agents/cursor.md +0 -36
- package/agents/generic.md +0 -49
- package/agents/windsurf.md +0 -20
- package/assets/FREE-PAID.md +0 -46
- package/assets/README.md +0 -20
- package/assets/ai-eliminator-rules.md +0 -140
- package/assets/chatgpt-instructions.txt +0 -8
- package/assets/detection-rules.json +0 -18
- package/assets/economic-drift-voice.md +0 -42
- package/assets/voice-dna-template.md +0 -88
- package/assets/voice-profile-schema.json +0 -28
- package/dist/index.js +0 -20911
- package/scripts/README.md +0 -23
- package/scripts/check-no-duplicates.js +0 -32
- package/scripts/install.ps1 +0 -101
- package/scripts/install.sh +0 -155
- package/scripts/postinstall-lib.js +0 -796
- package/scripts/postinstall.js +0 -35
- package/skills/README.md +0 -20
- package/skills/ai-writing-eliminator/SKILL.md +0 -63
- package/skills/hold-your-voice/SKILL.md +0 -174
- package/skills/voice-matcher/SKILL.md +0 -57
|
@@ -1,796 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* postinstall-lib.js — agent setup helpers (postinstall + doctor --fix-agents)
|
|
3
|
-
*/
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
|
|
7
|
-
function readPkgVersion(pkgDir) {
|
|
8
|
-
try {
|
|
9
|
-
return JSON.parse(fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf-8')).version;
|
|
10
|
-
} catch {
|
|
11
|
-
return 'unknown';
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function readAgentsMarker(markerPath) {
|
|
16
|
-
try {
|
|
17
|
-
if (!fs.existsSync(markerPath)) return null;
|
|
18
|
-
return JSON.parse(fs.readFileSync(markerPath, 'utf-8'));
|
|
19
|
-
} catch {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function shouldUpgradeAgent(destFile, markerPath, pkgVersion) {
|
|
25
|
-
try {
|
|
26
|
-
if (!fs.existsSync(markerPath)) return !fs.existsSync(destFile);
|
|
27
|
-
const marker = readAgentsMarker(markerPath);
|
|
28
|
-
return !marker || marker.version !== pkgVersion;
|
|
29
|
-
} catch {
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function copyAgent(src, dest, markerPath, pkgVersion, transform) {
|
|
35
|
-
if (!fs.existsSync(src)) return { copied: false, reason: 'missing-src' };
|
|
36
|
-
if (!shouldUpgradeAgent(dest, markerPath, pkgVersion) && fs.existsSync(dest)) {
|
|
37
|
-
return { copied: false, reason: 'up-to-date' };
|
|
38
|
-
}
|
|
39
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
40
|
-
const raw = fs.readFileSync(src, 'utf-8');
|
|
41
|
-
const content = typeof transform === 'function' ? transform(raw) : raw;
|
|
42
|
-
fs.writeFileSync(dest, content);
|
|
43
|
-
return { copied: true, reason: fs.existsSync(dest) ? 'upgraded' : 'created' };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function writeAgentsMarker(hyvDir, pkgVersion) {
|
|
47
|
-
const markerPath = path.join(hyvDir, 'agents-version.json');
|
|
48
|
-
if (!fs.existsSync(hyvDir)) fs.mkdirSync(hyvDir, { recursive: true, mode: 0o700 });
|
|
49
|
-
fs.writeFileSync(
|
|
50
|
-
markerPath,
|
|
51
|
-
JSON.stringify({ version: pkgVersion, updated_at: new Date().toISOString() }, null, 2),
|
|
52
|
-
{ mode: 0o600 }
|
|
53
|
-
);
|
|
54
|
-
return markerPath;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function onboardingPath(hyvDir) {
|
|
58
|
-
return path.join(hyvDir, 'onboarding-complete.json');
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function hasCompletedOnboarding(hyvDir) {
|
|
62
|
-
try {
|
|
63
|
-
return fs.existsSync(onboardingPath(hyvDir));
|
|
64
|
-
} catch {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function markOnboardingComplete(hyvDir, pkgVersion) {
|
|
70
|
-
if (!fs.existsSync(hyvDir)) fs.mkdirSync(hyvDir, { recursive: true, mode: 0o700 });
|
|
71
|
-
fs.writeFileSync(
|
|
72
|
-
onboardingPath(hyvDir),
|
|
73
|
-
JSON.stringify({ version: pkgVersion, completed_at: new Date().toISOString() }, null, 2),
|
|
74
|
-
{ mode: 0o600 }
|
|
75
|
-
);
|
|
76
|
-
}
|
|
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', '--stdio'] };
|
|
82
|
-
}
|
|
83
|
-
return { command: 'hyv', args: ['mcp', '--stdio'] };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/** OpenCode local MCP uses a command array: [node, entry, mcp] */
|
|
87
|
-
function resolveHyvMcpCommandArray(pkgDir) {
|
|
88
|
-
const { command, args } = resolveHyvMcpCommand(pkgDir);
|
|
89
|
-
return [command, ...args];
|
|
90
|
-
}
|
|
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
|
-
|
|
104
|
-
function parseJsonc(text) {
|
|
105
|
-
const noBlock = text.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
106
|
-
const noLine = noBlock.replace(/^\s*\/\/.*$/gm, '');
|
|
107
|
-
return JSON.parse(noLine);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function stringifyJsonc(config, originalText) {
|
|
111
|
-
const usesTrailingComma = /,\s*[}\]]/.test(originalText || '');
|
|
112
|
-
const space = (originalText || '').includes(': ') ? 2 : 2;
|
|
113
|
-
let out = JSON.stringify(config, null, space);
|
|
114
|
-
if (usesTrailingComma) {
|
|
115
|
-
out = out.replace(/,\n(\s*[}\]])/g, ',\n$1');
|
|
116
|
-
}
|
|
117
|
-
return out.endsWith('\n') ? out : `${out}\n`;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function readJsonObjectFromFile(file, parser) {
|
|
121
|
-
if (!fs.existsSync(file)) return { config: {}, original: '' };
|
|
122
|
-
const original = fs.readFileSync(file, 'utf-8');
|
|
123
|
-
if (!original.trim()) return { config: {}, original };
|
|
124
|
-
try {
|
|
125
|
-
const parsed = parser(original);
|
|
126
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
127
|
-
return { ok: false, reason: 'parse-error', error: 'expected json object' };
|
|
128
|
-
}
|
|
129
|
-
return { config: parsed, original };
|
|
130
|
-
} catch (err) {
|
|
131
|
-
return { ok: false, reason: 'parse-error', error: err.message };
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function mergeJsoncConfigFile(configFile, mutator, { backup = true } = {}) {
|
|
136
|
-
fs.mkdirSync(path.dirname(configFile), { recursive: true });
|
|
137
|
-
const read = readJsonObjectFromFile(configFile, parseJsonc);
|
|
138
|
-
if (read.ok === false) return read;
|
|
139
|
-
let { config, original } = read;
|
|
140
|
-
const next = mutator(config) || config;
|
|
141
|
-
if (backup && fs.existsSync(configFile)) {
|
|
142
|
-
try {
|
|
143
|
-
fs.copyFileSync(configFile, `${configFile}.hyv.bak`);
|
|
144
|
-
} catch {
|
|
145
|
-
// non-fatal
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
fs.writeFileSync(configFile, original ? stringifyJsonc(next, original) : `${JSON.stringify(next, null, 2)}\n`);
|
|
149
|
-
return { ok: true };
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function antigravityMcpConfigPath(home, isWin) {
|
|
153
|
-
return path.join(home, '.gemini', 'config', 'mcp_config.json');
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function opencodeConfigDir(home, isWin) {
|
|
157
|
-
if (isWin) return path.join(home, '.config', 'opencode');
|
|
158
|
-
if (process.platform === 'linux') return path.join(home, '.config', 'opencode');
|
|
159
|
-
return path.join(home, '.config', 'opencode');
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function chatgptHelperDir(home) {
|
|
163
|
-
return path.join(home, '.chatgpt');
|
|
164
|
-
}
|
|
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
|
-
|
|
386
|
-
function mergeJsonConfigFile(configFile, mutator, { backup = true } = {}) {
|
|
387
|
-
fs.mkdirSync(path.dirname(configFile), { recursive: true });
|
|
388
|
-
const read = readJsonObjectFromFile(configFile, (text) => JSON.parse(text));
|
|
389
|
-
if (read.ok === false) return read;
|
|
390
|
-
let { config, original } = read;
|
|
391
|
-
const next = mutator(config) || config;
|
|
392
|
-
if (backup && fs.existsSync(configFile)) {
|
|
393
|
-
try {
|
|
394
|
-
fs.copyFileSync(configFile, `${configFile}.hyv.bak`);
|
|
395
|
-
} catch {
|
|
396
|
-
// non-fatal
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
fs.writeFileSync(configFile, `${JSON.stringify(next, null, 2)}\n`);
|
|
400
|
-
return { ok: true };
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
function toCursorMdc(content) {
|
|
404
|
-
if (content.startsWith('---')) return content;
|
|
405
|
-
return [
|
|
406
|
-
'---',
|
|
407
|
-
'description: Hold Your Voice — scan and rewrite drafts for brand voice consistency',
|
|
408
|
-
'alwaysApply: true',
|
|
409
|
-
'---',
|
|
410
|
-
'',
|
|
411
|
-
content.trim(),
|
|
412
|
-
'',
|
|
413
|
-
].join('\n');
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
function toWindsurfRule(content) {
|
|
417
|
-
if (content.startsWith('---')) return content;
|
|
418
|
-
return ['---', 'trigger: always_on', '---', '', content.trim(), ''].join('\n');
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
function mergeAgentsMd(existing, addition, title) {
|
|
422
|
-
const marker = `<!-- hyv:${title} -->`;
|
|
423
|
-
if (existing.includes(marker)) {
|
|
424
|
-
const start = existing.indexOf(marker);
|
|
425
|
-
const end = existing.indexOf('<!-- /hyv -->', start);
|
|
426
|
-
if (end !== -1) {
|
|
427
|
-
return existing.slice(0, start) + marker + '\n' + addition.trim() + '\n<!-- /hyv -->\n' + existing.slice(end + '<!-- /hyv -->'.length);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
const block = `${marker}\n${addition.trim()}\n<!-- /hyv -->\n`;
|
|
431
|
-
return existing.trim() ? `${existing.trim()}\n\n${block}` : block;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
function claudeDesktopDir(home, isWin) {
|
|
435
|
-
if (isWin) return path.join(home, 'AppData', 'Roaming', 'Claude');
|
|
436
|
-
if (process.platform === 'linux') return path.join(home, '.config', 'Claude');
|
|
437
|
-
return path.join(home, 'Library', 'Application Support', 'Claude');
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Configure MCP + agent instructions for detected IDEs.
|
|
442
|
-
* @returns {{ configured: string[], warnings: string[], pkgVersion: string, detected: object[], skipped: string[] }}
|
|
443
|
-
*/
|
|
444
|
-
function setupAgents({
|
|
445
|
-
pkgDir,
|
|
446
|
-
home = require('os').homedir(),
|
|
447
|
-
quiet = false,
|
|
448
|
-
onlyDetected = false,
|
|
449
|
-
force = false,
|
|
450
|
-
} = {}) {
|
|
451
|
-
const configured = [];
|
|
452
|
-
const warnings = [];
|
|
453
|
-
const skipped = [];
|
|
454
|
-
const isWin = process.platform === 'win32';
|
|
455
|
-
const hyvDir = path.join(home, '.hyv');
|
|
456
|
-
const agentsMarker = path.join(hyvDir, 'agents-version.json');
|
|
457
|
-
const pkgVersion = readPkgVersion(pkgDir);
|
|
458
|
-
const mcpCmd = resolveHyvMcpCommand(pkgDir);
|
|
459
|
-
const autoConfigure = process.env.HYV_AUTO_CONFIGURE_AGENTS !== '0';
|
|
460
|
-
const detected = detectInstalledAgents(home, isWin);
|
|
461
|
-
const detectedIds = detected.map((entry) => entry.id);
|
|
462
|
-
|
|
463
|
-
if (!autoConfigure) {
|
|
464
|
-
return {
|
|
465
|
-
configured,
|
|
466
|
-
warnings: ['HYV_AUTO_CONFIGURE_AGENTS=0 — skipped agent setup'],
|
|
467
|
-
pkgVersion,
|
|
468
|
-
detected,
|
|
469
|
-
skipped: detectedIds,
|
|
470
|
-
};
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
function installAgent(src, dest, transform) {
|
|
474
|
-
const result = copyAgent(src, dest, agentsMarker, pkgVersion, transform);
|
|
475
|
-
return result.copied;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
function setupMcpJson(configFile, label) {
|
|
479
|
-
const desired = { command: mcpCmd.command, args: mcpCmd.args };
|
|
480
|
-
const result = mergeJsonConfigFile(configFile, (config) => {
|
|
481
|
-
if (!config.mcpServers) config.mcpServers = {};
|
|
482
|
-
const existing = config.mcpServers.hyv;
|
|
483
|
-
if (!existing) {
|
|
484
|
-
config.mcpServers.hyv = desired;
|
|
485
|
-
configured.push(`${label} mcp`);
|
|
486
|
-
} else if (force || !mcpJsonEntriesMatch(existing, desired)) {
|
|
487
|
-
config.mcpServers.hyv = desired;
|
|
488
|
-
configured.push(`${label} mcp (updated)`);
|
|
489
|
-
}
|
|
490
|
-
return config;
|
|
491
|
-
});
|
|
492
|
-
if (!result.ok) warnings.push(`${label}: could not update MCP config (${result.reason})`);
|
|
493
|
-
return result.ok;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// Claude Desktop MCP
|
|
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');
|
|
501
|
-
setupMcpJson(configFile, 'claude desktop');
|
|
502
|
-
} catch (err) {
|
|
503
|
-
warnings.push(`claude desktop: ${err.message}`);
|
|
504
|
-
}
|
|
505
|
-
} else if (onlyDetected) {
|
|
506
|
-
skipped.push('claude-desktop');
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
// Cursor — global MCP + always-on rule (.mdc)
|
|
510
|
-
if (!isAgentIdEnabled('cursor', onlyDetected, detectedIds)) {
|
|
511
|
-
if (onlyDetected) skipped.push('cursor');
|
|
512
|
-
} else try {
|
|
513
|
-
const cursorDir = path.join(home, '.cursor');
|
|
514
|
-
fs.mkdirSync(cursorDir, { recursive: true });
|
|
515
|
-
const mcpFile = path.join(cursorDir, 'mcp.json');
|
|
516
|
-
setupMcpJson(mcpFile, 'cursor');
|
|
517
|
-
const rulesFile = path.join(cursorDir, 'rules', 'hyv.mdc');
|
|
518
|
-
const src = path.join(pkgDir, 'agents', 'cursor.md');
|
|
519
|
-
if (installAgent(src, rulesFile, toCursorMdc)) configured.push('cursor');
|
|
520
|
-
// legacy .md (upgrade path)
|
|
521
|
-
const legacy = path.join(cursorDir, 'rules', 'hyv.md');
|
|
522
|
-
if (fs.existsSync(legacy)) {
|
|
523
|
-
try { fs.unlinkSync(legacy); } catch { /* ignore */ }
|
|
524
|
-
}
|
|
525
|
-
} catch (err) {
|
|
526
|
-
warnings.push(`cursor: ${err.message}`);
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
// Claude Code — slash command + skill
|
|
530
|
-
if (!isAgentIdEnabled('claude-code', onlyDetected, detectedIds)) {
|
|
531
|
-
if (onlyDetected) skipped.push('claude-code');
|
|
532
|
-
} else try {
|
|
533
|
-
const cmdDir = path.join(home, '.claude', 'commands');
|
|
534
|
-
const skillDir = path.join(home, '.claude', 'skills', 'hold-your-voice');
|
|
535
|
-
fs.mkdirSync(cmdDir, { recursive: true });
|
|
536
|
-
fs.mkdirSync(skillDir, { recursive: true });
|
|
537
|
-
const cmdFile = path.join(cmdDir, 'hyv.md');
|
|
538
|
-
const skillFile = path.join(skillDir, 'SKILL.md');
|
|
539
|
-
const cmdSrc = path.join(pkgDir, 'agents', 'claude-code.md');
|
|
540
|
-
const skillSrc = path.join(pkgDir, 'skills', 'hold-your-voice', 'SKILL.md');
|
|
541
|
-
if (installAgent(cmdSrc, cmdFile)) configured.push('claude code');
|
|
542
|
-
if (fs.existsSync(skillSrc) && installAgent(skillSrc, skillFile)) configured.push('claude code skill');
|
|
543
|
-
} catch (err) {
|
|
544
|
-
warnings.push(`claude code: ${err.message}`);
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
// Windsurf — rules with frontmatter
|
|
548
|
-
if (!isAgentIdEnabled('windsurf', onlyDetected, detectedIds)) {
|
|
549
|
-
if (onlyDetected) skipped.push('windsurf');
|
|
550
|
-
} else try {
|
|
551
|
-
const wsDirs = [
|
|
552
|
-
isWin ? path.join(home, 'AppData', 'Roaming', 'Windsurf') : path.join(home, '.windsurf'),
|
|
553
|
-
isWin ? null : path.join(home, 'Library', 'Application Support', 'Windsurf'),
|
|
554
|
-
].filter(Boolean);
|
|
555
|
-
const src = path.join(pkgDir, 'agents', 'windsurf.md');
|
|
556
|
-
for (const wsDir of wsDirs) {
|
|
557
|
-
fs.mkdirSync(wsDir, { recursive: true });
|
|
558
|
-
const rulesFile = path.join(wsDir, 'rules', 'hyv.md');
|
|
559
|
-
if (installAgent(src, rulesFile, toWindsurfRule)) {
|
|
560
|
-
configured.push('windsurf');
|
|
561
|
-
break;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
} catch (err) {
|
|
565
|
-
warnings.push(`windsurf: ${err.message}`);
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// Codex — ~/.codex/AGENTS.md
|
|
569
|
-
if (!isAgentIdEnabled('codex', onlyDetected, detectedIds)) {
|
|
570
|
-
if (onlyDetected) skipped.push('codex');
|
|
571
|
-
} else try {
|
|
572
|
-
const codexDir = path.join(home, '.codex');
|
|
573
|
-
fs.mkdirSync(codexDir, { recursive: true });
|
|
574
|
-
const agentsFile = path.join(codexDir, 'AGENTS.md');
|
|
575
|
-
const src = path.join(pkgDir, 'agents', 'AGENTS.md');
|
|
576
|
-
let existing = '';
|
|
577
|
-
if (fs.existsSync(agentsFile)) existing = fs.readFileSync(agentsFile, 'utf-8');
|
|
578
|
-
const addition = fs.existsSync(src) ? fs.readFileSync(src, 'utf-8') : '';
|
|
579
|
-
if (addition && shouldUpgradeAgent(agentsFile, agentsMarker, pkgVersion)) {
|
|
580
|
-
const merged = mergeAgentsMd(existing, addition, 'hold-your-voice');
|
|
581
|
-
fs.writeFileSync(agentsFile, merged);
|
|
582
|
-
configured.push('codex');
|
|
583
|
-
} else if (!fs.existsSync(agentsFile) && fs.existsSync(src)) {
|
|
584
|
-
fs.writeFileSync(agentsFile, addition.trim() + '\n');
|
|
585
|
-
configured.push('codex');
|
|
586
|
-
}
|
|
587
|
-
} catch (err) {
|
|
588
|
-
warnings.push(`codex: ${err.message}`);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
// Command Code skill
|
|
592
|
-
if (!isAgentIdEnabled('command-code', onlyDetected, detectedIds)) {
|
|
593
|
-
if (onlyDetected) skipped.push('command-code');
|
|
594
|
-
} else try {
|
|
595
|
-
const ccSkillDir = path.join(home, '.commandcode', 'skills', 'hyv');
|
|
596
|
-
fs.mkdirSync(ccSkillDir, { recursive: true });
|
|
597
|
-
const skillSrc = path.join(pkgDir, 'skills', 'hold-your-voice', 'SKILL.md');
|
|
598
|
-
const skillDest = path.join(ccSkillDir, 'SKILL.md');
|
|
599
|
-
if (fs.existsSync(skillSrc) && installAgent(skillSrc, skillDest)) configured.push('command code');
|
|
600
|
-
} catch (err) {
|
|
601
|
-
warnings.push(`command code: ${err.message}`);
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
// Antigravity — ~/.gemini/config/mcp_config.json (absolute paths for GUI launch)
|
|
605
|
-
if (!isAgentIdEnabled('antigravity', onlyDetected, detectedIds)) {
|
|
606
|
-
if (onlyDetected) skipped.push('antigravity');
|
|
607
|
-
} else try {
|
|
608
|
-
const agFile = antigravityMcpConfigPath(home, isWin);
|
|
609
|
-
const mcpCmd = resolveHyvMcpCommand(pkgDir);
|
|
610
|
-
const desired = { command: mcpCmd.command, args: mcpCmd.args };
|
|
611
|
-
const result = mergeJsonConfigFile(agFile, (config) => {
|
|
612
|
-
if (!config.mcpServers) config.mcpServers = {};
|
|
613
|
-
const existing = config.mcpServers.hyv;
|
|
614
|
-
if (!existing) {
|
|
615
|
-
config.mcpServers.hyv = desired;
|
|
616
|
-
configured.push('antigravity mcp');
|
|
617
|
-
} else if (force || !mcpJsonEntriesMatch(existing, desired)) {
|
|
618
|
-
config.mcpServers.hyv = desired;
|
|
619
|
-
configured.push('antigravity mcp (updated)');
|
|
620
|
-
}
|
|
621
|
-
return config;
|
|
622
|
-
});
|
|
623
|
-
if (!result.ok) warnings.push(`antigravity: could not update mcp config (${result.reason})`);
|
|
624
|
-
} catch (err) {
|
|
625
|
-
warnings.push(`antigravity: ${err.message}`);
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
// OpenCode — ~/.config/opencode/opencode.jsonc + AGENTS.md
|
|
629
|
-
if (!isAgentIdEnabled('opencode', onlyDetected, detectedIds)) {
|
|
630
|
-
if (onlyDetected) skipped.push('opencode');
|
|
631
|
-
} else try {
|
|
632
|
-
const ocDir = opencodeConfigDir(home, isWin);
|
|
633
|
-
const ocFile = path.join(ocDir, 'opencode.jsonc');
|
|
634
|
-
const cmdArr = resolveHyvMcpCommandArray(pkgDir);
|
|
635
|
-
const ocResult = mergeJsoncConfigFile(ocFile, (config) => {
|
|
636
|
-
if (!config.mcp) config.mcp = {};
|
|
637
|
-
const existing = config.mcp.hyv;
|
|
638
|
-
const desired = { type: 'local', command: cmdArr, enabled: true };
|
|
639
|
-
if (!existing) {
|
|
640
|
-
config.mcp.hyv = desired;
|
|
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)');
|
|
645
|
-
}
|
|
646
|
-
return config;
|
|
647
|
-
});
|
|
648
|
-
if (!ocResult.ok) warnings.push(`opencode: could not update opencode.jsonc (${ocResult.reason})`);
|
|
649
|
-
|
|
650
|
-
const agentsFile = path.join(ocDir, 'AGENTS.md');
|
|
651
|
-
const agentsSrc = path.join(pkgDir, 'agents', 'AGENTS.md');
|
|
652
|
-
let existing = '';
|
|
653
|
-
if (fs.existsSync(agentsFile)) existing = fs.readFileSync(agentsFile, 'utf-8');
|
|
654
|
-
const addition = fs.existsSync(agentsSrc) ? fs.readFileSync(agentsSrc, 'utf-8') : '';
|
|
655
|
-
if (addition && shouldUpgradeAgent(agentsFile, agentsMarker, pkgVersion)) {
|
|
656
|
-
const merged = mergeAgentsMd(existing, addition, 'hold-your-voice');
|
|
657
|
-
fs.mkdirSync(ocDir, { recursive: true });
|
|
658
|
-
fs.writeFileSync(agentsFile, merged);
|
|
659
|
-
configured.push('opencode agents');
|
|
660
|
-
} else if (!fs.existsSync(agentsFile) && addition) {
|
|
661
|
-
fs.mkdirSync(ocDir, { recursive: true });
|
|
662
|
-
fs.writeFileSync(agentsFile, `${addition.trim()}\n`);
|
|
663
|
-
configured.push('opencode agents');
|
|
664
|
-
}
|
|
665
|
-
} catch (err) {
|
|
666
|
-
warnings.push(`opencode: ${err.message}`);
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// ChatGPT — connector helper (UI has no config file; write exact values for manual add)
|
|
670
|
-
if (!isAgentIdEnabled('chatgpt', onlyDetected, detectedIds)) {
|
|
671
|
-
if (onlyDetected) skipped.push('chatgpt');
|
|
672
|
-
} else try {
|
|
673
|
-
const cgDir = chatgptHelperDir(home);
|
|
674
|
-
fs.mkdirSync(cgDir, { recursive: true });
|
|
675
|
-
const mcpCmd = resolveHyvMcpCommand(pkgDir);
|
|
676
|
-
const connectorGuide = [
|
|
677
|
-
'hold your voice — chatgpt desktop setup',
|
|
678
|
-
'',
|
|
679
|
-
'=== RECOMMENDED — PATH A: remote oauth (paid hyv users) ===',
|
|
680
|
-
' No local install. OAuth runs in ChatGPT\'s browser — no Windows firewall issues.',
|
|
681
|
-
' Requires: paid HYV plan + ChatGPT Desktop',
|
|
682
|
-
'',
|
|
683
|
-
' 1. In ChatGPT: Settings → turn Developer mode ON',
|
|
684
|
-
' (Accept the elevated risk warning — expected, not a HYV bug)',
|
|
685
|
-
' 2. Settings → Apps → New App',
|
|
686
|
-
' 3. Fill in:',
|
|
687
|
-
' Name: hold your voice',
|
|
688
|
-
' Description: Scans writing for AI patterns and voice drift',
|
|
689
|
-
' Connection: Server URL → https://holdyourvoice.com/mcp',
|
|
690
|
-
' Authentication: OAuth',
|
|
691
|
-
' Checkbox: "I understand and want to continue"',
|
|
692
|
-
' 4. Save → complete OAuth sign-in to holdyourvoice.com',
|
|
693
|
-
' 5. Test: "scan this with hold your voice"',
|
|
694
|
-
'',
|
|
695
|
-
' Cloud app setup: https://holdyourvoice.com/app/api/mcp',
|
|
696
|
-
' Wiki guide: https://holdyourvoice.com/wiki/use-hyv-in-chatgpt',
|
|
697
|
-
'',
|
|
698
|
-
'=== FALLBACK — PATH B: local connector (free users only) ===',
|
|
699
|
-
' Only if you do not have a paid HYV plan. Requires Node.js 18+.',
|
|
700
|
-
'',
|
|
701
|
-
' 1. Install hyv: npm i -g @holdyourvoice/hyv@latest && hyv welcome',
|
|
702
|
-
' 2. In ChatGPT: Settings → Connectors (or https://chatgpt.com/#settings/Connectors)',
|
|
703
|
-
' 3. Add connector:',
|
|
704
|
-
` Name: hold your voice`,
|
|
705
|
-
` Command: ${mcpCmd.command}`,
|
|
706
|
-
` Arguments: ${mcpCmd.args.join(' ')}`,
|
|
707
|
-
' 4. Save, restart ChatGPT Desktop',
|
|
708
|
-
' 5. Test: "scan this with hold your voice"',
|
|
709
|
-
'',
|
|
710
|
-
' If connector fails to start, use the absolute paths above.',
|
|
711
|
-
'',
|
|
712
|
-
'=== not seeing "New App"? ===',
|
|
713
|
-
' • Turn Developer mode ON in ChatGPT settings — Path A needs it',
|
|
714
|
-
' • If you only see Connectors, you are on Path B (free fallback)',
|
|
715
|
-
' • Browser ChatGPT cannot use MCP — desktop app only',
|
|
716
|
-
'',
|
|
717
|
-
'More help: hyv mcp --setup-chatgpt',
|
|
718
|
-
'',
|
|
719
|
-
].join('\n');
|
|
720
|
-
const guideFile = path.join(cgDir, 'hyv-mcp-connector.txt');
|
|
721
|
-
fs.writeFileSync(guideFile, connectorGuide);
|
|
722
|
-
configured.push('chatgpt connector guide');
|
|
723
|
-
|
|
724
|
-
const chatgptSrc = path.join(pkgDir, 'agents', 'chatgpt.md');
|
|
725
|
-
const instrFile = path.join(cgDir, 'hyv-instructions.txt');
|
|
726
|
-
if (fs.existsSync(chatgptSrc)) installAgent(chatgptSrc, instrFile);
|
|
727
|
-
} catch (err) {
|
|
728
|
-
warnings.push(`chatgpt: ${err.message}`);
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
// Generic reference copy (always when auto-configure runs)
|
|
732
|
-
try {
|
|
733
|
-
const genericSrc = path.join(pkgDir, 'agents', 'AGENTS.md');
|
|
734
|
-
const genericDest = path.join(hyvDir, 'agents', 'AGENTS.md');
|
|
735
|
-
if (fs.existsSync(genericSrc)) installAgent(genericSrc, genericDest);
|
|
736
|
-
} catch {
|
|
737
|
-
// non-fatal
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
writeAgentsMarker(hyvDir, pkgVersion);
|
|
741
|
-
if (!quiet && warnings.length) {
|
|
742
|
-
for (const w of warnings) process.stderr.write(`[hyv postinstall] warning: ${w}\n`);
|
|
743
|
-
}
|
|
744
|
-
return {
|
|
745
|
-
configured: [...new Set(configured)],
|
|
746
|
-
warnings,
|
|
747
|
-
pkgVersion,
|
|
748
|
-
detected,
|
|
749
|
-
skipped: [...new Set(skipped)],
|
|
750
|
-
};
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
/**
|
|
754
|
-
* Detect installed apps and configure hyv MCP/rules for each one.
|
|
755
|
-
* @returns {{ detected: object[], configured: string[], warnings: string[], skipped: string[] }}
|
|
756
|
-
*/
|
|
757
|
-
function integrateDetectedAgents({ pkgDir, home = require('os').homedir(), quiet = false, force = false } = {}) {
|
|
758
|
-
const detected = detectInstalledAgents(home);
|
|
759
|
-
if (detected.length === 0) {
|
|
760
|
-
return { detected, configured: [], warnings: [], skipped: [] };
|
|
761
|
-
}
|
|
762
|
-
const result = setupAgents({ pkgDir, home, quiet, onlyDetected: true, force });
|
|
763
|
-
return {
|
|
764
|
-
detected: result.detected || detected,
|
|
765
|
-
configured: result.configured || [],
|
|
766
|
-
warnings: result.warnings || [],
|
|
767
|
-
skipped: result.skipped || [],
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
module.exports = {
|
|
772
|
-
readPkgVersion,
|
|
773
|
-
readAgentsMarker,
|
|
774
|
-
shouldUpgradeAgent,
|
|
775
|
-
copyAgent,
|
|
776
|
-
writeAgentsMarker,
|
|
777
|
-
onboardingPath,
|
|
778
|
-
hasCompletedOnboarding,
|
|
779
|
-
markOnboardingComplete,
|
|
780
|
-
resolveHyvMcpCommand,
|
|
781
|
-
resolveHyvMcpCommandArray,
|
|
782
|
-
parseJsonc,
|
|
783
|
-
mergeJsonConfigFile,
|
|
784
|
-
mergeJsoncConfigFile,
|
|
785
|
-
toCursorMdc,
|
|
786
|
-
toWindsurfRule,
|
|
787
|
-
setupAgents,
|
|
788
|
-
detectInstalledAgents,
|
|
789
|
-
integrateDetectedAgents,
|
|
790
|
-
agentIdForConfiguredLabel,
|
|
791
|
-
isAgentIntegrated,
|
|
792
|
-
claudeDesktopDir,
|
|
793
|
-
antigravityMcpConfigPath,
|
|
794
|
-
opencodeConfigDir,
|
|
795
|
-
chatgptHelperDir,
|
|
796
|
-
};
|