@holdyourvoice/hyv 2.9.2 → 2.9.4
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/dist/index.js +70 -100
- package/package.json +1 -1
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.4] — 2026-06-12
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- Welcome step 4 — one sign-in only; opens dashboard billing tab (no second Google login on marketing site)
|
|
9
|
+
- `hyv plan --upgrade` opens authenticated dashboard billing instead of public pricing page
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- Step 4 copy tightened; spinners while account + profile sync run
|
|
13
|
+
|
|
14
|
+
## [2.9.3] — 2026-06-12
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Browser signup OAuth — clearer error when Google denies; server fix for `redirect_uri_mismatch` (requires API deploy)
|
|
18
|
+
|
|
5
19
|
## [2.9.2] — 2026-06-12
|
|
6
20
|
|
|
7
21
|
### Changed
|
package/dist/index.js
CHANGED
|
@@ -4612,40 +4612,6 @@ var require_source = __commonJS({
|
|
|
4612
4612
|
});
|
|
4613
4613
|
|
|
4614
4614
|
// src/lib/config.ts
|
|
4615
|
-
var config_exports = {};
|
|
4616
|
-
__export(config_exports, {
|
|
4617
|
-
API_BASE: () => API_BASE,
|
|
4618
|
-
AUTH_FILE: () => AUTH_FILE,
|
|
4619
|
-
CACHE_DIR: () => CACHE_DIR,
|
|
4620
|
-
CONFIG_FILE: () => CONFIG_FILE,
|
|
4621
|
-
HYV_DIR: () => HYV_DIR,
|
|
4622
|
-
LAST_SESSION_FILE: () => LAST_SESSION_FILE,
|
|
4623
|
-
PROFILES_DIR: () => PROFILES_DIR,
|
|
4624
|
-
QUEUE_DIR: () => QUEUE_DIR,
|
|
4625
|
-
appendSecureLine: () => appendSecureLine,
|
|
4626
|
-
assertSafeOAuthUrl: () => assertSafeOAuthUrl,
|
|
4627
|
-
assertSafeOpenUrl: () => assertSafeOpenUrl,
|
|
4628
|
-
assertSafeProfileName: () => assertSafeProfileName,
|
|
4629
|
-
clearAuth: () => clearAuth,
|
|
4630
|
-
clearQueuedSignals: () => clearQueuedSignals,
|
|
4631
|
-
cliApiUrl: () => cliApiUrl,
|
|
4632
|
-
ensureHyvDir: () => ensureHyvDir,
|
|
4633
|
-
getQueuedSignals: () => getQueuedSignals,
|
|
4634
|
-
getToken: () => getToken,
|
|
4635
|
-
isInitialized: () => isInitialized,
|
|
4636
|
-
listCachedProfiles: () => listCachedProfiles,
|
|
4637
|
-
profilePathForName: () => profilePathForName,
|
|
4638
|
-
queueSignal: () => queueSignal,
|
|
4639
|
-
readAuth: () => readAuth,
|
|
4640
|
-
readCachedProfile: () => readCachedProfile,
|
|
4641
|
-
readConfig: () => readConfig,
|
|
4642
|
-
readLastEditSession: () => readLastEditSession,
|
|
4643
|
-
saveLastEditSession: () => saveLastEditSession,
|
|
4644
|
-
writeAuth: () => writeAuth,
|
|
4645
|
-
writeCachedProfile: () => writeCachedProfile,
|
|
4646
|
-
writeConfig: () => writeConfig,
|
|
4647
|
-
writeSecureFile: () => writeSecureFile
|
|
4648
|
-
});
|
|
4649
4615
|
function validateApiBase(raw) {
|
|
4650
4616
|
const trimmed = raw.replace(/\/$/, "");
|
|
4651
4617
|
let parsed;
|
|
@@ -4666,7 +4632,7 @@ function cliApiUrl(apiPath) {
|
|
|
4666
4632
|
const p = apiPath.startsWith("/") ? apiPath : `/${apiPath}`;
|
|
4667
4633
|
return `${API_BASE}${p}`;
|
|
4668
4634
|
}
|
|
4669
|
-
function
|
|
4635
|
+
function assertSafeOpenUrl2(url) {
|
|
4670
4636
|
let parsed;
|
|
4671
4637
|
try {
|
|
4672
4638
|
parsed = new URL(url);
|
|
@@ -4737,10 +4703,6 @@ function ensureHyvDir() {
|
|
|
4737
4703
|
}
|
|
4738
4704
|
}
|
|
4739
4705
|
}
|
|
4740
|
-
function writeSecureFile(filePath, content) {
|
|
4741
|
-
ensureHyvDir();
|
|
4742
|
-
fs.writeFileSync(filePath, content, { mode: 384 });
|
|
4743
|
-
}
|
|
4744
4706
|
function appendSecureLine(filePath, line, dir) {
|
|
4745
4707
|
if (dir) {
|
|
4746
4708
|
if (!fs.existsSync(dir))
|
|
@@ -4779,11 +4741,6 @@ function writeAuth(auth) {
|
|
|
4779
4741
|
ensureHyvDir();
|
|
4780
4742
|
fs.writeFileSync(AUTH_FILE, JSON.stringify(auth, null, 2), { mode: 384 });
|
|
4781
4743
|
}
|
|
4782
|
-
function clearAuth() {
|
|
4783
|
-
if (fs.existsSync(AUTH_FILE)) {
|
|
4784
|
-
fs.unlinkSync(AUTH_FILE);
|
|
4785
|
-
}
|
|
4786
|
-
}
|
|
4787
4744
|
function readConfig() {
|
|
4788
4745
|
try {
|
|
4789
4746
|
if (!fs.existsSync(CONFIG_FILE))
|
|
@@ -4854,17 +4811,6 @@ function queueSignal(signal) {
|
|
|
4854
4811
|
const filePath = path.join(QUEUE_DIR, `${id}.json`);
|
|
4855
4812
|
fs.writeFileSync(filePath, JSON.stringify(signal, null, 2), { mode: 384 });
|
|
4856
4813
|
}
|
|
4857
|
-
function clearQueuedSignals() {
|
|
4858
|
-
try {
|
|
4859
|
-
if (!fs.existsSync(QUEUE_DIR))
|
|
4860
|
-
return;
|
|
4861
|
-
const files = fs.readdirSync(QUEUE_DIR).filter((f) => f.endsWith(".json"));
|
|
4862
|
-
for (const f of files) {
|
|
4863
|
-
fs.unlinkSync(path.join(QUEUE_DIR, f));
|
|
4864
|
-
}
|
|
4865
|
-
} catch {
|
|
4866
|
-
}
|
|
4867
|
-
}
|
|
4868
4814
|
function saveLastEditSession(session) {
|
|
4869
4815
|
ensureHyvDir();
|
|
4870
4816
|
const data = { ...session, saved_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
@@ -5355,6 +5301,7 @@ __export(auth_exports, {
|
|
|
5355
5301
|
authenticatedRequest: () => authenticatedRequest,
|
|
5356
5302
|
checkSession: () => checkSession,
|
|
5357
5303
|
getValidToken: () => getValidToken,
|
|
5304
|
+
openAuthenticatedDashboard: () => openAuthenticatedDashboard,
|
|
5358
5305
|
refreshToken: () => refreshToken,
|
|
5359
5306
|
verifyOAuthState: () => verifyOAuthState
|
|
5360
5307
|
});
|
|
@@ -5479,6 +5426,15 @@ async function authenticateWithBrowser() {
|
|
|
5479
5426
|
if (url.pathname === "/callback") {
|
|
5480
5427
|
const code = url.searchParams.get("code");
|
|
5481
5428
|
const state = url.searchParams.get("state");
|
|
5429
|
+
const oauthError = url.searchParams.get("error");
|
|
5430
|
+
if (oauthError) {
|
|
5431
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
5432
|
+
res.end(`<h1>Authentication failed</h1><p>${escapeHtml(oauthError)}</p>`);
|
|
5433
|
+
clearTimeout(timeout);
|
|
5434
|
+
server.close();
|
|
5435
|
+
reject(new Error(`Google sign-in failed: ${oauthError}`));
|
|
5436
|
+
return;
|
|
5437
|
+
}
|
|
5482
5438
|
if (!code || !state) {
|
|
5483
5439
|
res.writeHead(400, { "Content-Type": "text/html" });
|
|
5484
5440
|
res.end("<h1>Authentication failed</h1><p>Missing code or state.</p>");
|
|
@@ -5534,6 +5490,23 @@ async function authenticateWithBrowser() {
|
|
|
5534
5490
|
writeAuth(authData);
|
|
5535
5491
|
return authData;
|
|
5536
5492
|
}
|
|
5493
|
+
async function openAuthenticatedDashboard(opts = {}) {
|
|
5494
|
+
const response = await authenticatedRequest(cliApiUrl("/cli/auth/web-handoff"), {
|
|
5495
|
+
method: "POST",
|
|
5496
|
+
body: {
|
|
5497
|
+
next: opts.next || "/dashboard",
|
|
5498
|
+
tab: opts.tab || "billing"
|
|
5499
|
+
}
|
|
5500
|
+
});
|
|
5501
|
+
if (response.status !== 200) {
|
|
5502
|
+
throw new Error("Could not open dashboard \u2014 try https://holdyourvoice.com/dashboard");
|
|
5503
|
+
}
|
|
5504
|
+
const { url } = response.data;
|
|
5505
|
+
if (!url) {
|
|
5506
|
+
throw new Error("Dashboard handoff URL missing");
|
|
5507
|
+
}
|
|
5508
|
+
await (0, import_open.default)(assertSafeOpenUrl(url));
|
|
5509
|
+
}
|
|
5537
5510
|
async function refreshToken(tokenOverride) {
|
|
5538
5511
|
const token = tokenOverride || readAuth()?.token;
|
|
5539
5512
|
if (!token)
|
|
@@ -5625,7 +5598,7 @@ function printFreePaidMatrix(opts = {}) {
|
|
|
5625
5598
|
First month $1 \u2192 ${PRICING_URL}`));
|
|
5626
5599
|
console.log(import_chalk2.default.dim(" hyv init | hyv plan --upgrade\n"));
|
|
5627
5600
|
}
|
|
5628
|
-
var import_chalk2, NPX_EXAMPLES, FREE_CLI_COMMANDS, PAID_FEATURES, FREE_WEB_TOOLS, COMMUNITY_URL, PRICING_URL;
|
|
5601
|
+
var import_chalk2, NPX_EXAMPLES, FREE_CLI_COMMANDS, PAID_FEATURES, FREE_WEB_TOOLS, COMMUNITY_URL, PRICING_URL, DASHBOARD_BILLING_URL;
|
|
5629
5602
|
var init_free_paid = __esm({
|
|
5630
5603
|
"src/lib/free-paid.ts"() {
|
|
5631
5604
|
"use strict";
|
|
@@ -5673,6 +5646,7 @@ var init_free_paid = __esm({
|
|
|
5673
5646
|
];
|
|
5674
5647
|
COMMUNITY_URL = "https://holdyourvoice.com/community";
|
|
5675
5648
|
PRICING_URL = "https://holdyourvoice.com/#pricing";
|
|
5649
|
+
DASHBOARD_BILLING_URL = "https://holdyourvoice.com/dashboard?tab=billing";
|
|
5676
5650
|
}
|
|
5677
5651
|
});
|
|
5678
5652
|
|
|
@@ -11443,12 +11417,12 @@ function buildStepGuide(step, profileName) {
|
|
|
11443
11417
|
return [
|
|
11444
11418
|
"### Step 4 \u2014 save & unlock",
|
|
11445
11419
|
"",
|
|
11446
|
-
"Explain warmly:
|
|
11420
|
+
"Explain warmly: local profile until signup. One browser sign-in via CLI \u2014 then open dashboard billing (no second login).",
|
|
11447
11421
|
"Paid unlock is **$1 for the first month** \u2014 profiles that learn, hybrid rewrite, dashboard.",
|
|
11448
11422
|
"Scanning/fix/MCP stay free forever without an account.",
|
|
11449
11423
|
"",
|
|
11450
|
-
"1. `hyv init` \u2014
|
|
11451
|
-
"2.
|
|
11424
|
+
"1. `hyv init` or welcome step 4 \u2014 single Google sign-in from terminal",
|
|
11425
|
+
"2. Dashboard opens on billing tab \u2014 user picks plan there",
|
|
11452
11426
|
"",
|
|
11453
11427
|
`Pricing: ${PRICING_URL}`
|
|
11454
11428
|
].join("\n");
|
|
@@ -11775,14 +11749,13 @@ async function stepTest(profileName) {
|
|
|
11775
11749
|
}
|
|
11776
11750
|
async function stepSignup(profileName) {
|
|
11777
11751
|
console.log(import_chalk12.default.bold("\nstep 4 \xB7 save & unlock\n"));
|
|
11778
|
-
console.log(" your
|
|
11779
|
-
console.log("
|
|
11780
|
-
console.log("
|
|
11781
|
-
console.log("
|
|
11782
|
-
console.log("
|
|
11783
|
-
console.log(" access to profiles that learn, hybrid rewrite, and your dashboard.\n");
|
|
11752
|
+
console.log(" your profile works on this machine. scan anything, anytime \u2014 free forever.");
|
|
11753
|
+
console.log("");
|
|
11754
|
+
console.log(" create a free account to back it up and sync everywhere.");
|
|
11755
|
+
console.log(" then unlock learning for " + import_chalk12.default.bold("$1 your first month") + " \u2014 profiles that");
|
|
11756
|
+
console.log(" get sharper every rewrite, hybrid rewrites, and your dashboard.\n");
|
|
11784
11757
|
console.log(import_chalk12.default.dim(" free forever (no account): scan, fix, check, mcp"));
|
|
11785
|
-
console.log(import_chalk12.default.dim("
|
|
11758
|
+
console.log(import_chalk12.default.dim(" $1 first month: learning loop, rich rewrites, sync across devices\n"));
|
|
11786
11759
|
const ready = await askYesNo(" create your free account now? ($1 first month to unlock everything)");
|
|
11787
11760
|
if (!ready) {
|
|
11788
11761
|
console.log(import_chalk12.default.dim("\n no rush \u2014 your profile stays on this machine."));
|
|
@@ -11794,18 +11767,25 @@ async function stepSignup(profileName) {
|
|
|
11794
11767
|
return;
|
|
11795
11768
|
}
|
|
11796
11769
|
if (!isInitialized() || !getToken()) {
|
|
11797
|
-
console.log(import_chalk12.default.cyan("\n opening browser for signup...\n"));
|
|
11798
|
-
await authenticateWithBrowser();
|
|
11770
|
+
console.log(import_chalk12.default.cyan("\n opening browser for signup (one sign-in)...\n"));
|
|
11771
|
+
await withSpinner("creating your account\u2026", () => authenticateWithBrowser());
|
|
11772
|
+
await briefPause();
|
|
11773
|
+
console.log(import_chalk12.default.green(" \u2713 account created"));
|
|
11774
|
+
} else {
|
|
11775
|
+
console.log(import_chalk12.default.dim("\n already signed in \u2014 syncing profile..."));
|
|
11799
11776
|
}
|
|
11800
11777
|
const content = fs13.readFileSync(
|
|
11801
11778
|
path13.join(os7.homedir(), ".hyv", "profiles", `${profileName}.md`),
|
|
11802
11779
|
"utf-8"
|
|
11803
11780
|
);
|
|
11804
11781
|
try {
|
|
11805
|
-
const response = await
|
|
11806
|
-
|
|
11807
|
-
|
|
11808
|
-
|
|
11782
|
+
const response = await withSpinner(
|
|
11783
|
+
"syncing profile\u2026",
|
|
11784
|
+
() => authenticatedRequest(cliApiUrl("/cli/profiles/new"), {
|
|
11785
|
+
method: "POST",
|
|
11786
|
+
body: { name: profileName, content, source: "welcome" }
|
|
11787
|
+
})
|
|
11788
|
+
);
|
|
11809
11789
|
if (response.status === 200) {
|
|
11810
11790
|
console.log(import_chalk12.default.green(" \u2713 profile synced to your account"));
|
|
11811
11791
|
} else {
|
|
@@ -11814,12 +11794,15 @@ async function stepSignup(profileName) {
|
|
|
11814
11794
|
} catch {
|
|
11815
11795
|
console.log(import_chalk12.default.yellow(" profile saved locally \u2014 run `hyv sync` after you upgrade"));
|
|
11816
11796
|
}
|
|
11817
|
-
|
|
11818
|
-
|
|
11819
|
-
|
|
11820
|
-
|
|
11797
|
+
try {
|
|
11798
|
+
console.log(import_chalk12.default.cyan("\n opening billing in your dashboard ($1 first month)..."));
|
|
11799
|
+
await withSpinner("opening dashboard\u2026", () => openAuthenticatedDashboard({ tab: "billing" }));
|
|
11800
|
+
await briefPause();
|
|
11801
|
+
} catch {
|
|
11802
|
+
console.log(import_chalk12.default.yellow(" could not auto-open dashboard \u2014 visit holdyourvoice.com/dashboard"));
|
|
11803
|
+
}
|
|
11821
11804
|
markStepComplete("signup");
|
|
11822
|
-
console.log(import_chalk12.default.dim("\n
|
|
11805
|
+
console.log(import_chalk12.default.dim("\n you're signed in \u2014 pick a plan in billing, no second login.\n"));
|
|
11823
11806
|
}
|
|
11824
11807
|
async function runInteractiveWelcome() {
|
|
11825
11808
|
recordEvent("welcome_interactive");
|
|
@@ -16845,28 +16828,15 @@ async function showPlan() {
|
|
|
16845
16828
|
}
|
|
16846
16829
|
}
|
|
16847
16830
|
async function upgradePlan() {
|
|
16848
|
-
console.log(import_chalk14.default.cyan("\nOpening
|
|
16849
|
-
|
|
16850
|
-
|
|
16851
|
-
|
|
16852
|
-
|
|
16853
|
-
|
|
16854
|
-
|
|
16855
|
-
|
|
16856
|
-
|
|
16857
|
-
const data = response.data;
|
|
16858
|
-
const checkoutUrl = data.checkout_url;
|
|
16859
|
-
if (checkoutUrl) {
|
|
16860
|
-
console.log(import_chalk14.default.dim("Opening browser..."));
|
|
16861
|
-
await (0, import_open2.default)(assertSafeOpenUrl(checkoutUrl));
|
|
16862
|
-
console.log(import_chalk14.default.green("\n\u2713 Checkout opened in browser"));
|
|
16863
|
-
console.log(import_chalk14.default.dim("Complete the checkout to activate your plan."));
|
|
16864
|
-
} else {
|
|
16865
|
-
console.log(import_chalk14.default.yellow("No checkout URL received."));
|
|
16866
|
-
}
|
|
16867
|
-
} else {
|
|
16868
|
-
console.log(import_chalk14.default.yellow("Could not create checkout session."));
|
|
16869
|
-
console.log(import_chalk14.default.dim("Visit https://holdyourvoice.com/pricing to subscribe."));
|
|
16831
|
+
console.log(import_chalk14.default.cyan("\nOpening billing in your dashboard ($1 first month)...\n"));
|
|
16832
|
+
try {
|
|
16833
|
+
await openAuthenticatedDashboard({ tab: "billing" });
|
|
16834
|
+
console.log(import_chalk14.default.green("\n\u2713 Dashboard opened \u2014 you're already signed in"));
|
|
16835
|
+
console.log(import_chalk14.default.dim("Pick a plan in billing. No second login."));
|
|
16836
|
+
} catch {
|
|
16837
|
+
console.log(import_chalk14.default.yellow("Could not open dashboard automatically."));
|
|
16838
|
+
console.log(import_chalk14.default.dim(`Visit ${DASHBOARD_BILLING_URL} after running hyv init`));
|
|
16839
|
+
console.log(import_chalk14.default.dim(`Or see plans: ${PRICING_URL}`));
|
|
16870
16840
|
}
|
|
16871
16841
|
}
|
|
16872
16842
|
async function openBillingPortal() {
|
|
@@ -16880,7 +16850,7 @@ async function openBillingPortal() {
|
|
|
16880
16850
|
const portalUrl = data.portal_url;
|
|
16881
16851
|
if (portalUrl) {
|
|
16882
16852
|
console.log(import_chalk14.default.dim("Opening browser..."));
|
|
16883
|
-
await (0, import_open2.default)(
|
|
16853
|
+
await (0, import_open2.default)(assertSafeOpenUrl2(portalUrl));
|
|
16884
16854
|
console.log(import_chalk14.default.green("\n\u2713 Billing portal opened"));
|
|
16885
16855
|
} else {
|
|
16886
16856
|
console.log(import_chalk14.default.yellow("No portal URL received."));
|
package/package.json
CHANGED