@keywaysh/cli 0.1.4 → 0.1.6

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 +485 -293
  2. package/package.json +20 -13
package/dist/cli.js CHANGED
@@ -8,12 +8,11 @@ import {
8
8
 
9
9
  // src/cli.ts
10
10
  import { Command } from "commander";
11
- import pc10 from "picocolors";
11
+ import pc11 from "picocolors";
12
12
 
13
13
  // src/cmds/init.ts
14
- import pc5 from "picocolors";
14
+ import pc6 from "picocolors";
15
15
  import prompts4 from "prompts";
16
- import open2 from "open";
17
16
 
18
17
  // src/utils/git.ts
19
18
  import { execSync } from "child_process";
@@ -102,7 +101,7 @@ var INTERNAL_POSTHOG_HOST = "https://eu.i.posthog.com";
102
101
  // package.json
103
102
  var package_default = {
104
103
  name: "@keywaysh/cli",
105
- version: "0.1.4",
104
+ version: "0.1.6",
106
105
  description: "One link to all your secrets",
107
106
  type: "module",
108
107
  bin: {
@@ -120,6 +119,7 @@ var package_default = {
120
119
  prepublishOnly: "pnpm run build",
121
120
  test: "pnpm exec vitest run",
122
121
  "test:watch": "pnpm exec vitest",
122
+ "test:coverage": "pnpm exec vitest run --coverage",
123
123
  release: "npm version patch && git push && git push --tags",
124
124
  "release:minor": "npm version minor && git push && git push --tags",
125
125
  "release:major": "npm version major && git push && git push --tags"
@@ -146,6 +146,7 @@ var package_default = {
146
146
  node: ">=18.0.0"
147
147
  },
148
148
  dependencies: {
149
+ "balanced-match": "^3.0.1",
149
150
  commander: "^14.0.0",
150
151
  conf: "^15.0.2",
151
152
  open: "^11.0.0",
@@ -154,8 +155,11 @@ var package_default = {
154
155
  prompts: "^2.4.2"
155
156
  },
156
157
  devDependencies: {
158
+ "@types/balanced-match": "^3.0.2",
157
159
  "@types/node": "^24.2.0",
158
160
  "@types/prompts": "^2.4.9",
161
+ "@vitest/coverage-v8": "^3.0.0",
162
+ msw: "^2.12.4",
159
163
  tsup: "^8.5.0",
160
164
  tsx: "^4.20.3",
161
165
  typescript: "^5.9.2",
@@ -491,6 +495,19 @@ async function executeSync(accessToken, repoFullName, options) {
491
495
  const wrapped = await handleResponse(response);
492
496
  return wrapped.data;
493
497
  }
498
+ async function connectWithToken(accessToken, provider, providerToken) {
499
+ const response = await fetchWithTimeout(`${API_BASE_URL}/v1/integrations/${provider}/connect`, {
500
+ method: "POST",
501
+ headers: {
502
+ "Content-Type": "application/json",
503
+ "User-Agent": USER_AGENT,
504
+ Authorization: `Bearer ${accessToken}`
505
+ },
506
+ body: JSON.stringify({ token: providerToken })
507
+ });
508
+ const wrapped = await handleResponse(response);
509
+ return wrapped.data;
510
+ }
494
511
  async function checkVaultExists(accessToken, repoFullName) {
495
512
  const [owner, repo] = repoFullName.split("/");
496
513
  try {
@@ -673,23 +690,76 @@ import fs3 from "fs";
673
690
  import path3 from "path";
674
691
  import prompts from "prompts";
675
692
  import pc2 from "picocolors";
693
+ import balanced from "balanced-match";
676
694
  function generateBadge(repo) {
677
695
  return `[![Keyway Secrets](https://www.keyway.sh/badge.svg?repo=${repo})](https://www.keyway.sh/vaults/${repo})`;
678
696
  }
697
+ var BADGE_PREFIX = /\[!\[[^\]]*\]\([^)]*\)\]\(/g;
698
+ var H1_PATTERN = /^#\s+/;
699
+ var CODE_FENCE = /^```/;
700
+ function findLastBadgeEnd(line) {
701
+ let lastEnd = -1;
702
+ let match;
703
+ BADGE_PREFIX.lastIndex = 0;
704
+ while ((match = BADGE_PREFIX.exec(line)) !== null) {
705
+ const prefixEnd = match.index + match[0].length - 1;
706
+ const remainder = line.substring(prefixEnd);
707
+ const balancedMatch = balanced("(", ")", remainder);
708
+ if (balancedMatch) {
709
+ lastEnd = prefixEnd + balancedMatch.end + 1;
710
+ }
711
+ }
712
+ return lastEnd;
713
+ }
679
714
  function insertBadgeIntoReadme(readmeContent, badge) {
680
715
  if (readmeContent.includes("keyway.sh/badge.svg")) {
681
716
  return readmeContent;
682
717
  }
683
718
  const lines = readmeContent.split(/\r?\n/);
684
- const titleIndex = lines.findIndex((line) => /^#(?!#)\s+/.test(line.trim()));
685
- if (titleIndex !== -1) {
686
- const before = lines.slice(0, titleIndex + 1);
687
- const after = lines.slice(titleIndex + 1);
719
+ let inCodeBlock = false;
720
+ let inHtmlComment = false;
721
+ let lastBadgeLine = -1;
722
+ let lastBadgeEndIndex = -1;
723
+ let firstH1Line = -1;
724
+ for (let i = 0; i < lines.length; i++) {
725
+ const line = lines[i];
726
+ const trimmed = line.trim();
727
+ if (CODE_FENCE.test(trimmed)) {
728
+ inCodeBlock = !inCodeBlock;
729
+ continue;
730
+ }
731
+ if (inCodeBlock) continue;
732
+ if (trimmed.includes("<!--")) inHtmlComment = true;
733
+ if (trimmed.includes("-->")) {
734
+ inHtmlComment = false;
735
+ continue;
736
+ }
737
+ if (inHtmlComment) continue;
738
+ BADGE_PREFIX.lastIndex = 0;
739
+ if (BADGE_PREFIX.test(line)) {
740
+ lastBadgeLine = i;
741
+ lastBadgeEndIndex = findLastBadgeEnd(line);
742
+ }
743
+ if (firstH1Line === -1 && H1_PATTERN.test(line)) {
744
+ firstH1Line = i;
745
+ }
746
+ }
747
+ if (lastBadgeLine >= 0 && lastBadgeEndIndex > 0) {
748
+ const line = lines[lastBadgeLine];
749
+ lines[lastBadgeLine] = line.slice(0, lastBadgeEndIndex) + " " + badge + line.slice(lastBadgeEndIndex);
750
+ return lines.join("\n");
751
+ }
752
+ if (firstH1Line >= 0) {
753
+ const before = lines.slice(0, firstH1Line + 1);
754
+ const after = lines.slice(firstH1Line + 1);
688
755
  while (after.length > 0 && after[0].trim() === "") {
689
756
  after.shift();
690
757
  }
691
- const newLines = [...before, "", badge, "", ...after];
692
- return newLines.join("\n");
758
+ if (after.length > 0) {
759
+ return [...before, "", badge, "", ...after].join("\n");
760
+ } else {
761
+ return [...before, "", badge, ""].join("\n");
762
+ }
693
763
  }
694
764
  return `${badge}
695
765
 
@@ -708,8 +778,8 @@ function findReadmePath(cwd) {
708
778
  async function ensureReadme(repoName, cwd) {
709
779
  const existing = findReadmePath(cwd);
710
780
  if (existing) return existing;
711
- const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
712
- if (!isInteractive3) {
781
+ const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
782
+ if (!isInteractive2) {
713
783
  console.log(pc2.yellow('No README found. Run "keyway readme add-badge" from a repo with a README.'));
714
784
  return null;
715
785
  }
@@ -760,22 +830,49 @@ async function addBadgeToReadme(silent = false) {
760
830
  }
761
831
 
762
832
  // src/cmds/push.ts
763
- import pc4 from "picocolors";
833
+ import pc5 from "picocolors";
764
834
  import fs4 from "fs";
765
835
  import path4 from "path";
766
836
  import prompts3 from "prompts";
767
837
 
768
838
  // src/cmds/login.ts
769
- import pc3 from "picocolors";
839
+ import pc4 from "picocolors";
770
840
  import readline from "readline";
771
- import open from "open";
772
841
  import prompts2 from "prompts";
842
+
843
+ // src/utils/helpers.ts
844
+ import pc3 from "picocolors";
845
+ import open from "open";
773
846
  function sleep(ms) {
774
847
  return new Promise((resolve) => setTimeout(resolve, ms));
775
848
  }
849
+ async function openUrl(url) {
850
+ console.log(pc3.gray(`
851
+ Open this URL in your browser:
852
+ ${url}
853
+ `));
854
+ await open(url).catch(() => {
855
+ });
856
+ }
776
857
  function isInteractive() {
777
858
  return Boolean(process.stdout.isTTY && process.stdin.isTTY && !process.env.CI);
778
859
  }
860
+ function showUpgradePrompt(message, upgradeUrl) {
861
+ console.log("");
862
+ console.log(pc3.dim("\u2500".repeat(50)));
863
+ console.log("");
864
+ console.log(` ${pc3.yellow("\u26A1")} ${pc3.bold("Plan Limit Reached")}`);
865
+ console.log("");
866
+ console.log(pc3.white(` ${message}`));
867
+ console.log("");
868
+ console.log(` ${pc3.cyan("Upgrade now \u2192")} ${pc3.underline(upgradeUrl)}`);
869
+ console.log("");
870
+ console.log(pc3.dim("\u2500".repeat(50)));
871
+ console.log("");
872
+ }
873
+ var MAX_CONSECUTIVE_ERRORS = 5;
874
+
875
+ // src/cmds/login.ts
779
876
  async function promptYesNo(question, defaultYes = true) {
780
877
  return new Promise((resolve) => {
781
878
  const rl = readline.createInterface({
@@ -793,52 +890,60 @@ async function promptYesNo(question, defaultYes = true) {
793
890
  });
794
891
  }
795
892
  async function runLoginFlow() {
796
- console.log(pc3.blue("\u{1F510} Starting Keyway login...\n"));
893
+ console.log(pc4.blue("\u{1F510} Starting Keyway login...\n"));
797
894
  const repoName = detectGitRepo();
798
895
  const start = await startDeviceLogin(repoName);
799
896
  const verifyUrl = start.verificationUriComplete || start.verificationUri;
800
897
  if (!verifyUrl) {
801
898
  throw new Error("Missing verification URL from the auth server.");
802
899
  }
803
- console.log(`Code: ${pc3.bold(pc3.green(start.userCode))}`);
900
+ console.log(`Code: ${pc4.bold(pc4.green(start.userCode))}`);
901
+ await openUrl(verifyUrl);
804
902
  console.log("Waiting for auth...");
805
- open(verifyUrl).catch(() => {
806
- console.log(pc3.gray(`Open this URL in your browser: ${verifyUrl}`));
807
- });
808
903
  const pollIntervalMs = (start.interval ?? 5) * 1e3;
809
904
  const maxTimeoutMs = Math.min((start.expiresIn ?? 900) * 1e3, 30 * 60 * 1e3);
810
905
  const startTime = Date.now();
906
+ let consecutiveErrors = 0;
811
907
  while (true) {
812
908
  if (Date.now() - startTime > maxTimeoutMs) {
813
909
  throw new Error('Login timed out. Please run "keyway login" again.');
814
910
  }
815
911
  await sleep(pollIntervalMs);
816
- const result = await pollDeviceLogin(start.deviceCode);
817
- if (result.status === "pending") {
818
- continue;
819
- }
820
- if (result.status === "approved" && result.keywayToken) {
821
- await saveAuthToken(result.keywayToken, {
822
- githubLogin: result.githubLogin,
823
- expiresAt: result.expiresAt
824
- });
825
- trackEvent(AnalyticsEvents.CLI_LOGIN, {
826
- method: "device",
827
- repo: repoName
828
- });
829
- if (result.githubLogin) {
830
- identifyUser(result.githubLogin, {
831
- github_username: result.githubLogin,
832
- login_method: "device"
912
+ try {
913
+ const result = await pollDeviceLogin(start.deviceCode);
914
+ consecutiveErrors = 0;
915
+ if (result.status === "pending") {
916
+ continue;
917
+ }
918
+ if (result.status === "approved" && result.keywayToken) {
919
+ await saveAuthToken(result.keywayToken, {
920
+ githubLogin: result.githubLogin,
921
+ expiresAt: result.expiresAt
922
+ });
923
+ trackEvent(AnalyticsEvents.CLI_LOGIN, {
924
+ method: "device",
925
+ repo: repoName
833
926
  });
927
+ if (result.githubLogin) {
928
+ identifyUser(result.githubLogin, {
929
+ github_username: result.githubLogin,
930
+ login_method: "device"
931
+ });
932
+ }
933
+ console.log(pc4.green("\n\u2713 Login successful"));
934
+ if (result.githubLogin) {
935
+ console.log(`Authenticated GitHub user: ${pc4.cyan(result.githubLogin)}`);
936
+ }
937
+ return result.keywayToken;
834
938
  }
835
- console.log(pc3.green("\n\u2713 Login successful"));
836
- if (result.githubLogin) {
837
- console.log(`Authenticated GitHub user: ${pc3.cyan(result.githubLogin)}`);
939
+ throw new Error(result.message || "Authentication failed");
940
+ } catch (error) {
941
+ consecutiveErrors++;
942
+ if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
943
+ const errorMsg = error instanceof Error ? error.message : "Unknown error";
944
+ throw new Error(`Login failed after ${MAX_CONSECUTIVE_ERRORS} consecutive errors: ${errorMsg}`);
838
945
  }
839
- return result.keywayToken;
840
946
  }
841
- throw new Error(result.message || "Authentication failed");
842
947
  }
843
948
  }
844
949
  async function ensureLogin(options = {}) {
@@ -847,7 +952,7 @@ async function ensureLogin(options = {}) {
847
952
  return envToken;
848
953
  }
849
954
  if (process.env.GITHUB_TOKEN && !process.env.KEYWAY_TOKEN) {
850
- console.warn(pc3.yellow("Note: GITHUB_TOKEN found but not used. Set KEYWAY_TOKEN for Keyway authentication."));
955
+ console.warn(pc4.yellow("Note: GITHUB_TOKEN found but not used. Set KEYWAY_TOKEN for Keyway authentication."));
851
956
  }
852
957
  const stored = await getStoredAuth();
853
958
  if (stored?.keywayToken) {
@@ -867,16 +972,13 @@ async function ensureLogin(options = {}) {
867
972
  async function runTokenLogin() {
868
973
  const repoName = detectGitRepo();
869
974
  if (repoName) {
870
- console.log(`\u{1F4C1} Detected: ${pc3.cyan(repoName)}`);
975
+ console.log(`\u{1F4C1} Detected: ${pc4.cyan(repoName)}`);
871
976
  }
872
977
  const description = repoName ? `Keyway CLI for ${repoName}` : "Keyway CLI";
873
978
  const url = `https://github.com/settings/personal-access-tokens/new?description=${encodeURIComponent(description)}`;
874
- console.log("Opening GitHub...");
875
- open(url).catch(() => {
876
- console.log(pc3.gray(`Open this URL in your browser: ${url}`));
877
- });
878
- console.log(pc3.gray("Select the detected repo (or scope manually)."));
879
- console.log(pc3.gray("Permissions: Metadata \u2192 Read-only; Account permissions: None."));
979
+ await openUrl(url);
980
+ console.log(pc4.gray("Select the detected repo (or scope manually)."));
981
+ console.log(pc4.gray("Permissions: Metadata \u2192 Read-only; Account permissions: None."));
880
982
  const { token } = await prompts2(
881
983
  {
882
984
  type: "password",
@@ -913,7 +1015,7 @@ async function runTokenLogin() {
913
1015
  github_username: validation.username,
914
1016
  login_method: "pat"
915
1017
  });
916
- console.log(pc3.green("\u2705 Authenticated"), `as ${pc3.cyan(`@${validation.username}`)}`);
1018
+ console.log(pc4.green("\u2705 Authenticated"), `as ${pc4.cyan(`@${validation.username}`)}`);
917
1019
  return trimmedToken;
918
1020
  }
919
1021
  async function loginCommand(options = {}) {
@@ -929,15 +1031,15 @@ async function loginCommand(options = {}) {
929
1031
  command: "login",
930
1032
  error: truncateMessage(message)
931
1033
  });
932
- console.error(pc3.red(`
1034
+ console.error(pc4.red(`
933
1035
  \u2717 ${message}`));
934
1036
  process.exit(1);
935
1037
  }
936
1038
  }
937
1039
  async function logoutCommand() {
938
1040
  clearAuth();
939
- console.log(pc3.green("\u2713 Logged out of Keyway"));
940
- console.log(pc3.gray(`Auth cache cleared: ${getAuthFilePath()}`));
1041
+ console.log(pc4.green("\u2713 Logged out of Keyway"));
1042
+ console.log(pc4.gray(`Auth cache cleared: ${getAuthFilePath()}`));
941
1043
  }
942
1044
 
943
1045
  // src/cmds/push.ts
@@ -954,7 +1056,7 @@ function discoverEnvCandidates(cwd) {
954
1056
  const entries = fs4.readdirSync(cwd);
955
1057
  const hasEnvLocal = entries.includes(".env.local");
956
1058
  if (hasEnvLocal) {
957
- console.log(pc4.gray("\u2139\uFE0F Detected .env.local \u2014 not synced by design (machine-specific secrets)"));
1059
+ console.log(pc5.gray("\u2139\uFE0F Detected .env.local \u2014 not synced by design (machine-specific secrets)"));
958
1060
  }
959
1061
  const candidates = entries.filter((name) => name.startsWith(".env") && name !== ".env.local").map((name) => {
960
1062
  const fullPath = path4.join(cwd, name);
@@ -980,8 +1082,8 @@ function discoverEnvCandidates(cwd) {
980
1082
  }
981
1083
  async function pushCommand(options) {
982
1084
  try {
983
- console.log(pc4.blue("\u{1F510} Pushing secrets to Keyway...\n"));
984
- const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
1085
+ console.log(pc5.blue("\u{1F510} Pushing secrets to Keyway...\n"));
1086
+ const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
985
1087
  let environment = options.env;
986
1088
  let envFile = options.file;
987
1089
  const candidates = discoverEnvCandidates(process.cwd());
@@ -991,7 +1093,7 @@ async function pushCommand(options) {
991
1093
  envFile = match.file;
992
1094
  }
993
1095
  }
994
- if (!environment && !envFile && isInteractive3 && candidates.length > 0) {
1096
+ if (!environment && !envFile && isInteractive2 && candidates.length > 0) {
995
1097
  const { choice } = await prompts3(
996
1098
  {
997
1099
  type: "select",
@@ -1045,7 +1147,7 @@ async function pushCommand(options) {
1045
1147
  }
1046
1148
  let envFilePath = path4.resolve(process.cwd(), envFile);
1047
1149
  if (!fs4.existsSync(envFilePath)) {
1048
- if (!isInteractive3) {
1150
+ if (!isInteractive2) {
1049
1151
  throw new Error(`File not found: ${envFile}. Provide --file <path> or run interactively to choose a file.`);
1050
1152
  }
1051
1153
  const { newPath } = await prompts3(
@@ -1080,14 +1182,14 @@ async function pushCommand(options) {
1080
1182
  const trimmed = line.trim();
1081
1183
  return trimmed.length > 0 && !trimmed.startsWith("#");
1082
1184
  });
1083
- console.log(`File: ${pc4.cyan(envFile)}`);
1084
- console.log(`Environment: ${pc4.cyan(environment)}`);
1085
- console.log(`Variables: ${pc4.cyan(lines.length.toString())}`);
1185
+ console.log(`File: ${pc5.cyan(envFile)}`);
1186
+ console.log(`Environment: ${pc5.cyan(environment)}`);
1187
+ console.log(`Variables: ${pc5.cyan(lines.length.toString())}`);
1086
1188
  const repoFullName = getCurrentRepoFullName();
1087
- console.log(`Repository: ${pc4.cyan(repoFullName)}`);
1189
+ console.log(`Repository: ${pc5.cyan(repoFullName)}`);
1088
1190
  if (!options.yes) {
1089
- const isInteractive4 = process.stdin.isTTY && process.stdout.isTTY;
1090
- if (!isInteractive4) {
1191
+ const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
1192
+ if (!isInteractive3) {
1091
1193
  throw new Error("Confirmation required. Re-run with --yes in non-interactive environments.");
1092
1194
  }
1093
1195
  const { confirm } = await prompts3(
@@ -1104,7 +1206,7 @@ async function pushCommand(options) {
1104
1206
  }
1105
1207
  );
1106
1208
  if (!confirm) {
1107
- console.log(pc4.yellow("Push aborted."));
1209
+ console.log(pc5.yellow("Push aborted."));
1108
1210
  return;
1109
1211
  }
1110
1212
  }
@@ -1116,20 +1218,20 @@ async function pushCommand(options) {
1116
1218
  });
1117
1219
  console.log("\nUploading secrets...");
1118
1220
  const response = await pushSecrets(repoFullName, environment, content, accessToken);
1119
- console.log(pc4.green("\n\u2713 " + response.message));
1221
+ console.log(pc5.green("\n\u2713 " + response.message));
1120
1222
  if (response.stats) {
1121
1223
  const { created, updated, deleted } = response.stats;
1122
1224
  const parts = [];
1123
- if (created > 0) parts.push(pc4.green(`+${created} created`));
1124
- if (updated > 0) parts.push(pc4.yellow(`~${updated} updated`));
1125
- if (deleted > 0) parts.push(pc4.red(`-${deleted} deleted`));
1225
+ if (created > 0) parts.push(pc5.green(`+${created} created`));
1226
+ if (updated > 0) parts.push(pc5.yellow(`~${updated} updated`));
1227
+ if (deleted > 0) parts.push(pc5.red(`-${deleted} deleted`));
1126
1228
  if (parts.length > 0) {
1127
1229
  console.log(`Stats: ${parts.join(", ")}`);
1128
1230
  }
1129
1231
  }
1130
1232
  console.log(`
1131
1233
  Your secrets are now encrypted and stored securely.`);
1132
- console.log(`To retrieve them, run: ${pc4.cyan(`keyway pull --env ${environment}`)}`);
1234
+ console.log(`To retrieve them, run: ${pc5.cyan(`keyway pull --env ${environment}`)}`);
1133
1235
  await shutdownAnalytics();
1134
1236
  } catch (error) {
1135
1237
  let message;
@@ -1142,13 +1244,18 @@ Your secrets are now encrypted and stored securely.`);
1142
1244
  const availableEnvs = envNotFoundMatch[2];
1143
1245
  message = `Environment '${requestedEnv}' does not exist in this vault.`;
1144
1246
  hint = `Available environments: ${availableEnvs}
1145
- Use ${pc4.cyan(`keyway push --env <environment>`)} to specify one, or create '${requestedEnv}' via the dashboard.`;
1247
+ Use ${pc5.cyan(`keyway push --env <environment>`)} to specify one, or create '${requestedEnv}' via the dashboard.`;
1146
1248
  }
1147
- if (error.statusCode === 403 && error.upgradeUrl) {
1148
- hint = `${pc4.yellow("\u26A1")} Upgrade to Pro: ${pc4.cyan(error.upgradeUrl)}`;
1149
- } else if (error.statusCode === 403 && message.toLowerCase().includes("read-only")) {
1150
- message = "This vault is read-only on your current plan.";
1151
- hint = `Upgrade to Pro to unlock editing: ${pc4.cyan("https://keyway.sh/settings")}`;
1249
+ if (error.statusCode === 403 && (error.upgradeUrl || message.toLowerCase().includes("read-only"))) {
1250
+ const upgradeMessage = message.toLowerCase().includes("read-only") ? "This vault is read-only on your current plan." : message;
1251
+ const upgradeUrl = error.upgradeUrl || "https://keyway.sh/settings";
1252
+ trackEvent(AnalyticsEvents.CLI_ERROR, {
1253
+ command: "push",
1254
+ error: upgradeMessage
1255
+ });
1256
+ await shutdownAnalytics();
1257
+ showUpgradePrompt(upgradeMessage, upgradeUrl);
1258
+ process.exit(1);
1152
1259
  }
1153
1260
  } else if (error instanceof Error) {
1154
1261
  message = truncateMessage(error.message);
@@ -1160,10 +1267,10 @@ Use ${pc4.cyan(`keyway push --env <environment>`)} to specify one, or create '${
1160
1267
  error: message
1161
1268
  });
1162
1269
  await shutdownAnalytics();
1163
- console.error(pc4.red(`
1270
+ console.error(pc5.red(`
1164
1271
  \u2717 ${message}`));
1165
1272
  if (hint) {
1166
- console.error(pc4.gray(`
1273
+ console.error(pc5.gray(`
1167
1274
  ${hint}`));
1168
1275
  }
1169
1276
  process.exit(1);
@@ -1174,12 +1281,6 @@ ${hint}`));
1174
1281
  var DASHBOARD_URL = "https://www.keyway.sh/dashboard/vaults";
1175
1282
  var POLL_INTERVAL_MS = 3e3;
1176
1283
  var POLL_TIMEOUT_MS = 12e4;
1177
- function sleep2(ms) {
1178
- return new Promise((resolve) => setTimeout(resolve, ms));
1179
- }
1180
- function isInteractive2() {
1181
- return Boolean(process.stdout.isTTY && process.stdin.isTTY && !process.env.CI);
1182
- }
1183
1284
  async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
1184
1285
  const [repoOwner, repoName] = repoFullName.split("/");
1185
1286
  const envToken = process.env.KEYWAY_TOKEN;
@@ -1198,12 +1299,12 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
1198
1299
  }
1199
1300
  }
1200
1301
  const allowPrompt = options.allowPrompt !== false;
1201
- if (!allowPrompt || !isInteractive2()) {
1302
+ if (!allowPrompt || !isInteractive()) {
1202
1303
  throw new Error('No Keyway session found. Run "keyway login" to authenticate.');
1203
1304
  }
1204
1305
  console.log("");
1205
- console.log(pc5.gray(" Keyway uses a GitHub App for secure access."));
1206
- console.log(pc5.gray(" Installing the app will also log you in."));
1306
+ console.log(pc6.gray(" Keyway uses a GitHub App for secure access."));
1307
+ console.log(pc6.gray(" Installing the app will also log you in."));
1207
1308
  console.log("");
1208
1309
  const { shouldProceed } = await prompts4({
1209
1310
  type: "confirm",
@@ -1216,16 +1317,15 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
1216
1317
  }
1217
1318
  const deviceStart = await startDeviceLogin(repoFullName);
1218
1319
  const installUrl = deviceStart.githubAppInstallUrl || "https://github.com/apps/keyway/installations/new";
1219
- console.log(pc5.gray("\n Opening browser..."));
1220
- await open2(installUrl);
1221
- console.log("");
1222
- console.log(pc5.blue("\u23F3 Waiting for installation & authorization..."));
1223
- console.log(pc5.gray(" (Press Ctrl+C to cancel)\n"));
1320
+ await openUrl(installUrl);
1321
+ console.log(pc6.blue("\u23F3 Waiting for installation & authorization..."));
1322
+ console.log(pc6.gray(" (Press Ctrl+C to cancel)\n"));
1224
1323
  const pollIntervalMs = Math.max((deviceStart.interval ?? 5) * 1e3, POLL_INTERVAL_MS);
1225
1324
  const startTime = Date.now();
1226
1325
  let accessToken = null;
1326
+ let consecutiveErrors = 0;
1227
1327
  while (Date.now() - startTime < POLL_TIMEOUT_MS) {
1228
- await sleep2(pollIntervalMs);
1328
+ await sleep(pollIntervalMs);
1229
1329
  try {
1230
1330
  if (!accessToken) {
1231
1331
  const result = await pollDeviceLogin(deviceStart.deviceCode);
@@ -1235,7 +1335,7 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
1235
1335
  githubLogin: result.githubLogin,
1236
1336
  expiresAt: result.expiresAt
1237
1337
  });
1238
- console.log(pc5.green("\u2713 Signed in!"));
1338
+ console.log(pc6.green("\u2713 Signed in!"));
1239
1339
  if (result.githubLogin) {
1240
1340
  identifyUser(result.githubLogin, {
1241
1341
  github_username: result.githubLogin,
@@ -1247,18 +1347,24 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
1247
1347
  if (accessToken) {
1248
1348
  const installStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
1249
1349
  if (installStatus.installed) {
1250
- console.log(pc5.green("\u2713 GitHub App installed!"));
1350
+ console.log(pc6.green("\u2713 GitHub App installed!"));
1251
1351
  console.log("");
1252
1352
  return accessToken;
1253
1353
  }
1254
1354
  }
1255
- process.stdout.write(pc5.gray("."));
1256
- } catch {
1355
+ consecutiveErrors = 0;
1356
+ process.stdout.write(pc6.gray("."));
1357
+ } catch (error) {
1358
+ consecutiveErrors++;
1359
+ if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
1360
+ const errorMsg = error instanceof Error ? error.message : "Unknown error";
1361
+ throw new Error(`Setup failed after ${MAX_CONSECUTIVE_ERRORS} consecutive errors: ${errorMsg}`);
1362
+ }
1257
1363
  }
1258
1364
  }
1259
1365
  console.log("");
1260
- console.log(pc5.yellow("\u26A0 Timed out waiting for setup."));
1261
- console.log(pc5.gray(` Install the GitHub App: ${installUrl}`));
1366
+ console.log(pc6.yellow("\u26A0 Timed out waiting for setup."));
1367
+ console.log(pc6.gray(` Install the GitHub App: ${installUrl}`));
1262
1368
  throw new Error("Setup timed out. Please try again.");
1263
1369
  }
1264
1370
  async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
@@ -1268,7 +1374,7 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
1268
1374
  status = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
1269
1375
  } catch (error) {
1270
1376
  if (error instanceof APIError && error.statusCode === 401) {
1271
- console.log(pc5.yellow("\n\u26A0 Session expired or invalid. Clearing credentials..."));
1377
+ console.log(pc6.yellow("\n\u26A0 Session expired or invalid. Clearing credentials..."));
1272
1378
  const { clearAuth: clearAuth2 } = await import("./auth-QLPQ24HZ.js");
1273
1379
  clearAuth2();
1274
1380
  return null;
@@ -1279,13 +1385,13 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
1279
1385
  return accessToken;
1280
1386
  }
1281
1387
  console.log("");
1282
- console.log(pc5.yellow("\u26A0 GitHub App not installed for this repository"));
1388
+ console.log(pc6.yellow("\u26A0 GitHub App not installed for this repository"));
1283
1389
  console.log("");
1284
- console.log(pc5.gray(" The Keyway GitHub App is required to securely manage secrets."));
1285
- console.log(pc5.gray(" It only requests minimal permissions (repository metadata)."));
1390
+ console.log(pc6.gray(" The Keyway GitHub App is required to securely manage secrets."));
1391
+ console.log(pc6.gray(" It only requests minimal permissions (repository metadata)."));
1286
1392
  console.log("");
1287
- if (!isInteractive2()) {
1288
- console.log(pc5.gray(` Install the Keyway GitHub App: ${status.installUrl}`));
1393
+ if (!isInteractive()) {
1394
+ console.log(pc6.gray(` Install the Keyway GitHub App: ${status.installUrl}`));
1289
1395
  throw new Error("GitHub App installation required.");
1290
1396
  }
1291
1397
  const { shouldInstall } = await prompts4({
@@ -1295,67 +1401,72 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
1295
1401
  initial: true
1296
1402
  });
1297
1403
  if (!shouldInstall) {
1298
- console.log(pc5.gray(`
1404
+ console.log(pc6.gray(`
1299
1405
  You can install later: ${status.installUrl}`));
1300
1406
  throw new Error("GitHub App installation required.");
1301
1407
  }
1302
- console.log(pc5.gray("\n Opening browser..."));
1303
- await open2(status.installUrl);
1304
- console.log("");
1305
- console.log(pc5.blue("\u23F3 Waiting for GitHub App installation..."));
1306
- console.log(pc5.gray(" (Press Ctrl+C to cancel)\n"));
1408
+ await openUrl(status.installUrl);
1409
+ console.log(pc6.blue("\u23F3 Waiting for GitHub App installation..."));
1410
+ console.log(pc6.gray(" (Press Ctrl+C to cancel)\n"));
1307
1411
  const startTime = Date.now();
1412
+ let consecutiveErrors = 0;
1308
1413
  while (Date.now() - startTime < POLL_TIMEOUT_MS) {
1309
- await sleep2(POLL_INTERVAL_MS);
1414
+ await sleep(POLL_INTERVAL_MS);
1310
1415
  try {
1311
1416
  const pollStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
1312
1417
  if (pollStatus.installed) {
1313
- console.log(pc5.green("\u2713 GitHub App installed!"));
1418
+ console.log(pc6.green("\u2713 GitHub App installed!"));
1314
1419
  console.log("");
1315
1420
  return accessToken;
1316
1421
  }
1317
- process.stdout.write(pc5.gray("."));
1318
- } catch {
1422
+ consecutiveErrors = 0;
1423
+ process.stdout.write(pc6.gray("."));
1424
+ } catch (error) {
1425
+ consecutiveErrors++;
1426
+ if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
1427
+ const errorMsg = error instanceof Error ? error.message : "Unknown error";
1428
+ throw new Error(`Installation check failed after ${MAX_CONSECUTIVE_ERRORS} consecutive errors: ${errorMsg}`);
1429
+ }
1319
1430
  }
1320
1431
  }
1321
1432
  console.log("");
1322
- console.log(pc5.yellow("\u26A0 Timed out waiting for installation."));
1323
- console.log(pc5.gray(` You can install the GitHub App later: ${status.installUrl}`));
1433
+ console.log(pc6.yellow("\u26A0 Timed out waiting for installation."));
1434
+ console.log(pc6.gray(` You can install the GitHub App later: ${status.installUrl}`));
1324
1435
  throw new Error("GitHub App installation timed out.");
1325
1436
  }
1326
1437
  async function initCommand(options = {}) {
1327
1438
  try {
1328
1439
  const repoFullName = getCurrentRepoFullName();
1329
1440
  const dashboardLink = `${DASHBOARD_URL}/${repoFullName}`;
1330
- console.log(pc5.blue("\u{1F510} Initializing Keyway vault...\n"));
1331
- console.log(` ${pc5.gray("Repository:")} ${pc5.white(repoFullName)}`);
1441
+ console.log(pc6.blue("\u{1F510} Initializing Keyway vault...\n"));
1442
+ console.log(` ${pc6.gray("Repository:")} ${pc6.white(repoFullName)}`);
1332
1443
  const accessToken = await ensureLoginAndGitHubApp(repoFullName, {
1333
1444
  allowPrompt: options.loginPrompt !== false
1334
1445
  });
1335
1446
  trackEvent(AnalyticsEvents.CLI_INIT, { repoFullName, githubAppInstalled: true });
1336
1447
  const vaultExists = await checkVaultExists(accessToken, repoFullName);
1337
1448
  if (vaultExists) {
1338
- console.log(pc5.green("\n\u2713 Already initialized!\n"));
1339
- console.log(` ${pc5.yellow("\u2192")} Run ${pc5.cyan("keyway push")} to sync your secrets`);
1340
- console.log(` ${pc5.blue("\u2394")} Dashboard: ${pc5.underline(dashboardLink)}`);
1449
+ console.log(pc6.green("\n\u2713 Already initialized!\n"));
1450
+ console.log(` ${pc6.yellow("\u2192")} Run ${pc6.cyan("keyway push")} to sync your secrets`);
1451
+ console.log(` ${pc6.blue("\u2394")} Dashboard: ${pc6.underline(dashboardLink)}`);
1341
1452
  console.log("");
1342
1453
  await shutdownAnalytics();
1343
1454
  return;
1344
1455
  }
1345
1456
  await initVault(repoFullName, accessToken);
1346
- console.log(pc5.green("\u2713 Vault created!"));
1457
+ console.log(pc6.green("\u2713 Vault created!"));
1347
1458
  try {
1348
1459
  const badgeAdded = await addBadgeToReadme(true);
1349
1460
  if (badgeAdded) {
1350
- console.log(pc5.green("\u2713 Badge added to README.md"));
1461
+ console.log(pc6.green("\u2713 Badge added to README.md"));
1351
1462
  }
1352
1463
  } catch {
1353
1464
  }
1354
1465
  console.log("");
1355
1466
  const envCandidates = discoverEnvCandidates(process.cwd());
1356
- const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
1357
- if (envCandidates.length > 0 && isInteractive3) {
1358
- console.log(pc5.gray(` Found ${envCandidates.length} env file(s): ${envCandidates.map((c) => c.file).join(", ")}
1467
+ const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
1468
+ if (envCandidates.length > 0 && isInteractive2) {
1469
+ console.log(pc6.gray(` Found ${envCandidates.length} env file(s): ${envCandidates.map((c) => c.file).join(", ")}
1359
1470
  `));
1360
1471
  const { shouldPush } = await prompts4({
1361
1472
  type: "confirm",
@@ -1369,42 +1480,32 @@ async function initCommand(options = {}) {
1369
1480
  return;
1370
1481
  }
1371
1482
  }
1372
- console.log(pc5.dim("\u2500".repeat(50)));
1483
+ console.log(pc6.dim("\u2500".repeat(50)));
1373
1484
  console.log("");
1374
1485
  if (envCandidates.length === 0) {
1375
- console.log(` ${pc5.yellow("\u2192")} Create a ${pc5.cyan(".env")} file with your secrets`);
1376
- console.log(` ${pc5.yellow("\u2192")} Run ${pc5.cyan("keyway push")} to sync them
1486
+ console.log(`${pc6.yellow("\u26A0")} No .env file found - your vault is empty`);
1487
+ console.log(` Next: Create ${pc6.cyan(".env")} and run ${pc6.cyan("keyway push")}
1377
1488
  `);
1378
1489
  } else {
1379
- console.log(` ${pc5.yellow("\u2192")} Run ${pc5.cyan("keyway push")} to sync your secrets
1490
+ console.log(` ${pc6.yellow("\u2192")} Run ${pc6.cyan("keyway push")} to sync your secrets
1380
1491
  `);
1381
1492
  }
1382
- console.log(` ${pc5.blue("\u2394")} Dashboard: ${pc5.underline(dashboardLink)}`);
1493
+ console.log(` ${pc6.blue("\u2394")} Dashboard: ${pc6.underline(dashboardLink)}`);
1383
1494
  console.log("");
1384
1495
  await shutdownAnalytics();
1385
1496
  } catch (error) {
1386
1497
  if (error instanceof APIError) {
1387
1498
  if (error.statusCode === 409) {
1388
- console.log(pc5.green("\n\u2713 Already initialized!\n"));
1389
- console.log(` ${pc5.yellow("\u2192")} Run ${pc5.cyan("keyway push")} to sync your secrets`);
1390
- console.log(` ${pc5.blue("\u2394")} Dashboard: ${pc5.underline(`${DASHBOARD_URL}/${getCurrentRepoFullName()}`)}`);
1499
+ console.log(pc6.green("\n\u2713 Already initialized!\n"));
1500
+ console.log(` ${pc6.yellow("\u2192")} Run ${pc6.cyan("keyway push")} to sync your secrets`);
1501
+ console.log(` ${pc6.blue("\u2394")} Dashboard: ${pc6.underline(`${DASHBOARD_URL}/${getCurrentRepoFullName()}`)}`);
1391
1502
  console.log("");
1392
1503
  await shutdownAnalytics();
1393
1504
  return;
1394
1505
  }
1395
1506
  if (error.error === "Plan Limit Reached" || error.upgradeUrl) {
1396
1507
  const upgradeUrl = error.upgradeUrl || "https://keyway.sh/pricing";
1397
- console.log("");
1398
- console.log(pc5.dim("\u2500".repeat(50)));
1399
- console.log("");
1400
- console.log(` ${pc5.yellow("\u26A1")} ${pc5.bold("Plan Limit Reached")}`);
1401
- console.log("");
1402
- console.log(pc5.white(` ${error.message}`));
1403
- console.log("");
1404
- console.log(` ${pc5.cyan("Upgrade now \u2192")} ${pc5.underline(upgradeUrl)}`);
1405
- console.log("");
1406
- console.log(pc5.dim("\u2500".repeat(50)));
1407
- console.log("");
1508
+ showUpgradePrompt(error.message, upgradeUrl);
1408
1509
  await shutdownAnalytics();
1409
1510
  process.exit(1);
1410
1511
  }
@@ -1415,14 +1516,14 @@ async function initCommand(options = {}) {
1415
1516
  error: message
1416
1517
  });
1417
1518
  await shutdownAnalytics();
1418
- console.error(pc5.red(`
1519
+ console.error(pc6.red(`
1419
1520
  \u2717 ${message}`));
1420
1521
  process.exit(1);
1421
1522
  }
1422
1523
  }
1423
1524
 
1424
1525
  // src/cmds/pull.ts
1425
- import pc6 from "picocolors";
1526
+ import pc7 from "picocolors";
1426
1527
  import fs5 from "fs";
1427
1528
  import path5 from "path";
1428
1529
  import prompts5 from "prompts";
@@ -1430,10 +1531,10 @@ async function pullCommand(options) {
1430
1531
  try {
1431
1532
  const environment = options.env || "development";
1432
1533
  const envFile = options.file || ".env";
1433
- console.log(pc6.blue("\u{1F510} Pulling secrets from Keyway...\n"));
1434
- console.log(`Environment: ${pc6.cyan(environment)}`);
1534
+ console.log(pc7.blue("\u{1F510} Pulling secrets from Keyway...\n"));
1535
+ console.log(`Environment: ${pc7.cyan(environment)}`);
1435
1536
  const repoFullName = getCurrentRepoFullName();
1436
- console.log(`Repository: ${pc6.cyan(repoFullName)}`);
1537
+ console.log(`Repository: ${pc7.cyan(repoFullName)}`);
1437
1538
  const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
1438
1539
  trackEvent(AnalyticsEvents.CLI_PULL, {
1439
1540
  repoFullName,
@@ -1443,11 +1544,11 @@ async function pullCommand(options) {
1443
1544
  const response = await pullSecrets(repoFullName, environment, accessToken);
1444
1545
  const envFilePath = path5.resolve(process.cwd(), envFile);
1445
1546
  if (fs5.existsSync(envFilePath)) {
1446
- const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
1547
+ const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
1447
1548
  if (options.yes) {
1448
- console.log(pc6.yellow(`
1549
+ console.log(pc7.yellow(`
1449
1550
  \u26A0 Overwriting existing file: ${envFile}`));
1450
- } else if (!isInteractive3) {
1551
+ } else if (!isInteractive2) {
1451
1552
  throw new Error(`File ${envFile} exists. Re-run with --yes to overwrite or choose a different --file.`);
1452
1553
  } else {
1453
1554
  const { confirm } = await prompts5(
@@ -1464,7 +1565,7 @@ async function pullCommand(options) {
1464
1565
  }
1465
1566
  );
1466
1567
  if (!confirm) {
1467
- console.log(pc6.yellow("Pull aborted."));
1568
+ console.log(pc7.yellow("Pull aborted."));
1468
1569
  return;
1469
1570
  }
1470
1571
  }
@@ -1474,11 +1575,11 @@ async function pullCommand(options) {
1474
1575
  const trimmed = line.trim();
1475
1576
  return trimmed.length > 0 && !trimmed.startsWith("#");
1476
1577
  });
1477
- console.log(pc6.green(`
1578
+ console.log(pc7.green(`
1478
1579
  \u2713 Secrets downloaded successfully`));
1479
1580
  console.log(`
1480
- File: ${pc6.cyan(envFile)}`);
1481
- console.log(`Variables: ${pc6.cyan(lines.length.toString())}`);
1581
+ File: ${pc7.cyan(envFile)}`);
1582
+ console.log(`Variables: ${pc7.cyan(lines.length.toString())}`);
1482
1583
  await shutdownAnalytics();
1483
1584
  } catch (error) {
1484
1585
  const message = error instanceof APIError ? `API ${error.statusCode}: ${error.message}` : error instanceof Error ? truncateMessage(error.message) : "Unknown error";
@@ -1487,14 +1588,14 @@ File: ${pc6.cyan(envFile)}`);
1487
1588
  error: message
1488
1589
  });
1489
1590
  await shutdownAnalytics();
1490
- console.error(pc6.red(`
1591
+ console.error(pc7.red(`
1491
1592
  \u2717 ${message}`));
1492
1593
  process.exit(1);
1493
1594
  }
1494
1595
  }
1495
1596
 
1496
1597
  // src/cmds/doctor.ts
1497
- import pc7 from "picocolors";
1598
+ import pc8 from "picocolors";
1498
1599
 
1499
1600
  // src/core/doctor.ts
1500
1601
  import { execSync as execSync2 } from "child_process";
@@ -1748,9 +1849,9 @@ async function runAllChecks(options = {}) {
1748
1849
  // src/cmds/doctor.ts
1749
1850
  function formatSummary(results) {
1750
1851
  const parts = [
1751
- pc7.green(`${results.summary.pass} passed`),
1752
- results.summary.warn > 0 ? pc7.yellow(`${results.summary.warn} warnings`) : null,
1753
- results.summary.fail > 0 ? pc7.red(`${results.summary.fail} failed`) : null
1852
+ pc8.green(`${results.summary.pass} passed`),
1853
+ results.summary.warn > 0 ? pc8.yellow(`${results.summary.warn} warnings`) : null,
1854
+ results.summary.fail > 0 ? pc8.red(`${results.summary.fail} failed`) : null
1754
1855
  ].filter(Boolean);
1755
1856
  return parts.join(", ");
1756
1857
  }
@@ -1767,20 +1868,20 @@ async function doctorCommand(options = {}) {
1767
1868
  process.stdout.write(JSON.stringify(results, null, 0) + "\n");
1768
1869
  process.exit(results.exitCode);
1769
1870
  }
1770
- console.log(pc7.cyan("\n\u{1F50D} Keyway Doctor - Environment Check\n"));
1871
+ console.log(pc8.cyan("\n\u{1F50D} Keyway Doctor - Environment Check\n"));
1771
1872
  results.checks.forEach((check) => {
1772
- const icon = check.status === "pass" ? pc7.green("\u2713") : check.status === "warn" ? pc7.yellow("!") : pc7.red("\u2717");
1773
- const detail = check.detail ? pc7.dim(` \u2014 ${check.detail}`) : "";
1873
+ const icon = check.status === "pass" ? pc8.green("\u2713") : check.status === "warn" ? pc8.yellow("!") : pc8.red("\u2717");
1874
+ const detail = check.detail ? pc8.dim(` \u2014 ${check.detail}`) : "";
1774
1875
  console.log(` ${icon} ${check.name}${detail}`);
1775
1876
  });
1776
1877
  console.log(`
1777
1878
  Summary: ${formatSummary(results)}`);
1778
1879
  if (results.summary.fail > 0) {
1779
- console.log(pc7.red("\u26A0 Some checks failed. Please resolve the issues above before using Keyway."));
1880
+ console.log(pc8.red("\u26A0 Some checks failed. Please resolve the issues above before using Keyway."));
1780
1881
  } else if (results.summary.warn > 0) {
1781
- console.log(pc7.yellow("\u26A0 Some warnings detected. Keyway should work but consider addressing them."));
1882
+ console.log(pc8.yellow("\u26A0 Some warnings detected. Keyway should work but consider addressing them."));
1782
1883
  } else {
1783
- console.log(pc7.green("\u2728 All checks passed! Your environment is ready for Keyway."));
1884
+ console.log(pc8.green("\u2728 All checks passed! Your environment is ready for Keyway."));
1784
1885
  }
1785
1886
  process.exit(results.exitCode);
1786
1887
  } catch (error) {
@@ -1801,7 +1902,7 @@ Summary: ${formatSummary(results)}`);
1801
1902
  };
1802
1903
  process.stdout.write(JSON.stringify(errorResult, null, 0) + "\n");
1803
1904
  } else {
1804
- console.error(pc7.red(`
1905
+ console.error(pc8.red(`
1805
1906
  \u2717 ${message}`));
1806
1907
  }
1807
1908
  process.exit(1);
@@ -1809,9 +1910,82 @@ Summary: ${formatSummary(results)}`);
1809
1910
  }
1810
1911
 
1811
1912
  // src/cmds/connect.ts
1812
- import pc8 from "picocolors";
1813
- import open3 from "open";
1913
+ import pc9 from "picocolors";
1814
1914
  import prompts6 from "prompts";
1915
+ var TOKEN_AUTH_PROVIDERS = ["railway"];
1916
+ function getTokenCreationUrl(provider) {
1917
+ switch (provider) {
1918
+ case "railway":
1919
+ return "https://railway.com/account/tokens";
1920
+ default:
1921
+ return "";
1922
+ }
1923
+ }
1924
+ async function connectWithTokenFlow(accessToken, provider, displayName) {
1925
+ const tokenUrl = getTokenCreationUrl(provider);
1926
+ if (provider === "railway") {
1927
+ console.log(pc9.yellow("\nTip: Select the workspace containing your projects."));
1928
+ console.log(pc9.yellow(` Do NOT use "No workspace" - it won't have access to your projects.`));
1929
+ }
1930
+ await openUrl(tokenUrl);
1931
+ const { token } = await prompts6({
1932
+ type: "password",
1933
+ name: "token",
1934
+ message: `${displayName} API Token:`
1935
+ });
1936
+ if (!token) {
1937
+ console.log(pc9.gray("Cancelled."));
1938
+ return false;
1939
+ }
1940
+ console.log(pc9.gray("\nValidating token..."));
1941
+ try {
1942
+ const result = await connectWithToken(accessToken, provider, token);
1943
+ if (result.success) {
1944
+ console.log(pc9.green(`
1945
+ \u2713 Connected to ${displayName}!`));
1946
+ console.log(pc9.gray(` Account: ${result.user.username}`));
1947
+ if (result.user.teamName) {
1948
+ console.log(pc9.gray(` Team: ${result.user.teamName}`));
1949
+ }
1950
+ return true;
1951
+ } else {
1952
+ console.log(pc9.red("\n\u2717 Connection failed."));
1953
+ return false;
1954
+ }
1955
+ } catch (error) {
1956
+ const message = error instanceof Error ? error.message : "Token validation failed";
1957
+ console.log(pc9.red(`
1958
+ \u2717 ${message}`));
1959
+ return false;
1960
+ }
1961
+ }
1962
+ async function connectWithOAuthFlow(accessToken, provider, displayName) {
1963
+ const authUrl = getProviderAuthUrl(provider);
1964
+ const startTime = /* @__PURE__ */ new Date();
1965
+ await openUrl(authUrl);
1966
+ console.log(pc9.gray("Waiting for authorization..."));
1967
+ const maxAttempts = 60;
1968
+ let attempts = 0;
1969
+ while (attempts < maxAttempts) {
1970
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
1971
+ attempts++;
1972
+ try {
1973
+ const { connections } = await getConnections(accessToken);
1974
+ const newConn = connections.find(
1975
+ (c) => c.provider === provider && new Date(c.createdAt) > startTime
1976
+ );
1977
+ if (newConn) {
1978
+ console.log(pc9.green(`
1979
+ \u2713 Connected to ${displayName}!`));
1980
+ return true;
1981
+ }
1982
+ } catch {
1983
+ }
1984
+ }
1985
+ console.log(pc9.red("\n\u2717 Authorization timeout."));
1986
+ console.log(pc9.gray("Run `keyway connections` to check if the connection was established."));
1987
+ return false;
1988
+ }
1815
1989
  async function connectCommand(provider, options = {}) {
1816
1990
  try {
1817
1991
  const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
@@ -1819,13 +1993,13 @@ async function connectCommand(provider, options = {}) {
1819
1993
  const providerInfo = providers.find((p) => p.name === provider.toLowerCase());
1820
1994
  if (!providerInfo) {
1821
1995
  const available = providers.map((p) => p.name).join(", ");
1822
- console.error(pc8.red(`Unknown provider: ${provider}`));
1823
- console.log(pc8.gray(`Available providers: ${available || "none"}`));
1996
+ console.error(pc9.red(`Unknown provider: ${provider}`));
1997
+ console.log(pc9.gray(`Available providers: ${available || "none"}`));
1824
1998
  process.exit(1);
1825
1999
  }
1826
2000
  if (!providerInfo.configured) {
1827
- console.error(pc8.red(`Provider ${providerInfo.displayName} is not configured on the server.`));
1828
- console.log(pc8.gray("Contact your administrator to enable this integration."));
2001
+ console.error(pc9.red(`Provider ${providerInfo.displayName} is not configured on the server.`));
2002
+ console.log(pc9.gray("Contact your administrator to enable this integration."));
1829
2003
  process.exit(1);
1830
2004
  }
1831
2005
  const { connections } = await getConnections(accessToken);
@@ -1838,43 +2012,18 @@ async function connectCommand(provider, options = {}) {
1838
2012
  initial: false
1839
2013
  });
1840
2014
  if (!reconnect) {
1841
- console.log(pc8.gray("Keeping existing connection."));
2015
+ console.log(pc9.gray("Keeping existing connection."));
1842
2016
  return;
1843
2017
  }
1844
2018
  }
1845
- console.log(pc8.blue(`
2019
+ console.log(pc9.blue(`
1846
2020
  Connecting to ${providerInfo.displayName}...
1847
2021
  `));
1848
- const authUrl = getProviderAuthUrl(provider.toLowerCase());
1849
- const startTime = /* @__PURE__ */ new Date();
1850
- console.log(pc8.gray("Opening browser for authorization..."));
1851
- console.log(pc8.gray(`If the browser doesn't open, visit: ${authUrl}`));
1852
- await open3(authUrl).catch(() => {
1853
- });
1854
- console.log(pc8.gray("Waiting for authorization..."));
1855
- const maxAttempts = 60;
1856
- let attempts = 0;
1857
2022
  let connected = false;
1858
- while (attempts < maxAttempts) {
1859
- await new Promise((resolve) => setTimeout(resolve, 5e3));
1860
- attempts++;
1861
- try {
1862
- const { connections: connections2 } = await getConnections(accessToken);
1863
- const newConn = connections2.find(
1864
- (c) => c.provider === provider.toLowerCase() && new Date(c.createdAt) > startTime
1865
- );
1866
- if (newConn) {
1867
- connected = true;
1868
- console.log(pc8.green(`
1869
- \u2713 Connected to ${providerInfo.displayName}!`));
1870
- break;
1871
- }
1872
- } catch {
1873
- }
1874
- }
1875
- if (!connected) {
1876
- console.log(pc8.red("\n\u2717 Authorization timeout."));
1877
- console.log(pc8.gray("Run `keyway connections` to check if the connection was established."));
2023
+ if (TOKEN_AUTH_PROVIDERS.includes(provider.toLowerCase())) {
2024
+ connected = await connectWithTokenFlow(accessToken, provider.toLowerCase(), providerInfo.displayName);
2025
+ } else {
2026
+ connected = await connectWithOAuthFlow(accessToken, provider.toLowerCase(), providerInfo.displayName);
1878
2027
  }
1879
2028
  trackEvent(AnalyticsEvents.CLI_CONNECT, {
1880
2029
  provider: provider.toLowerCase(),
@@ -1886,7 +2035,7 @@ Connecting to ${providerInfo.displayName}...
1886
2035
  command: "connect",
1887
2036
  error: truncateMessage(message)
1888
2037
  });
1889
- console.error(pc8.red(`
2038
+ console.error(pc9.red(`
1890
2039
  \u2717 ${message}`));
1891
2040
  process.exit(1);
1892
2041
  }
@@ -1896,24 +2045,24 @@ async function connectionsCommand(options = {}) {
1896
2045
  const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
1897
2046
  const { connections } = await getConnections(accessToken);
1898
2047
  if (connections.length === 0) {
1899
- console.log(pc8.gray("No provider connections found."));
1900
- console.log(pc8.gray("\nConnect to a provider with: keyway connect <provider>"));
1901
- console.log(pc8.gray("Available providers: vercel"));
2048
+ console.log(pc9.gray("No provider connections found."));
2049
+ console.log(pc9.gray("\nConnect to a provider with: keyway connect <provider>"));
2050
+ console.log(pc9.gray("Available providers: vercel, railway"));
1902
2051
  return;
1903
2052
  }
1904
- console.log(pc8.blue("\n\u{1F4E1} Provider Connections\n"));
2053
+ console.log(pc9.blue("\n\u{1F4E1} Provider Connections\n"));
1905
2054
  for (const conn of connections) {
1906
2055
  const providerName = conn.provider.charAt(0).toUpperCase() + conn.provider.slice(1);
1907
- const teamInfo = conn.providerTeamId ? pc8.gray(` (Team: ${conn.providerTeamId})`) : "";
2056
+ const teamInfo = conn.providerTeamId ? pc9.gray(` (Team: ${conn.providerTeamId})`) : "";
1908
2057
  const date = new Date(conn.createdAt).toLocaleDateString();
1909
- console.log(` ${pc8.green("\u25CF")} ${pc8.bold(providerName)}${teamInfo}`);
1910
- console.log(pc8.gray(` Connected: ${date}`));
1911
- console.log(pc8.gray(` ID: ${conn.id}`));
2058
+ console.log(` ${pc9.green("\u25CF")} ${pc9.bold(providerName)}${teamInfo}`);
2059
+ console.log(pc9.gray(` Connected: ${date}`));
2060
+ console.log(pc9.gray(` ID: ${conn.id}`));
1912
2061
  console.log("");
1913
2062
  }
1914
2063
  } catch (error) {
1915
2064
  const message = error instanceof Error ? error.message : "Failed to list connections";
1916
- console.error(pc8.red(`
2065
+ console.error(pc9.red(`
1917
2066
  \u2717 ${message}`));
1918
2067
  process.exit(1);
1919
2068
  }
@@ -1924,7 +2073,7 @@ async function disconnectCommand(provider, options = {}) {
1924
2073
  const { connections } = await getConnections(accessToken);
1925
2074
  const connection = connections.find((c) => c.provider === provider.toLowerCase());
1926
2075
  if (!connection) {
1927
- console.log(pc8.gray(`No connection found for provider: ${provider}`));
2076
+ console.log(pc9.gray(`No connection found for provider: ${provider}`));
1928
2077
  return;
1929
2078
  }
1930
2079
  const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
@@ -1935,11 +2084,11 @@ async function disconnectCommand(provider, options = {}) {
1935
2084
  initial: false
1936
2085
  });
1937
2086
  if (!confirm) {
1938
- console.log(pc8.gray("Cancelled."));
2087
+ console.log(pc9.gray("Cancelled."));
1939
2088
  return;
1940
2089
  }
1941
2090
  await deleteConnection(accessToken, connection.id);
1942
- console.log(pc8.green(`
2091
+ console.log(pc9.green(`
1943
2092
  \u2713 Disconnected from ${providerName}`));
1944
2093
  trackEvent(AnalyticsEvents.CLI_DISCONNECT, {
1945
2094
  provider: provider.toLowerCase()
@@ -1950,14 +2099,14 @@ async function disconnectCommand(provider, options = {}) {
1950
2099
  command: "disconnect",
1951
2100
  error: truncateMessage(message)
1952
2101
  });
1953
- console.error(pc8.red(`
2102
+ console.error(pc9.red(`
1954
2103
  \u2717 ${message}`));
1955
2104
  process.exit(1);
1956
2105
  }
1957
2106
  }
1958
2107
 
1959
2108
  // src/cmds/sync.ts
1960
- import pc9 from "picocolors";
2109
+ import pc10 from "picocolors";
1961
2110
  import prompts7 from "prompts";
1962
2111
  function mapToVercelEnvironment(keywayEnv) {
1963
2112
  const mapping = {
@@ -1968,6 +2117,25 @@ function mapToVercelEnvironment(keywayEnv) {
1968
2117
  };
1969
2118
  return mapping[keywayEnv.toLowerCase()] || "production";
1970
2119
  }
2120
+ function mapToRailwayEnvironment(keywayEnv) {
2121
+ const mapping = {
2122
+ production: "production",
2123
+ staging: "staging",
2124
+ dev: "development",
2125
+ development: "development"
2126
+ };
2127
+ return mapping[keywayEnv.toLowerCase()] || "production";
2128
+ }
2129
+ function mapToProviderEnvironment(provider, keywayEnv) {
2130
+ switch (provider.toLowerCase()) {
2131
+ case "vercel":
2132
+ return mapToVercelEnvironment(keywayEnv);
2133
+ case "railway":
2134
+ return mapToRailwayEnvironment(keywayEnv);
2135
+ default:
2136
+ return keywayEnv;
2137
+ }
2138
+ }
1971
2139
  function findMatchingProject(projects, repoFullName) {
1972
2140
  const repoFullNameLower = repoFullName.toLowerCase();
1973
2141
  const repoName = repoFullName.split("/")[1]?.toLowerCase();
@@ -2007,11 +2175,11 @@ async function promptProjectSelection(projects, repoFullName) {
2007
2175
  let title = p.name;
2008
2176
  const badges = [];
2009
2177
  if (p.linkedRepo?.toLowerCase() === repoFullName.toLowerCase()) {
2010
- badges.push(pc9.green("\u2190 linked"));
2178
+ badges.push(pc10.green("\u2190 linked"));
2011
2179
  } else if (p.name.toLowerCase() === repoName) {
2012
- badges.push(pc9.green("\u2190 same name"));
2180
+ badges.push(pc10.green("\u2190 same name"));
2013
2181
  } else if (p.linkedRepo) {
2014
- badges.push(pc9.gray(`\u2192 ${p.linkedRepo}`));
2182
+ badges.push(pc10.gray(`\u2192 ${p.linkedRepo}`));
2015
2183
  }
2016
2184
  if (badges.length > 0) {
2017
2185
  title = `${p.name} ${badges.join(" ")}`;
@@ -2025,7 +2193,7 @@ async function promptProjectSelection(projects, repoFullName) {
2025
2193
  choices
2026
2194
  });
2027
2195
  if (!projectChoice) {
2028
- console.log(pc9.gray("Cancelled."));
2196
+ console.log(pc10.gray("Cancelled."));
2029
2197
  process.exit(0);
2030
2198
  }
2031
2199
  return projects.find((p) => p.id === projectChoice);
@@ -2033,28 +2201,48 @@ async function promptProjectSelection(projects, repoFullName) {
2033
2201
  async function syncCommand(provider, options = {}) {
2034
2202
  try {
2035
2203
  if (options.pull && options.allowDelete) {
2036
- console.error(pc9.red("Error: --allow-delete cannot be used with --pull"));
2037
- console.log(pc9.gray("The --allow-delete flag is only for push operations."));
2204
+ console.error(pc10.red("Error: --allow-delete cannot be used with --pull"));
2205
+ console.log(pc10.gray("The --allow-delete flag is only for push operations."));
2038
2206
  process.exit(1);
2039
2207
  }
2040
2208
  const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
2041
2209
  const repoFullName = detectGitRepo();
2042
2210
  if (!repoFullName) {
2043
- console.error(pc9.red("Could not detect Git repository."));
2044
- console.log(pc9.gray("Run this command from a Git repository directory."));
2211
+ console.error(pc10.red("Could not detect Git repository."));
2212
+ console.log(pc10.gray("Run this command from a Git repository directory."));
2045
2213
  process.exit(1);
2046
2214
  }
2047
- console.log(pc9.gray(`Repository: ${repoFullName}`));
2048
- const { connections } = await getConnections(accessToken);
2049
- const connection = connections.find((c) => c.provider === provider.toLowerCase());
2215
+ console.log(pc10.gray(`Repository: ${repoFullName}`));
2216
+ let { connections } = await getConnections(accessToken);
2217
+ let connection = connections.find((c) => c.provider === provider.toLowerCase());
2050
2218
  if (!connection) {
2051
- console.error(pc9.red(`Not connected to ${provider}.`));
2052
- console.log(pc9.gray(`Run: keyway connect ${provider}`));
2053
- process.exit(1);
2219
+ const providerDisplayName = provider.charAt(0).toUpperCase() + provider.slice(1);
2220
+ console.log(pc10.yellow(`
2221
+ Not connected to ${providerDisplayName}.`));
2222
+ const { shouldConnect } = await prompts7({
2223
+ type: "confirm",
2224
+ name: "shouldConnect",
2225
+ message: `Connect to ${providerDisplayName} now?`,
2226
+ initial: true
2227
+ });
2228
+ if (!shouldConnect) {
2229
+ console.log(pc10.gray("Cancelled."));
2230
+ process.exit(0);
2231
+ }
2232
+ await connectCommand(provider, { loginPrompt: false });
2233
+ const refreshed = await getConnections(accessToken);
2234
+ connections = refreshed.connections;
2235
+ connection = connections.find((c) => c.provider === provider.toLowerCase());
2236
+ if (!connection) {
2237
+ console.error(pc10.red(`
2238
+ Connection to ${providerDisplayName} failed.`));
2239
+ process.exit(1);
2240
+ }
2241
+ console.log("");
2054
2242
  }
2055
2243
  const { projects } = await getConnectionProjects(accessToken, connection.id);
2056
2244
  if (projects.length === 0) {
2057
- console.error(pc9.red(`No projects found in your ${provider} account.`));
2245
+ console.error(pc10.red(`No projects found in your ${provider} account.`));
2058
2246
  process.exit(1);
2059
2247
  }
2060
2248
  let selectedProject;
@@ -2063,21 +2251,21 @@ async function syncCommand(provider, options = {}) {
2063
2251
  (p) => p.id === options.project || p.name.toLowerCase() === options.project?.toLowerCase()
2064
2252
  );
2065
2253
  if (!found) {
2066
- console.error(pc9.red(`Project not found: ${options.project}`));
2067
- console.log(pc9.gray("Available projects:"));
2068
- projects.forEach((p) => console.log(pc9.gray(` - ${p.name}`)));
2254
+ console.error(pc10.red(`Project not found: ${options.project}`));
2255
+ console.log(pc10.gray("Available projects:"));
2256
+ projects.forEach((p) => console.log(pc10.gray(` - ${p.name}`)));
2069
2257
  process.exit(1);
2070
2258
  }
2071
2259
  selectedProject = found;
2072
2260
  if (!projectMatchesRepo(selectedProject, repoFullName)) {
2073
2261
  console.log("");
2074
- 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"));
2075
- console.log(pc9.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
2076
- 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"));
2077
- console.log(pc9.yellow(` Current repo: ${repoFullName}`));
2078
- console.log(pc9.yellow(` Selected project: ${selectedProject.name}`));
2262
+ console.log(pc10.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"));
2263
+ console.log(pc10.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
2264
+ console.log(pc10.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"));
2265
+ console.log(pc10.yellow(` Current repo: ${repoFullName}`));
2266
+ console.log(pc10.yellow(` Selected project: ${selectedProject.name}`));
2079
2267
  if (selectedProject.linkedRepo) {
2080
- console.log(pc9.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2268
+ console.log(pc10.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2081
2269
  }
2082
2270
  console.log("");
2083
2271
  }
@@ -2086,9 +2274,9 @@ async function syncCommand(provider, options = {}) {
2086
2274
  if (autoMatch && (autoMatch.matchType === "linked_repo" || autoMatch.matchType === "exact_name")) {
2087
2275
  selectedProject = autoMatch.project;
2088
2276
  const matchReason = autoMatch.matchType === "linked_repo" ? `linked to ${repoFullName}` : "exact name match";
2089
- console.log(pc9.green(`\u2713 Auto-selected project: ${selectedProject.name} (${matchReason})`));
2277
+ console.log(pc10.green(`\u2713 Auto-selected project: ${selectedProject.name} (${matchReason})`));
2090
2278
  } else if (autoMatch && autoMatch.matchType === "partial_name") {
2091
- console.log(pc9.yellow(`Detected project: ${autoMatch.project.name} (partial match)`));
2279
+ console.log(pc10.yellow(`Detected project: ${autoMatch.project.name} (partial match)`));
2092
2280
  const { useDetected } = await prompts7({
2093
2281
  type: "confirm",
2094
2282
  name: "useDetected",
@@ -2104,13 +2292,13 @@ async function syncCommand(provider, options = {}) {
2104
2292
  selectedProject = projects[0];
2105
2293
  if (!projectMatchesRepo(selectedProject, repoFullName)) {
2106
2294
  console.log("");
2107
- 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"));
2108
- console.log(pc9.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
2109
- 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"));
2110
- console.log(pc9.yellow(` Current repo: ${repoFullName}`));
2111
- console.log(pc9.yellow(` Only project: ${selectedProject.name}`));
2295
+ console.log(pc10.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"));
2296
+ console.log(pc10.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
2297
+ console.log(pc10.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"));
2298
+ console.log(pc10.yellow(` Current repo: ${repoFullName}`));
2299
+ console.log(pc10.yellow(` Only project: ${selectedProject.name}`));
2112
2300
  if (selectedProject.linkedRepo) {
2113
- console.log(pc9.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2301
+ console.log(pc10.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2114
2302
  }
2115
2303
  console.log("");
2116
2304
  const { continueAnyway } = await prompts7({
@@ -2120,14 +2308,14 @@ async function syncCommand(provider, options = {}) {
2120
2308
  initial: false
2121
2309
  });
2122
2310
  if (!continueAnyway) {
2123
- console.log(pc9.gray("Cancelled."));
2311
+ console.log(pc10.gray("Cancelled."));
2124
2312
  process.exit(0);
2125
2313
  }
2126
2314
  }
2127
2315
  } else {
2128
- console.log(pc9.yellow(`
2316
+ console.log(pc10.yellow(`
2129
2317
  \u26A0\uFE0F No matching project found for ${repoFullName}`));
2130
- console.log(pc9.gray("Select a project manually:\n"));
2318
+ console.log(pc10.gray("Select a project manually:\n"));
2131
2319
  selectedProject = await promptProjectSelection(projects, repoFullName);
2132
2320
  }
2133
2321
  }
@@ -2135,13 +2323,13 @@ async function syncCommand(provider, options = {}) {
2135
2323
  const autoMatch = findMatchingProject(projects, repoFullName);
2136
2324
  if (autoMatch && autoMatch.project.id !== selectedProject.id) {
2137
2325
  console.log("");
2138
- 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"));
2139
- console.log(pc9.yellow("\u2502 \u26A0\uFE0F WARNING: You selected a different project \u2502"));
2140
- 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"));
2141
- console.log(pc9.yellow(` Current repo: ${repoFullName}`));
2142
- console.log(pc9.yellow(` Selected project: ${selectedProject.name}`));
2326
+ console.log(pc10.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"));
2327
+ console.log(pc10.yellow("\u2502 \u26A0\uFE0F WARNING: You selected a different project \u2502"));
2328
+ console.log(pc10.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"));
2329
+ console.log(pc10.yellow(` Current repo: ${repoFullName}`));
2330
+ console.log(pc10.yellow(` Selected project: ${selectedProject.name}`));
2143
2331
  if (selectedProject.linkedRepo) {
2144
- console.log(pc9.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2332
+ console.log(pc10.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2145
2333
  }
2146
2334
  console.log("");
2147
2335
  const { continueAnyway } = await prompts7({
@@ -2151,7 +2339,7 @@ async function syncCommand(provider, options = {}) {
2151
2339
  initial: false
2152
2340
  });
2153
2341
  if (!continueAnyway) {
2154
- console.log(pc9.gray("Cancelled."));
2342
+ console.log(pc10.gray("Cancelled."));
2155
2343
  process.exit(0);
2156
2344
  }
2157
2345
  }
@@ -2173,12 +2361,12 @@ async function syncCommand(provider, options = {}) {
2173
2361
  initial: Math.max(0, vaultEnvs.indexOf("production"))
2174
2362
  });
2175
2363
  if (!selectedEnv) {
2176
- console.log(pc9.gray("Cancelled."));
2364
+ console.log(pc10.gray("Cancelled."));
2177
2365
  process.exit(0);
2178
2366
  }
2179
2367
  keywayEnv = selectedEnv;
2180
2368
  if (!options.providerEnv) {
2181
- providerEnv = mapToVercelEnvironment(keywayEnv);
2369
+ providerEnv = mapToProviderEnvironment(provider, keywayEnv);
2182
2370
  }
2183
2371
  }
2184
2372
  if (needsDirectionPrompt) {
@@ -2192,7 +2380,7 @@ async function syncCommand(provider, options = {}) {
2192
2380
  ]
2193
2381
  });
2194
2382
  if (!selectedDirection) {
2195
- console.log(pc9.gray("Cancelled."));
2383
+ console.log(pc10.gray("Cancelled."));
2196
2384
  process.exit(0);
2197
2385
  }
2198
2386
  direction = selectedDirection;
@@ -2209,9 +2397,9 @@ async function syncCommand(provider, options = {}) {
2209
2397
  keywayEnv
2210
2398
  );
2211
2399
  if (status.isFirstSync && direction === "push" && status.vaultIsEmpty && status.providerHasSecrets) {
2212
- console.log(pc9.yellow(`
2400
+ console.log(pc10.yellow(`
2213
2401
  \u26A0\uFE0F Your Keyway vault is empty for "${keywayEnv}", but ${providerName} has ${status.providerSecretCount} secrets.`));
2214
- console.log(pc9.gray(` (Use --environment to sync a different environment)`));
2402
+ console.log(pc10.gray(` (Use --environment to sync a different environment)`));
2215
2403
  const { importFirst } = await prompts7({
2216
2404
  type: "confirm",
2217
2405
  name: "importFirst",
@@ -2253,7 +2441,7 @@ async function syncCommand(provider, options = {}) {
2253
2441
  command: "sync",
2254
2442
  error: truncateMessage(message)
2255
2443
  });
2256
- console.error(pc9.red(`
2444
+ console.error(pc10.red(`
2257
2445
  \u2717 ${message}`));
2258
2446
  process.exit(1);
2259
2447
  }
@@ -2270,33 +2458,33 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
2270
2458
  });
2271
2459
  const totalChanges = preview.toCreate.length + preview.toUpdate.length + preview.toDelete.length;
2272
2460
  if (totalChanges === 0) {
2273
- console.log(pc9.green("\n\u2713 Already in sync. No changes needed."));
2461
+ console.log(pc10.green("\n\u2713 Already in sync. No changes needed."));
2274
2462
  return;
2275
2463
  }
2276
- console.log(pc9.blue("\n\u{1F4CB} Sync Preview\n"));
2464
+ console.log(pc10.blue("\n\u{1F4CB} Sync Preview\n"));
2277
2465
  if (preview.toCreate.length > 0) {
2278
- console.log(pc9.green(` + ${preview.toCreate.length} to create`));
2279
- preview.toCreate.slice(0, 5).forEach((key) => console.log(pc9.gray(` ${key}`)));
2466
+ console.log(pc10.green(` + ${preview.toCreate.length} to create`));
2467
+ preview.toCreate.slice(0, 5).forEach((key) => console.log(pc10.gray(` ${key}`)));
2280
2468
  if (preview.toCreate.length > 5) {
2281
- console.log(pc9.gray(` ... and ${preview.toCreate.length - 5} more`));
2469
+ console.log(pc10.gray(` ... and ${preview.toCreate.length - 5} more`));
2282
2470
  }
2283
2471
  }
2284
2472
  if (preview.toUpdate.length > 0) {
2285
- console.log(pc9.yellow(` ~ ${preview.toUpdate.length} to update`));
2286
- preview.toUpdate.slice(0, 5).forEach((key) => console.log(pc9.gray(` ${key}`)));
2473
+ console.log(pc10.yellow(` ~ ${preview.toUpdate.length} to update`));
2474
+ preview.toUpdate.slice(0, 5).forEach((key) => console.log(pc10.gray(` ${key}`)));
2287
2475
  if (preview.toUpdate.length > 5) {
2288
- console.log(pc9.gray(` ... and ${preview.toUpdate.length - 5} more`));
2476
+ console.log(pc10.gray(` ... and ${preview.toUpdate.length - 5} more`));
2289
2477
  }
2290
2478
  }
2291
2479
  if (preview.toDelete.length > 0) {
2292
- console.log(pc9.red(` - ${preview.toDelete.length} to delete`));
2293
- preview.toDelete.slice(0, 5).forEach((key) => console.log(pc9.gray(` ${key}`)));
2480
+ console.log(pc10.red(` - ${preview.toDelete.length} to delete`));
2481
+ preview.toDelete.slice(0, 5).forEach((key) => console.log(pc10.gray(` ${key}`)));
2294
2482
  if (preview.toDelete.length > 5) {
2295
- console.log(pc9.gray(` ... and ${preview.toDelete.length - 5} more`));
2483
+ console.log(pc10.gray(` ... and ${preview.toDelete.length - 5} more`));
2296
2484
  }
2297
2485
  }
2298
2486
  if (preview.toSkip.length > 0) {
2299
- console.log(pc9.gray(` \u25CB ${preview.toSkip.length} unchanged`));
2487
+ console.log(pc10.gray(` \u25CB ${preview.toSkip.length} unchanged`));
2300
2488
  }
2301
2489
  console.log("");
2302
2490
  if (!skipConfirm) {
@@ -2308,11 +2496,11 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
2308
2496
  initial: true
2309
2497
  });
2310
2498
  if (!confirm) {
2311
- console.log(pc9.gray("Cancelled."));
2499
+ console.log(pc10.gray("Cancelled."));
2312
2500
  return;
2313
2501
  }
2314
2502
  }
2315
- console.log(pc9.blue("\n\u23F3 Syncing...\n"));
2503
+ console.log(pc10.blue("\n\u23F3 Syncing...\n"));
2316
2504
  const result = await executeSync(accessToken, repoFullName, {
2317
2505
  connectionId,
2318
2506
  projectId: project.id,
@@ -2322,11 +2510,11 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
2322
2510
  allowDelete
2323
2511
  });
2324
2512
  if (result.success) {
2325
- console.log(pc9.green("\u2713 Sync complete"));
2326
- console.log(pc9.gray(` Created: ${result.stats.created}`));
2327
- console.log(pc9.gray(` Updated: ${result.stats.updated}`));
2513
+ console.log(pc10.green("\u2713 Sync complete"));
2514
+ console.log(pc10.gray(` Created: ${result.stats.created}`));
2515
+ console.log(pc10.gray(` Updated: ${result.stats.updated}`));
2328
2516
  if (result.stats.deleted > 0) {
2329
- console.log(pc9.gray(` Deleted: ${result.stats.deleted}`));
2517
+ console.log(pc10.gray(` Deleted: ${result.stats.deleted}`));
2330
2518
  }
2331
2519
  trackEvent(AnalyticsEvents.CLI_SYNC, {
2332
2520
  provider,
@@ -2336,20 +2524,24 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
2336
2524
  deleted: result.stats.deleted
2337
2525
  });
2338
2526
  } else {
2339
- console.error(pc9.red(`
2527
+ console.error(pc10.red(`
2340
2528
  \u2717 ${result.error}`));
2341
2529
  process.exit(1);
2342
2530
  }
2343
2531
  }
2344
2532
 
2345
2533
  // src/cli.ts
2534
+ process.on("unhandledRejection", (reason) => {
2535
+ console.error(pc11.red("Unhandled error:"), reason);
2536
+ process.exit(1);
2537
+ });
2346
2538
  var program = new Command();
2347
2539
  var TAGLINE = "Sync secrets with your team and infra";
2348
2540
  var showBanner = () => {
2349
- const text = pc10.bold(pc10.cyan("Keyway CLI"));
2541
+ const text = pc11.bold(pc11.cyan("Keyway CLI"));
2350
2542
  console.log(`
2351
2543
  ${text}
2352
- ${pc10.gray(TAGLINE)}
2544
+ ${pc11.gray(TAGLINE)}
2353
2545
  `);
2354
2546
  };
2355
2547
  showBanner();
@@ -2386,6 +2578,6 @@ program.command("sync <provider>").description("Sync secrets with a provider (e.
2386
2578
  await syncCommand(provider, options);
2387
2579
  });
2388
2580
  program.parseAsync().catch((error) => {
2389
- console.error(pc10.red("Error:"), error.message || error);
2581
+ console.error(pc11.red("Error:"), error.message || error);
2390
2582
  process.exit(1);
2391
2583
  });