@holdyourvoice/hyv 2.9.13 → 2.9.14

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.
Files changed (2) hide show
  1. package/dist/index.js +147 -46
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -5406,7 +5406,7 @@ var init_free_paid = __esm({
5406
5406
  ];
5407
5407
  COMMUNITY_URL = "https://holdyourvoice.com/community";
5408
5408
  PRICING_URL = "https://holdyourvoice.com/#pricing";
5409
- DASHBOARD_BILLING_URL = "https://holdyourvoice.com/dashboard?tab=billing";
5409
+ DASHBOARD_BILLING_URL = "https://holdyourvoice.com/app/billing";
5410
5410
  }
5411
5411
  });
5412
5412
 
@@ -5710,7 +5710,7 @@ async function authenticateWithBrowser() {
5710
5710
  return authData;
5711
5711
  }
5712
5712
  async function openAuthenticatedDashboard(opts = {}) {
5713
- const nextPath = opts.next || "/dashboard?tab=billing";
5713
+ const nextPath = opts.next || "/app/billing";
5714
5714
  try {
5715
5715
  const response = await authenticatedRequest(cliApiUrl("/cli/auth/web-handoff"), {
5716
5716
  method: "POST",
@@ -5725,7 +5725,7 @@ async function openAuthenticatedDashboard(opts = {}) {
5725
5725
  }
5726
5726
  } catch {
5727
5727
  }
5728
- const billingUrl = nextPath === "/dashboard?tab=billing" || nextPath === "/app/billing" ? DASHBOARD_BILLING_URL : assertSafeOpenUrl(`https://holdyourvoice.com${nextPath}`);
5728
+ const billingUrl = nextPath === "/app/billing" ? DASHBOARD_BILLING_URL : assertSafeOpenUrl(`https://holdyourvoice.com${nextPath}`);
5729
5729
  await (0, import_open.default)(billingUrl);
5730
5730
  }
5731
5731
  async function refreshToken(tokenOverride) {
@@ -5883,7 +5883,7 @@ async function request2(method, path27, body) {
5883
5883
  if (res.status === 401)
5884
5884
  throw new Error("your session expired. run: hyv init to sign in again.");
5885
5885
  if (res.status === 403)
5886
- throw new Error("you don't have access to this feature. check your plan at holdyourvoice.com/dashboard?tab=billing");
5886
+ throw new Error("you don't have access to this feature. check your plan at holdyourvoice.com/app/billing");
5887
5887
  if (!res.ok) {
5888
5888
  throw new Error(`something went wrong (${res.status}). try again or contact support.`);
5889
5889
  }
@@ -5943,7 +5943,9 @@ async function getAccessState() {
5943
5943
  try {
5944
5944
  const session = await checkSession();
5945
5945
  const plan = (session.plan || "none").toLowerCase();
5946
- const hasPaidPlan = session.valid && plan !== "none" && plan !== "free" && plan !== "expired" && plan !== "unknown";
5946
+ const subStatus = String(session.subscription_status || "").toLowerCase();
5947
+ const activeSubscription = !subStatus || ["active", "past_due", "trialing"].includes(subStatus);
5948
+ const hasPaidPlan = session.valid && activeSubscription && plan !== "none" && plan !== "free" && plan !== "expired" && plan !== "unknown" && plan !== "pending";
5947
5949
  const state = {
5948
5950
  authenticated: session.valid,
5949
5951
  hasPaidPlan,
@@ -11458,6 +11460,25 @@ var init_terminal_ui = __esm({
11458
11460
  });
11459
11461
 
11460
11462
  // src/lib/welcome-flow.ts
11463
+ function localProfilePath(name) {
11464
+ return path14.join(os8.homedir(), ".hyv", "profiles", `${name}.md`);
11465
+ }
11466
+ function listLocalProfileNames() {
11467
+ const profilesDir = path14.join(os8.homedir(), ".hyv", "profiles");
11468
+ if (!fs15.existsSync(profilesDir))
11469
+ return [];
11470
+ return fs15.readdirSync(profilesDir).filter((entry) => entry.endsWith(".md")).map((entry) => entry.slice(0, -3));
11471
+ }
11472
+ function formatPlanLabel(plan) {
11473
+ const labels = {
11474
+ individual: "Individual",
11475
+ multiple: "Multiple",
11476
+ solo: "Solo",
11477
+ team: "Team",
11478
+ agency: "Agency"
11479
+ };
11480
+ return labels[plan.toLowerCase()] || plan;
11481
+ }
11461
11482
  function readWelcomeState() {
11462
11483
  try {
11463
11484
  if (!fs15.existsSync(STATE_FILE)) {
@@ -11794,8 +11815,28 @@ function formatScanResult(text, profile) {
11794
11815
  }
11795
11816
  async function stepName() {
11796
11817
  console.log(import_chalk12.default.bold("\nstep 1 \xB7 name your profile\n"));
11818
+ const state = readWelcomeState();
11819
+ const existing = listLocalProfileNames();
11820
+ if (state.profile_name && existing.includes(state.profile_name)) {
11821
+ const reuse = await askYesNo(` reuse profile "${state.profile_name}"?`, true);
11822
+ if (reuse) {
11823
+ markStepComplete("name");
11824
+ return state.profile_name;
11825
+ }
11826
+ } else if (existing.length === 1) {
11827
+ const only = existing[0];
11828
+ const reuse = await askYesNo(` reuse existing profile "${only}"?`, true);
11829
+ if (reuse) {
11830
+ writeWelcomeState({ profile_name: only });
11831
+ markStepComplete("name");
11832
+ return only;
11833
+ }
11834
+ }
11797
11835
  const name = await askLine(import_chalk12.default.cyan(" profile name (e.g. my-voice): "));
11798
- return assertSafeProfileName(name);
11836
+ const safe = assertSafeProfileName(name);
11837
+ writeWelcomeState({ profile_name: safe });
11838
+ markStepComplete("name");
11839
+ return safe;
11799
11840
  }
11800
11841
  async function stepSamples(profileName) {
11801
11842
  console.log(import_chalk12.default.bold("\nstep 2 \xB7 add writing samples\n"));
@@ -11859,6 +11900,8 @@ _Add pasted samples to train this profile._
11859
11900
  console.log(import_chalk12.default.green(`
11860
11901
  \u2713 profile saved`));
11861
11902
  console.log(import_chalk12.default.dim(` ${saved}`));
11903
+ writeWelcomeState({ profile_name: profileName });
11904
+ markStepComplete("samples");
11862
11905
  }
11863
11906
  async function stepTest(profileName) {
11864
11907
  console.log(import_chalk12.default.bold("\nstep 3 \xB7 test on a draft"));
@@ -11899,42 +11942,25 @@ async function stepTest(profileName) {
11899
11942
  console.log(import_chalk12.default.dim(` try: hyv rewrite ${rewriteTarget} --profile ${profileName}`));
11900
11943
  markStepComplete("test");
11901
11944
  }
11902
- async function stepSignup(profileName) {
11903
- console.log(import_chalk12.default.bold("\nstep 4 \xB7 save & unlock\n"));
11904
- console.log(" your profile works on this machine. scan anything, anytime \u2014 free forever.");
11905
- console.log("");
11906
- console.log(" create a free account to back it up and sync everywhere.");
11907
- console.log(" then unlock learning for " + import_chalk12.default.bold("$1 your first month") + " \u2014 profiles that");
11908
- console.log(" get sharper every rewrite, hybrid rewrites, and your dashboard.\n");
11909
- console.log(import_chalk12.default.dim(" free forever (no account): scan, fix, check, mcp"));
11910
- console.log(import_chalk12.default.dim(" $1 first month: learning loop, rich rewrites, sync across devices\n"));
11911
- const ready = await askYesNo(" create your free account now? ($1 first month to unlock everything)");
11912
- if (!ready) {
11913
- console.log(import_chalk12.default.dim("\n no rush \u2014 your profile stays on this machine."));
11914
- console.log(import_chalk12.default.dim(" whenever you want backup + learning:"));
11915
- console.log(import_chalk12.default.dim(" hyv init"));
11916
- console.log(import_chalk12.default.dim(" hyv plan --upgrade ($1 first month)"));
11917
- console.log(import_chalk12.default.dim(` ${PRICING_URL}
11918
- `));
11919
- return;
11945
+ async function syncProfileToAccount(profileName) {
11946
+ const profilePath = localProfilePath(profileName);
11947
+ if (!fs15.existsSync(profilePath)) {
11948
+ console.log(import_chalk12.default.yellow(` no local profile at ${profilePath}`));
11949
+ return false;
11920
11950
  }
11921
11951
  if (!isInitialized() || !getToken()) {
11922
- console.log(import_chalk12.default.cyan("\n opening browser for signup (one sign-in)...\n"));
11923
- await withSpinner("creating your account\u2026", () => authenticateWithBrowser());
11952
+ console.log(import_chalk12.default.cyan("\n opening browser for sign-in...\n"));
11953
+ await withSpinner("signing in\u2026", () => authenticateWithBrowser());
11924
11954
  await briefPause();
11925
- console.log(import_chalk12.default.green(" \u2713 account created"));
11926
- } else {
11927
- console.log(import_chalk12.default.dim("\n already signed in \u2014 syncing profile..."));
11955
+ console.log(import_chalk12.default.green(" \u2713 signed in"));
11928
11956
  }
11929
- const content = fs15.readFileSync(
11930
- path14.join(os8.homedir(), ".hyv", "profiles", `${profileName}.md`),
11931
- "utf-8"
11932
- );
11957
+ const content = fs15.readFileSync(profilePath, "utf-8");
11933
11958
  try {
11934
11959
  const response = await withSpinner(
11935
11960
  "syncing profile\u2026",
11936
11961
  () => authenticatedRequest(cliApiUrl("/cli/profiles/new"), {
11937
11962
  method: "POST",
11963
+ timeout: PROFILE_SYNC_TIMEOUT_MS,
11938
11964
  body: { name: profileName, content, source: "welcome" }
11939
11965
  })
11940
11966
  );
@@ -11945,27 +11971,100 @@ async function stepSignup(profileName) {
11945
11971
  } else {
11946
11972
  console.log(import_chalk12.default.green(" \u2713 profile synced to your account"));
11947
11973
  }
11948
- } else {
11949
- const detail = response.data?.error;
11950
- console.log(import_chalk12.default.yellow(
11951
- ` profile saved locally \u2014 retry \`hyv welcome\` when online${detail ? ` (${detail})` : ` (HTTP ${response.status})`}`
11952
- ));
11974
+ return true;
11953
11975
  }
11976
+ const detail = response.data?.error;
11977
+ console.log(import_chalk12.default.yellow(
11978
+ ` profile saved locally \u2014 retry \`hyv sync\`${detail ? ` (${detail})` : ` (HTTP ${response.status})`}`
11979
+ ));
11954
11980
  } catch (err) {
11955
11981
  console.log(import_chalk12.default.yellow(
11956
- ` profile saved locally \u2014 retry \`hyv welcome\` (${err?.message || "sync failed"})`
11982
+ ` profile saved locally \u2014 retry \`hyv sync\` (${err?.message || "sync failed"})`
11957
11983
  ));
11958
11984
  }
11985
+ return false;
11986
+ }
11987
+ async function stepSignup(profileName) {
11988
+ console.log(import_chalk12.default.bold("\nstep 4 \xB7 save & unlock\n"));
11989
+ const access = await getAccessState();
11990
+ if (access.hasPaidPlan) {
11991
+ console.log(` you're on ${import_chalk12.default.bold(formatPlanLabel(access.plan))} \u2014 learning, sync, and dashboard are unlocked.`);
11992
+ console.log(import_chalk12.default.dim("\n syncing your local profile to your account..."));
11993
+ await syncProfileToAccount(profileName);
11994
+ markStepComplete("signup");
11995
+ console.log(import_chalk12.default.dim("\n run `hyv scan draft.md` or `hyv sync` anytime.\n"));
11996
+ return;
11997
+ }
11998
+ console.log(" your profile works on this machine. scan anything, anytime \u2014 free forever.");
11999
+ console.log("");
12000
+ if (access.authenticated) {
12001
+ console.log(" you're signed in. sync this profile, or upgrade for learning + rich rewrites.");
12002
+ console.log(import_chalk12.default.dim("\n free forever: scan, fix, check, mcp"));
12003
+ console.log(import_chalk12.default.dim(" paid: learning loop, hybrid rewrites, sync across devices\n"));
12004
+ const syncNow = await askYesNo(" sync profile to your account now?", true);
12005
+ if (syncNow) {
12006
+ await syncProfileToAccount(profileName);
12007
+ }
12008
+ const upgrade = await askYesNo(" open billing to upgrade?", false);
12009
+ if (upgrade) {
12010
+ await withSpinner("opening billing\u2026", () => openAuthenticatedDashboard({ next: "/app/billing" }));
12011
+ await briefPause();
12012
+ }
12013
+ markStepComplete("signup");
12014
+ console.log(import_chalk12.default.dim("\n upgrade anytime: `hyv plan --upgrade`\n"));
12015
+ return;
12016
+ }
12017
+ console.log(" create a free account to back it up and sync everywhere.");
12018
+ console.log(" then unlock learning for " + import_chalk12.default.bold("$1 your first month") + " \u2014 profiles that");
12019
+ console.log(" get sharper every rewrite, hybrid rewrites, and your dashboard.\n");
12020
+ console.log(import_chalk12.default.dim(" free forever (no account): scan, fix, check, mcp"));
12021
+ console.log(import_chalk12.default.dim(" $1 first month: learning loop, rich rewrites, sync across devices\n"));
12022
+ const ready = await askYesNo(" create your free account now? ($1 first month to unlock everything)");
12023
+ if (!ready) {
12024
+ console.log(import_chalk12.default.dim("\n no rush \u2014 your profile stays on this machine."));
12025
+ console.log(import_chalk12.default.dim(" whenever you want backup + learning:"));
12026
+ console.log(import_chalk12.default.dim(" hyv init"));
12027
+ console.log(import_chalk12.default.dim(" hyv plan --upgrade ($1 first month)"));
12028
+ console.log(import_chalk12.default.dim(` ${PRICING_URL}
12029
+ `));
12030
+ return;
12031
+ }
12032
+ await syncProfileToAccount(profileName);
11959
12033
  console.log(import_chalk12.default.cyan("\n opening billing in your dashboard ($1 first month)..."));
11960
- await withSpinner("opening billing\u2026", () => openAuthenticatedDashboard({ next: "/dashboard?tab=billing" }));
12034
+ await withSpinner("opening billing\u2026", () => openAuthenticatedDashboard({ next: "/app/billing" }));
11961
12035
  await briefPause();
11962
12036
  markStepComplete("signup");
11963
12037
  console.log(import_chalk12.default.dim("\n you're signed in \u2014 pick a plan in billing, no second login.\n"));
11964
12038
  }
12039
+ async function maybeRunPaidWelcomeShortcut() {
12040
+ const access = await getAccessState();
12041
+ if (!access.authenticated || !access.hasPaidPlan)
12042
+ return null;
12043
+ const state = readWelcomeState();
12044
+ const profiles = listLocalProfileNames();
12045
+ const profileName = state.profile_name && profiles.includes(state.profile_name) ? state.profile_name : profiles[0];
12046
+ if (!profileName)
12047
+ return null;
12048
+ console.log(import_chalk12.default.green(`
12049
+ ${formatPlanLabel(access.plan)} plan active \u2014 profile "${profileName}" ready.`));
12050
+ const resync = await askYesNo(" sync profile to your account now?", true);
12051
+ if (resync) {
12052
+ await syncProfileToAccount(profileName);
12053
+ }
12054
+ markStepComplete("signup");
12055
+ console.log(import_chalk12.default.green("\ndone \u2014 your voice profile is ready.\n"));
12056
+ console.log(import_chalk12.default.dim(" hyv scan draft.md"));
12057
+ console.log(import_chalk12.default.dim(` hyv rewrite draft.md --profile ${profileName}`));
12058
+ console.log(import_chalk12.default.dim(" hyv mcp --setup\n"));
12059
+ return profileName;
12060
+ }
11965
12061
  async function runInteractiveWelcome() {
11966
12062
  recordEvent("welcome_interactive");
11967
12063
  console.log("\n" + buildWelcomeHeader());
11968
12064
  try {
12065
+ const shortcut = await maybeRunPaidWelcomeShortcut();
12066
+ if (shortcut)
12067
+ return;
11969
12068
  const profileName = await stepName();
11970
12069
  await stepSamples(profileName);
11971
12070
  await stepTest(profileName);
@@ -11992,7 +12091,7 @@ function getMcpWelcomeResponse(args2) {
11992
12091
  }
11993
12092
  return buildWelcomeGuide({ profileName: args2.profile, forLlm: true });
11994
12093
  }
11995
- var import_chalk12, fs15, http2, https2, os8, path14, readline, WELCOME_TAGLINE, FLOW_STEPS, STATE_FILE;
12094
+ var import_chalk12, fs15, http2, https2, os8, path14, readline, PROFILE_SYNC_TIMEOUT_MS, WELCOME_TAGLINE, FLOW_STEPS, STATE_FILE;
11996
12095
  var init_welcome_flow = __esm({
11997
12096
  "src/lib/welcome-flow.ts"() {
11998
12097
  "use strict";
@@ -12009,10 +12108,12 @@ var init_welcome_flow = __esm({
12009
12108
  init_terminal_ui();
12010
12109
  init_config();
12011
12110
  init_auth();
12111
+ init_access();
12012
12112
  init_scan();
12013
12113
  init_free_paid();
12014
12114
  init_telemetry();
12015
12115
  init_document_text();
12116
+ PROFILE_SYNC_TIMEOUT_MS = 9e4;
12016
12117
  WELCOME_TAGLINE = "Hold Your Voice \u2014 make your AI agents sound exactly like you.";
12017
12118
  FLOW_STEPS = [
12018
12119
  { n: 1, key: "name", title: "name your profile", hint: "pick a short name \u2014 e.g. my-voice" },
@@ -12144,7 +12245,7 @@ __export(billing_upgrade_exports, {
12144
12245
  });
12145
12246
  async function openBillingUpgrade(opts = {}) {
12146
12247
  const plan = opts.plan || "individual";
12147
- const next = `/dashboard?tab=billing&checkout=${plan}`;
12248
+ const next = `/app/billing?checkout=${plan}`;
12148
12249
  const token = getToken();
12149
12250
  if (!token) {
12150
12251
  console.log(import_chalk14.default.cyan("\nSign in to upgrade \u2014 opening browser...\n"));
@@ -18353,8 +18454,8 @@ function registerBatchCommand(program3) {
18353
18454
  program3.command("batch").description("Scan or fix multiple files matching a glob").argument("<pattern>", 'Glob pattern (e.g., "posts/**/*.md")').option("--fix", "Apply auto-fixes (default: scan only)").option("-i, --in-place", "Write fixes back to files").option("-y, --yes", "Confirm destructive in-place fixes without prompting").option("--threshold <n>", "Fail if any file score < threshold").option("--fail-on-hit", "Exit with code 2 if any file has issues").option("--sort <field>", "Sort by: issues, score, name", "issues").option("--format <type>", "Output format (text, json, csv)", "text").option("--profile <name>", "Voice profile").option("--ignore <patterns>", "Comma-separated glob ignores").action(async (pattern, options) => {
18354
18455
  try {
18355
18456
  const profile = await loadProfileForCommand(options.profile);
18356
- const glob = require_index_min();
18357
- const files = glob.sync(pattern, {
18457
+ const { globSync } = require_index_min();
18458
+ const files = globSync(pattern, {
18358
18459
  ignore: options.ignore ? options.ignore.split(",") : void 0,
18359
18460
  nodir: true
18360
18461
  });
@@ -18680,9 +18781,9 @@ var import_chalk30 = __toESM(require_source());
18680
18781
  var PAGES = {
18681
18782
  dashboard: "https://holdyourvoice.com/dashboard",
18682
18783
  profiles: "https://holdyourvoice.com/dashboard?tab=profiles",
18683
- pricing: "https://holdyourvoice.com/dashboard?tab=billing",
18784
+ pricing: "https://holdyourvoice.com/app/billing",
18684
18785
  settings: "https://holdyourvoice.com/dashboard",
18685
- billing: "https://holdyourvoice.com/dashboard?tab=billing"
18786
+ billing: "https://holdyourvoice.com/app/billing"
18686
18787
  };
18687
18788
  function registerOpenCommand(program3) {
18688
18789
  program3.command("open").description("Open the web dashboard in your browser").option("--page <path>", "Page: dashboard, profiles, pricing, settings", "dashboard").option("--profile <name>", "Deep-link to a specific profile").option("--no-browser", "Print URL only, don't open").action(async (options) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holdyourvoice/hyv",
3
- "version": "2.9.13",
3
+ "version": "2.9.14",
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": {
@@ -50,7 +50,7 @@
50
50
  "@types/node": "^20.19.42",
51
51
  "esbuild": "^0.20.0",
52
52
  "typescript": "^5.3.3",
53
- "vitest": "^4.1.8"
53
+ "vitest": "^2.0.0"
54
54
  },
55
55
  "engines": {
56
56
  "node": ">=18"