@vocoder/cli 0.15.0 → 0.16.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/dist/bin.mjs CHANGED
@@ -9,12 +9,11 @@ import {
9
9
  computeFingerprint,
10
10
  detectLocalEcosystem,
11
11
  getPackagesToInstall,
12
- getSetupSnippets,
13
12
  loadVocoderConfig,
14
13
  readAuthData,
15
14
  verifyStoredAuth,
16
15
  writeAuthData
17
- } from "./chunk-62KCB6C6.mjs";
16
+ } from "./chunk-IK4ZCESQ.mjs";
18
17
 
19
18
  // src/bin.ts
20
19
  import { Command } from "commander";
@@ -172,7 +171,7 @@ ${symbol(this.state)} App directories
172
171
  ];
173
172
  for (let i = 0; i < added.length; i++) {
174
173
  const isCursor = i === cursor && !addCursor;
175
- const icon = isCursor ? active("\u25FC") : info("\u25FC");
174
+ const icon = active("\u25FC");
176
175
  const label = isCursor ? bld(added[i]) : added[i];
177
176
  lines.push(`${info(S_BAR)} ${icon} ${label}`);
178
177
  }
@@ -373,7 +372,7 @@ function buildList(filtered, cursor, scrollOffset, selected, filter, customPatte
373
372
  const item = filtered[i];
374
373
  const isCursor = i === cursor && !addCursor;
375
374
  const isChecked = selected.has(item.value);
376
- const icon = isChecked ? isCursor ? info("\u25FC") : info("\u25FC") : isCursor ? active("\u25FB") : dim("\u25FB");
375
+ const icon = isChecked ? active("\u25FC") : isCursor ? active("\u25FB") : dim("\u25FB");
377
376
  let label = item.isCustom ? `${item.label} ${dim("(custom)")}` : item.label;
378
377
  if (isCursor) label = bld(label);
379
378
  lines.push(`${info(S_BAR2)} ${icon} ${label}`);
@@ -389,7 +388,7 @@ function buildList(filtered, cursor, scrollOffset, selected, filter, customPatte
389
388
  lines.push(dim(`${S_BAR2} No branches detected`));
390
389
  }
391
390
  const hidden = filtered.length - (end - scrollOffset);
392
- if (hidden > 0) lines.push(dim(`${S_BAR2} ${hidden} more`));
391
+ if (hidden > 0) lines.push(dim(`${S_BAR2} ${hidden} more \u2014 keep typing to narrow`));
393
392
  return lines.join("\n");
394
393
  }
395
394
  async function filterableBranchSelect(params) {
@@ -434,7 +433,16 @@ async function filterableBranchSelect(params) {
434
433
  ${symbol2(this.state)} ${message}
435
434
  `;
436
435
  const inputHint = filter.length > 0 ? filter : dim("type to filter \xB7 type a custom pattern to add it");
437
- const footer = selected.size > 0 ? dim(`${S_BAR2} ${selected.size} selected \xB7 \u2191\u2193 navigate \xB7 Space to select \xB7 Enter to confirm`) : optional ? dim(`${S_BAR2} \u2191\u2193 navigate \xB7 Space to select \xB7 Enter to skip`) : dim(`${S_BAR2} \u2191\u2193 navigate \xB7 Space to select \xB7 Enter to confirm`);
436
+ const trimmedFilter = filter.trim();
437
+ const footer = (() => {
438
+ if (trimmedFilter.length > 0 && isNewPattern()) {
439
+ return dim(`${S_BAR2} Space to add "${trimmedFilter}" \xB7 \u2191\u2193 navigate \xB7 Enter to confirm`);
440
+ }
441
+ if (selected.size > 0) {
442
+ return dim(`${S_BAR2} ${selected.size} selected \xB7 \u2191\u2193 navigate \xB7 Space to select \xB7 Enter to confirm`);
443
+ }
444
+ return optional ? dim(`${S_BAR2} \u2191\u2193 navigate \xB7 Space to select \xB7 Enter to skip`) : dim(`${S_BAR2} \u2191\u2193 navigate \xB7 Space to select \xB7 Enter to confirm`);
445
+ })();
438
446
  switch (this.state) {
439
447
  case "submit": {
440
448
  const summary = selected.size > 0 ? bld(Array.from(selected).join(", ")) : dim("none");
@@ -479,6 +487,9 @@ ${symbol2(this.state)} ${message}
479
487
  scrollOffset = 0;
480
488
  addCursor = false;
481
489
  }
490
+ if (isNewPattern()) {
491
+ addCursor = true;
492
+ }
482
493
  });
483
494
  prompt.on("cursor", (action) => {
484
495
  const filtered = getFiltered();
@@ -495,9 +506,9 @@ ${symbol2(this.state)} ${message}
495
506
  addCursor = true;
496
507
  else if (!addCursor) cursor = Math.min(filtered.length - 1, cursor + 1);
497
508
  break;
498
- case "space":
499
- if (addCursor) {
500
- const t = filter.trim();
509
+ case "space": {
510
+ const t = filter.trim();
511
+ if (addCursor || t.length > 0 && isNewPattern()) {
501
512
  const err = validateBranchPattern(t) ?? (excludedSet.has(t) ? "Already used for automatic translation" : null);
502
513
  if (!err) {
503
514
  customPatterns.push(t);
@@ -515,6 +526,7 @@ ${symbol2(this.state)} ${message}
515
526
  }
516
527
  }
517
528
  break;
529
+ }
518
530
  }
519
531
  });
520
532
  prompt.on("finalize", () => {
@@ -564,14 +576,13 @@ function buildList2(filtered, cursor, scrollOffset, selected) {
564
576
  const opt = filtered[i];
565
577
  const isCursor = i === cursor;
566
578
  const isChecked = isMulti && selected.has(opt.bcp47);
567
- const icon = isMulti ? isChecked ? isCursor ? info("\u25FC") : info("\u25FC") : isCursor ? active("\u25FB") : dim("\u25FB") : isCursor ? active("\u25CF") : dim("\u25CB");
579
+ const icon = isMulti ? isChecked ? active("\u25FC") : isCursor ? active("\u25FB") : dim("\u25FB") : isCursor ? active("\u25CF") : dim("\u25CB");
568
580
  visibleLines.push(
569
581
  `${info(S_BAR3)} ${icon} ${isCursor ? bld(opt.label) : opt.label}`
570
582
  );
571
583
  }
572
584
  const hidden = filtered.length - (end - scrollOffset);
573
- if (hidden > 0)
574
- visibleLines.push(dim(`${S_BAR3} ${hidden} more \u2014 keep typing to narrow`));
585
+ if (hidden > 0) visibleLines.push(dim(`${S_BAR3} ${hidden} more \u2014 keep typing to narrow`));
575
586
  if (filtered.length === 0) visibleLines.push(dim(`${S_BAR3} No matches`));
576
587
  return visibleLines.join("\n");
577
588
  }
@@ -793,51 +804,45 @@ async function runProjectCreate(params) {
793
804
  {
794
805
  let initial = initialBranches;
795
806
  while (pushBranches.length === 0) {
796
- const result = await filterableBranchSelect({
807
+ const result2 = await filterableBranchSelect({
797
808
  message: "Which branches should trigger translations?",
798
809
  branches: detected.branches,
799
810
  defaultBranch: detected.defaultBranch,
800
811
  initialValues: initial
801
812
  });
802
- if (result === null) return null;
803
- if (result.length === 0) {
813
+ if (result2 === null) return null;
814
+ if (result2.length === 0) {
804
815
  p3.log.warn(
805
816
  "At least one branch is required. Please select at least one."
806
817
  );
807
818
  initial = [detected.defaultBranch];
808
819
  } else {
809
- pushBranches = result;
820
+ pushBranches = result2;
810
821
  }
811
822
  }
812
823
  }
813
824
  const targetBranches = pushBranches;
814
- try {
815
- const result = await api.createProject(userToken, {
816
- organizationId,
817
- name: projectName,
818
- sourceLocale,
819
- targetLocales,
820
- targetBranches,
821
- appDirs,
822
- repoCanonical
823
- });
824
- p3.log.success(`Project ${chalk2.bold(result.projectName)} created!`);
825
- return {
826
- projectId: result.projectId,
827
- projectName: result.projectName,
828
- apiKey: result.apiKey,
829
- sourceLocale,
830
- targetLocales,
831
- targetBranches,
832
- repositoryBound: result.repositoryBound,
833
- configureUrl: result.configureUrl,
834
- apps: result.apps
835
- };
836
- } catch (error) {
837
- const message = error instanceof Error ? error.message : "Unknown error";
838
- p3.log.error(`Failed to create project: ${message}`);
839
- return null;
840
- }
825
+ const result = await api.createProject(userToken, {
826
+ organizationId,
827
+ name: projectName,
828
+ sourceLocale,
829
+ targetLocales,
830
+ targetBranches,
831
+ appDirs,
832
+ repoCanonical
833
+ });
834
+ p3.log.success(`Project ${chalk2.bold(result.projectName)} created!`);
835
+ return {
836
+ projectId: result.projectId,
837
+ projectName: result.projectName,
838
+ apiKey: result.apiKey,
839
+ sourceLocale,
840
+ targetLocales,
841
+ targetBranches,
842
+ repositoryBound: result.repositoryBound,
843
+ configureUrl: result.configureUrl,
844
+ apps: result.apps
845
+ };
841
846
  }
842
847
  async function runAppCreate(params) {
843
848
  const { api, userToken, projectId, projectName, repoCanonical } = params;
@@ -890,48 +895,42 @@ async function runAppCreate(params) {
890
895
  {
891
896
  let initial = [detectedApp.defaultBranch];
892
897
  while (appPushBranches.length === 0) {
893
- const result = await filterableBranchSelect({
898
+ const result2 = await filterableBranchSelect({
894
899
  message: "Which branches should trigger translations?",
895
900
  branches: detectedApp.branches,
896
901
  defaultBranch: detectedApp.defaultBranch,
897
902
  initialValues: initial
898
903
  });
899
- if (result === null) return null;
900
- if (result.length === 0) {
904
+ if (result2 === null) return null;
905
+ if (result2.length === 0) {
901
906
  p3.log.warn("At least one branch is required.");
902
907
  initial = [detectedApp.defaultBranch];
903
908
  } else {
904
- appPushBranches = result;
909
+ appPushBranches = result2;
905
910
  }
906
911
  }
907
912
  }
908
913
  const targetBranches = appPushBranches;
909
- try {
910
- const result = await api.createApp(userToken, {
911
- projectId,
912
- appDir,
913
- sourceLocale,
914
- targetLocales,
915
- targetBranches,
916
- repoCanonical: repoCanonical ?? ""
917
- });
918
- p3.log.success(
919
- `App ${chalk2.bold(appDir || "(root)")} added to ${chalk2.bold(projectName)}!`
920
- );
921
- return {
922
- projectId: result.projectId,
923
- projectName: result.projectName,
924
- appDir: result.appDir,
925
- appId: result.appId,
926
- sourceLocale,
927
- targetLocales,
928
- targetBranches
929
- };
930
- } catch (error) {
931
- const message = error instanceof Error ? error.message : "Unknown error";
932
- p3.log.error(`Failed to add app: ${message}`);
933
- return null;
934
- }
914
+ const result = await api.createApp(userToken, {
915
+ projectId,
916
+ appDir,
917
+ sourceLocale,
918
+ targetLocales,
919
+ targetBranches,
920
+ repoCanonical: repoCanonical ?? ""
921
+ });
922
+ p3.log.success(
923
+ `App ${chalk2.bold(appDir || "(root)")} added to ${chalk2.bold(projectName)}!`
924
+ );
925
+ return {
926
+ projectId: result.projectId,
927
+ projectName: result.projectName,
928
+ appDir: result.appDir,
929
+ appId: result.appId,
930
+ sourceLocale,
931
+ targetLocales,
932
+ targetBranches
933
+ };
935
934
  }
936
935
 
937
936
  // src/utils/github-connect.ts
@@ -1141,7 +1140,6 @@ async function runGitHubDiscoveryFlow(params) {
1141
1140
  callbackPort: server?.port
1142
1141
  });
1143
1142
  p4.log.info("Opening GitHub to authorize your account...");
1144
- p4.note("Complete authorization in your browser.");
1145
1143
  if (process.stdin.isTTY && process.stdout.isTTY && process.env.CI !== "true") {
1146
1144
  const shouldOpen = params.yes ? true : await p4.confirm({ message: "Open in your browser?" });
1147
1145
  if (p4.isCancel(shouldOpen)) {
@@ -1193,7 +1191,7 @@ async function selectGitHubInstallation(installations, canInstallNew) {
1193
1191
  value: String(inst.installationId),
1194
1192
  label: inst.accountLogin,
1195
1193
  hint: [
1196
- inst.accountType === "Organization" ? "organization" : "personal",
1194
+ inst.accountType === "Organization" ? "GitHub org" : "personal account",
1197
1195
  inst.conflictLabel ? `connected to ${inst.conflictLabel}` : "",
1198
1196
  inst.isSuspended ? "suspended" : ""
1199
1197
  ].filter(Boolean).join(" \xB7 ") || void 0
@@ -1201,11 +1199,11 @@ async function selectGitHubInstallation(installations, canInstallNew) {
1201
1199
  if (canInstallNew) {
1202
1200
  options.push({
1203
1201
  value: "install_new",
1204
- label: `Install on a new account ${chalk3.dim("(creates a new personal workspace)")}`
1202
+ label: `Install on a new account ${chalk3.dim("(creates a new workspace)")}`
1205
1203
  });
1206
1204
  }
1207
1205
  const selected = await p4.select({
1208
- message: "Select a GitHub installation",
1206
+ message: "Which GitHub account should this workspace connect to?",
1209
1207
  options
1210
1208
  });
1211
1209
  if (p4.isCancel(selected)) return null;
@@ -1416,7 +1414,7 @@ function printPlanLimitMessage(apiUrl, message) {
1416
1414
  p6.log.info(`Manage subscription: ${getSubscriptionSettingsUrl(apiUrl)}`);
1417
1415
  }
1418
1416
  function runScaffold(params) {
1419
- const { sourceLocale, targetBranches } = params;
1417
+ const { targetBranches } = params;
1420
1418
  const detection = detectLocalEcosystem();
1421
1419
  const useTypeScript = detection.isTypeScript;
1422
1420
  if (detection.ecosystem) {
@@ -1459,43 +1457,8 @@ function runScaffold(params) {
1459
1457
  } else if (detection.ecosystem) {
1460
1458
  p6.log.info(`Packages: ${chalk5.green("already installed")}`);
1461
1459
  }
1462
- const snippets = getSetupSnippets({
1463
- framework: detection.framework,
1464
- ecosystem: detection.ecosystem,
1465
- sourceLocale,
1466
- targetBranches
1467
- });
1468
- const steps = [];
1469
- if (snippets.pluginStep) {
1470
- steps.push({
1471
- label: snippets.pluginStep.file,
1472
- hint: "register the build plugin so Vocoder can extract your strings",
1473
- code: snippets.pluginStep.code
1474
- });
1475
- }
1476
- if (snippets.providerStep) {
1477
- steps.push({
1478
- label: snippets.providerStep.file,
1479
- hint: "wrap your app so translations load at runtime",
1480
- code: snippets.providerStep.code
1481
- });
1482
- }
1483
- steps.push({
1484
- label: "wrap translatable text",
1485
- hint: "mark strings for extraction \u2014 Vocoder picks these up on push",
1486
- code: snippets.wrapStep.code
1487
- });
1488
- p6.log.message("");
1489
- p6.log.message(chalk5.bold("Finish setup in your code"));
1490
- p6.log.message("");
1491
- for (let i = 0; i < steps.length; i++) {
1492
- const step = steps[i];
1493
- p6.log.step(`${chalk5.bold(step.label)} ${chalk5.dim(`\u2014 ${step.hint}`)}`);
1494
- printCodeBlock(step.code);
1495
- if (i < steps.length - 1) p6.log.message("");
1496
- }
1497
- p6.log.message("");
1498
1460
  const branchList = targetBranches.length > 0 ? targetBranches.map((b) => highlight(b)).join(" or ") : highlight("your target branch");
1461
+ p6.log.message("");
1499
1462
  p6.log.success(`Push to ${branchList} to trigger your first translation run.`);
1500
1463
  p6.log.message(info(" Docs: https://vocoder.app/docs/getting-started"));
1501
1464
  }
@@ -1551,28 +1514,100 @@ function writeAppConfigs(apps, targetBranches, useTypeScript, repoRoot) {
1551
1514
  }
1552
1515
  }
1553
1516
  }
1554
- function printCodeBlock(code) {
1555
- const lines = code.split("\n");
1556
- const maxLen = lines.reduce(
1557
- (max, line) => Math.max(max, line.length),
1558
- 0
1517
+ var MCP_DOCS_URL = "https://vocoder.app/docs/mcp";
1518
+ function mcpServerJson(apiKey) {
1519
+ return JSON.stringify(
1520
+ {
1521
+ mcpServers: {
1522
+ vocoder: {
1523
+ type: "stdio",
1524
+ command: "npx",
1525
+ args: ["-y", "@vocoder/mcp"],
1526
+ env: { VOCODER_API_KEY: apiKey }
1527
+ }
1528
+ }
1529
+ },
1530
+ null,
1531
+ 2
1559
1532
  );
1560
- const bar = chalk5.gray("\u2502");
1561
- const pad = (s) => s + " ".repeat(maxLen - s.length);
1562
- process.stdout.write(`${chalk5.gray("\u2502")}
1533
+ }
1534
+ async function runMcpSetup(apiKey) {
1535
+ const tool = await p6.select({
1536
+ message: "Which AI editor?",
1537
+ options: [
1538
+ { value: "claude", label: "Claude Code" },
1539
+ { value: "cursor", label: "Cursor" },
1540
+ { value: "windsurf", label: "Windsurf" },
1541
+ { value: "vscode", label: "VS Code (GitHub Copilot)" },
1542
+ { value: "other", label: "Other \u2014 show the config JSON" }
1543
+ ]
1544
+ });
1545
+ if (p6.isCancel(tool)) return;
1546
+ if (tool === "claude") {
1547
+ try {
1548
+ execSync3(
1549
+ `claude mcp add --scope user --transport stdio --env VOCODER_API_KEY=${apiKey} vocoder -- npx -y @vocoder/mcp`,
1550
+ { stdio: "pipe" }
1551
+ );
1552
+ p6.log.success("Vocoder MCP server registered in Claude Code.");
1553
+ } catch {
1554
+ p6.log.message("Run this to register the MCP server:");
1555
+ printCommand(
1556
+ `claude mcp add --scope user --transport stdio --env VOCODER_API_KEY=${apiKey} vocoder -- npx -y @vocoder/mcp`
1557
+ );
1558
+ p6.log.message(info(` Docs: ${MCP_DOCS_URL}`));
1559
+ }
1560
+ return;
1561
+ }
1562
+ const configPaths = {
1563
+ cursor: { path: "~/.cursor/mcp.json", merge: true },
1564
+ windsurf: { path: "~/.codeium/windsurf/mcp_config.json", merge: true },
1565
+ vscode: { path: ".vscode/mcp.json", merge: true },
1566
+ other: { path: ".mcp.json", merge: false }
1567
+ };
1568
+ const { path: configPath, merge } = configPaths[tool];
1569
+ const mergeNote = merge ? chalk5.dim(` Merge into ${highlight(configPath)} (create if missing):`) : chalk5.dim(` Add to ${highlight(configPath)}:`);
1570
+ p6.log.message(mergeNote);
1571
+ printCodeBlock(mcpServerJson(apiKey));
1572
+ p6.log.message(info(` Docs: ${MCP_DOCS_URL}`));
1573
+ }
1574
+ function tryClipboard(text2) {
1575
+ const tools = [
1576
+ { cmd: "pbcopy" },
1577
+ { cmd: "xclip", args: ["-selection", "clipboard"] },
1578
+ { cmd: "xsel", args: ["--clipboard", "--input"] },
1579
+ { cmd: "wl-copy" },
1580
+ { cmd: "clip" }
1581
+ ];
1582
+ for (const { cmd, args = [] } of tools) {
1583
+ try {
1584
+ execSync3([cmd, ...args].join(" "), {
1585
+ input: text2,
1586
+ stdio: ["pipe", "ignore", "ignore"]
1587
+ });
1588
+ return true;
1589
+ } catch {
1590
+ continue;
1591
+ }
1592
+ }
1593
+ return false;
1594
+ }
1595
+ function printCommand(cmd) {
1596
+ const copied = tryClipboard(cmd);
1597
+ process.stdout.write("\n");
1598
+ process.stdout.write(` ${chalk5.dim("$")} ${chalk5.cyan(cmd)}
1563
1599
  `);
1564
- process.stdout.write(
1565
- `${chalk5.gray("\u2502")} ${chalk5.gray(`\u250C${"\u2500".repeat(maxLen + 2)}\u2510`)}
1566
- `
1567
- );
1568
- for (const line of lines) {
1569
- process.stdout.write(`${chalk5.gray("\u2502")} ${bar} ${pad(line)} ${bar}
1600
+ if (copied) process.stdout.write(` ${chalk5.dim("\u2191 copied to clipboard")}
1601
+ `);
1602
+ process.stdout.write("\n");
1603
+ }
1604
+ function printCodeBlock(code) {
1605
+ process.stdout.write("\n");
1606
+ for (const line of code.split("\n")) {
1607
+ process.stdout.write(` ${line}
1570
1608
  `);
1571
1609
  }
1572
- process.stdout.write(
1573
- `${chalk5.gray("\u2502")} ${chalk5.gray(`\u2514${"\u2500".repeat(maxLen + 2)}\u2518`)}
1574
- `
1575
- );
1610
+ process.stdout.write("\n");
1576
1611
  }
1577
1612
  async function runAuthFlow(api, options, reauth = false, repoCanonical) {
1578
1613
  let server = null;
@@ -1585,6 +1620,7 @@ async function runAuthFlow(api, options, reauth = false, repoCanonical) {
1585
1620
  const session = await api.startCliAuthSession(server?.port, repoCanonical);
1586
1621
  const browserUrl = reauth ? session.verificationUrl : session.installUrl ?? session.verificationUrl;
1587
1622
  const expiresAt = new Date(session.expiresAt).getTime();
1623
+ p6.log.info(browserUrl);
1588
1624
  if (options.ci) {
1589
1625
  process.stdout.write(`VOCODER_AUTH_URL: ${browserUrl}
1590
1626
  `);
@@ -2145,10 +2181,7 @@ async function init(options = {}) {
2145
2181
  return 1;
2146
2182
  }
2147
2183
  const detection2 = detectLocalEcosystem();
2148
- runScaffold({
2149
- sourceLocale: appResult.sourceLocale,
2150
- targetBranches: appResult.targetBranches
2151
- });
2184
+ runScaffold({ targetBranches: appResult.targetBranches });
2152
2185
  writeAppConfigs(
2153
2186
  [{ appDir: appResult.appDir, appId: appResult.appId }],
2154
2187
  appResult.targetBranches,
@@ -2192,6 +2225,7 @@ async function init(options = {}) {
2192
2225
  remainingApps = ws.maxApps === -1 ? void 0 : Math.max(0, ws.maxApps - ws.appCount);
2193
2226
  }
2194
2227
  } catch {
2228
+ p6.log.warn("Could not verify plan limits \u2014 proceeding, the server will enforce them.");
2195
2229
  }
2196
2230
  const projectResult = await runProjectCreate({
2197
2231
  api,
@@ -2204,10 +2238,7 @@ async function init(options = {}) {
2204
2238
  defaultBranches: ["main"],
2205
2239
  maxAppDirs: remainingApps
2206
2240
  });
2207
- if (!projectResult) {
2208
- p6.log.error("Project creation failed. Run `vocoder init` again.");
2209
- return 1;
2210
- }
2241
+ if (!projectResult) return 1;
2211
2242
  if (!projectResult.repositoryBound && identity?.repoCanonical) {
2212
2243
  p6.log.warn(
2213
2244
  `This repository isn't accessible to your GitHub App installation.
@@ -2221,10 +2252,7 @@ Translations won't run automatically until you grant access.
2221
2252
  );
2222
2253
  }
2223
2254
  const detection = detectLocalEcosystem();
2224
- runScaffold({
2225
- sourceLocale: projectResult.sourceLocale,
2226
- targetBranches: projectResult.targetBranches
2227
- });
2255
+ runScaffold({ targetBranches: projectResult.targetBranches });
2228
2256
  writeAppConfigs(
2229
2257
  projectResult.apps,
2230
2258
  projectResult.targetBranches,
@@ -2232,17 +2260,20 @@ Translations won't run automatically until you grant access.
2232
2260
  identity?.repoRoot
2233
2261
  );
2234
2262
  printApiKey(projectResult.apiKey, identity?.repoRoot);
2263
+ const wantsMcp = await p6.confirm({
2264
+ message: "Set up the Vocoder MCP server for your AI editor?"
2265
+ });
2266
+ if (!p6.isCancel(wantsMcp) && wantsMcp) {
2267
+ await runMcpSetup(projectResult.apiKey);
2268
+ }
2235
2269
  p6.outro("You're all set.");
2236
2270
  return 0;
2237
2271
  } catch (error) {
2238
- if (error instanceof Error) {
2239
- if (isPlanLimitFailure(error.message)) {
2240
- printPlanLimitMessage(apiUrl, error.message);
2241
- return 1;
2242
- }
2243
- p6.log.error(`Error: ${error.message}`);
2272
+ const message = error instanceof Error ? error.message : "Unknown setup error";
2273
+ if (isPlanLimitFailure(message)) {
2274
+ printPlanLimitMessage(apiUrl, message);
2244
2275
  } else {
2245
- p6.log.error("Unknown setup error");
2276
+ p6.log.error(message);
2246
2277
  }
2247
2278
  return 1;
2248
2279
  }
@@ -2762,7 +2793,7 @@ async function sync(options = {}) {
2762
2793
  includePattern: mergedConfig.includePattern,
2763
2794
  excludePattern: mergedConfig.excludePattern,
2764
2795
  timeout: waitTimeoutMs,
2765
- ...fileConfig?.appIndustry ? { appIndustry: fileConfig.appIndustry } : {},
2796
+ ...fileConfig?.industry ?? fileConfig?.appIndustry ? { industry: fileConfig?.industry ?? fileConfig?.appIndustry } : {},
2766
2797
  ...fileConfig?.formality ? { formality: fileConfig.formality } : {}
2767
2798
  };
2768
2799
  spinner7.stop(`Branch: ${highlight(branch)}`);
@@ -2855,8 +2886,7 @@ async function sync(options = {}) {
2855
2886
  requestedMaxWaitMs: waitTimeoutMs,
2856
2887
  clientRunId: randomUUID(),
2857
2888
  force: options.force,
2858
- // Sync appIndustry from vocoder.config.ts to App on every push
2859
- ...config.appIndustry ? { appIndustry: config.appIndustry } : {}
2889
+ ...config.industry ? { industry: config.industry } : {}
2860
2890
  },
2861
2891
  repoIdentity ? { ...repoIdentity, commitSha } : { commitSha }
2862
2892
  );