@holdyourvoice/hyv 2.9.24 → 2.9.26
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 +14 -0
- package/assets/ai-eliminator-rules.md +10 -0
- package/dist/index.js +206 -29
- package/package.json +1 -1
- package/scripts/postinstall-lib.js +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable CLI changes. Also mirrored to [holdyourvoice.com/changelog](https://holdyourvoice.com/changelog) for user-facing releases.
|
|
4
4
|
|
|
5
|
+
## [2.9.26] — 2026-07-09
|
|
6
|
+
|
|
7
|
+
### Improved
|
|
8
|
+
- **Tier 1 structural detection** — cross-sentence binary reframes (`this is not X. it is Y.`), sermon anaphora, and founder-carousel metaphors now sort first in scan output and rewrite prompts
|
|
9
|
+
- **Rewrite prompts** split tier 1 structural hits vs other patterns so the model fixes architecture before vocabulary slop
|
|
10
|
+
- **18 new structural cousin rules** synced in local scan engine (`scan.ts`, `tier-one.ts`)
|
|
11
|
+
|
|
12
|
+
## [2.9.25] — 2026-07-04
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- **MCP stdio pollution** — `hyv mcp` no longer prints setup banners to stdout when spawned by Cursor/Claude/ChatGPT (fixes "Unexpected token … is not valid JSON" toast spam on app open)
|
|
16
|
+
- Auto-detects piped stdout (`!isTTY`) or explicit `--stdio` flag; MCP configs now use `mcp --stdio`
|
|
17
|
+
- Interactive terminal runs still show setup on stderr before the JSON-RPC server starts
|
|
18
|
+
|
|
5
19
|
## [2.9.15] — 2026-06-18
|
|
6
20
|
|
|
7
21
|
### Added
|
|
@@ -21,6 +21,16 @@ patterns, and uniform sentence rhythm within paragraphs.
|
|
|
21
21
|
- "It's not X, it's Y" / "The point isn't X, it's Y"
|
|
22
22
|
- "You don't need X, you need Y"
|
|
23
23
|
- "Not just X, but Y" / "Not only... but also"
|
|
24
|
+
- **structural cousins (top priority):** cross-sentence forms the vocabulary scan misses:
|
|
25
|
+
- "This is not X. It is Y." / "Is not X. It is Y."
|
|
26
|
+
- "You are not X. You are Y." / "You are no longer X. You are Y."
|
|
27
|
+
- "You stop X. You start Y."
|
|
28
|
+
- "They don't need X. They need Y."
|
|
29
|
+
- "They call it X. They call it Y." (staccato anaphora)
|
|
30
|
+
- Parallel sermon: "X because… / they X because…" and "endure through… / they endure through…"
|
|
31
|
+
- Founder-carousel metaphors: "competing with the void", "architect of a new reality", "friction into momentum", "cognitive architecture", "defines what good looks like", "the hidden goal", "quiet promise", "simple but uncomfortable:"
|
|
32
|
+
|
|
33
|
+
**Tier 1 priority:** rules with ids `structural_*` and `founder_*` are primary flags. They sort first in scan output, win hit-budget slots in the editor, weigh heavier in cleanliness scoring, lead rewrite prompts, and gate rewrite output before vocabulary slop.
|
|
24
34
|
|
|
25
35
|
### Staccato drama (#2, #95, #96)
|
|
26
36
|
- Short punchy sentence runs: "No X. No Y. Just Z."
|
package/dist/index.js
CHANGED
|
@@ -5413,7 +5413,7 @@ function printFreePaidMatrix(opts = {}) {
|
|
|
5413
5413
|
First month $1 \u2192 ${PRICING_URL}`));
|
|
5414
5414
|
console.log(import_chalk.default.dim(" hyv init | hyv plan --upgrade\n"));
|
|
5415
5415
|
}
|
|
5416
|
-
var import_chalk, NPX_EXAMPLES, FREE_CLI_COMMANDS, PAID_FEATURES, FREE_WEB_TOOLS, COMMUNITY_URL, PRICING_URL, DASHBOARD_BILLING_URL;
|
|
5416
|
+
var import_chalk, NPX_EXAMPLES, FREE_CLI_COMMANDS, PAID_FEATURES, FREE_WEB_TOOLS, COMMUNITY_URL, X_URL, PRICING_URL, DASHBOARD_BILLING_URL;
|
|
5417
5417
|
var init_free_paid = __esm({
|
|
5418
5418
|
"src/lib/free-paid.ts"() {
|
|
5419
5419
|
"use strict";
|
|
@@ -5460,6 +5460,7 @@ var init_free_paid = __esm({
|
|
|
5460
5460
|
{ name: "Blog", url: "https://holdyourvoice.com/blog" }
|
|
5461
5461
|
];
|
|
5462
5462
|
COMMUNITY_URL = "https://holdyourvoice.com/community";
|
|
5463
|
+
X_URL = "https://x.com/istupidpreneur";
|
|
5463
5464
|
PRICING_URL = "https://holdyourvoice.com/#pricing";
|
|
5464
5465
|
DASHBOARD_BILLING_URL = "https://holdyourvoice.com/dashboard?tab=billing";
|
|
5465
5466
|
}
|
|
@@ -11315,6 +11316,23 @@ var init_patterns = __esm({
|
|
|
11315
11316
|
{ id: "struct.lesson-setup", category: "structure", severity: "yellow", regex: /\bhere'?s what .{3,60} taught\b/gi, suggestion: "just share the lesson" },
|
|
11316
11317
|
// THE BIG ONE — antithesis negation pattern (Voice DNA: FATAL)
|
|
11317
11318
|
{ id: "struct.this-isnt-x-this-is-y", category: "structure", severity: "red", regex: /\bthis isn'?t .{2,40}\.?\s*(?:this is|it'?s) .{2,40}/gi, suggestion: "FATAL: delete the negation, just state the positive claim" },
|
|
11319
|
+
{ id: "struct.this-is-not-it-is", category: "structure", severity: "red", regex: /\bthis\s+is\s+not\b.{3,80}\.\s*it\s+is\b/gi, suggestion: 'FATAL: "this is not X. it is Y" \u2014 state what it is directly' },
|
|
11320
|
+
{ id: "struct.is-not-it-is", category: "structure", severity: "red", regex: /\bis\s+not\b.{3,80}\.\s*it\s+is\b/gi, suggestion: "FATAL: negation-then-reveal \u2014 drop the denial" },
|
|
11321
|
+
{ id: "struct.you-are-not-you-are", category: "structure", severity: "red", regex: /\byou\s+are\s+not\b.{3,80}\.\s*you\s+are\b/gi, suggestion: "FATAL: identity reframe carousel \u2014 say what they are" },
|
|
11322
|
+
{ id: "struct.you-no-longer-you-are", category: "structure", severity: "red", regex: /\byou\s+are\s+no\s+longer\b.{3,80}\.\s*you\s+are\b/gi, suggestion: "FATAL: escalation reframe \u2014 one grounded sentence" },
|
|
11323
|
+
{ id: "struct.you-stop-you-start", category: "structure", severity: "red", regex: /\byou\s+stop\b.{3,50}\.\s*you\s+start\b/gi, suggestion: "FATAL: pivot cadence \u2014 describe the behavior change" },
|
|
11324
|
+
{ id: "struct.need-negation-pivot", category: "structure", severity: "red", regex: /\b(?:do\s+not|don'?t)\s+need\b.{3,50}\.\s*(?:they|you)\s+need\b/gi, suggestion: "FATAL: need antithesis \u2014 say what they need" },
|
|
11325
|
+
{ id: "struct.they-call-it-anaphora", category: "structure", severity: "red", regex: /\bthey\s+call\s+it\b[^.!?]{2,50}[.!?]\s*they\s+call\s+it\b/gi, suggestion: "FATAL: staccato anaphora \u2014 name it once" },
|
|
11326
|
+
{ id: "struct.parallel-because", category: "structure", severity: "red", regex: /\b(\w+)\s+because\b[^.!?]{3,60}[.!?]\s*they\s+\1\s+because\b/gi, suggestion: "FATAL: parallel sermon rhythm \u2014 merge into one sentence" },
|
|
11327
|
+
{ id: "struct.parallel-endure", category: "structure", severity: "red", regex: /\bendure\s+through\b[^.!?]{3,60}[.!?]\s*they\s+endure\s+through\b/gi, suggestion: "FATAL: parallel maxim \u2014 say it once" },
|
|
11328
|
+
{ id: "struct.simple-but-uncomfortable", category: "structure", severity: "red", regex: /\bsimple\s+but\s+uncomfortable\s*:/gi, suggestion: "truth-posture colon reveal \u2014 state the mechanism" },
|
|
11329
|
+
{ id: "struct.hidden-goal", category: "structure", severity: "red", regex: /\bthe\s+hidden\s+goal\b/gi, suggestion: "reveal framing \u2014 say the goal directly" },
|
|
11330
|
+
{ id: "struct.quiet-promise", category: "structure", severity: "red", regex: /\bquiet\s+promise\b/gi, suggestion: "profundity metaphor \u2014 name the actual commitment" },
|
|
11331
|
+
{ id: "struct.competing-void", category: "structure", severity: "red", regex: /\bcompeting\s+with\s+the\s+void\b/gi, suggestion: "screenshot profundity \u2014 name the real competitor" },
|
|
11332
|
+
{ id: "struct.architect-reality", category: "structure", severity: "red", regex: /\barchitect\s+of\s+(?:a\s+)?new\s+reality\b/gi, suggestion: "thought-leader grandiosity \u2014 describe what you build" },
|
|
11333
|
+
{ id: "struct.friction-momentum", category: "structure", severity: "red", regex: /\b(?:turns?\s+)?friction\s+into\s+momentum\b/gi, suggestion: "transformation buzzphrase \u2014 explain the mechanism" },
|
|
11334
|
+
{ id: "struct.cognitive-architecture", category: "structure", severity: "red", regex: /\bcognitive\s+architecture\b/gi, suggestion: "abstract systems metaphor \u2014 use a concrete scene" },
|
|
11335
|
+
{ id: "struct.defines-good", category: "structure", severity: "red", regex: /\bdefines\s+what\s+good\s+looks\s+like\b/gi, suggestion: "consultant language \u2014 show an example of good" },
|
|
11318
11336
|
{ id: "struct.not-x-y", category: "structure", severity: "red", regex: /\bnot .{2,30}\.?\s*.{2,30}\b/gi, suggestion: 'the "Not X. Y." pattern is an AI tell \u2014 just state Y' },
|
|
11319
11337
|
{ id: "struct.forget-x", category: "structure", severity: "red", regex: /\bforget .{2,40}\.?\s*(?:this is|it'?s|you need)/gi, suggestion: "don't negate \u2014 just state what you mean" }
|
|
11320
11338
|
];
|
|
@@ -11374,6 +11392,34 @@ var init_patterns = __esm({
|
|
|
11374
11392
|
}
|
|
11375
11393
|
});
|
|
11376
11394
|
|
|
11395
|
+
// src/lib/tier-one.ts
|
|
11396
|
+
function isTierOneRule(ruleId) {
|
|
11397
|
+
const id = String(ruleId || "");
|
|
11398
|
+
if (TIER_ONE_PREFIXES.some((p) => id.startsWith(p)))
|
|
11399
|
+
return true;
|
|
11400
|
+
return TIER_ONE_SIGNAL_IDS.has(id);
|
|
11401
|
+
}
|
|
11402
|
+
function tierSortKey(ruleId) {
|
|
11403
|
+
return isTierOneRule(ruleId) ? 0 : 1;
|
|
11404
|
+
}
|
|
11405
|
+
var TIER_ONE_PREFIXES, TIER_ONE_SIGNAL_IDS;
|
|
11406
|
+
var init_tier_one = __esm({
|
|
11407
|
+
"src/lib/tier-one.ts"() {
|
|
11408
|
+
"use strict";
|
|
11409
|
+
TIER_ONE_PREFIXES = ["structural_", "founder_"];
|
|
11410
|
+
TIER_ONE_SIGNAL_IDS = /* @__PURE__ */ new Set([
|
|
11411
|
+
"struct.this-isnt-x-this-is-y",
|
|
11412
|
+
"struct.not-x-y",
|
|
11413
|
+
"struct.negation-cascade",
|
|
11414
|
+
"struct.which-is-another",
|
|
11415
|
+
"struct.in-other-words",
|
|
11416
|
+
"struct.moment-becomes",
|
|
11417
|
+
"struct.same-better",
|
|
11418
|
+
"struct.generic-buyer"
|
|
11419
|
+
]);
|
|
11420
|
+
}
|
|
11421
|
+
});
|
|
11422
|
+
|
|
11377
11423
|
// src/lib/scan.ts
|
|
11378
11424
|
function words(text) {
|
|
11379
11425
|
return (text || "").match(/[a-zA-Z][a-zA-Z0-9']*/g) || [];
|
|
@@ -11484,6 +11530,9 @@ function scanText(text) {
|
|
|
11484
11530
|
}
|
|
11485
11531
|
}
|
|
11486
11532
|
return hits.sort((a, b) => {
|
|
11533
|
+
const tier = tierSortKey(a.rule) - tierSortKey(b.rule);
|
|
11534
|
+
if (tier !== 0)
|
|
11535
|
+
return tier;
|
|
11487
11536
|
if (a.line !== b.line)
|
|
11488
11537
|
return a.line - b.line;
|
|
11489
11538
|
return a.rule.localeCompare(b.rule);
|
|
@@ -11494,6 +11543,7 @@ var init_scan = __esm({
|
|
|
11494
11543
|
"src/lib/scan.ts"() {
|
|
11495
11544
|
"use strict";
|
|
11496
11545
|
init_patterns();
|
|
11546
|
+
init_tier_one();
|
|
11497
11547
|
AI_PATTERN_RULES = [
|
|
11498
11548
|
{
|
|
11499
11549
|
id: "ai_antithesis",
|
|
@@ -11554,6 +11604,75 @@ var init_scan = __esm({
|
|
|
11554
11604
|
{
|
|
11555
11605
|
id: "founder_cadence_same_better",
|
|
11556
11606
|
pattern: /\bsame\s+[^.!?\n]{1,35}[.!?]\s*(?:better|nicer|cleaner|calmer|safer)\s+[^.!?\n]{1,35}[.!?]?/i
|
|
11607
|
+
},
|
|
11608
|
+
// structural cousins — cross-sentence binary reframing (top priority)
|
|
11609
|
+
{
|
|
11610
|
+
id: "structural_this_is_not_it_is",
|
|
11611
|
+
pattern: /\bthis\s+is\s+not\b.{3,80}\.\s*it\s+is\b/i
|
|
11612
|
+
},
|
|
11613
|
+
{
|
|
11614
|
+
id: "structural_is_not_it_is",
|
|
11615
|
+
pattern: /\bis\s+not\b.{3,80}\.\s*it\s+is\b/i
|
|
11616
|
+
},
|
|
11617
|
+
{
|
|
11618
|
+
id: "structural_you_are_not_you_are",
|
|
11619
|
+
pattern: /\byou\s+are\s+not\b.{3,80}\.\s*you\s+are\b/i
|
|
11620
|
+
},
|
|
11621
|
+
{
|
|
11622
|
+
id: "structural_you_no_longer_you_are",
|
|
11623
|
+
pattern: /\byou\s+are\s+no\s+longer\b.{3,80}\.\s*you\s+are\b/i
|
|
11624
|
+
},
|
|
11625
|
+
{
|
|
11626
|
+
id: "structural_you_stop_you_start",
|
|
11627
|
+
pattern: /\byou\s+stop\b.{3,50}\.\s*you\s+start\b/i
|
|
11628
|
+
},
|
|
11629
|
+
{
|
|
11630
|
+
id: "structural_need_negation_pivot",
|
|
11631
|
+
pattern: /\b(?:do\s+not|don'?t)\s+need\b.{3,50}\.\s*(?:they|you)\s+need\b/i
|
|
11632
|
+
},
|
|
11633
|
+
{
|
|
11634
|
+
id: "structural_they_call_it_anaphora",
|
|
11635
|
+
pattern: /\bthey\s+call\s+it\b[^.!?]{2,50}[.!?]\s*they\s+call\s+it\b/i
|
|
11636
|
+
},
|
|
11637
|
+
{
|
|
11638
|
+
id: "structural_parallel_verb_because",
|
|
11639
|
+
pattern: /\b(\w+)\s+because\b[^.!?]{3,60}[.!?]\s*they\s+\1\s+because\b/i
|
|
11640
|
+
},
|
|
11641
|
+
{
|
|
11642
|
+
id: "structural_parallel_endure_through",
|
|
11643
|
+
pattern: /\bendure\s+through\b[^.!?]{3,60}[.!?]\s*they\s+endure\s+through\b/i
|
|
11644
|
+
},
|
|
11645
|
+
{
|
|
11646
|
+
id: "structural_simple_but_uncomfortable",
|
|
11647
|
+
pattern: /\bsimple\s+but\s+uncomfortable\s*:/i
|
|
11648
|
+
},
|
|
11649
|
+
{
|
|
11650
|
+
id: "structural_hidden_goal",
|
|
11651
|
+
pattern: /\bthe\s+hidden\s+goal\b/i
|
|
11652
|
+
},
|
|
11653
|
+
{
|
|
11654
|
+
id: "structural_quiet_promise",
|
|
11655
|
+
pattern: /\bquiet\s+promise\b/i
|
|
11656
|
+
},
|
|
11657
|
+
{
|
|
11658
|
+
id: "founder_competing_with_void",
|
|
11659
|
+
pattern: /\bcompeting\s+with\s+the\s+void\b/i
|
|
11660
|
+
},
|
|
11661
|
+
{
|
|
11662
|
+
id: "founder_architect_of_reality",
|
|
11663
|
+
pattern: /\barchitect\s+of\s+(?:a\s+)?new\s+reality\b/i
|
|
11664
|
+
},
|
|
11665
|
+
{
|
|
11666
|
+
id: "founder_friction_into_momentum",
|
|
11667
|
+
pattern: /\b(?:turns?\s+)?friction\s+into\s+momentum\b/i
|
|
11668
|
+
},
|
|
11669
|
+
{
|
|
11670
|
+
id: "founder_cognitive_architecture",
|
|
11671
|
+
pattern: /\bcognitive\s+architecture\b/i
|
|
11672
|
+
},
|
|
11673
|
+
{
|
|
11674
|
+
id: "founder_defines_good_looks_like",
|
|
11675
|
+
pattern: /\bdefines\s+what\s+good\s+looks\s+like\b/i
|
|
11557
11676
|
}
|
|
11558
11677
|
];
|
|
11559
11678
|
ABSTRACT_STYLE_WORDS = /* @__PURE__ */ new Set([
|
|
@@ -16371,6 +16490,8 @@ var path11 = __toESM(require("path"));
|
|
|
16371
16490
|
init_config();
|
|
16372
16491
|
init_signals();
|
|
16373
16492
|
init_profile_parse();
|
|
16493
|
+
init_scan();
|
|
16494
|
+
init_tier_one();
|
|
16374
16495
|
function loadPromptTemplate() {
|
|
16375
16496
|
const templatePath = path11.join(CACHE_DIR2, "prompt-template.md");
|
|
16376
16497
|
try {
|
|
@@ -16419,6 +16540,7 @@ Rules:
|
|
|
16419
16540
|
- "Not X. Y." is banned. Just state Y.
|
|
16420
16541
|
- "Forget X. This is Y." is banned. Just state Y.
|
|
16421
16542
|
- ANY sentence that negates one framing then asserts a corrected one fails.
|
|
16543
|
+
- Tier 1 structural patterns (binary reframes, negation pivots, parallel sermon cadence) outrank vocabulary fixes. Kill the architecture first.
|
|
16422
16544
|
|
|
16423
16545
|
Voice profile:
|
|
16424
16546
|
{profile_block}
|
|
@@ -16426,8 +16548,11 @@ Voice profile:
|
|
|
16426
16548
|
Extra constraints:
|
|
16427
16549
|
{constraints_block}
|
|
16428
16550
|
|
|
16429
|
-
|
|
16430
|
-
{
|
|
16551
|
+
Tier 1 \u2014 structural (fix first):
|
|
16552
|
+
{tier_one_lines}
|
|
16553
|
+
|
|
16554
|
+
Other flagged patterns:
|
|
16555
|
+
{other_lines}
|
|
16431
16556
|
|
|
16432
16557
|
Draft with line numbers ({draft_name}):
|
|
16433
16558
|
{numbered_draft}
|
|
@@ -16487,6 +16612,38 @@ function buildProfileBlock(fullProfile, fallbackName) {
|
|
|
16487
16612
|
}
|
|
16488
16613
|
return { block: "(no voice profile supplied \u2014 use generic voice rules)", name: null };
|
|
16489
16614
|
}
|
|
16615
|
+
function collectPromptIssues(draftContent, fullProfile) {
|
|
16616
|
+
const issues = [];
|
|
16617
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16618
|
+
const addIssue = (line, rule, detail) => {
|
|
16619
|
+
const key = `${line}|${rule}|${detail.slice(0, 80)}`;
|
|
16620
|
+
if (seen.has(key))
|
|
16621
|
+
return;
|
|
16622
|
+
seen.add(key);
|
|
16623
|
+
issues.push({ line, rule, detail, tierOne: isTierOneRule(rule) });
|
|
16624
|
+
};
|
|
16625
|
+
for (const signal of extractSignals(draftContent, fullProfile).signals) {
|
|
16626
|
+
addIssue(signal.line, signal.id, signal.suggestion);
|
|
16627
|
+
}
|
|
16628
|
+
for (const hit of scanText(draftContent)) {
|
|
16629
|
+
addIssue(hit.line, hit.rule, hit.phrase || hit.text || "");
|
|
16630
|
+
}
|
|
16631
|
+
issues.sort((a, b) => {
|
|
16632
|
+
const tier = tierSortKey(a.rule) - tierSortKey(b.rule);
|
|
16633
|
+
if (tier !== 0)
|
|
16634
|
+
return tier;
|
|
16635
|
+
if (a.line !== b.line)
|
|
16636
|
+
return a.line - b.line;
|
|
16637
|
+
return a.rule.localeCompare(b.rule);
|
|
16638
|
+
});
|
|
16639
|
+
return issues;
|
|
16640
|
+
}
|
|
16641
|
+
function formatIssueLines(issues, tierOneOnly) {
|
|
16642
|
+
const filtered = issues.filter((i) => i.tierOne === tierOneOnly);
|
|
16643
|
+
if (!filtered.length)
|
|
16644
|
+
return "- none found";
|
|
16645
|
+
return filtered.map((i) => `- line ${i.line} [${i.rule}]: ${i.detail}`).join("\n");
|
|
16646
|
+
}
|
|
16490
16647
|
function buildRewritePrompt(options) {
|
|
16491
16648
|
const {
|
|
16492
16649
|
draftPath,
|
|
@@ -16497,16 +16654,17 @@ function buildRewritePrompt(options) {
|
|
|
16497
16654
|
skipScan = false
|
|
16498
16655
|
} = options;
|
|
16499
16656
|
const { block: profileBlock, name: profileUsed } = buildProfileBlock(fullProfile, profileName);
|
|
16500
|
-
const
|
|
16501
|
-
const
|
|
16657
|
+
const issues = skipScan ? [] : collectPromptIssues(draftContent, fullProfile);
|
|
16658
|
+
const tierOneLines = formatIssueLines(issues, true);
|
|
16659
|
+
const otherLines = formatIssueLines(issues, false);
|
|
16502
16660
|
const draftLines = draftContent.split("\n");
|
|
16503
16661
|
const numberedDraft = draftLines.map((line, i) => `${i + 1}: ${line}`).join("\n");
|
|
16504
16662
|
const template = loadPromptTemplate();
|
|
16505
|
-
const prompt = template.replace("{profile_block}", profileBlock).replace("{constraints_block}", constraints || "(none)").replace("{
|
|
16663
|
+
const prompt = template.replace("{profile_block}", profileBlock).replace("{constraints_block}", constraints || "(none)").replace("{tier_one_lines}", tierOneLines).replace("{other_lines}", otherLines).replace("{draft_name}", draftPath).replace("{numbered_draft}", numberedDraft);
|
|
16506
16664
|
return {
|
|
16507
16665
|
prompt,
|
|
16508
16666
|
profileUsed,
|
|
16509
|
-
issuesFound:
|
|
16667
|
+
issuesFound: issues.length
|
|
16510
16668
|
};
|
|
16511
16669
|
}
|
|
16512
16670
|
|
|
@@ -17766,6 +17924,18 @@ var import_child_process4 = require("child_process");
|
|
|
17766
17924
|
// src/lib/cli-entry.ts
|
|
17767
17925
|
var fs18 = __toESM(require("fs"));
|
|
17768
17926
|
var path17 = __toESM(require("path"));
|
|
17927
|
+
|
|
17928
|
+
// src/lib/mcp-stdio.ts
|
|
17929
|
+
function isMcpStdioMode(opts) {
|
|
17930
|
+
if (opts?.stdio)
|
|
17931
|
+
return true;
|
|
17932
|
+
if (process.env.HYV_MCP_STDIO === "1" || process.env.HYV_MCP_STDIO === "true")
|
|
17933
|
+
return true;
|
|
17934
|
+
return !process.stdout.isTTY;
|
|
17935
|
+
}
|
|
17936
|
+
var MCP_SERVER_ARGS = ["mcp", "--stdio"];
|
|
17937
|
+
|
|
17938
|
+
// src/lib/cli-entry.ts
|
|
17769
17939
|
function resolveCliEntry() {
|
|
17770
17940
|
const candidates = [
|
|
17771
17941
|
path17.resolve(process.argv[1] || ""),
|
|
@@ -17777,9 +17947,9 @@ function resolveCliEntry() {
|
|
|
17777
17947
|
function mcpServerCommand() {
|
|
17778
17948
|
const entry = resolveCliEntry();
|
|
17779
17949
|
if (entry) {
|
|
17780
|
-
return { command: process.execPath, args: [entry,
|
|
17950
|
+
return { command: process.execPath, args: [entry, ...MCP_SERVER_ARGS] };
|
|
17781
17951
|
}
|
|
17782
|
-
return { command: "hyv", args: [
|
|
17952
|
+
return { command: "hyv", args: [...MCP_SERVER_ARGS] };
|
|
17783
17953
|
}
|
|
17784
17954
|
function mcpServerSnippet() {
|
|
17785
17955
|
const { command, args: args2 } = mcpServerCommand();
|
|
@@ -17792,9 +17962,9 @@ async function testMcpStdioSubprocess() {
|
|
|
17792
17962
|
if (!entry)
|
|
17793
17963
|
return { ok: false };
|
|
17794
17964
|
return new Promise((resolve18) => {
|
|
17795
|
-
const child = (0, import_child_process4.spawn)(process.execPath, [entry, "mcp"], {
|
|
17965
|
+
const child = (0, import_child_process4.spawn)(process.execPath, [entry, "mcp", "--stdio"], {
|
|
17796
17966
|
stdio: ["pipe", "pipe", "pipe"],
|
|
17797
|
-
env: { ...process.env, HYV_POSTINSTALL_QUIET: "1" }
|
|
17967
|
+
env: { ...process.env, HYV_POSTINSTALL_QUIET: "1", HYV_MCP_STDIO: "1" }
|
|
17798
17968
|
});
|
|
17799
17969
|
let buffer = "";
|
|
17800
17970
|
let settled = false;
|
|
@@ -19159,16 +19329,17 @@ More: hyv content ci`
|
|
|
19159
19329
|
},
|
|
19160
19330
|
share: {
|
|
19161
19331
|
title: "Share your voice fingerprint",
|
|
19162
|
-
body: `Social post template:
|
|
19332
|
+
body: `Social post template (X / LinkedIn):
|
|
19163
19333
|
|
|
19164
19334
|
I run my AI drafts through @holdyourvoice/hyv before they ship.
|
|
19165
19335
|
|
|
19166
|
-
Free local scan caught
|
|
19336
|
+
Free local scan caught 34 patterns in 2 seconds \u2014 score 0/100. no API, offline.
|
|
19167
19337
|
|
|
19168
19338
|
Cursor/Claude agents can call hyv mcp tools directly.
|
|
19169
19339
|
|
|
19170
19340
|
Try: npx @holdyourvoice/hyv welcome
|
|
19171
19341
|
|
|
19342
|
+
${X_URL}
|
|
19172
19343
|
${COMMUNITY_URL}`
|
|
19173
19344
|
}
|
|
19174
19345
|
};
|
|
@@ -19448,7 +19619,7 @@ function formatChatGptConnectorGuide() {
|
|
|
19448
19619
|
"2. Settings \u2192 Connectors \u2192 Add connector",
|
|
19449
19620
|
"3. Name: **hold your voice**",
|
|
19450
19621
|
"4. Command: **hyv** (or absolute path below if connector fails)",
|
|
19451
|
-
"5. Arguments: **mcp**",
|
|
19622
|
+
"5. Arguments: **mcp --stdio**",
|
|
19452
19623
|
"",
|
|
19453
19624
|
"If the connector fails to start, use absolute paths:",
|
|
19454
19625
|
` command: ${mcpCmd.command}`,
|
|
@@ -20334,7 +20505,7 @@ function printMcpSetup() {
|
|
|
20334
20505
|
console.log(import_chalk35.default.dim(" Skill: ~/.commandcode/skills/hyv/SKILL.md\n"));
|
|
20335
20506
|
console.log(import_chalk35.default.bold("ChatGPT Desktop (manual connector)"));
|
|
20336
20507
|
console.log(import_chalk35.default.dim(" Settings \u2192 Connectors \u2192 add connector"));
|
|
20337
|
-
console.log(import_chalk35.default.dim(" Name: hold your voice | Command: hyv | Arguments: mcp"));
|
|
20508
|
+
console.log(import_chalk35.default.dim(" Name: hold your voice | Command: hyv | Arguments: mcp --stdio"));
|
|
20338
20509
|
console.log(import_chalk35.default.dim(" Guide: ~/.chatgpt/hyv-mcp-connector.txt (after hyv doctor --fix-agents)"));
|
|
20339
20510
|
console.log(import_chalk35.default.dim(" hyv mcp --setup-chatgpt"));
|
|
20340
20511
|
if (IS_WIN2) {
|
|
@@ -20638,7 +20809,7 @@ registerWelcomeCommand(program2);
|
|
|
20638
20809
|
registerContentCommand(program2);
|
|
20639
20810
|
registerUpgradeCommand(program2);
|
|
20640
20811
|
registerShareCommand(program2);
|
|
20641
|
-
program2.command("mcp").description("Detect installed apps, configure MCP, and start the MCP server").option("--setup", "Show MCP setup for Claude Desktop, Cursor, Windsurf, Codex").option("--test", "Run MCP health check (tools, profile, stdio)").option("--setup-chatgpt", "Show ChatGPT connector setup instructions").option("--force", "Force re-configure MCP even if already integrated").action(async (opts) => {
|
|
20812
|
+
program2.command("mcp").description("Detect installed apps, configure MCP, and start the MCP server").option("--setup", "Show MCP setup for Claude Desktop, Cursor, Windsurf, Codex").option("--test", "Run MCP health check (tools, profile, stdio)").option("--setup-chatgpt", "Show ChatGPT connector setup instructions").option("--force", "Force re-configure MCP even if already integrated").option("--stdio", "Stdio MCP mode \u2014 no stdout banners (auto when stdout is piped)").action(async (opts) => {
|
|
20642
20813
|
if (opts.setup) {
|
|
20643
20814
|
printMcpSetup();
|
|
20644
20815
|
return;
|
|
@@ -20675,7 +20846,7 @@ program2.command("mcp").description("Detect installed apps, configure MCP, and s
|
|
|
20675
20846
|
console.log(import_chalk36.default.dim(" 3. Add connector:"));
|
|
20676
20847
|
console.log(import_chalk36.default.dim(" Name: ") + import_chalk36.default.cyan("hold your voice"));
|
|
20677
20848
|
console.log(import_chalk36.default.dim(" Command: ") + import_chalk36.default.cyan("hyv") + import_chalk36.default.dim(" (full path on Windows if needed)"));
|
|
20678
|
-
console.log(import_chalk36.default.dim(" Arguments: ") + import_chalk36.default.cyan("mcp"));
|
|
20849
|
+
console.log(import_chalk36.default.dim(" Arguments: ") + import_chalk36.default.cyan("mcp --stdio"));
|
|
20679
20850
|
console.log(import_chalk36.default.dim(" 4. Save, restart ChatGPT Desktop"));
|
|
20680
20851
|
console.log(import_chalk36.default.dim(' 5. Test: "scan this with hold your voice"\n'));
|
|
20681
20852
|
console.log(import_chalk36.default.dim(" If connector fails to start, use full paths from:"));
|
|
@@ -20697,20 +20868,26 @@ program2.command("mcp").description("Detect installed apps, configure MCP, and s
|
|
|
20697
20868
|
console.log("");
|
|
20698
20869
|
return;
|
|
20699
20870
|
}
|
|
20700
|
-
|
|
20871
|
+
if (isMcpStdioMode(opts)) {
|
|
20872
|
+
await startMcpServer();
|
|
20873
|
+
return;
|
|
20874
|
+
}
|
|
20875
|
+
const err = (msg) => process.stderr.write(msg + "\n");
|
|
20876
|
+
err(import_chalk36.default.bold("\nhold your voice \u2014 detecting and configuring mcp...\n"));
|
|
20701
20877
|
const result = runMcpAutoIntegrate({ force: Boolean(opts.force) });
|
|
20878
|
+
const reportLines = [];
|
|
20879
|
+
const origLog = console.log;
|
|
20880
|
+
console.log = (...args2) => {
|
|
20881
|
+
reportLines.push(args2.map(String).join(" "));
|
|
20882
|
+
};
|
|
20702
20883
|
printMcpIntegrateReport(result, { force: Boolean(opts.force) });
|
|
20703
|
-
console.log
|
|
20704
|
-
|
|
20705
|
-
|
|
20706
|
-
|
|
20707
|
-
|
|
20708
|
-
|
|
20709
|
-
|
|
20710
|
-
console.log(import_chalk36.default.dim(" It auto-detects installed apps, configures MCP servers, and is ready"));
|
|
20711
|
-
console.log(import_chalk36.default.dim(" for scanning, rewriting, and voice-profile validation in any tool.\n"));
|
|
20712
|
-
console.log(import_chalk36.default.dim("\nstarting MCP server (press Ctrl+C to stop)...\n"));
|
|
20713
|
-
startMcpServer();
|
|
20884
|
+
console.log = origLog;
|
|
20885
|
+
for (const line of reportLines)
|
|
20886
|
+
err(line);
|
|
20887
|
+
err(import_chalk36.default.green.bold("\n\u2713 hold your voice \u2014 mcp setup complete\n"));
|
|
20888
|
+
err(import_chalk36.default.dim(" restart cursor / claude desktop if they were open during setup"));
|
|
20889
|
+
err(import_chalk36.default.dim("\nstarting MCP server (press Ctrl+C to stop) \u2014 JSON-RPC on stdout...\n"));
|
|
20890
|
+
await startMcpServer();
|
|
20714
20891
|
});
|
|
20715
20892
|
program2.command("export").description("Export voice profile for LLMs").argument("[format]", "Export format (claude, chatgpt, generic, cursor)", "claude").option("--output <file>", "Write to file instead of stdout").option("--json", "Output as JSON").action(async (format, opts) => {
|
|
20716
20893
|
await exportCommand(format, opts);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holdyourvoice/hyv",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.26",
|
|
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": {
|
|
@@ -78,9 +78,9 @@ function markOnboardingComplete(hyvDir, pkgVersion) {
|
|
|
78
78
|
function resolveHyvMcpCommand(pkgDir) {
|
|
79
79
|
const entry = path.join(pkgDir, 'dist', 'index.js');
|
|
80
80
|
if (fs.existsSync(entry)) {
|
|
81
|
-
return { command: process.execPath, args: [entry, 'mcp'] };
|
|
81
|
+
return { command: process.execPath, args: [entry, 'mcp', '--stdio'] };
|
|
82
82
|
}
|
|
83
|
-
return { command: 'hyv', args: ['mcp'] };
|
|
83
|
+
return { command: 'hyv', args: ['mcp', '--stdio'] };
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
/** OpenCode local MCP uses a command array: [node, entry, mcp] */
|