@keywaysh/cli 0.1.1 → 0.1.3

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/cli.js +334 -241
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -8,15 +8,18 @@ import {
8
8
 
9
9
  // src/cli.ts
10
10
  import { Command } from "commander";
11
- import pc9 from "picocolors";
11
+ import pc10 from "picocolors";
12
12
 
13
13
  // src/cmds/init.ts
14
- import pc4 from "picocolors";
14
+ import pc5 from "picocolors";
15
15
  import prompts4 from "prompts";
16
16
  import open2 from "open";
17
17
 
18
18
  // src/utils/git.ts
19
19
  import { execSync } from "child_process";
20
+ import fs from "fs";
21
+ import path from "path";
22
+ import pc from "picocolors";
20
23
  function getCurrentRepoFullName() {
21
24
  try {
22
25
  if (!isGitRepository()) {
@@ -67,6 +70,29 @@ function parseGitHubUrl(url) {
67
70
  }
68
71
  throw new Error(`Invalid GitHub URL: ${url}`);
69
72
  }
73
+ function checkEnvGitignore() {
74
+ try {
75
+ const gitRoot = execSync("git rev-parse --show-toplevel", {
76
+ encoding: "utf-8",
77
+ stdio: "pipe"
78
+ }).trim();
79
+ const gitignorePath = path.join(gitRoot, ".gitignore");
80
+ if (!fs.existsSync(gitignorePath)) {
81
+ return false;
82
+ }
83
+ const content = fs.readFileSync(gitignorePath, "utf-8");
84
+ const lines = content.split("\n").map((l) => l.trim());
85
+ const envPatterns = [".env", ".env*", ".env.*", "*.env"];
86
+ return envPatterns.some((pattern) => lines.includes(pattern));
87
+ } catch {
88
+ return true;
89
+ }
90
+ }
91
+ function warnIfEnvNotGitignored() {
92
+ if (!checkEnvGitignore()) {
93
+ console.log(pc.yellow("\u26A0\uFE0F .env files are not in .gitignore - secrets may be committed"));
94
+ }
95
+ }
70
96
 
71
97
  // src/config/internal.ts
72
98
  var INTERNAL_API_URL = "https://api.keyway.sh";
@@ -76,7 +102,7 @@ var INTERNAL_POSTHOG_HOST = "https://eu.i.posthog.com";
76
102
  // package.json
77
103
  var package_default = {
78
104
  name: "@keywaysh/cli",
79
- version: "0.1.1",
105
+ version: "0.1.3",
80
106
  description: "One link to all your secrets",
81
107
  type: "module",
82
108
  bin: {
@@ -465,6 +491,25 @@ async function executeSync(accessToken, repoFullName, options) {
465
491
  const wrapped = await handleResponse(response);
466
492
  return wrapped.data;
467
493
  }
494
+ async function getVaultEnvironments(accessToken, repoFullName) {
495
+ const [owner, repo] = repoFullName.split("/");
496
+ try {
497
+ const response = await fetchWithTimeout(
498
+ `${API_BASE_URL}/v1/vaults/${owner}/${repo}`,
499
+ {
500
+ method: "GET",
501
+ headers: {
502
+ "User-Agent": USER_AGENT,
503
+ Authorization: `Bearer ${accessToken}`
504
+ }
505
+ }
506
+ );
507
+ const wrapped = await handleResponse(response);
508
+ return wrapped.data.environments || ["production"];
509
+ } catch {
510
+ return ["production"];
511
+ }
512
+ }
468
513
  async function checkGitHubAppInstallation(repoOwner, repoName, accessToken) {
469
514
  const response = await fetchWithTimeout(`${API_BASE_URL}/v1/github/check-installation`, {
470
515
  method: "POST",
@@ -482,32 +527,32 @@ async function checkGitHubAppInstallation(repoOwner, repoName, accessToken) {
482
527
  // src/utils/analytics.ts
483
528
  import { PostHog } from "posthog-node";
484
529
  import crypto from "crypto";
485
- import path from "path";
530
+ import path2 from "path";
486
531
  import os from "os";
487
- import fs from "fs";
532
+ import fs2 from "fs";
488
533
  var posthog = null;
489
534
  var distinctId = null;
490
- var CONFIG_DIR = path.join(os.homedir(), ".config", "keyway");
491
- var ID_FILE = path.join(CONFIG_DIR, "id.json");
535
+ var CONFIG_DIR = path2.join(os.homedir(), ".config", "keyway");
536
+ var ID_FILE = path2.join(CONFIG_DIR, "id.json");
492
537
  var TELEMETRY_DISABLED = process.env.KEYWAY_DISABLE_TELEMETRY === "1";
493
538
  var CI = process.env.CI === "true" || process.env.CI === "1";
494
539
  function getDistinctId() {
495
540
  if (distinctId) return distinctId;
496
541
  try {
497
- if (!fs.existsSync(CONFIG_DIR)) {
498
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
542
+ if (!fs2.existsSync(CONFIG_DIR)) {
543
+ fs2.mkdirSync(CONFIG_DIR, { recursive: true });
499
544
  }
500
- if (fs.existsSync(ID_FILE)) {
501
- const content = fs.readFileSync(ID_FILE, "utf-8");
545
+ if (fs2.existsSync(ID_FILE)) {
546
+ const content = fs2.readFileSync(ID_FILE, "utf-8");
502
547
  const config2 = JSON.parse(content);
503
548
  distinctId = config2.distinctId;
504
549
  return distinctId;
505
550
  }
506
551
  distinctId = crypto.randomUUID();
507
552
  const config = { distinctId };
508
- fs.writeFileSync(ID_FILE, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
553
+ fs2.writeFileSync(ID_FILE, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
509
554
  try {
510
- fs.chmodSync(ID_FILE, 384);
555
+ fs2.chmodSync(ID_FILE, 384);
511
556
  } catch {
512
557
  }
513
558
  return distinctId;
@@ -606,10 +651,10 @@ var AnalyticsEvents = {
606
651
  };
607
652
 
608
653
  // src/cmds/readme.ts
609
- import fs2 from "fs";
610
- import path2 from "path";
654
+ import fs3 from "fs";
655
+ import path3 from "path";
611
656
  import prompts from "prompts";
612
- import pc from "picocolors";
657
+ import pc2 from "picocolors";
613
658
  function generateBadge(repo) {
614
659
  return `[![Keyway Secrets](https://www.keyway.sh/badge.svg?repo=${repo})](https://www.keyway.sh/vaults/${repo})`;
615
660
  }
@@ -635,8 +680,8 @@ ${readmeContent}`;
635
680
  function findReadmePath(cwd) {
636
681
  const candidates = ["README.md", "readme.md", "Readme.md"];
637
682
  for (const candidate of candidates) {
638
- const candidatePath = path2.join(cwd, candidate);
639
- if (fs2.existsSync(candidatePath)) {
683
+ const candidatePath = path3.join(cwd, candidate);
684
+ if (fs3.existsSync(candidatePath)) {
640
685
  return candidatePath;
641
686
  }
642
687
  }
@@ -647,7 +692,7 @@ async function ensureReadme(repoName, cwd) {
647
692
  if (existing) return existing;
648
693
  const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
649
694
  if (!isInteractive3) {
650
- console.log(pc.yellow('No README found. Run "keyway readme add-badge" from a repo with a README.'));
695
+ console.log(pc2.yellow('No README found. Run "keyway readme add-badge" from a repo with a README.'));
651
696
  return null;
652
697
  }
653
698
  const { confirm } = await prompts(
@@ -662,14 +707,14 @@ async function ensureReadme(repoName, cwd) {
662
707
  }
663
708
  );
664
709
  if (!confirm) {
665
- console.log(pc.yellow("Skipping badge insertion (no README)."));
710
+ console.log(pc2.yellow("Skipping badge insertion (no README)."));
666
711
  return null;
667
712
  }
668
- const defaultPath = path2.join(cwd, "README.md");
713
+ const defaultPath = path3.join(cwd, "README.md");
669
714
  const content = `# ${repoName}
670
715
 
671
716
  `;
672
- fs2.writeFileSync(defaultPath, content, "utf-8");
717
+ fs3.writeFileSync(defaultPath, content, "utf-8");
673
718
  return defaultPath;
674
719
  }
675
720
  async function addBadgeToReadme(silent = false) {
@@ -681,29 +726,29 @@ async function addBadgeToReadme(silent = false) {
681
726
  const readmePath = await ensureReadme(repo, cwd);
682
727
  if (!readmePath) return false;
683
728
  const badge = generateBadge(repo);
684
- const content = fs2.readFileSync(readmePath, "utf-8");
729
+ const content = fs3.readFileSync(readmePath, "utf-8");
685
730
  const updated = insertBadgeIntoReadme(content, badge);
686
731
  if (updated === content) {
687
732
  if (!silent) {
688
- console.log(pc.gray("Keyway badge already present in README."));
733
+ console.log(pc2.gray("Keyway badge already present in README."));
689
734
  }
690
735
  return false;
691
736
  }
692
- fs2.writeFileSync(readmePath, updated, "utf-8");
737
+ fs3.writeFileSync(readmePath, updated, "utf-8");
693
738
  if (!silent) {
694
- console.log(pc.green(`\u2713 Keyway badge added to ${path2.basename(readmePath)}`));
739
+ console.log(pc2.green(`\u2713 Keyway badge added to ${path3.basename(readmePath)}`));
695
740
  }
696
741
  return true;
697
742
  }
698
743
 
699
744
  // src/cmds/push.ts
700
- import pc3 from "picocolors";
701
- import fs3 from "fs";
702
- import path3 from "path";
745
+ import pc4 from "picocolors";
746
+ import fs4 from "fs";
747
+ import path4 from "path";
703
748
  import prompts3 from "prompts";
704
749
 
705
750
  // src/cmds/login.ts
706
- import pc2 from "picocolors";
751
+ import pc3 from "picocolors";
707
752
  import readline from "readline";
708
753
  import open from "open";
709
754
  import prompts2 from "prompts";
@@ -730,17 +775,17 @@ async function promptYesNo(question, defaultYes = true) {
730
775
  });
731
776
  }
732
777
  async function runLoginFlow() {
733
- console.log(pc2.blue("\u{1F510} Starting Keyway login...\n"));
778
+ console.log(pc3.blue("\u{1F510} Starting Keyway login...\n"));
734
779
  const repoName = detectGitRepo();
735
780
  const start = await startDeviceLogin(repoName);
736
781
  const verifyUrl = start.verificationUriComplete || start.verificationUri;
737
782
  if (!verifyUrl) {
738
783
  throw new Error("Missing verification URL from the auth server.");
739
784
  }
740
- console.log(`Code: ${pc2.bold(pc2.green(start.userCode))}`);
785
+ console.log(`Code: ${pc3.bold(pc3.green(start.userCode))}`);
741
786
  console.log("Waiting for auth...");
742
787
  open(verifyUrl).catch(() => {
743
- console.log(pc2.gray(`Open this URL in your browser: ${verifyUrl}`));
788
+ console.log(pc3.gray(`Open this URL in your browser: ${verifyUrl}`));
744
789
  });
745
790
  const pollIntervalMs = (start.interval ?? 5) * 1e3;
746
791
  const maxTimeoutMs = Math.min((start.expiresIn ?? 900) * 1e3, 30 * 60 * 1e3);
@@ -769,9 +814,9 @@ async function runLoginFlow() {
769
814
  login_method: "device"
770
815
  });
771
816
  }
772
- console.log(pc2.green("\n\u2713 Login successful"));
817
+ console.log(pc3.green("\n\u2713 Login successful"));
773
818
  if (result.githubLogin) {
774
- console.log(`Authenticated GitHub user: ${pc2.cyan(result.githubLogin)}`);
819
+ console.log(`Authenticated GitHub user: ${pc3.cyan(result.githubLogin)}`);
775
820
  }
776
821
  return result.keywayToken;
777
822
  }
@@ -784,7 +829,7 @@ async function ensureLogin(options = {}) {
784
829
  return envToken;
785
830
  }
786
831
  if (process.env.GITHUB_TOKEN && !process.env.KEYWAY_TOKEN) {
787
- console.warn(pc2.yellow("Note: GITHUB_TOKEN found but not used. Set KEYWAY_TOKEN for Keyway authentication."));
832
+ console.warn(pc3.yellow("Note: GITHUB_TOKEN found but not used. Set KEYWAY_TOKEN for Keyway authentication."));
788
833
  }
789
834
  const stored = await getStoredAuth();
790
835
  if (stored?.keywayToken) {
@@ -804,16 +849,16 @@ async function ensureLogin(options = {}) {
804
849
  async function runTokenLogin() {
805
850
  const repoName = detectGitRepo();
806
851
  if (repoName) {
807
- console.log(`\u{1F4C1} Detected: ${pc2.cyan(repoName)}`);
852
+ console.log(`\u{1F4C1} Detected: ${pc3.cyan(repoName)}`);
808
853
  }
809
854
  const description = repoName ? `Keyway CLI for ${repoName}` : "Keyway CLI";
810
855
  const url = `https://github.com/settings/personal-access-tokens/new?description=${encodeURIComponent(description)}`;
811
856
  console.log("Opening GitHub...");
812
857
  open(url).catch(() => {
813
- console.log(pc2.gray(`Open this URL in your browser: ${url}`));
858
+ console.log(pc3.gray(`Open this URL in your browser: ${url}`));
814
859
  });
815
- console.log(pc2.gray("Select the detected repo (or scope manually)."));
816
- console.log(pc2.gray("Permissions: Metadata \u2192 Read-only; Account permissions: None."));
860
+ console.log(pc3.gray("Select the detected repo (or scope manually)."));
861
+ console.log(pc3.gray("Permissions: Metadata \u2192 Read-only; Account permissions: None."));
817
862
  const { token } = await prompts2(
818
863
  {
819
864
  type: "password",
@@ -850,7 +895,7 @@ async function runTokenLogin() {
850
895
  github_username: validation.username,
851
896
  login_method: "pat"
852
897
  });
853
- console.log(pc2.green("\u2705 Authenticated"), `as ${pc2.cyan(`@${validation.username}`)}`);
898
+ console.log(pc3.green("\u2705 Authenticated"), `as ${pc3.cyan(`@${validation.username}`)}`);
854
899
  return trimmedToken;
855
900
  }
856
901
  async function loginCommand(options = {}) {
@@ -866,20 +911,20 @@ async function loginCommand(options = {}) {
866
911
  command: "login",
867
912
  error: truncateMessage(message)
868
913
  });
869
- console.error(pc2.red(`
914
+ console.error(pc3.red(`
870
915
  \u2717 ${message}`));
871
916
  process.exit(1);
872
917
  }
873
918
  }
874
919
  async function logoutCommand() {
875
920
  clearAuth();
876
- console.log(pc2.green("\u2713 Logged out of Keyway"));
877
- console.log(pc2.gray(`Auth cache cleared: ${getAuthFilePath()}`));
921
+ console.log(pc3.green("\u2713 Logged out of Keyway"));
922
+ console.log(pc3.gray(`Auth cache cleared: ${getAuthFilePath()}`));
878
923
  }
879
924
 
880
925
  // src/cmds/push.ts
881
926
  function deriveEnvFromFile(file) {
882
- const base = path3.basename(file);
927
+ const base = path4.basename(file);
883
928
  const match = base.match(/\.env(?:\.(.+))?$/);
884
929
  if (match) {
885
930
  return match[1] || "development";
@@ -888,15 +933,15 @@ function deriveEnvFromFile(file) {
888
933
  }
889
934
  function discoverEnvCandidates(cwd) {
890
935
  try {
891
- const entries = fs3.readdirSync(cwd);
936
+ const entries = fs4.readdirSync(cwd);
892
937
  const hasEnvLocal = entries.includes(".env.local");
893
938
  if (hasEnvLocal) {
894
- console.log(pc3.gray("\u2139\uFE0F Detected .env.local \u2014 not synced by design (machine-specific secrets)"));
939
+ console.log(pc4.gray("\u2139\uFE0F Detected .env.local \u2014 not synced by design (machine-specific secrets)"));
895
940
  }
896
941
  const candidates = entries.filter((name) => name.startsWith(".env") && name !== ".env.local").map((name) => {
897
- const fullPath = path3.join(cwd, name);
942
+ const fullPath = path4.join(cwd, name);
898
943
  try {
899
- const stat = fs3.statSync(fullPath);
944
+ const stat = fs4.statSync(fullPath);
900
945
  if (!stat.isFile()) return null;
901
946
  return { file: name, env: deriveEnvFromFile(name) };
902
947
  } catch {
@@ -917,7 +962,7 @@ function discoverEnvCandidates(cwd) {
917
962
  }
918
963
  async function pushCommand(options) {
919
964
  try {
920
- console.log(pc3.blue("\u{1F510} Pushing secrets to Keyway...\n"));
965
+ console.log(pc4.blue("\u{1F510} Pushing secrets to Keyway...\n"));
921
966
  const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
922
967
  let environment = options.env;
923
968
  let envFile = options.file;
@@ -959,8 +1004,8 @@ async function pushCommand(options) {
959
1004
  message: "Path to env file:",
960
1005
  validate: (value) => {
961
1006
  if (!value) return "Path is required";
962
- const resolved = path3.resolve(process.cwd(), value);
963
- if (!fs3.existsSync(resolved)) return `File not found: ${value}`;
1007
+ const resolved = path4.resolve(process.cwd(), value);
1008
+ if (!fs4.existsSync(resolved)) return `File not found: ${value}`;
964
1009
  return true;
965
1010
  }
966
1011
  },
@@ -980,8 +1025,8 @@ async function pushCommand(options) {
980
1025
  if (!envFile) {
981
1026
  envFile = ".env";
982
1027
  }
983
- let envFilePath = path3.resolve(process.cwd(), envFile);
984
- if (!fs3.existsSync(envFilePath)) {
1028
+ let envFilePath = path4.resolve(process.cwd(), envFile);
1029
+ if (!fs4.existsSync(envFilePath)) {
985
1030
  if (!isInteractive3) {
986
1031
  throw new Error(`File not found: ${envFile}. Provide --file <path> or run interactively to choose a file.`);
987
1032
  }
@@ -992,8 +1037,8 @@ async function pushCommand(options) {
992
1037
  message: `File not found: ${envFile}. Enter an env file path to use:`,
993
1038
  validate: (value) => {
994
1039
  if (!value || typeof value !== "string") return "Path is required";
995
- const resolved = path3.resolve(process.cwd(), value);
996
- if (!fs3.existsSync(resolved)) return `File not found: ${value}`;
1040
+ const resolved = path4.resolve(process.cwd(), value);
1041
+ if (!fs4.existsSync(resolved)) return `File not found: ${value}`;
997
1042
  return true;
998
1043
  }
999
1044
  },
@@ -1007,9 +1052,9 @@ async function pushCommand(options) {
1007
1052
  throw new Error("Push cancelled (no env file provided).");
1008
1053
  }
1009
1054
  envFile = newPath.trim();
1010
- envFilePath = path3.resolve(process.cwd(), envFile);
1055
+ envFilePath = path4.resolve(process.cwd(), envFile);
1011
1056
  }
1012
- const content = fs3.readFileSync(envFilePath, "utf-8");
1057
+ const content = fs4.readFileSync(envFilePath, "utf-8");
1013
1058
  if (content.trim().length === 0) {
1014
1059
  throw new Error(`File is empty: ${envFile}`);
1015
1060
  }
@@ -1017,11 +1062,11 @@ async function pushCommand(options) {
1017
1062
  const trimmed = line.trim();
1018
1063
  return trimmed.length > 0 && !trimmed.startsWith("#");
1019
1064
  });
1020
- console.log(`File: ${pc3.cyan(envFile)}`);
1021
- console.log(`Environment: ${pc3.cyan(environment)}`);
1022
- console.log(`Variables: ${pc3.cyan(lines.length.toString())}`);
1065
+ console.log(`File: ${pc4.cyan(envFile)}`);
1066
+ console.log(`Environment: ${pc4.cyan(environment)}`);
1067
+ console.log(`Variables: ${pc4.cyan(lines.length.toString())}`);
1023
1068
  const repoFullName = getCurrentRepoFullName();
1024
- console.log(`Repository: ${pc3.cyan(repoFullName)}`);
1069
+ console.log(`Repository: ${pc4.cyan(repoFullName)}`);
1025
1070
  if (!options.yes) {
1026
1071
  const isInteractive4 = process.stdin.isTTY && process.stdout.isTTY;
1027
1072
  if (!isInteractive4) {
@@ -1041,7 +1086,7 @@ async function pushCommand(options) {
1041
1086
  }
1042
1087
  );
1043
1088
  if (!confirm) {
1044
- console.log(pc3.yellow("Push aborted."));
1089
+ console.log(pc4.yellow("Push aborted."));
1045
1090
  return;
1046
1091
  }
1047
1092
  }
@@ -1053,20 +1098,20 @@ async function pushCommand(options) {
1053
1098
  });
1054
1099
  console.log("\nUploading secrets...");
1055
1100
  const response = await pushSecrets(repoFullName, environment, content, accessToken);
1056
- console.log(pc3.green("\n\u2713 " + response.message));
1101
+ console.log(pc4.green("\n\u2713 " + response.message));
1057
1102
  if (response.stats) {
1058
1103
  const { created, updated, deleted } = response.stats;
1059
1104
  const parts = [];
1060
- if (created > 0) parts.push(pc3.green(`+${created} created`));
1061
- if (updated > 0) parts.push(pc3.yellow(`~${updated} updated`));
1062
- if (deleted > 0) parts.push(pc3.red(`-${deleted} deleted`));
1105
+ if (created > 0) parts.push(pc4.green(`+${created} created`));
1106
+ if (updated > 0) parts.push(pc4.yellow(`~${updated} updated`));
1107
+ if (deleted > 0) parts.push(pc4.red(`-${deleted} deleted`));
1063
1108
  if (parts.length > 0) {
1064
1109
  console.log(`Stats: ${parts.join(", ")}`);
1065
1110
  }
1066
1111
  }
1067
1112
  console.log(`
1068
1113
  Your secrets are now encrypted and stored securely.`);
1069
- console.log(`To retrieve them, run: ${pc3.cyan(`keyway pull --env ${environment}`)}`);
1114
+ console.log(`To retrieve them, run: ${pc4.cyan(`keyway pull --env ${environment}`)}`);
1070
1115
  await shutdownAnalytics();
1071
1116
  } catch (error) {
1072
1117
  let message;
@@ -1079,13 +1124,13 @@ Your secrets are now encrypted and stored securely.`);
1079
1124
  const availableEnvs = envNotFoundMatch[2];
1080
1125
  message = `Environment '${requestedEnv}' does not exist in this vault.`;
1081
1126
  hint = `Available environments: ${availableEnvs}
1082
- Use ${pc3.cyan(`keyway push --env <environment>`)} to specify one, or create '${requestedEnv}' via the dashboard.`;
1127
+ Use ${pc4.cyan(`keyway push --env <environment>`)} to specify one, or create '${requestedEnv}' via the dashboard.`;
1083
1128
  }
1084
1129
  if (error.statusCode === 403 && error.upgradeUrl) {
1085
- hint = `${pc3.yellow("\u26A1")} Upgrade to Pro: ${pc3.cyan(error.upgradeUrl)}`;
1130
+ hint = `${pc4.yellow("\u26A1")} Upgrade to Pro: ${pc4.cyan(error.upgradeUrl)}`;
1086
1131
  } else if (error.statusCode === 403 && message.toLowerCase().includes("read-only")) {
1087
1132
  message = "This vault is read-only on your current plan.";
1088
- hint = `Upgrade to Pro to unlock editing: ${pc3.cyan("https://keyway.sh/settings")}`;
1133
+ hint = `Upgrade to Pro to unlock editing: ${pc4.cyan("https://keyway.sh/settings")}`;
1089
1134
  }
1090
1135
  } else if (error instanceof Error) {
1091
1136
  message = truncateMessage(error.message);
@@ -1097,10 +1142,10 @@ Use ${pc3.cyan(`keyway push --env <environment>`)} to specify one, or create '${
1097
1142
  error: message
1098
1143
  });
1099
1144
  await shutdownAnalytics();
1100
- console.error(pc3.red(`
1145
+ console.error(pc4.red(`
1101
1146
  \u2717 ${message}`));
1102
1147
  if (hint) {
1103
- console.error(pc3.gray(`
1148
+ console.error(pc4.gray(`
1104
1149
  ${hint}`));
1105
1150
  }
1106
1151
  process.exit(1);
@@ -1139,8 +1184,8 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
1139
1184
  throw new Error('No Keyway session found. Run "keyway login" to authenticate.');
1140
1185
  }
1141
1186
  console.log("");
1142
- console.log(pc4.gray(" Keyway uses a GitHub App for secure access."));
1143
- console.log(pc4.gray(" Installing the app will also log you in."));
1187
+ console.log(pc5.gray(" Keyway uses a GitHub App for secure access."));
1188
+ console.log(pc5.gray(" Installing the app will also log you in."));
1144
1189
  console.log("");
1145
1190
  const { shouldProceed } = await prompts4({
1146
1191
  type: "confirm",
@@ -1153,11 +1198,11 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
1153
1198
  }
1154
1199
  const deviceStart = await startDeviceLogin(repoFullName);
1155
1200
  const installUrl = deviceStart.githubAppInstallUrl || "https://github.com/apps/keyway/installations/new";
1156
- console.log(pc4.gray("\n Opening browser..."));
1201
+ console.log(pc5.gray("\n Opening browser..."));
1157
1202
  await open2(installUrl);
1158
1203
  console.log("");
1159
- console.log(pc4.blue("\u23F3 Waiting for installation & authorization..."));
1160
- console.log(pc4.gray(" (Press Ctrl+C to cancel)\n"));
1204
+ console.log(pc5.blue("\u23F3 Waiting for installation & authorization..."));
1205
+ console.log(pc5.gray(" (Press Ctrl+C to cancel)\n"));
1161
1206
  const pollIntervalMs = Math.max((deviceStart.interval ?? 5) * 1e3, POLL_INTERVAL_MS);
1162
1207
  const startTime = Date.now();
1163
1208
  let accessToken = null;
@@ -1172,7 +1217,7 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
1172
1217
  githubLogin: result.githubLogin,
1173
1218
  expiresAt: result.expiresAt
1174
1219
  });
1175
- console.log(pc4.green("\u2713 Signed in!"));
1220
+ console.log(pc5.green("\u2713 Signed in!"));
1176
1221
  if (result.githubLogin) {
1177
1222
  identifyUser(result.githubLogin, {
1178
1223
  github_username: result.githubLogin,
@@ -1184,18 +1229,18 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
1184
1229
  if (accessToken) {
1185
1230
  const installStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
1186
1231
  if (installStatus.installed) {
1187
- console.log(pc4.green("\u2713 GitHub App installed!"));
1232
+ console.log(pc5.green("\u2713 GitHub App installed!"));
1188
1233
  console.log("");
1189
1234
  return accessToken;
1190
1235
  }
1191
1236
  }
1192
- process.stdout.write(pc4.gray("."));
1237
+ process.stdout.write(pc5.gray("."));
1193
1238
  } catch {
1194
1239
  }
1195
1240
  }
1196
1241
  console.log("");
1197
- console.log(pc4.yellow("\u26A0 Timed out waiting for setup."));
1198
- console.log(pc4.gray(` Install the GitHub App: ${installUrl}`));
1242
+ console.log(pc5.yellow("\u26A0 Timed out waiting for setup."));
1243
+ console.log(pc5.gray(` Install the GitHub App: ${installUrl}`));
1199
1244
  throw new Error("Setup timed out. Please try again.");
1200
1245
  }
1201
1246
  async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
@@ -1205,7 +1250,7 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
1205
1250
  status = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
1206
1251
  } catch (error) {
1207
1252
  if (error instanceof APIError && error.statusCode === 401) {
1208
- console.log(pc4.yellow("\n\u26A0 Session expired or invalid. Clearing credentials..."));
1253
+ console.log(pc5.yellow("\n\u26A0 Session expired or invalid. Clearing credentials..."));
1209
1254
  const { clearAuth: clearAuth2 } = await import("./auth-QLPQ24HZ.js");
1210
1255
  clearAuth2();
1211
1256
  return null;
@@ -1216,13 +1261,13 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
1216
1261
  return accessToken;
1217
1262
  }
1218
1263
  console.log("");
1219
- console.log(pc4.yellow("\u26A0 GitHub App not installed for this repository"));
1264
+ console.log(pc5.yellow("\u26A0 GitHub App not installed for this repository"));
1220
1265
  console.log("");
1221
- console.log(pc4.gray(" The Keyway GitHub App is required to securely manage secrets."));
1222
- console.log(pc4.gray(" It only requests minimal permissions (repository metadata)."));
1266
+ console.log(pc5.gray(" The Keyway GitHub App is required to securely manage secrets."));
1267
+ console.log(pc5.gray(" It only requests minimal permissions (repository metadata)."));
1223
1268
  console.log("");
1224
1269
  if (!isInteractive2()) {
1225
- console.log(pc4.gray(` Install the Keyway GitHub App: ${status.installUrl}`));
1270
+ console.log(pc5.gray(` Install the Keyway GitHub App: ${status.installUrl}`));
1226
1271
  throw new Error("GitHub App installation required.");
1227
1272
  }
1228
1273
  const { shouldInstall } = await prompts4({
@@ -1232,50 +1277,50 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
1232
1277
  initial: true
1233
1278
  });
1234
1279
  if (!shouldInstall) {
1235
- console.log(pc4.gray(`
1280
+ console.log(pc5.gray(`
1236
1281
  You can install later: ${status.installUrl}`));
1237
1282
  throw new Error("GitHub App installation required.");
1238
1283
  }
1239
- console.log(pc4.gray("\n Opening browser..."));
1284
+ console.log(pc5.gray("\n Opening browser..."));
1240
1285
  await open2(status.installUrl);
1241
1286
  console.log("");
1242
- console.log(pc4.blue("\u23F3 Waiting for GitHub App installation..."));
1243
- console.log(pc4.gray(" (Press Ctrl+C to cancel)\n"));
1287
+ console.log(pc5.blue("\u23F3 Waiting for GitHub App installation..."));
1288
+ console.log(pc5.gray(" (Press Ctrl+C to cancel)\n"));
1244
1289
  const startTime = Date.now();
1245
1290
  while (Date.now() - startTime < POLL_TIMEOUT_MS) {
1246
1291
  await sleep2(POLL_INTERVAL_MS);
1247
1292
  try {
1248
1293
  const pollStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
1249
1294
  if (pollStatus.installed) {
1250
- console.log(pc4.green("\u2713 GitHub App installed!"));
1295
+ console.log(pc5.green("\u2713 GitHub App installed!"));
1251
1296
  console.log("");
1252
1297
  return accessToken;
1253
1298
  }
1254
- process.stdout.write(pc4.gray("."));
1299
+ process.stdout.write(pc5.gray("."));
1255
1300
  } catch {
1256
1301
  }
1257
1302
  }
1258
1303
  console.log("");
1259
- console.log(pc4.yellow("\u26A0 Timed out waiting for installation."));
1260
- console.log(pc4.gray(` You can install the GitHub App later: ${status.installUrl}`));
1304
+ console.log(pc5.yellow("\u26A0 Timed out waiting for installation."));
1305
+ console.log(pc5.gray(` You can install the GitHub App later: ${status.installUrl}`));
1261
1306
  throw new Error("GitHub App installation timed out.");
1262
1307
  }
1263
1308
  async function initCommand(options = {}) {
1264
1309
  try {
1265
1310
  const repoFullName = getCurrentRepoFullName();
1266
1311
  const dashboardLink = `${DASHBOARD_URL}/${repoFullName}`;
1267
- console.log(pc4.blue("\u{1F510} Initializing Keyway vault...\n"));
1268
- console.log(` ${pc4.gray("Repository:")} ${pc4.white(repoFullName)}`);
1312
+ console.log(pc5.blue("\u{1F510} Initializing Keyway vault...\n"));
1313
+ console.log(` ${pc5.gray("Repository:")} ${pc5.white(repoFullName)}`);
1269
1314
  const accessToken = await ensureLoginAndGitHubApp(repoFullName, {
1270
1315
  allowPrompt: options.loginPrompt !== false
1271
1316
  });
1272
1317
  trackEvent(AnalyticsEvents.CLI_INIT, { repoFullName, githubAppInstalled: true });
1273
1318
  await initVault(repoFullName, accessToken);
1274
- console.log(pc4.green("\u2713 Vault created!"));
1319
+ console.log(pc5.green("\u2713 Vault created!"));
1275
1320
  try {
1276
1321
  const badgeAdded = await addBadgeToReadme(true);
1277
1322
  if (badgeAdded) {
1278
- console.log(pc4.green("\u2713 Badge added to README.md"));
1323
+ console.log(pc5.green("\u2713 Badge added to README.md"));
1279
1324
  }
1280
1325
  } catch {
1281
1326
  }
@@ -1283,7 +1328,7 @@ async function initCommand(options = {}) {
1283
1328
  const envCandidates = discoverEnvCandidates(process.cwd());
1284
1329
  const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
1285
1330
  if (envCandidates.length > 0 && isInteractive3) {
1286
- console.log(pc4.gray(` Found ${envCandidates.length} env file(s): ${envCandidates.map((c) => c.file).join(", ")}
1331
+ console.log(pc5.gray(` Found ${envCandidates.length} env file(s): ${envCandidates.map((c) => c.file).join(", ")}
1287
1332
  `));
1288
1333
  const { shouldPush } = await prompts4({
1289
1334
  type: "confirm",
@@ -1297,25 +1342,25 @@ async function initCommand(options = {}) {
1297
1342
  return;
1298
1343
  }
1299
1344
  }
1300
- console.log(pc4.dim("\u2500".repeat(50)));
1345
+ console.log(pc5.dim("\u2500".repeat(50)));
1301
1346
  console.log("");
1302
1347
  if (envCandidates.length === 0) {
1303
- console.log(` ${pc4.yellow("\u2192")} Create a ${pc4.cyan(".env")} file with your secrets`);
1304
- console.log(` ${pc4.yellow("\u2192")} Run ${pc4.cyan("keyway push")} to sync them
1348
+ console.log(` ${pc5.yellow("\u2192")} Create a ${pc5.cyan(".env")} file with your secrets`);
1349
+ console.log(` ${pc5.yellow("\u2192")} Run ${pc5.cyan("keyway push")} to sync them
1305
1350
  `);
1306
1351
  } else {
1307
- console.log(` ${pc4.yellow("\u2192")} Run ${pc4.cyan("keyway push")} to sync your secrets
1352
+ console.log(` ${pc5.yellow("\u2192")} Run ${pc5.cyan("keyway push")} to sync your secrets
1308
1353
  `);
1309
1354
  }
1310
- console.log(` ${pc4.blue("\u2394")} Dashboard: ${pc4.underline(dashboardLink)}`);
1355
+ console.log(` ${pc5.blue("\u2394")} Dashboard: ${pc5.underline(dashboardLink)}`);
1311
1356
  console.log("");
1312
1357
  await shutdownAnalytics();
1313
1358
  } catch (error) {
1314
1359
  if (error instanceof APIError) {
1315
1360
  if (error.statusCode === 409) {
1316
- console.log(pc4.yellow("\n\u26A0 Vault already exists for this repository.\n"));
1317
- console.log(` ${pc4.yellow("\u2192")} Run ${pc4.cyan("keyway push")} to sync your secrets`);
1318
- console.log(` ${pc4.blue("\u2394")} Dashboard: ${pc4.underline(`${DASHBOARD_URL}/${getCurrentRepoFullName()}`)}`);
1361
+ console.log(pc5.green("\n\u2713 Already initialized!\n"));
1362
+ console.log(` ${pc5.yellow("\u2192")} Run ${pc5.cyan("keyway push")} to sync your secrets`);
1363
+ console.log(` ${pc5.blue("\u2394")} Dashboard: ${pc5.underline(`${DASHBOARD_URL}/${getCurrentRepoFullName()}`)}`);
1319
1364
  console.log("");
1320
1365
  await shutdownAnalytics();
1321
1366
  return;
@@ -1323,15 +1368,15 @@ async function initCommand(options = {}) {
1323
1368
  if (error.error === "Plan Limit Reached" || error.upgradeUrl) {
1324
1369
  const upgradeUrl = error.upgradeUrl || "https://keyway.sh/pricing";
1325
1370
  console.log("");
1326
- console.log(pc4.dim("\u2500".repeat(50)));
1371
+ console.log(pc5.dim("\u2500".repeat(50)));
1327
1372
  console.log("");
1328
- console.log(` ${pc4.yellow("\u26A1")} ${pc4.bold("Plan Limit Reached")}`);
1373
+ console.log(` ${pc5.yellow("\u26A1")} ${pc5.bold("Plan Limit Reached")}`);
1329
1374
  console.log("");
1330
- console.log(pc4.white(` ${error.message}`));
1375
+ console.log(pc5.white(` ${error.message}`));
1331
1376
  console.log("");
1332
- console.log(` ${pc4.cyan("Upgrade now \u2192")} ${pc4.underline(upgradeUrl)}`);
1377
+ console.log(` ${pc5.cyan("Upgrade now \u2192")} ${pc5.underline(upgradeUrl)}`);
1333
1378
  console.log("");
1334
- console.log(pc4.dim("\u2500".repeat(50)));
1379
+ console.log(pc5.dim("\u2500".repeat(50)));
1335
1380
  console.log("");
1336
1381
  await shutdownAnalytics();
1337
1382
  process.exit(1);
@@ -1343,25 +1388,25 @@ async function initCommand(options = {}) {
1343
1388
  error: message
1344
1389
  });
1345
1390
  await shutdownAnalytics();
1346
- console.error(pc4.red(`
1391
+ console.error(pc5.red(`
1347
1392
  \u2717 ${message}`));
1348
1393
  process.exit(1);
1349
1394
  }
1350
1395
  }
1351
1396
 
1352
1397
  // src/cmds/pull.ts
1353
- import pc5 from "picocolors";
1354
- import fs4 from "fs";
1355
- import path4 from "path";
1398
+ import pc6 from "picocolors";
1399
+ import fs5 from "fs";
1400
+ import path5 from "path";
1356
1401
  import prompts5 from "prompts";
1357
1402
  async function pullCommand(options) {
1358
1403
  try {
1359
1404
  const environment = options.env || "development";
1360
1405
  const envFile = options.file || ".env";
1361
- console.log(pc5.blue("\u{1F510} Pulling secrets from Keyway...\n"));
1362
- console.log(`Environment: ${pc5.cyan(environment)}`);
1406
+ console.log(pc6.blue("\u{1F510} Pulling secrets from Keyway...\n"));
1407
+ console.log(`Environment: ${pc6.cyan(environment)}`);
1363
1408
  const repoFullName = getCurrentRepoFullName();
1364
- console.log(`Repository: ${pc5.cyan(repoFullName)}`);
1409
+ console.log(`Repository: ${pc6.cyan(repoFullName)}`);
1365
1410
  const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
1366
1411
  trackEvent(AnalyticsEvents.CLI_PULL, {
1367
1412
  repoFullName,
@@ -1369,11 +1414,11 @@ async function pullCommand(options) {
1369
1414
  });
1370
1415
  console.log("\nDownloading secrets...");
1371
1416
  const response = await pullSecrets(repoFullName, environment, accessToken);
1372
- const envFilePath = path4.resolve(process.cwd(), envFile);
1373
- if (fs4.existsSync(envFilePath)) {
1417
+ const envFilePath = path5.resolve(process.cwd(), envFile);
1418
+ if (fs5.existsSync(envFilePath)) {
1374
1419
  const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
1375
1420
  if (options.yes) {
1376
- console.log(pc5.yellow(`
1421
+ console.log(pc6.yellow(`
1377
1422
  \u26A0 Overwriting existing file: ${envFile}`));
1378
1423
  } else if (!isInteractive3) {
1379
1424
  throw new Error(`File ${envFile} exists. Re-run with --yes to overwrite or choose a different --file.`);
@@ -1392,21 +1437,21 @@ async function pullCommand(options) {
1392
1437
  }
1393
1438
  );
1394
1439
  if (!confirm) {
1395
- console.log(pc5.yellow("Pull aborted."));
1440
+ console.log(pc6.yellow("Pull aborted."));
1396
1441
  return;
1397
1442
  }
1398
1443
  }
1399
1444
  }
1400
- fs4.writeFileSync(envFilePath, response.content, "utf-8");
1445
+ fs5.writeFileSync(envFilePath, response.content, "utf-8");
1401
1446
  const lines = response.content.split("\n").filter((line) => {
1402
1447
  const trimmed = line.trim();
1403
1448
  return trimmed.length > 0 && !trimmed.startsWith("#");
1404
1449
  });
1405
- console.log(pc5.green(`
1450
+ console.log(pc6.green(`
1406
1451
  \u2713 Secrets downloaded successfully`));
1407
1452
  console.log(`
1408
- File: ${pc5.cyan(envFile)}`);
1409
- console.log(`Variables: ${pc5.cyan(lines.length.toString())}`);
1453
+ File: ${pc6.cyan(envFile)}`);
1454
+ console.log(`Variables: ${pc6.cyan(lines.length.toString())}`);
1410
1455
  await shutdownAnalytics();
1411
1456
  } catch (error) {
1412
1457
  const message = error instanceof APIError ? `API ${error.statusCode}: ${error.message}` : error instanceof Error ? truncateMessage(error.message) : "Unknown error";
@@ -1415,14 +1460,14 @@ File: ${pc5.cyan(envFile)}`);
1415
1460
  error: message
1416
1461
  });
1417
1462
  await shutdownAnalytics();
1418
- console.error(pc5.red(`
1463
+ console.error(pc6.red(`
1419
1464
  \u2717 ${message}`));
1420
1465
  process.exit(1);
1421
1466
  }
1422
1467
  }
1423
1468
 
1424
1469
  // src/cmds/doctor.ts
1425
- import pc6 from "picocolors";
1470
+ import pc7 from "picocolors";
1426
1471
 
1427
1472
  // src/core/doctor.ts
1428
1473
  import { execSync as execSync2 } from "child_process";
@@ -1676,9 +1721,9 @@ async function runAllChecks(options = {}) {
1676
1721
  // src/cmds/doctor.ts
1677
1722
  function formatSummary(results) {
1678
1723
  const parts = [
1679
- pc6.green(`${results.summary.pass} passed`),
1680
- results.summary.warn > 0 ? pc6.yellow(`${results.summary.warn} warnings`) : null,
1681
- results.summary.fail > 0 ? pc6.red(`${results.summary.fail} failed`) : null
1724
+ pc7.green(`${results.summary.pass} passed`),
1725
+ results.summary.warn > 0 ? pc7.yellow(`${results.summary.warn} warnings`) : null,
1726
+ results.summary.fail > 0 ? pc7.red(`${results.summary.fail} failed`) : null
1682
1727
  ].filter(Boolean);
1683
1728
  return parts.join(", ");
1684
1729
  }
@@ -1695,20 +1740,20 @@ async function doctorCommand(options = {}) {
1695
1740
  process.stdout.write(JSON.stringify(results, null, 0) + "\n");
1696
1741
  process.exit(results.exitCode);
1697
1742
  }
1698
- console.log(pc6.cyan("\n\u{1F50D} Keyway Doctor - Environment Check\n"));
1743
+ console.log(pc7.cyan("\n\u{1F50D} Keyway Doctor - Environment Check\n"));
1699
1744
  results.checks.forEach((check) => {
1700
- const icon = check.status === "pass" ? pc6.green("\u2713") : check.status === "warn" ? pc6.yellow("!") : pc6.red("\u2717");
1701
- const detail = check.detail ? pc6.dim(` \u2014 ${check.detail}`) : "";
1745
+ const icon = check.status === "pass" ? pc7.green("\u2713") : check.status === "warn" ? pc7.yellow("!") : pc7.red("\u2717");
1746
+ const detail = check.detail ? pc7.dim(` \u2014 ${check.detail}`) : "";
1702
1747
  console.log(` ${icon} ${check.name}${detail}`);
1703
1748
  });
1704
1749
  console.log(`
1705
1750
  Summary: ${formatSummary(results)}`);
1706
1751
  if (results.summary.fail > 0) {
1707
- console.log(pc6.red("\u26A0 Some checks failed. Please resolve the issues above before using Keyway."));
1752
+ console.log(pc7.red("\u26A0 Some checks failed. Please resolve the issues above before using Keyway."));
1708
1753
  } else if (results.summary.warn > 0) {
1709
- console.log(pc6.yellow("\u26A0 Some warnings detected. Keyway should work but consider addressing them."));
1754
+ console.log(pc7.yellow("\u26A0 Some warnings detected. Keyway should work but consider addressing them."));
1710
1755
  } else {
1711
- console.log(pc6.green("\u2728 All checks passed! Your environment is ready for Keyway."));
1756
+ console.log(pc7.green("\u2728 All checks passed! Your environment is ready for Keyway."));
1712
1757
  }
1713
1758
  process.exit(results.exitCode);
1714
1759
  } catch (error) {
@@ -1729,7 +1774,7 @@ Summary: ${formatSummary(results)}`);
1729
1774
  };
1730
1775
  process.stdout.write(JSON.stringify(errorResult, null, 0) + "\n");
1731
1776
  } else {
1732
- console.error(pc6.red(`
1777
+ console.error(pc7.red(`
1733
1778
  \u2717 ${message}`));
1734
1779
  }
1735
1780
  process.exit(1);
@@ -1737,7 +1782,7 @@ Summary: ${formatSummary(results)}`);
1737
1782
  }
1738
1783
 
1739
1784
  // src/cmds/connect.ts
1740
- import pc7 from "picocolors";
1785
+ import pc8 from "picocolors";
1741
1786
  import open3 from "open";
1742
1787
  import prompts6 from "prompts";
1743
1788
  async function connectCommand(provider, options = {}) {
@@ -1747,13 +1792,13 @@ async function connectCommand(provider, options = {}) {
1747
1792
  const providerInfo = providers.find((p) => p.name === provider.toLowerCase());
1748
1793
  if (!providerInfo) {
1749
1794
  const available = providers.map((p) => p.name).join(", ");
1750
- console.error(pc7.red(`Unknown provider: ${provider}`));
1751
- console.log(pc7.gray(`Available providers: ${available || "none"}`));
1795
+ console.error(pc8.red(`Unknown provider: ${provider}`));
1796
+ console.log(pc8.gray(`Available providers: ${available || "none"}`));
1752
1797
  process.exit(1);
1753
1798
  }
1754
1799
  if (!providerInfo.configured) {
1755
- console.error(pc7.red(`Provider ${providerInfo.displayName} is not configured on the server.`));
1756
- console.log(pc7.gray("Contact your administrator to enable this integration."));
1800
+ console.error(pc8.red(`Provider ${providerInfo.displayName} is not configured on the server.`));
1801
+ console.log(pc8.gray("Contact your administrator to enable this integration."));
1757
1802
  process.exit(1);
1758
1803
  }
1759
1804
  const { connections } = await getConnections(accessToken);
@@ -1766,20 +1811,20 @@ async function connectCommand(provider, options = {}) {
1766
1811
  initial: false
1767
1812
  });
1768
1813
  if (!reconnect) {
1769
- console.log(pc7.gray("Keeping existing connection."));
1814
+ console.log(pc8.gray("Keeping existing connection."));
1770
1815
  return;
1771
1816
  }
1772
1817
  }
1773
- console.log(pc7.blue(`
1818
+ console.log(pc8.blue(`
1774
1819
  Connecting to ${providerInfo.displayName}...
1775
1820
  `));
1776
1821
  const authUrl = getProviderAuthUrl(provider.toLowerCase());
1777
1822
  const startTime = /* @__PURE__ */ new Date();
1778
- console.log(pc7.gray("Opening browser for authorization..."));
1779
- console.log(pc7.gray(`If the browser doesn't open, visit: ${authUrl}`));
1823
+ console.log(pc8.gray("Opening browser for authorization..."));
1824
+ console.log(pc8.gray(`If the browser doesn't open, visit: ${authUrl}`));
1780
1825
  await open3(authUrl).catch(() => {
1781
1826
  });
1782
- console.log(pc7.gray("Waiting for authorization..."));
1827
+ console.log(pc8.gray("Waiting for authorization..."));
1783
1828
  const maxAttempts = 60;
1784
1829
  let attempts = 0;
1785
1830
  let connected = false;
@@ -1793,7 +1838,7 @@ Connecting to ${providerInfo.displayName}...
1793
1838
  );
1794
1839
  if (newConn) {
1795
1840
  connected = true;
1796
- console.log(pc7.green(`
1841
+ console.log(pc8.green(`
1797
1842
  \u2713 Connected to ${providerInfo.displayName}!`));
1798
1843
  break;
1799
1844
  }
@@ -1801,8 +1846,8 @@ Connecting to ${providerInfo.displayName}...
1801
1846
  }
1802
1847
  }
1803
1848
  if (!connected) {
1804
- console.log(pc7.red("\n\u2717 Authorization timeout."));
1805
- console.log(pc7.gray("Run `keyway connections` to check if the connection was established."));
1849
+ console.log(pc8.red("\n\u2717 Authorization timeout."));
1850
+ console.log(pc8.gray("Run `keyway connections` to check if the connection was established."));
1806
1851
  }
1807
1852
  trackEvent(AnalyticsEvents.CLI_CONNECT, {
1808
1853
  provider: provider.toLowerCase(),
@@ -1814,7 +1859,7 @@ Connecting to ${providerInfo.displayName}...
1814
1859
  command: "connect",
1815
1860
  error: truncateMessage(message)
1816
1861
  });
1817
- console.error(pc7.red(`
1862
+ console.error(pc8.red(`
1818
1863
  \u2717 ${message}`));
1819
1864
  process.exit(1);
1820
1865
  }
@@ -1824,24 +1869,24 @@ async function connectionsCommand(options = {}) {
1824
1869
  const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
1825
1870
  const { connections } = await getConnections(accessToken);
1826
1871
  if (connections.length === 0) {
1827
- console.log(pc7.gray("No provider connections found."));
1828
- console.log(pc7.gray("\nConnect to a provider with: keyway connect <provider>"));
1829
- console.log(pc7.gray("Available providers: vercel"));
1872
+ console.log(pc8.gray("No provider connections found."));
1873
+ console.log(pc8.gray("\nConnect to a provider with: keyway connect <provider>"));
1874
+ console.log(pc8.gray("Available providers: vercel"));
1830
1875
  return;
1831
1876
  }
1832
- console.log(pc7.blue("\n\u{1F4E1} Provider Connections\n"));
1877
+ console.log(pc8.blue("\n\u{1F4E1} Provider Connections\n"));
1833
1878
  for (const conn of connections) {
1834
1879
  const providerName = conn.provider.charAt(0).toUpperCase() + conn.provider.slice(1);
1835
- const teamInfo = conn.providerTeamId ? pc7.gray(` (Team: ${conn.providerTeamId})`) : "";
1880
+ const teamInfo = conn.providerTeamId ? pc8.gray(` (Team: ${conn.providerTeamId})`) : "";
1836
1881
  const date = new Date(conn.createdAt).toLocaleDateString();
1837
- console.log(` ${pc7.green("\u25CF")} ${pc7.bold(providerName)}${teamInfo}`);
1838
- console.log(pc7.gray(` Connected: ${date}`));
1839
- console.log(pc7.gray(` ID: ${conn.id}`));
1882
+ console.log(` ${pc8.green("\u25CF")} ${pc8.bold(providerName)}${teamInfo}`);
1883
+ console.log(pc8.gray(` Connected: ${date}`));
1884
+ console.log(pc8.gray(` ID: ${conn.id}`));
1840
1885
  console.log("");
1841
1886
  }
1842
1887
  } catch (error) {
1843
1888
  const message = error instanceof Error ? error.message : "Failed to list connections";
1844
- console.error(pc7.red(`
1889
+ console.error(pc8.red(`
1845
1890
  \u2717 ${message}`));
1846
1891
  process.exit(1);
1847
1892
  }
@@ -1852,7 +1897,7 @@ async function disconnectCommand(provider, options = {}) {
1852
1897
  const { connections } = await getConnections(accessToken);
1853
1898
  const connection = connections.find((c) => c.provider === provider.toLowerCase());
1854
1899
  if (!connection) {
1855
- console.log(pc7.gray(`No connection found for provider: ${provider}`));
1900
+ console.log(pc8.gray(`No connection found for provider: ${provider}`));
1856
1901
  return;
1857
1902
  }
1858
1903
  const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
@@ -1863,11 +1908,11 @@ async function disconnectCommand(provider, options = {}) {
1863
1908
  initial: false
1864
1909
  });
1865
1910
  if (!confirm) {
1866
- console.log(pc7.gray("Cancelled."));
1911
+ console.log(pc8.gray("Cancelled."));
1867
1912
  return;
1868
1913
  }
1869
1914
  await deleteConnection(accessToken, connection.id);
1870
- console.log(pc7.green(`
1915
+ console.log(pc8.green(`
1871
1916
  \u2713 Disconnected from ${providerName}`));
1872
1917
  trackEvent(AnalyticsEvents.CLI_DISCONNECT, {
1873
1918
  provider: provider.toLowerCase()
@@ -1878,15 +1923,24 @@ async function disconnectCommand(provider, options = {}) {
1878
1923
  command: "disconnect",
1879
1924
  error: truncateMessage(message)
1880
1925
  });
1881
- console.error(pc7.red(`
1926
+ console.error(pc8.red(`
1882
1927
  \u2717 ${message}`));
1883
1928
  process.exit(1);
1884
1929
  }
1885
1930
  }
1886
1931
 
1887
1932
  // src/cmds/sync.ts
1888
- import pc8 from "picocolors";
1933
+ import pc9 from "picocolors";
1889
1934
  import prompts7 from "prompts";
1935
+ function mapToVercelEnvironment(keywayEnv) {
1936
+ const mapping = {
1937
+ production: "production",
1938
+ staging: "preview",
1939
+ dev: "development",
1940
+ development: "development"
1941
+ };
1942
+ return mapping[keywayEnv.toLowerCase()] || "production";
1943
+ }
1890
1944
  function findMatchingProject(projects, repoFullName) {
1891
1945
  const repoFullNameLower = repoFullName.toLowerCase();
1892
1946
  const repoName = repoFullName.split("/")[1]?.toLowerCase();
@@ -1926,11 +1980,11 @@ async function promptProjectSelection(projects, repoFullName) {
1926
1980
  let title = p.name;
1927
1981
  const badges = [];
1928
1982
  if (p.linkedRepo?.toLowerCase() === repoFullName.toLowerCase()) {
1929
- badges.push(pc8.green("\u2190 linked"));
1983
+ badges.push(pc9.green("\u2190 linked"));
1930
1984
  } else if (p.name.toLowerCase() === repoName) {
1931
- badges.push(pc8.green("\u2190 same name"));
1985
+ badges.push(pc9.green("\u2190 same name"));
1932
1986
  } else if (p.linkedRepo) {
1933
- badges.push(pc8.gray(`\u2192 ${p.linkedRepo}`));
1987
+ badges.push(pc9.gray(`\u2192 ${p.linkedRepo}`));
1934
1988
  }
1935
1989
  if (badges.length > 0) {
1936
1990
  title = `${p.name} ${badges.join(" ")}`;
@@ -1944,7 +1998,7 @@ async function promptProjectSelection(projects, repoFullName) {
1944
1998
  choices
1945
1999
  });
1946
2000
  if (!projectChoice) {
1947
- console.log(pc8.gray("Cancelled."));
2001
+ console.log(pc9.gray("Cancelled."));
1948
2002
  process.exit(0);
1949
2003
  }
1950
2004
  return projects.find((p) => p.id === projectChoice);
@@ -1952,28 +2006,28 @@ async function promptProjectSelection(projects, repoFullName) {
1952
2006
  async function syncCommand(provider, options = {}) {
1953
2007
  try {
1954
2008
  if (options.pull && options.allowDelete) {
1955
- console.error(pc8.red("Error: --allow-delete cannot be used with --pull"));
1956
- console.log(pc8.gray("The --allow-delete flag is only for push operations."));
2009
+ console.error(pc9.red("Error: --allow-delete cannot be used with --pull"));
2010
+ console.log(pc9.gray("The --allow-delete flag is only for push operations."));
1957
2011
  process.exit(1);
1958
2012
  }
1959
2013
  const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
1960
2014
  const repoFullName = detectGitRepo();
1961
2015
  if (!repoFullName) {
1962
- console.error(pc8.red("Could not detect Git repository."));
1963
- console.log(pc8.gray("Run this command from a Git repository directory."));
2016
+ console.error(pc9.red("Could not detect Git repository."));
2017
+ console.log(pc9.gray("Run this command from a Git repository directory."));
1964
2018
  process.exit(1);
1965
2019
  }
1966
- console.log(pc8.gray(`Repository: ${repoFullName}`));
2020
+ console.log(pc9.gray(`Repository: ${repoFullName}`));
1967
2021
  const { connections } = await getConnections(accessToken);
1968
2022
  const connection = connections.find((c) => c.provider === provider.toLowerCase());
1969
2023
  if (!connection) {
1970
- console.error(pc8.red(`Not connected to ${provider}.`));
1971
- console.log(pc8.gray(`Run: keyway connect ${provider}`));
2024
+ console.error(pc9.red(`Not connected to ${provider}.`));
2025
+ console.log(pc9.gray(`Run: keyway connect ${provider}`));
1972
2026
  process.exit(1);
1973
2027
  }
1974
2028
  const { projects } = await getConnectionProjects(accessToken, connection.id);
1975
2029
  if (projects.length === 0) {
1976
- console.error(pc8.red(`No projects found in your ${provider} account.`));
2030
+ console.error(pc9.red(`No projects found in your ${provider} account.`));
1977
2031
  process.exit(1);
1978
2032
  }
1979
2033
  let selectedProject;
@@ -1982,21 +2036,21 @@ async function syncCommand(provider, options = {}) {
1982
2036
  (p) => p.id === options.project || p.name.toLowerCase() === options.project?.toLowerCase()
1983
2037
  );
1984
2038
  if (!found) {
1985
- console.error(pc8.red(`Project not found: ${options.project}`));
1986
- console.log(pc8.gray("Available projects:"));
1987
- projects.forEach((p) => console.log(pc8.gray(` - ${p.name}`)));
2039
+ console.error(pc9.red(`Project not found: ${options.project}`));
2040
+ console.log(pc9.gray("Available projects:"));
2041
+ projects.forEach((p) => console.log(pc9.gray(` - ${p.name}`)));
1988
2042
  process.exit(1);
1989
2043
  }
1990
2044
  selectedProject = found;
1991
2045
  if (!projectMatchesRepo(selectedProject, repoFullName)) {
1992
2046
  console.log("");
1993
- console.log(pc8.yellow("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
1994
- console.log(pc8.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
1995
- console.log(pc8.yellow("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
1996
- console.log(pc8.yellow(` Current repo: ${repoFullName}`));
1997
- console.log(pc8.yellow(` Selected project: ${selectedProject.name}`));
2047
+ console.log(pc9.yellow("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
2048
+ console.log(pc9.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
2049
+ console.log(pc9.yellow("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
2050
+ console.log(pc9.yellow(` Current repo: ${repoFullName}`));
2051
+ console.log(pc9.yellow(` Selected project: ${selectedProject.name}`));
1998
2052
  if (selectedProject.linkedRepo) {
1999
- console.log(pc8.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2053
+ console.log(pc9.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2000
2054
  }
2001
2055
  console.log("");
2002
2056
  }
@@ -2005,9 +2059,9 @@ async function syncCommand(provider, options = {}) {
2005
2059
  if (autoMatch && (autoMatch.matchType === "linked_repo" || autoMatch.matchType === "exact_name")) {
2006
2060
  selectedProject = autoMatch.project;
2007
2061
  const matchReason = autoMatch.matchType === "linked_repo" ? `linked to ${repoFullName}` : "exact name match";
2008
- console.log(pc8.green(`\u2713 Auto-selected project: ${selectedProject.name} (${matchReason})`));
2062
+ console.log(pc9.green(`\u2713 Auto-selected project: ${selectedProject.name} (${matchReason})`));
2009
2063
  } else if (autoMatch && autoMatch.matchType === "partial_name") {
2010
- console.log(pc8.yellow(`Detected project: ${autoMatch.project.name} (partial match)`));
2064
+ console.log(pc9.yellow(`Detected project: ${autoMatch.project.name} (partial match)`));
2011
2065
  const { useDetected } = await prompts7({
2012
2066
  type: "confirm",
2013
2067
  name: "useDetected",
@@ -2023,13 +2077,13 @@ async function syncCommand(provider, options = {}) {
2023
2077
  selectedProject = projects[0];
2024
2078
  if (!projectMatchesRepo(selectedProject, repoFullName)) {
2025
2079
  console.log("");
2026
- console.log(pc8.yellow("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
2027
- console.log(pc8.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
2028
- console.log(pc8.yellow("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
2029
- console.log(pc8.yellow(` Current repo: ${repoFullName}`));
2030
- console.log(pc8.yellow(` Only project: ${selectedProject.name}`));
2080
+ console.log(pc9.yellow("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
2081
+ console.log(pc9.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
2082
+ console.log(pc9.yellow("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
2083
+ console.log(pc9.yellow(` Current repo: ${repoFullName}`));
2084
+ console.log(pc9.yellow(` Only project: ${selectedProject.name}`));
2031
2085
  if (selectedProject.linkedRepo) {
2032
- console.log(pc8.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2086
+ console.log(pc9.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2033
2087
  }
2034
2088
  console.log("");
2035
2089
  const { continueAnyway } = await prompts7({
@@ -2039,14 +2093,14 @@ async function syncCommand(provider, options = {}) {
2039
2093
  initial: false
2040
2094
  });
2041
2095
  if (!continueAnyway) {
2042
- console.log(pc8.gray("Cancelled."));
2096
+ console.log(pc9.gray("Cancelled."));
2043
2097
  process.exit(0);
2044
2098
  }
2045
2099
  }
2046
2100
  } else {
2047
- console.log(pc8.yellow(`
2101
+ console.log(pc9.yellow(`
2048
2102
  \u26A0\uFE0F No matching project found for ${repoFullName}`));
2049
- console.log(pc8.gray("Select a project manually:\n"));
2103
+ console.log(pc9.gray("Select a project manually:\n"));
2050
2104
  selectedProject = await promptProjectSelection(projects, repoFullName);
2051
2105
  }
2052
2106
  }
@@ -2054,13 +2108,13 @@ async function syncCommand(provider, options = {}) {
2054
2108
  const autoMatch = findMatchingProject(projects, repoFullName);
2055
2109
  if (autoMatch && autoMatch.project.id !== selectedProject.id) {
2056
2110
  console.log("");
2057
- console.log(pc8.yellow("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
2058
- console.log(pc8.yellow("\u2502 \u26A0\uFE0F WARNING: You selected a different project \u2502"));
2059
- console.log(pc8.yellow("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
2060
- console.log(pc8.yellow(` Current repo: ${repoFullName}`));
2061
- console.log(pc8.yellow(` Selected project: ${selectedProject.name}`));
2111
+ console.log(pc9.yellow("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
2112
+ console.log(pc9.yellow("\u2502 \u26A0\uFE0F WARNING: You selected a different project \u2502"));
2113
+ console.log(pc9.yellow("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
2114
+ console.log(pc9.yellow(` Current repo: ${repoFullName}`));
2115
+ console.log(pc9.yellow(` Selected project: ${selectedProject.name}`));
2062
2116
  if (selectedProject.linkedRepo) {
2063
- console.log(pc8.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2117
+ console.log(pc9.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2064
2118
  }
2065
2119
  console.log("");
2066
2120
  const { continueAnyway } = await prompts7({
@@ -2070,18 +2124,56 @@ async function syncCommand(provider, options = {}) {
2070
2124
  initial: false
2071
2125
  });
2072
2126
  if (!continueAnyway) {
2073
- console.log(pc8.gray("Cancelled."));
2127
+ console.log(pc9.gray("Cancelled."));
2074
2128
  process.exit(0);
2075
2129
  }
2076
2130
  }
2077
2131
  }
2078
- const keywayEnv = options.environment || "production";
2079
- const providerEnv = options.providerEnv || "production";
2080
- const direction = options.pull ? "pull" : "push";
2081
2132
  const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
2082
- console.log(pc8.gray(`Project: ${selectedProject.name}`));
2083
- console.log(pc8.gray(`Environment: ${keywayEnv}${providerEnv !== keywayEnv ? ` \u2192 ${providerEnv}` : ""}`));
2084
- console.log(pc8.gray(`Direction: ${direction === "push" ? "Keyway \u2192 " + providerName : providerName + " \u2192 Keyway"}`));
2133
+ let keywayEnv = options.environment;
2134
+ let providerEnv = options.providerEnv;
2135
+ let direction = options.push ? "push" : options.pull ? "pull" : void 0;
2136
+ const needsEnvPrompt = !options.environment;
2137
+ const needsDirectionPrompt = !direction;
2138
+ if (needsEnvPrompt || needsDirectionPrompt) {
2139
+ if (needsEnvPrompt) {
2140
+ const vaultEnvs = await getVaultEnvironments(accessToken, repoFullName);
2141
+ const { selectedEnv } = await prompts7({
2142
+ type: "select",
2143
+ name: "selectedEnv",
2144
+ message: "Keyway environment:",
2145
+ choices: vaultEnvs.map((e) => ({ title: e, value: e })),
2146
+ initial: Math.max(0, vaultEnvs.indexOf("production"))
2147
+ });
2148
+ if (!selectedEnv) {
2149
+ console.log(pc9.gray("Cancelled."));
2150
+ process.exit(0);
2151
+ }
2152
+ keywayEnv = selectedEnv;
2153
+ if (!options.providerEnv) {
2154
+ providerEnv = mapToVercelEnvironment(keywayEnv);
2155
+ }
2156
+ }
2157
+ if (needsDirectionPrompt) {
2158
+ const { selectedDirection } = await prompts7({
2159
+ type: "select",
2160
+ name: "selectedDirection",
2161
+ message: "Sync direction:",
2162
+ choices: [
2163
+ { title: `Keyway \u2192 ${providerName}`, value: "push" },
2164
+ { title: `${providerName} \u2192 Keyway`, value: "pull" }
2165
+ ]
2166
+ });
2167
+ if (!selectedDirection) {
2168
+ console.log(pc9.gray("Cancelled."));
2169
+ process.exit(0);
2170
+ }
2171
+ direction = selectedDirection;
2172
+ }
2173
+ }
2174
+ keywayEnv = keywayEnv || "production";
2175
+ providerEnv = providerEnv || "production";
2176
+ direction = direction || "push";
2085
2177
  const status = await getSyncStatus(
2086
2178
  accessToken,
2087
2179
  repoFullName,
@@ -2089,10 +2181,10 @@ async function syncCommand(provider, options = {}) {
2089
2181
  selectedProject.id,
2090
2182
  keywayEnv
2091
2183
  );
2092
- if (status.isFirstSync && !options.pull && status.vaultIsEmpty && status.providerHasSecrets) {
2093
- console.log(pc8.yellow(`
2184
+ if (status.isFirstSync && direction === "push" && status.vaultIsEmpty && status.providerHasSecrets) {
2185
+ console.log(pc9.yellow(`
2094
2186
  \u26A0\uFE0F Your Keyway vault is empty for "${keywayEnv}", but ${providerName} has ${status.providerSecretCount} secrets.`));
2095
- console.log(pc8.gray(` (Use --environment to sync a different environment)`));
2187
+ console.log(pc9.gray(` (Use --environment to sync a different environment)`));
2096
2188
  const { importFirst } = await prompts7({
2097
2189
  type: "confirm",
2098
2190
  name: "importFirst",
@@ -2134,7 +2226,7 @@ async function syncCommand(provider, options = {}) {
2134
2226
  command: "sync",
2135
2227
  error: truncateMessage(message)
2136
2228
  });
2137
- console.error(pc8.red(`
2229
+ console.error(pc9.red(`
2138
2230
  \u2717 ${message}`));
2139
2231
  process.exit(1);
2140
2232
  }
@@ -2151,33 +2243,33 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
2151
2243
  });
2152
2244
  const totalChanges = preview.toCreate.length + preview.toUpdate.length + preview.toDelete.length;
2153
2245
  if (totalChanges === 0) {
2154
- console.log(pc8.green("\n\u2713 Already in sync. No changes needed."));
2246
+ console.log(pc9.green("\n\u2713 Already in sync. No changes needed."));
2155
2247
  return;
2156
2248
  }
2157
- console.log(pc8.blue("\n\u{1F4CB} Sync Preview\n"));
2249
+ console.log(pc9.blue("\n\u{1F4CB} Sync Preview\n"));
2158
2250
  if (preview.toCreate.length > 0) {
2159
- console.log(pc8.green(` + ${preview.toCreate.length} to create`));
2160
- preview.toCreate.slice(0, 5).forEach((key) => console.log(pc8.gray(` ${key}`)));
2251
+ console.log(pc9.green(` + ${preview.toCreate.length} to create`));
2252
+ preview.toCreate.slice(0, 5).forEach((key) => console.log(pc9.gray(` ${key}`)));
2161
2253
  if (preview.toCreate.length > 5) {
2162
- console.log(pc8.gray(` ... and ${preview.toCreate.length - 5} more`));
2254
+ console.log(pc9.gray(` ... and ${preview.toCreate.length - 5} more`));
2163
2255
  }
2164
2256
  }
2165
2257
  if (preview.toUpdate.length > 0) {
2166
- console.log(pc8.yellow(` ~ ${preview.toUpdate.length} to update`));
2167
- preview.toUpdate.slice(0, 5).forEach((key) => console.log(pc8.gray(` ${key}`)));
2258
+ console.log(pc9.yellow(` ~ ${preview.toUpdate.length} to update`));
2259
+ preview.toUpdate.slice(0, 5).forEach((key) => console.log(pc9.gray(` ${key}`)));
2168
2260
  if (preview.toUpdate.length > 5) {
2169
- console.log(pc8.gray(` ... and ${preview.toUpdate.length - 5} more`));
2261
+ console.log(pc9.gray(` ... and ${preview.toUpdate.length - 5} more`));
2170
2262
  }
2171
2263
  }
2172
2264
  if (preview.toDelete.length > 0) {
2173
- console.log(pc8.red(` - ${preview.toDelete.length} to delete`));
2174
- preview.toDelete.slice(0, 5).forEach((key) => console.log(pc8.gray(` ${key}`)));
2265
+ console.log(pc9.red(` - ${preview.toDelete.length} to delete`));
2266
+ preview.toDelete.slice(0, 5).forEach((key) => console.log(pc9.gray(` ${key}`)));
2175
2267
  if (preview.toDelete.length > 5) {
2176
- console.log(pc8.gray(` ... and ${preview.toDelete.length - 5} more`));
2268
+ console.log(pc9.gray(` ... and ${preview.toDelete.length - 5} more`));
2177
2269
  }
2178
2270
  }
2179
2271
  if (preview.toSkip.length > 0) {
2180
- console.log(pc8.gray(` \u25CB ${preview.toSkip.length} unchanged`));
2272
+ console.log(pc9.gray(` \u25CB ${preview.toSkip.length} unchanged`));
2181
2273
  }
2182
2274
  console.log("");
2183
2275
  if (!skipConfirm) {
@@ -2189,11 +2281,11 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
2189
2281
  initial: true
2190
2282
  });
2191
2283
  if (!confirm) {
2192
- console.log(pc8.gray("Cancelled."));
2284
+ console.log(pc9.gray("Cancelled."));
2193
2285
  return;
2194
2286
  }
2195
2287
  }
2196
- console.log(pc8.blue("\n\u23F3 Syncing...\n"));
2288
+ console.log(pc9.blue("\n\u23F3 Syncing...\n"));
2197
2289
  const result = await executeSync(accessToken, repoFullName, {
2198
2290
  connectionId,
2199
2291
  projectId: project.id,
@@ -2203,11 +2295,11 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
2203
2295
  allowDelete
2204
2296
  });
2205
2297
  if (result.success) {
2206
- console.log(pc8.green("\u2713 Sync complete"));
2207
- console.log(pc8.gray(` Created: ${result.stats.created}`));
2208
- console.log(pc8.gray(` Updated: ${result.stats.updated}`));
2298
+ console.log(pc9.green("\u2713 Sync complete"));
2299
+ console.log(pc9.gray(` Created: ${result.stats.created}`));
2300
+ console.log(pc9.gray(` Updated: ${result.stats.updated}`));
2209
2301
  if (result.stats.deleted > 0) {
2210
- console.log(pc8.gray(` Deleted: ${result.stats.deleted}`));
2302
+ console.log(pc9.gray(` Deleted: ${result.stats.deleted}`));
2211
2303
  }
2212
2304
  trackEvent(AnalyticsEvents.CLI_SYNC, {
2213
2305
  provider,
@@ -2217,7 +2309,7 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
2217
2309
  deleted: result.stats.deleted
2218
2310
  });
2219
2311
  } else {
2220
- console.error(pc8.red(`
2312
+ console.error(pc9.red(`
2221
2313
  \u2717 ${result.error}`));
2222
2314
  process.exit(1);
2223
2315
  }
@@ -2227,13 +2319,14 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
2227
2319
  var program = new Command();
2228
2320
  var TAGLINE = "Sync secrets with your team and infra";
2229
2321
  var showBanner = () => {
2230
- const text = pc9.bold(pc9.cyan("Keyway CLI"));
2322
+ const text = pc10.bold(pc10.cyan("Keyway CLI"));
2231
2323
  console.log(`
2232
2324
  ${text}
2233
- ${pc9.gray(TAGLINE)}
2325
+ ${pc10.gray(TAGLINE)}
2234
2326
  `);
2235
2327
  };
2236
2328
  showBanner();
2329
+ warnIfEnvNotGitignored();
2237
2330
  program.name("keyway").description(TAGLINE).version(package_default.version);
2238
2331
  program.command("init").description("Initialize a vault for the current repository").option("--no-login-prompt", "Fail instead of prompting to login if unauthenticated").action(async (options) => {
2239
2332
  await initCommand(options);
@@ -2262,10 +2355,10 @@ program.command("connections").description("List your provider connections").opt
2262
2355
  program.command("disconnect <provider>").description("Disconnect from a provider").option("--no-login-prompt", "Fail instead of prompting to login if unauthenticated").action(async (provider, options) => {
2263
2356
  await disconnectCommand(provider, options);
2264
2357
  });
2265
- program.command("sync <provider>").description("Sync secrets with a provider (e.g., vercel)").option("--pull", "Import secrets from provider to Keyway").option("-e, --environment <env>", "Keyway environment (default: production)").option("--provider-env <env>", "Provider environment (default: production)").option("--project <project>", "Provider project name or ID").option("--allow-delete", "Allow deleting secrets not in source").option("-y, --yes", "Skip confirmation prompt").option("--no-login-prompt", "Fail instead of prompting to login if unauthenticated").action(async (provider, options) => {
2358
+ program.command("sync <provider>").description("Sync secrets with a provider (e.g., vercel)").option("--push", "Export secrets from Keyway to provider").option("--pull", "Import secrets from provider to Keyway").option("-e, --environment <env>", "Keyway environment (default: production)").option("--provider-env <env>", "Provider environment (default: production)").option("--project <project>", "Provider project name or ID").option("--allow-delete", "Allow deleting secrets not in source").option("-y, --yes", "Skip confirmation prompt").option("--no-login-prompt", "Fail instead of prompting to login if unauthenticated").action(async (provider, options) => {
2266
2359
  await syncCommand(provider, options);
2267
2360
  });
2268
2361
  program.parseAsync().catch((error) => {
2269
- console.error(pc9.red("Error:"), error.message || error);
2362
+ console.error(pc10.red("Error:"), error.message || error);
2270
2363
  process.exit(1);
2271
2364
  });