@vocoder/cli 0.1.20 → 0.1.21

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/README.md CHANGED
@@ -11,7 +11,7 @@ npm install -g @vocoder/cli
11
11
  Or use without installing:
12
12
 
13
13
  ```bash
14
- npx vocoder <command>
14
+ npx @vocoder/cli <command>
15
15
  ```
16
16
 
17
17
  ## Commands
@@ -56,14 +56,19 @@ The CLI opens the Vocoder GitHub App installation page. Authorizing the App crea
56
56
 
57
57
  ◒ Creating project...
58
58
 
59
- Step 1: Add the plugin to vite.config.ts
60
- ◆ Step 2: Wrap your app with VocoderProvider
61
- ◆ Step 3: Mark strings for translation with <T>
59
+ Finish setup in your code
62
60
 
63
- Use Vocoder with Claude Code
64
- claude mcp add --scope project --transport stdio \
65
- │ --env VOCODER_API_KEY=vc_xxxx \
66
- │ vocoder -- npx -y @vocoder/mcp
61
+ vite.config.ts — register the build plugin so Vocoder can extract your strings
62
+ your root layout or App component — wrap your app so translations load at runtime
63
+ ◆ wrap translatable text — mark strings for extraction — Vocoder picks these up on push
64
+
65
+ ✓ Push to main to trigger your first translation run.
66
+
67
+ ◆ Your API Key
68
+ │ ┌─────────────────────────────────────────┐
69
+ │ │ VOCODER_API_KEY=vcp_xxxx │
70
+ │ └─────────────────────────────────────────┘
71
+ ✓ Saved to .env
67
72
 
68
73
  ◇ You're all set.
69
74
  ```
@@ -121,10 +126,14 @@ Reads `VOCODER_API_KEY` from environment or `.env`. Detects `<T>` and `t()` usag
121
126
 
122
127
  | Flag | Description |
123
128
  |---|---|
129
+ | `--include <glob>` | Glob pattern for files to scan (repeatable). Default: `**/*.{tsx,jsx,ts,js}` |
130
+ | `--exclude <glob>` | Glob pattern to skip (repeatable). Merged with built-in excludes |
124
131
  | `--locale <code>` | Sync only this target locale |
125
132
  | `--dry-run` | Show what would be synced without submitting |
126
133
  | `--verbose` | Show extraction and sync details |
127
134
 
135
+ Patterns can also be set via env vars: `VOCODER_INCLUDE_PATTERN` and `VOCODER_EXCLUDE_PATTERN` (comma-separated).
136
+
128
137
  ---
129
138
 
130
139
  ### `vocoder logout`
package/dist/bin.mjs CHANGED
@@ -12,6 +12,8 @@ import { Command } from "commander";
12
12
 
13
13
  // src/commands/init.ts
14
14
  import { execSync as execSync3, spawn as spawn2 } from "child_process";
15
+ import { existsSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
16
+ import { join as join2 } from "path";
15
17
  import * as p5 from "@clack/prompts";
16
18
  import chalk6 from "chalk";
17
19
  import { config as loadEnv } from "dotenv";
@@ -1955,62 +1957,74 @@ function runScaffold(params) {
1955
1957
  sourceLocale,
1956
1958
  targetBranches
1957
1959
  });
1958
- let stepNum = 1;
1960
+ const steps = [];
1959
1961
  if (snippets.pluginStep) {
1960
- p5.log.message("");
1961
- p5.log.step(
1962
- `${chalk6.bold(`Step ${stepNum}:`)} Add the plugin to ${chalk6.cyan(snippets.pluginStep.file)}`
1963
- );
1964
- printCodeBlock(snippets.pluginStep.code);
1965
- stepNum++;
1962
+ steps.push({
1963
+ label: snippets.pluginStep.file,
1964
+ hint: "register the build plugin so Vocoder can extract your strings",
1965
+ code: snippets.pluginStep.code
1966
+ });
1966
1967
  }
1967
1968
  if (snippets.providerStep) {
1969
+ steps.push({
1970
+ label: snippets.providerStep.file,
1971
+ hint: "wrap your app so translations load at runtime",
1972
+ code: snippets.providerStep.code
1973
+ });
1974
+ }
1975
+ steps.push({
1976
+ label: "wrap translatable text",
1977
+ hint: "mark strings for extraction \u2014 Vocoder picks these up on push",
1978
+ code: snippets.wrapStep.code
1979
+ });
1980
+ p5.log.message("");
1981
+ p5.log.message(chalk6.bold("Finish setup in your code"));
1982
+ p5.log.message("");
1983
+ for (let i = 0; i < steps.length; i++) {
1984
+ const step = steps[i];
1968
1985
  p5.log.step(
1969
- `${chalk6.bold(`Step ${stepNum}:`)} Add the provider to ${chalk6.cyan(snippets.providerStep.file)}`
1986
+ `${chalk6.bold(step.label)} ${chalk6.dim(`\u2014 ${step.hint}`)}`
1970
1987
  );
1971
- printCodeBlock(snippets.providerStep.code);
1972
- stepNum++;
1988
+ printCodeBlock(step.code);
1989
+ if (i < steps.length - 1) p5.log.message("");
1973
1990
  }
1974
- p5.log.step(`${chalk6.bold(`Step ${stepNum}:`)} Wrap translatable strings`);
1975
- printCodeBlock(snippets.wrapStep.code);
1976
1991
  p5.log.message("");
1977
- for (const line of snippets.whatsNext.split("\n")) {
1978
- p5.log.success(line);
1992
+ const branchList = targetBranches.length > 0 ? targetBranches.map((b) => chalk6.cyan(b)).join(" or ") : chalk6.cyan("your target branch");
1993
+ p5.log.success(
1994
+ `Push to ${branchList} to trigger your first translation run.`
1995
+ );
1996
+ p5.log.message(chalk6.gray(" Docs: https://vocoder.app/docs/getting-started"));
1997
+ }
1998
+ function writeApiKeyToEnv(apiKey) {
1999
+ const envPath = join2(process.cwd(), ".env");
2000
+ if (!existsSync(envPath)) return false;
2001
+ try {
2002
+ const content = readFileSync2(envPath, "utf-8");
2003
+ const keyLine = `VOCODER_API_KEY=${apiKey}`;
2004
+ let updated;
2005
+ if (/^VOCODER_API_KEY=/m.test(content)) {
2006
+ updated = content.replace(/^VOCODER_API_KEY=.*/m, keyLine);
2007
+ } else {
2008
+ const sep = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
2009
+ updated = `${content}${sep}${keyLine}
2010
+ `;
2011
+ }
2012
+ writeFileSync2(envPath, updated);
2013
+ return true;
2014
+ } catch {
2015
+ return false;
1979
2016
  }
1980
2017
  }
1981
- function printMcpSetup(apiKey) {
1982
- const addCommand = `claude mcp add --scope project --transport stdio \\
1983
- --env VOCODER_API_KEY=${apiKey} \\
1984
- vocoder -- npx -y @vocoder/mcp`;
1985
- const teamConfig = JSON.stringify(
1986
- {
1987
- mcpServers: {
1988
- vocoder: {
1989
- type: "stdio",
1990
- command: "npx",
1991
- args: ["-y", "@vocoder/mcp"],
1992
- // biome-ignore lint/suspicious/noTemplateCurlyInString: MCP config template, not a JS template literal
1993
- env: { VOCODER_API_KEY: "${env:VOCODER_API_KEY}" }
1994
- }
1995
- }
1996
- },
1997
- null,
1998
- 2
1999
- );
2000
- p5.log.message("");
2001
- p5.log.message(chalk6.bold("Use Vocoder with Claude Code"));
2002
- p5.log.message("Run this to add the MCP server to your project:");
2018
+ function printApiKey(apiKey) {
2019
+ const saved = writeApiKeyToEnv(apiKey);
2003
2020
  p5.log.message("");
2004
- printCodeBlock(addCommand);
2005
- p5.log.message("");
2006
- p5.log.message(
2007
- "To share with your team, commit " + chalk6.cyan(".mcp.json") + " with an env var reference"
2008
- );
2009
- p5.log.message("so each developer supplies their own key:");
2010
- p5.log.message("");
2011
- printCodeBlock(teamConfig);
2012
- p5.log.message("");
2013
- p5.log.message(chalk6.gray("Setup instructions: https://vocoder.app/docs/mcp"));
2021
+ p5.log.message(chalk6.bold("Your API Key"));
2022
+ printCodeBlock(`VOCODER_API_KEY=${apiKey}`);
2023
+ if (saved) {
2024
+ p5.log.success(chalk6.dim("Saved to .env"));
2025
+ } else {
2026
+ p5.log.message(chalk6.dim(" Add the above to your .env file"));
2027
+ }
2014
2028
  }
2015
2029
  function printCodeBlock(code) {
2016
2030
  const lines = code.split("\n");
@@ -2234,7 +2248,7 @@ async function init(options = {}) {
2234
2248
  exactMatch.projectId
2235
2249
  );
2236
2250
  spinner4.stop("New API key generated");
2237
- printMcpSetup(apiKey);
2251
+ printApiKey(apiKey);
2238
2252
  } catch (err) {
2239
2253
  spinner4.stop("Failed to generate key");
2240
2254
  const msg = err instanceof Error ? err.message : String(err);
@@ -2709,7 +2723,7 @@ Translations won't run automatically until you grant access.
2709
2723
  sourceLocale: projectResult.sourceLocale,
2710
2724
  targetBranches: projectResult.targetBranches
2711
2725
  });
2712
- printMcpSetup(projectResult.apiKey);
2726
+ printApiKey(projectResult.apiKey);
2713
2727
  p5.outro("You're all set.");
2714
2728
  return 0;
2715
2729
  } catch (error) {
@@ -2747,9 +2761,9 @@ async function logout(options = {}) {
2747
2761
 
2748
2762
  // src/commands/sync.ts
2749
2763
  import { createHash, randomUUID } from "crypto";
2750
- import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
2751
- import { join as join2 } from "path";
2752
- import * as p7 from "@clack/prompts";
2764
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
2765
+ import { join as join3 } from "path";
2766
+ import * as p8 from "@clack/prompts";
2753
2767
  import chalk8 from "chalk";
2754
2768
 
2755
2769
  // src/utils/branch.ts
@@ -2819,6 +2833,7 @@ function matchBranchPattern(branch, pattern) {
2819
2833
  }
2820
2834
 
2821
2835
  // src/utils/config.ts
2836
+ import * as p7 from "@clack/prompts";
2822
2837
  import chalk7 from "chalk";
2823
2838
  import { config as loadEnv2 } from "dotenv";
2824
2839
  loadEnv2();
@@ -2896,7 +2911,7 @@ async function getMergedConfig(cliOptions, verbose = false, _startDir) {
2896
2911
  excludePattern = cliOptions.exclude;
2897
2912
  configSources.excludePattern = "CLI flag";
2898
2913
  } else if (envExcludePattern) {
2899
- excludePattern = envExcludePattern.split(",").map((p9) => p9.trim()).filter(Boolean);
2914
+ excludePattern = envExcludePattern.split(",").map((p10) => p10.trim()).filter(Boolean);
2900
2915
  configSources.excludePattern = "environment";
2901
2916
  } else {
2902
2917
  excludePattern = defaults.excludePattern;
@@ -2944,24 +2959,16 @@ async function getMergedConfig(cliOptions, verbose = false, _startDir) {
2944
2959
  configSources.noFallback = "environment";
2945
2960
  }
2946
2961
  if (verbose) {
2947
- console.log(chalk7.dim("\n Configuration sources:"));
2948
- console.log(
2949
- chalk7.dim(` Include patterns: ${configSources.includePattern}`)
2950
- );
2951
- if (excludePattern.length > 0) {
2952
- console.log(
2953
- chalk7.dim(` Exclude patterns: ${configSources.excludePattern}`)
2954
- );
2955
- }
2956
- console.log(chalk7.dim(` API key: ${configSources.apiKey}`));
2957
- console.log(chalk7.dim(` API URL: ${configSources.apiUrl}
2958
- `));
2959
- console.log(chalk7.dim(` Sync mode: ${configSources.mode}`));
2960
- if (maxWaitMs) {
2961
- console.log(chalk7.dim(` Max wait: ${configSources.maxWaitMs}`));
2962
- }
2963
- console.log(chalk7.dim(` No fallback: ${configSources.noFallback}
2964
- `));
2962
+ const lines = [
2963
+ `Include patterns: ${chalk7.cyan(configSources.includePattern)}`,
2964
+ ...excludePattern.length > 0 ? [`Exclude patterns: ${chalk7.cyan(configSources.excludePattern)}`] : [],
2965
+ `API key: ${chalk7.cyan(configSources.apiKey)}`,
2966
+ `API URL: ${chalk7.cyan(configSources.apiUrl)}`,
2967
+ `Sync mode: ${chalk7.cyan(configSources.mode)}`,
2968
+ ...maxWaitMs ? [`Max wait: ${chalk7.cyan(String(configSources.maxWaitMs))}`] : [],
2969
+ `No fallback: ${chalk7.cyan(String(configSources.noFallback))}`
2970
+ ];
2971
+ p7.note(lines.join("\n"), "Configuration sources");
2965
2972
  }
2966
2973
  return {
2967
2974
  includePattern,
@@ -2982,9 +2989,9 @@ function computeStringsHash(texts) {
2982
2989
  }
2983
2990
  function readCachedStringsHash(projectRoot, branch) {
2984
2991
  const filePath = getCacheFilePath(projectRoot, branch);
2985
- if (!existsSync(filePath)) return null;
2992
+ if (!existsSync2(filePath)) return null;
2986
2993
  try {
2987
- const raw = JSON.parse(readFileSync2(filePath, "utf-8"));
2994
+ const raw = JSON.parse(readFileSync3(filePath, "utf-8"));
2988
2995
  if (isRecord(raw) && typeof raw.stringsHash === "string")
2989
2996
  return raw.stringsHash;
2990
2997
  } catch {
@@ -3036,7 +3043,7 @@ function parseTranslations(value) {
3036
3043
  }
3037
3044
  function getCacheFilePath(projectRoot, branch) {
3038
3045
  const branchHash = createHash("sha1").update(branch).digest("hex").slice(0, 12);
3039
- return join2(
3046
+ return join3(
3040
3047
  projectRoot,
3041
3048
  "node_modules",
3042
3049
  ".vocoder",
@@ -3049,11 +3056,11 @@ function readLocalSnapshotCache(params) {
3049
3056
  const candidateBranches = params.branch === "main" ? ["main"] : [params.branch, "main"];
3050
3057
  for (const candidateBranch of candidateBranches) {
3051
3058
  const cacheFilePath = getCacheFilePath(params.projectRoot, candidateBranch);
3052
- if (!existsSync(cacheFilePath)) {
3059
+ if (!existsSync2(cacheFilePath)) {
3053
3060
  continue;
3054
3061
  }
3055
3062
  try {
3056
- const raw = readFileSync2(cacheFilePath, "utf-8");
3063
+ const raw = readFileSync3(cacheFilePath, "utf-8");
3057
3064
  const parsed = JSON.parse(raw);
3058
3065
  if (!isRecord(parsed)) {
3059
3066
  continue;
@@ -3079,7 +3086,7 @@ function readLocalSnapshotCache(params) {
3079
3086
  function writeLocalSnapshotCache(params) {
3080
3087
  const cacheFilePath = getCacheFilePath(params.projectRoot, params.branch);
3081
3088
  mkdirSync2(
3082
- join2(params.projectRoot, "node_modules", ".vocoder", "cache", "sync"),
3089
+ join3(params.projectRoot, "node_modules", ".vocoder", "cache", "sync"),
3083
3090
  {
3084
3091
  recursive: true
3085
3092
  }
@@ -3096,7 +3103,7 @@ function writeLocalSnapshotCache(params) {
3096
3103
  ...params.localeMetadata ? { localeMetadata: params.localeMetadata } : {},
3097
3104
  translations: params.translations
3098
3105
  };
3099
- writeFileSync2(cacheFilePath, JSON.stringify(payload, null, 2), "utf-8");
3106
+ writeFileSync3(cacheFilePath, JSON.stringify(payload, null, 2), "utf-8");
3100
3107
  return cacheFilePath;
3101
3108
  }
3102
3109
  function resolveEffectiveModeFromPolicy(params) {
@@ -3250,8 +3257,8 @@ async function fetchApiSnapshot(api, params) {
3250
3257
  async function sync(options = {}) {
3251
3258
  const startTime = Date.now();
3252
3259
  const projectRoot = process.cwd();
3253
- p7.intro("Vocoder Sync");
3254
- const spinner4 = p7.spinner();
3260
+ p8.intro("Vocoder Sync");
3261
+ const spinner4 = p8.spinner();
3255
3262
  try {
3256
3263
  spinner4.start("Detecting branch");
3257
3264
  const branch = detectBranch(options.branch);
@@ -3280,12 +3287,12 @@ async function sync(options = {}) {
3280
3287
  };
3281
3288
  spinner4.stop("Project configuration loaded");
3282
3289
  if (!options.force && !isTargetBranch(branch, config.targetBranches)) {
3283
- p7.log.warn(
3290
+ p8.log.warn(
3284
3291
  `Skipping translations (${chalk8.cyan(branch)} is not a target branch)`
3285
3292
  );
3286
- p7.log.info(`Target branches: ${config.targetBranches.join(", ")}`);
3287
- p7.log.info("Use --force to translate anyway");
3288
- p7.outro("");
3293
+ p8.log.info(`Target branches: ${config.targetBranches.join(", ")}`);
3294
+ p8.log.info("Use --force to translate anyway");
3295
+ p8.outro("");
3289
3296
  return 0;
3290
3297
  }
3291
3298
  const patternsDisplay = Array.isArray(config.includePattern) ? config.includePattern.join(", ") : config.includePattern;
@@ -3298,10 +3305,10 @@ async function sync(options = {}) {
3298
3305
  );
3299
3306
  if (extractedStrings.length === 0) {
3300
3307
  spinner4.stop("No translatable strings found");
3301
- p7.log.warn(
3308
+ p8.log.warn(
3302
3309
  "Make sure you are wrapping translatable strings with Vocoder"
3303
3310
  );
3304
- p7.outro("");
3311
+ p8.outro("");
3305
3312
  return 0;
3306
3313
  }
3307
3314
  spinner4.stop(
@@ -3312,10 +3319,10 @@ async function sync(options = {}) {
3312
3319
  if (extractedStrings.length > 5) {
3313
3320
  sampleLines.push(` ... and ${extractedStrings.length - 5} more`);
3314
3321
  }
3315
- p7.note(sampleLines.join("\n"), "Sample strings");
3322
+ p8.note(sampleLines.join("\n"), "Sample strings");
3316
3323
  }
3317
3324
  if (options.dryRun) {
3318
- p7.note(
3325
+ p8.note(
3319
3326
  [
3320
3327
  `Strings: ${extractedStrings.length}`,
3321
3328
  `Branch: ${branch}`,
@@ -3326,12 +3333,12 @@ async function sync(options = {}) {
3326
3333
  ].join("\n"),
3327
3334
  "Dry run - would translate"
3328
3335
  );
3329
- p7.outro("No API calls made.");
3336
+ p8.outro("No API calls made.");
3330
3337
  return 0;
3331
3338
  }
3332
3339
  const repoIdentity = resolveGitRepositoryIdentity();
3333
3340
  if (!repoIdentity && options.verbose) {
3334
- p7.log.warn(
3341
+ p8.log.warn(
3335
3342
  "Could not detect git remote origin. Sync will continue without repo metadata."
3336
3343
  );
3337
3344
  }
@@ -3339,16 +3346,32 @@ async function sync(options = {}) {
3339
3346
  const stringEntries = buildStringEntries(extractedStrings);
3340
3347
  const sourceStrings = stringEntries.map((entry) => entry.text);
3341
3348
  if (options.verbose && stringEntries.length !== extractedStrings.length) {
3342
- p7.log.info(
3349
+ p8.log.info(
3343
3350
  `Deduped ${extractedStrings.length} extracted entries into ${stringEntries.length} unique source strings`
3344
3351
  );
3345
3352
  }
3346
3353
  const currentHash = computeStringsHash(sourceStrings);
3347
3354
  if (!options.force) {
3348
3355
  const cachedHash = readCachedStringsHash(projectRoot, branch);
3356
+ if (options.verbose) {
3357
+ const cacheFile = getCacheFilePath(projectRoot, branch);
3358
+ if (cachedHash) {
3359
+ p8.log.info(
3360
+ `Local cache: ${chalk8.dim(cacheFile)}
3361
+ cached hash ${chalk8.cyan(cachedHash.slice(0, 8))}\u2026 vs current ${chalk8.cyan(currentHash.slice(0, 8))}\u2026 \u2014 ${cachedHash === currentHash ? chalk8.green("match") : chalk8.yellow("changed")}`
3362
+ );
3363
+ } else {
3364
+ p8.log.info(`No local cache found at ${chalk8.dim(cacheFile)} \u2014 will submit to API`);
3365
+ }
3366
+ }
3349
3367
  if (cachedHash && cachedHash === currentHash) {
3368
+ if (options.verbose) {
3369
+ p8.log.info(
3370
+ "Skipping API submission \u2014 delete node_modules/.vocoder to force a fresh sync"
3371
+ );
3372
+ }
3350
3373
  const duration2 = ((Date.now() - startTime) / 1e3).toFixed(1);
3351
- p7.outro(`Up to date (${duration2}s)`);
3374
+ p8.outro(`Up to date (${duration2}s)`);
3352
3375
  return 0;
3353
3376
  }
3354
3377
  }
@@ -3373,31 +3396,31 @@ async function sync(options = {}) {
3373
3396
  policy: config.syncPolicy
3374
3397
  });
3375
3398
  if (options.verbose) {
3376
- p7.log.info(`Requested mode: ${requestedMode}`);
3377
- p7.log.info(`Effective mode: ${effectiveMode}`);
3378
- p7.log.info(`Wait timeout: ${waitTimeoutMs}ms`);
3399
+ p8.log.info(`Requested mode: ${requestedMode}`);
3400
+ p8.log.info(`Effective mode: ${effectiveMode}`);
3401
+ p8.log.info(`Wait timeout: ${waitTimeoutMs}ms`);
3379
3402
  if (batchResponse.queueStatus) {
3380
- p7.log.info(`Queue status: ${batchResponse.queueStatus}`);
3403
+ p8.log.info(`Queue status: ${batchResponse.queueStatus}`);
3381
3404
  }
3382
3405
  }
3383
3406
  if (batchResponse.status === "UP_TO_DATE" && batchResponse.noChanges) {
3384
- p7.log.success("No changes detected - strings are up to date");
3407
+ p8.log.success("No changes detected - strings are up to date");
3385
3408
  }
3386
- p7.log.info(`New strings: ${chalk8.cyan(batchResponse.newStrings)}`);
3409
+ p8.log.info(`New strings: ${chalk8.cyan(batchResponse.newStrings)}`);
3387
3410
  if (batchResponse.deletedStrings && batchResponse.deletedStrings > 0) {
3388
- p7.log.info(
3411
+ p8.log.info(
3389
3412
  `Deleted strings: ${chalk8.yellow(batchResponse.deletedStrings)} (archived)`
3390
3413
  );
3391
3414
  }
3392
- p7.log.info(`Total strings: ${chalk8.cyan(batchResponse.totalStrings)}`);
3415
+ p8.log.info(`Total strings: ${chalk8.cyan(batchResponse.totalStrings)}`);
3393
3416
  if (batchResponse.newStrings === 0) {
3394
- p7.log.success("No new strings - using existing translations");
3417
+ p8.log.success("No new strings - using existing translations");
3395
3418
  } else {
3396
- p7.log.info(
3419
+ p8.log.info(
3397
3420
  `Syncing to ${config.targetLocales.length} locales (${config.targetLocales.join(", ")})`
3398
3421
  );
3399
3422
  if (batchResponse.estimatedTime) {
3400
- p7.log.info(`Estimated time: ~${batchResponse.estimatedTime}s`);
3423
+ p8.log.info(`Estimated time: ~${batchResponse.estimatedTime}s`);
3401
3424
  }
3402
3425
  }
3403
3426
  let artifacts = null;
@@ -3435,7 +3458,7 @@ async function sync(options = {}) {
3435
3458
  if (effectiveMode === "required") {
3436
3459
  throw waitError;
3437
3460
  }
3438
- p7.log.warn(`Best-effort wait ended early: ${waitError.message}`);
3461
+ p8.log.warn(`Best-effort wait ended early: ${waitError.message}`);
3439
3462
  }
3440
3463
  }
3441
3464
  if (!artifacts) {
@@ -3469,7 +3492,7 @@ async function sync(options = {}) {
3469
3492
  spinner4.stop("Failed to fetch API snapshot");
3470
3493
  if (options.verbose) {
3471
3494
  const message = error instanceof Error ? error.message : "Unknown snapshot fetch error";
3472
- p7.log.warn(`Snapshot fetch error: ${message}`);
3495
+ p8.log.warn(`Snapshot fetch error: ${message}`);
3473
3496
  }
3474
3497
  }
3475
3498
  }
@@ -3503,61 +3526,61 @@ async function sync(options = {}) {
3503
3526
  completedAt: artifacts.completedAt ?? (artifacts.source === "fresh" ? (/* @__PURE__ */ new Date()).toISOString() : null)
3504
3527
  });
3505
3528
  if (options.verbose) {
3506
- p7.log.info(`Cached snapshot: ${cachePath}`);
3529
+ p8.log.info(`Cached snapshot: ${cachePath}`);
3507
3530
  }
3508
3531
  } catch (error) {
3509
3532
  if (options.verbose) {
3510
3533
  const message = error instanceof Error ? error.message : "Unknown cache write error";
3511
- p7.log.warn(`Failed to write local snapshot cache: ${message}`);
3534
+ p8.log.warn(`Failed to write local snapshot cache: ${message}`);
3512
3535
  }
3513
3536
  }
3514
3537
  if (artifacts.source !== "fresh") {
3515
3538
  const sourceLabel = artifacts.source === "local-cache" ? "local cached snapshot" : "completed API snapshot";
3516
- p7.log.warn(
3539
+ p8.log.warn(
3517
3540
  `Using ${sourceLabel}. New strings may appear after the background sync completes.`
3518
3541
  );
3519
3542
  }
3520
3543
  const duration = ((Date.now() - startTime) / 1e3).toFixed(1);
3521
- p7.outro(`Sync complete! (${duration}s)`);
3544
+ p8.outro(`Sync complete! (${duration}s)`);
3522
3545
  return 0;
3523
3546
  } catch (error) {
3524
3547
  spinner4.stop();
3525
3548
  if (error instanceof VocoderAPIError && error.syncPolicyError) {
3526
- p7.log.error(error.syncPolicyError.message);
3549
+ p8.log.error(error.syncPolicyError.message);
3527
3550
  const guidance = getSyncPolicyErrorGuidance(error.syncPolicyError);
3528
3551
  for (const line of guidance) {
3529
- p7.log.info(line);
3552
+ p8.log.info(line);
3530
3553
  }
3531
3554
  return 1;
3532
3555
  }
3533
3556
  if (error instanceof VocoderAPIError && error.limitError) {
3534
3557
  const { limitError } = error;
3535
- p7.log.error(limitError.message);
3558
+ p8.log.error(limitError.message);
3536
3559
  const guidance = getLimitErrorGuidance(limitError);
3537
3560
  for (const line of guidance) {
3538
- p7.log.info(line);
3561
+ p8.log.info(line);
3539
3562
  }
3540
3563
  return 1;
3541
3564
  }
3542
3565
  if (error instanceof Error) {
3543
- p7.log.error(error.message);
3566
+ p8.log.error(error.message);
3544
3567
  if (error.message.includes("VOCODER_API_KEY")) {
3545
- p7.log.warn(
3568
+ p8.log.warn(
3546
3569
  "VOCODER_API_KEY is only needed for `vocoder sync` (CLI push)."
3547
3570
  );
3548
- p7.log.info(" Create one at: https://vocoder.app/dashboard");
3549
- p7.log.info(' Then: export VOCODER_API_KEY="vc_..." or add it to .env');
3550
- p7.log.info("");
3551
- p7.log.info(
3571
+ p8.log.info(" Create one at: https://vocoder.app/dashboard");
3572
+ p8.log.info(' Then: export VOCODER_API_KEY="vc_..." or add it to .env');
3573
+ p8.log.info("");
3574
+ p8.log.info(
3552
3575
  " Note: If you use @vocoder/unplugin, `vocoder sync` is optional."
3553
3576
  );
3554
- p7.log.info(" Translations are fetched automatically at build time.");
3577
+ p8.log.info(" Translations are fetched automatically at build time.");
3555
3578
  } else if (error.message.includes("git branch")) {
3556
- p7.log.warn("Run from a git repository, or use:");
3557
- p7.log.info(" vocoder sync --branch main");
3579
+ p8.log.warn("Run from a git repository, or use:");
3580
+ p8.log.info(" vocoder sync --branch main");
3558
3581
  }
3559
3582
  if (options.verbose) {
3560
- p7.log.info(`Full error: ${error.stack ?? error}`);
3583
+ p8.log.info(`Full error: ${error.stack ?? error}`);
3561
3584
  }
3562
3585
  }
3563
3586
  return 1;
@@ -3565,26 +3588,26 @@ async function sync(options = {}) {
3565
3588
  }
3566
3589
 
3567
3590
  // src/commands/whoami.ts
3568
- import * as p8 from "@clack/prompts";
3591
+ import * as p9 from "@clack/prompts";
3569
3592
  import chalk9 from "chalk";
3570
3593
  async function whoami(options = {}) {
3571
3594
  const stored = readAuthData();
3572
3595
  if (!stored) {
3573
- p8.log.info("Not logged in. Run `vocoder init` to authenticate.");
3596
+ p9.log.info("Not logged in. Run `vocoder init` to authenticate.");
3574
3597
  return 1;
3575
3598
  }
3576
3599
  const apiUrl = options.apiUrl ?? stored.apiUrl ?? "https://vocoder.app";
3577
3600
  const api = new VocoderAPI({ apiUrl, apiKey: "" });
3578
3601
  try {
3579
3602
  const info = await api.getCliUserInfo(stored.token);
3580
- p8.log.info(`Logged in as ${chalk9.bold(info.email)}`);
3603
+ p9.log.info(`Logged in as ${chalk9.bold(info.email)}`);
3581
3604
  if (info.name) {
3582
- p8.log.info(`Name: ${info.name}`);
3605
+ p9.log.info(`Name: ${info.name}`);
3583
3606
  }
3584
- p8.log.info(`API: ${apiUrl}`);
3607
+ p9.log.info(`API: ${apiUrl}`);
3585
3608
  return 0;
3586
3609
  } catch {
3587
- p8.log.error(
3610
+ p9.log.error(
3588
3611
  "Stored credentials are invalid or expired. Run `vocoder init` to re-authenticate."
3589
3612
  );
3590
3613
  return 1;