@keywaysh/cli 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,16 +1,25 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ clearAuth,
4
+ getAuthFilePath,
5
+ getStoredAuth,
6
+ saveAuthToken
7
+ } from "./chunk-F4C46224.js";
2
8
 
3
9
  // src/cli.ts
4
10
  import { Command } from "commander";
5
- import pc9 from "picocolors";
11
+ import pc10 from "picocolors";
6
12
 
7
13
  // src/cmds/init.ts
8
- import pc4 from "picocolors";
14
+ import pc5 from "picocolors";
9
15
  import prompts4 from "prompts";
10
16
  import open2 from "open";
11
17
 
12
18
  // src/utils/git.ts
13
19
  import { execSync } from "child_process";
20
+ import fs from "fs";
21
+ import path from "path";
22
+ import pc from "picocolors";
14
23
  function getCurrentRepoFullName() {
15
24
  try {
16
25
  if (!isGitRepository()) {
@@ -61,6 +70,29 @@ function parseGitHubUrl(url) {
61
70
  }
62
71
  throw new Error(`Invalid GitHub URL: ${url}`);
63
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
+ }
64
96
 
65
97
  // src/config/internal.ts
66
98
  var INTERNAL_API_URL = "https://api.keyway.sh";
@@ -70,7 +102,7 @@ var INTERNAL_POSTHOG_HOST = "https://eu.i.posthog.com";
70
102
  // package.json
71
103
  var package_default = {
72
104
  name: "@keywaysh/cli",
73
- version: "0.1.0",
105
+ version: "0.1.2",
74
106
  description: "One link to all your secrets",
75
107
  type: "module",
76
108
  bin: {
@@ -459,6 +491,25 @@ async function executeSync(accessToken, repoFullName, options) {
459
491
  const wrapped = await handleResponse(response);
460
492
  return wrapped.data;
461
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
+ }
462
513
  async function checkGitHubAppInstallation(repoOwner, repoName, accessToken) {
463
514
  const response = await fetchWithTimeout(`${API_BASE_URL}/v1/github/check-installation`, {
464
515
  method: "POST",
@@ -476,32 +527,32 @@ async function checkGitHubAppInstallation(repoOwner, repoName, accessToken) {
476
527
  // src/utils/analytics.ts
477
528
  import { PostHog } from "posthog-node";
478
529
  import crypto from "crypto";
479
- import path from "path";
530
+ import path2 from "path";
480
531
  import os from "os";
481
- import fs from "fs";
532
+ import fs2 from "fs";
482
533
  var posthog = null;
483
534
  var distinctId = null;
484
- var CONFIG_DIR = path.join(os.homedir(), ".config", "keyway");
485
- 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");
486
537
  var TELEMETRY_DISABLED = process.env.KEYWAY_DISABLE_TELEMETRY === "1";
487
538
  var CI = process.env.CI === "true" || process.env.CI === "1";
488
539
  function getDistinctId() {
489
540
  if (distinctId) return distinctId;
490
541
  try {
491
- if (!fs.existsSync(CONFIG_DIR)) {
492
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
542
+ if (!fs2.existsSync(CONFIG_DIR)) {
543
+ fs2.mkdirSync(CONFIG_DIR, { recursive: true });
493
544
  }
494
- if (fs.existsSync(ID_FILE)) {
495
- const content = fs.readFileSync(ID_FILE, "utf-8");
545
+ if (fs2.existsSync(ID_FILE)) {
546
+ const content = fs2.readFileSync(ID_FILE, "utf-8");
496
547
  const config2 = JSON.parse(content);
497
548
  distinctId = config2.distinctId;
498
549
  return distinctId;
499
550
  }
500
551
  distinctId = crypto.randomUUID();
501
552
  const config = { distinctId };
502
- 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 });
503
554
  try {
504
- fs.chmodSync(ID_FILE, 384);
555
+ fs2.chmodSync(ID_FILE, 384);
505
556
  } catch {
506
557
  }
507
558
  return distinctId;
@@ -600,10 +651,10 @@ var AnalyticsEvents = {
600
651
  };
601
652
 
602
653
  // src/cmds/readme.ts
603
- import fs2 from "fs";
604
- import path2 from "path";
654
+ import fs3 from "fs";
655
+ import path3 from "path";
605
656
  import prompts from "prompts";
606
- import pc from "picocolors";
657
+ import pc2 from "picocolors";
607
658
  function generateBadge(repo) {
608
659
  return `[![Keyway Secrets](https://www.keyway.sh/badge.svg?repo=${repo})](https://www.keyway.sh/vaults/${repo})`;
609
660
  }
@@ -629,8 +680,8 @@ ${readmeContent}`;
629
680
  function findReadmePath(cwd) {
630
681
  const candidates = ["README.md", "readme.md", "Readme.md"];
631
682
  for (const candidate of candidates) {
632
- const candidatePath = path2.join(cwd, candidate);
633
- if (fs2.existsSync(candidatePath)) {
683
+ const candidatePath = path3.join(cwd, candidate);
684
+ if (fs3.existsSync(candidatePath)) {
634
685
  return candidatePath;
635
686
  }
636
687
  }
@@ -641,7 +692,7 @@ async function ensureReadme(repoName, cwd) {
641
692
  if (existing) return existing;
642
693
  const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
643
694
  if (!isInteractive3) {
644
- 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.'));
645
696
  return null;
646
697
  }
647
698
  const { confirm } = await prompts(
@@ -656,14 +707,14 @@ async function ensureReadme(repoName, cwd) {
656
707
  }
657
708
  );
658
709
  if (!confirm) {
659
- console.log(pc.yellow("Skipping badge insertion (no README)."));
710
+ console.log(pc2.yellow("Skipping badge insertion (no README)."));
660
711
  return null;
661
712
  }
662
- const defaultPath = path2.join(cwd, "README.md");
713
+ const defaultPath = path3.join(cwd, "README.md");
663
714
  const content = `# ${repoName}
664
715
 
665
716
  `;
666
- fs2.writeFileSync(defaultPath, content, "utf-8");
717
+ fs3.writeFileSync(defaultPath, content, "utf-8");
667
718
  return defaultPath;
668
719
  }
669
720
  async function addBadgeToReadme(silent = false) {
@@ -675,130 +726,32 @@ async function addBadgeToReadme(silent = false) {
675
726
  const readmePath = await ensureReadme(repo, cwd);
676
727
  if (!readmePath) return false;
677
728
  const badge = generateBadge(repo);
678
- const content = fs2.readFileSync(readmePath, "utf-8");
729
+ const content = fs3.readFileSync(readmePath, "utf-8");
679
730
  const updated = insertBadgeIntoReadme(content, badge);
680
731
  if (updated === content) {
681
732
  if (!silent) {
682
- console.log(pc.gray("Keyway badge already present in README."));
733
+ console.log(pc2.gray("Keyway badge already present in README."));
683
734
  }
684
735
  return false;
685
736
  }
686
- fs2.writeFileSync(readmePath, updated, "utf-8");
737
+ fs3.writeFileSync(readmePath, updated, "utf-8");
687
738
  if (!silent) {
688
- 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)}`));
689
740
  }
690
741
  return true;
691
742
  }
692
743
 
693
744
  // src/cmds/push.ts
694
- import pc3 from "picocolors";
695
- import fs3 from "fs";
696
- import path3 from "path";
745
+ import pc4 from "picocolors";
746
+ import fs4 from "fs";
747
+ import path4 from "path";
697
748
  import prompts3 from "prompts";
698
749
 
699
750
  // src/cmds/login.ts
700
- import pc2 from "picocolors";
751
+ import pc3 from "picocolors";
701
752
  import readline from "readline";
702
753
  import open from "open";
703
754
  import prompts2 from "prompts";
704
-
705
- // src/utils/auth.ts
706
- import Conf from "conf";
707
- import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
708
- import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from "fs";
709
- import { join } from "path";
710
- import { homedir } from "os";
711
- var store = new Conf({
712
- projectName: "keyway",
713
- configName: "config",
714
- fileMode: 384
715
- });
716
- var KEY_DIR = join(homedir(), ".keyway");
717
- var KEY_FILE = join(KEY_DIR, ".key");
718
- function getOrCreateEncryptionKey() {
719
- if (!existsSync(KEY_DIR)) {
720
- mkdirSync(KEY_DIR, { recursive: true, mode: 448 });
721
- }
722
- if (existsSync(KEY_FILE)) {
723
- const keyHex2 = readFileSync(KEY_FILE, "utf-8").trim();
724
- if (keyHex2.length === 64) {
725
- return Buffer.from(keyHex2, "hex");
726
- }
727
- }
728
- const key = randomBytes(32);
729
- const keyHex = key.toString("hex");
730
- writeFileSync(KEY_FILE, keyHex, { mode: 384 });
731
- try {
732
- chmodSync(KEY_FILE, 384);
733
- } catch {
734
- }
735
- return key;
736
- }
737
- function encryptToken(token) {
738
- const key = getOrCreateEncryptionKey();
739
- const iv = randomBytes(16);
740
- const cipher = createCipheriv("aes-256-gcm", key, iv);
741
- const encrypted = Buffer.concat([cipher.update(token, "utf8"), cipher.final()]);
742
- const authTag = cipher.getAuthTag();
743
- return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted.toString("hex")}`;
744
- }
745
- function decryptToken(encryptedData) {
746
- const key = getOrCreateEncryptionKey();
747
- const parts = encryptedData.split(":");
748
- if (parts.length !== 3) {
749
- throw new Error("Invalid encrypted token format");
750
- }
751
- const iv = Buffer.from(parts[0], "hex");
752
- const authTag = Buffer.from(parts[1], "hex");
753
- const encrypted = Buffer.from(parts[2], "hex");
754
- const decipher = createDecipheriv("aes-256-gcm", key, iv);
755
- decipher.setAuthTag(authTag);
756
- const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
757
- return decrypted.toString("utf8");
758
- }
759
- function isExpired(auth) {
760
- if (!auth.expiresAt) return false;
761
- const expires = Date.parse(auth.expiresAt);
762
- if (Number.isNaN(expires)) return false;
763
- return expires <= Date.now();
764
- }
765
- async function getStoredAuth() {
766
- const encryptedData = store.get("auth");
767
- if (!encryptedData) {
768
- return null;
769
- }
770
- try {
771
- const decrypted = decryptToken(encryptedData);
772
- const auth = JSON.parse(decrypted);
773
- if (isExpired(auth)) {
774
- clearAuth();
775
- return null;
776
- }
777
- return auth;
778
- } catch {
779
- console.error("Failed to decrypt stored auth, clearing...");
780
- clearAuth();
781
- return null;
782
- }
783
- }
784
- async function saveAuthToken(token, meta) {
785
- const auth = {
786
- keywayToken: token,
787
- githubLogin: meta?.githubLogin,
788
- expiresAt: meta?.expiresAt,
789
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
790
- };
791
- const encrypted = encryptToken(JSON.stringify(auth));
792
- store.set("auth", encrypted);
793
- }
794
- function clearAuth() {
795
- store.delete("auth");
796
- }
797
- function getAuthFilePath() {
798
- return store.path;
799
- }
800
-
801
- // src/cmds/login.ts
802
755
  function sleep(ms) {
803
756
  return new Promise((resolve) => setTimeout(resolve, ms));
804
757
  }
@@ -822,17 +775,17 @@ async function promptYesNo(question, defaultYes = true) {
822
775
  });
823
776
  }
824
777
  async function runLoginFlow() {
825
- console.log(pc2.blue("\u{1F510} Starting Keyway login...\n"));
778
+ console.log(pc3.blue("\u{1F510} Starting Keyway login...\n"));
826
779
  const repoName = detectGitRepo();
827
780
  const start = await startDeviceLogin(repoName);
828
781
  const verifyUrl = start.verificationUriComplete || start.verificationUri;
829
782
  if (!verifyUrl) {
830
783
  throw new Error("Missing verification URL from the auth server.");
831
784
  }
832
- console.log(`Code: ${pc2.bold(pc2.green(start.userCode))}`);
785
+ console.log(`Code: ${pc3.bold(pc3.green(start.userCode))}`);
833
786
  console.log("Waiting for auth...");
834
787
  open(verifyUrl).catch(() => {
835
- console.log(pc2.gray(`Open this URL in your browser: ${verifyUrl}`));
788
+ console.log(pc3.gray(`Open this URL in your browser: ${verifyUrl}`));
836
789
  });
837
790
  const pollIntervalMs = (start.interval ?? 5) * 1e3;
838
791
  const maxTimeoutMs = Math.min((start.expiresIn ?? 900) * 1e3, 30 * 60 * 1e3);
@@ -861,9 +814,9 @@ async function runLoginFlow() {
861
814
  login_method: "device"
862
815
  });
863
816
  }
864
- console.log(pc2.green("\n\u2713 Login successful"));
817
+ console.log(pc3.green("\n\u2713 Login successful"));
865
818
  if (result.githubLogin) {
866
- console.log(`Authenticated GitHub user: ${pc2.cyan(result.githubLogin)}`);
819
+ console.log(`Authenticated GitHub user: ${pc3.cyan(result.githubLogin)}`);
867
820
  }
868
821
  return result.keywayToken;
869
822
  }
@@ -876,7 +829,7 @@ async function ensureLogin(options = {}) {
876
829
  return envToken;
877
830
  }
878
831
  if (process.env.GITHUB_TOKEN && !process.env.KEYWAY_TOKEN) {
879
- 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."));
880
833
  }
881
834
  const stored = await getStoredAuth();
882
835
  if (stored?.keywayToken) {
@@ -896,16 +849,16 @@ async function ensureLogin(options = {}) {
896
849
  async function runTokenLogin() {
897
850
  const repoName = detectGitRepo();
898
851
  if (repoName) {
899
- console.log(`\u{1F4C1} Detected: ${pc2.cyan(repoName)}`);
852
+ console.log(`\u{1F4C1} Detected: ${pc3.cyan(repoName)}`);
900
853
  }
901
854
  const description = repoName ? `Keyway CLI for ${repoName}` : "Keyway CLI";
902
855
  const url = `https://github.com/settings/personal-access-tokens/new?description=${encodeURIComponent(description)}`;
903
856
  console.log("Opening GitHub...");
904
857
  open(url).catch(() => {
905
- console.log(pc2.gray(`Open this URL in your browser: ${url}`));
858
+ console.log(pc3.gray(`Open this URL in your browser: ${url}`));
906
859
  });
907
- console.log(pc2.gray("Select the detected repo (or scope manually)."));
908
- 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."));
909
862
  const { token } = await prompts2(
910
863
  {
911
864
  type: "password",
@@ -942,7 +895,7 @@ async function runTokenLogin() {
942
895
  github_username: validation.username,
943
896
  login_method: "pat"
944
897
  });
945
- console.log(pc2.green("\u2705 Authenticated"), `as ${pc2.cyan(`@${validation.username}`)}`);
898
+ console.log(pc3.green("\u2705 Authenticated"), `as ${pc3.cyan(`@${validation.username}`)}`);
946
899
  return trimmedToken;
947
900
  }
948
901
  async function loginCommand(options = {}) {
@@ -958,20 +911,20 @@ async function loginCommand(options = {}) {
958
911
  command: "login",
959
912
  error: truncateMessage(message)
960
913
  });
961
- console.error(pc2.red(`
914
+ console.error(pc3.red(`
962
915
  \u2717 ${message}`));
963
916
  process.exit(1);
964
917
  }
965
918
  }
966
919
  async function logoutCommand() {
967
920
  clearAuth();
968
- console.log(pc2.green("\u2713 Logged out of Keyway"));
969
- 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()}`));
970
923
  }
971
924
 
972
925
  // src/cmds/push.ts
973
926
  function deriveEnvFromFile(file) {
974
- const base = path3.basename(file);
927
+ const base = path4.basename(file);
975
928
  const match = base.match(/\.env(?:\.(.+))?$/);
976
929
  if (match) {
977
930
  return match[1] || "development";
@@ -980,15 +933,15 @@ function deriveEnvFromFile(file) {
980
933
  }
981
934
  function discoverEnvCandidates(cwd) {
982
935
  try {
983
- const entries = fs3.readdirSync(cwd);
936
+ const entries = fs4.readdirSync(cwd);
984
937
  const hasEnvLocal = entries.includes(".env.local");
985
938
  if (hasEnvLocal) {
986
- 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)"));
987
940
  }
988
941
  const candidates = entries.filter((name) => name.startsWith(".env") && name !== ".env.local").map((name) => {
989
- const fullPath = path3.join(cwd, name);
942
+ const fullPath = path4.join(cwd, name);
990
943
  try {
991
- const stat = fs3.statSync(fullPath);
944
+ const stat = fs4.statSync(fullPath);
992
945
  if (!stat.isFile()) return null;
993
946
  return { file: name, env: deriveEnvFromFile(name) };
994
947
  } catch {
@@ -1009,7 +962,7 @@ function discoverEnvCandidates(cwd) {
1009
962
  }
1010
963
  async function pushCommand(options) {
1011
964
  try {
1012
- console.log(pc3.blue("\u{1F510} Pushing secrets to Keyway...\n"));
965
+ console.log(pc4.blue("\u{1F510} Pushing secrets to Keyway...\n"));
1013
966
  const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
1014
967
  let environment = options.env;
1015
968
  let envFile = options.file;
@@ -1051,8 +1004,8 @@ async function pushCommand(options) {
1051
1004
  message: "Path to env file:",
1052
1005
  validate: (value) => {
1053
1006
  if (!value) return "Path is required";
1054
- const resolved = path3.resolve(process.cwd(), value);
1055
- 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}`;
1056
1009
  return true;
1057
1010
  }
1058
1011
  },
@@ -1072,8 +1025,8 @@ async function pushCommand(options) {
1072
1025
  if (!envFile) {
1073
1026
  envFile = ".env";
1074
1027
  }
1075
- let envFilePath = path3.resolve(process.cwd(), envFile);
1076
- if (!fs3.existsSync(envFilePath)) {
1028
+ let envFilePath = path4.resolve(process.cwd(), envFile);
1029
+ if (!fs4.existsSync(envFilePath)) {
1077
1030
  if (!isInteractive3) {
1078
1031
  throw new Error(`File not found: ${envFile}. Provide --file <path> or run interactively to choose a file.`);
1079
1032
  }
@@ -1084,8 +1037,8 @@ async function pushCommand(options) {
1084
1037
  message: `File not found: ${envFile}. Enter an env file path to use:`,
1085
1038
  validate: (value) => {
1086
1039
  if (!value || typeof value !== "string") return "Path is required";
1087
- const resolved = path3.resolve(process.cwd(), value);
1088
- 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}`;
1089
1042
  return true;
1090
1043
  }
1091
1044
  },
@@ -1099,9 +1052,9 @@ async function pushCommand(options) {
1099
1052
  throw new Error("Push cancelled (no env file provided).");
1100
1053
  }
1101
1054
  envFile = newPath.trim();
1102
- envFilePath = path3.resolve(process.cwd(), envFile);
1055
+ envFilePath = path4.resolve(process.cwd(), envFile);
1103
1056
  }
1104
- const content = fs3.readFileSync(envFilePath, "utf-8");
1057
+ const content = fs4.readFileSync(envFilePath, "utf-8");
1105
1058
  if (content.trim().length === 0) {
1106
1059
  throw new Error(`File is empty: ${envFile}`);
1107
1060
  }
@@ -1109,11 +1062,11 @@ async function pushCommand(options) {
1109
1062
  const trimmed = line.trim();
1110
1063
  return trimmed.length > 0 && !trimmed.startsWith("#");
1111
1064
  });
1112
- console.log(`File: ${pc3.cyan(envFile)}`);
1113
- console.log(`Environment: ${pc3.cyan(environment)}`);
1114
- 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())}`);
1115
1068
  const repoFullName = getCurrentRepoFullName();
1116
- console.log(`Repository: ${pc3.cyan(repoFullName)}`);
1069
+ console.log(`Repository: ${pc4.cyan(repoFullName)}`);
1117
1070
  if (!options.yes) {
1118
1071
  const isInteractive4 = process.stdin.isTTY && process.stdout.isTTY;
1119
1072
  if (!isInteractive4) {
@@ -1133,7 +1086,7 @@ async function pushCommand(options) {
1133
1086
  }
1134
1087
  );
1135
1088
  if (!confirm) {
1136
- console.log(pc3.yellow("Push aborted."));
1089
+ console.log(pc4.yellow("Push aborted."));
1137
1090
  return;
1138
1091
  }
1139
1092
  }
@@ -1145,20 +1098,20 @@ async function pushCommand(options) {
1145
1098
  });
1146
1099
  console.log("\nUploading secrets...");
1147
1100
  const response = await pushSecrets(repoFullName, environment, content, accessToken);
1148
- console.log(pc3.green("\n\u2713 " + response.message));
1101
+ console.log(pc4.green("\n\u2713 " + response.message));
1149
1102
  if (response.stats) {
1150
1103
  const { created, updated, deleted } = response.stats;
1151
1104
  const parts = [];
1152
- if (created > 0) parts.push(pc3.green(`+${created} created`));
1153
- if (updated > 0) parts.push(pc3.yellow(`~${updated} updated`));
1154
- 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`));
1155
1108
  if (parts.length > 0) {
1156
1109
  console.log(`Stats: ${parts.join(", ")}`);
1157
1110
  }
1158
1111
  }
1159
1112
  console.log(`
1160
1113
  Your secrets are now encrypted and stored securely.`);
1161
- 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}`)}`);
1162
1115
  await shutdownAnalytics();
1163
1116
  } catch (error) {
1164
1117
  let message;
@@ -1171,13 +1124,13 @@ Your secrets are now encrypted and stored securely.`);
1171
1124
  const availableEnvs = envNotFoundMatch[2];
1172
1125
  message = `Environment '${requestedEnv}' does not exist in this vault.`;
1173
1126
  hint = `Available environments: ${availableEnvs}
1174
- 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.`;
1175
1128
  }
1176
1129
  if (error.statusCode === 403 && error.upgradeUrl) {
1177
- hint = `${pc3.yellow("\u26A1")} Upgrade to Pro: ${pc3.cyan(error.upgradeUrl)}`;
1130
+ hint = `${pc4.yellow("\u26A1")} Upgrade to Pro: ${pc4.cyan(error.upgradeUrl)}`;
1178
1131
  } else if (error.statusCode === 403 && message.toLowerCase().includes("read-only")) {
1179
1132
  message = "This vault is read-only on your current plan.";
1180
- 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")}`;
1181
1134
  }
1182
1135
  } else if (error instanceof Error) {
1183
1136
  message = truncateMessage(error.message);
@@ -1189,10 +1142,10 @@ Use ${pc3.cyan(`keyway push --env <environment>`)} to specify one, or create '${
1189
1142
  error: message
1190
1143
  });
1191
1144
  await shutdownAnalytics();
1192
- console.error(pc3.red(`
1145
+ console.error(pc4.red(`
1193
1146
  \u2717 ${message}`));
1194
1147
  if (hint) {
1195
- console.error(pc3.gray(`
1148
+ console.error(pc4.gray(`
1196
1149
  ${hint}`));
1197
1150
  }
1198
1151
  process.exit(1);
@@ -1213,19 +1166,26 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
1213
1166
  const [repoOwner, repoName] = repoFullName.split("/");
1214
1167
  const envToken = process.env.KEYWAY_TOKEN;
1215
1168
  if (envToken) {
1216
- return ensureGitHubAppInstalledOnly(repoFullName, envToken);
1169
+ const result = await ensureGitHubAppInstalledOnly(repoFullName, envToken);
1170
+ if (result === null) {
1171
+ throw new Error("KEYWAY_TOKEN is invalid or expired. Please update the token.");
1172
+ }
1173
+ return result;
1217
1174
  }
1218
1175
  const stored = await getStoredAuth();
1219
1176
  if (stored?.keywayToken) {
1220
- return ensureGitHubAppInstalledOnly(repoFullName, stored.keywayToken);
1177
+ const result = await ensureGitHubAppInstalledOnly(repoFullName, stored.keywayToken);
1178
+ if (result !== null) {
1179
+ return result;
1180
+ }
1221
1181
  }
1222
1182
  const allowPrompt = options.allowPrompt !== false;
1223
1183
  if (!allowPrompt || !isInteractive2()) {
1224
1184
  throw new Error('No Keyway session found. Run "keyway login" to authenticate.');
1225
1185
  }
1226
1186
  console.log("");
1227
- console.log(pc4.gray(" Keyway uses a GitHub App for secure access."));
1228
- 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."));
1229
1189
  console.log("");
1230
1190
  const { shouldProceed } = await prompts4({
1231
1191
  type: "confirm",
@@ -1238,11 +1198,11 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
1238
1198
  }
1239
1199
  const deviceStart = await startDeviceLogin(repoFullName);
1240
1200
  const installUrl = deviceStart.githubAppInstallUrl || "https://github.com/apps/keyway/installations/new";
1241
- console.log(pc4.gray("\n Opening browser..."));
1201
+ console.log(pc5.gray("\n Opening browser..."));
1242
1202
  await open2(installUrl);
1243
1203
  console.log("");
1244
- console.log(pc4.blue("\u23F3 Waiting for installation & authorization..."));
1245
- 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"));
1246
1206
  const pollIntervalMs = Math.max((deviceStart.interval ?? 5) * 1e3, POLL_INTERVAL_MS);
1247
1207
  const startTime = Date.now();
1248
1208
  let accessToken = null;
@@ -1257,7 +1217,7 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
1257
1217
  githubLogin: result.githubLogin,
1258
1218
  expiresAt: result.expiresAt
1259
1219
  });
1260
- console.log(pc4.green("\u2713 Signed in!"));
1220
+ console.log(pc5.green("\u2713 Signed in!"));
1261
1221
  if (result.githubLogin) {
1262
1222
  identifyUser(result.githubLogin, {
1263
1223
  github_username: result.githubLogin,
@@ -1269,34 +1229,45 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
1269
1229
  if (accessToken) {
1270
1230
  const installStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
1271
1231
  if (installStatus.installed) {
1272
- console.log(pc4.green("\u2713 GitHub App installed!"));
1232
+ console.log(pc5.green("\u2713 GitHub App installed!"));
1273
1233
  console.log("");
1274
1234
  return accessToken;
1275
1235
  }
1276
1236
  }
1277
- process.stdout.write(pc4.gray("."));
1237
+ process.stdout.write(pc5.gray("."));
1278
1238
  } catch {
1279
1239
  }
1280
1240
  }
1281
1241
  console.log("");
1282
- console.log(pc4.yellow("\u26A0 Timed out waiting for setup."));
1283
- 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}`));
1284
1244
  throw new Error("Setup timed out. Please try again.");
1285
1245
  }
1286
1246
  async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
1287
1247
  const [repoOwner, repoName] = repoFullName.split("/");
1288
- const status = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
1248
+ let status;
1249
+ try {
1250
+ status = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
1251
+ } catch (error) {
1252
+ if (error instanceof APIError && error.statusCode === 401) {
1253
+ console.log(pc5.yellow("\n\u26A0 Session expired or invalid. Clearing credentials..."));
1254
+ const { clearAuth: clearAuth2 } = await import("./auth-QLPQ24HZ.js");
1255
+ clearAuth2();
1256
+ return null;
1257
+ }
1258
+ throw error;
1259
+ }
1289
1260
  if (status.installed) {
1290
1261
  return accessToken;
1291
1262
  }
1292
1263
  console.log("");
1293
- 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"));
1294
1265
  console.log("");
1295
- console.log(pc4.gray(" The Keyway GitHub App is required to securely manage secrets."));
1296
- 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)."));
1297
1268
  console.log("");
1298
1269
  if (!isInteractive2()) {
1299
- console.log(pc4.gray(` Install the Keyway GitHub App: ${status.installUrl}`));
1270
+ console.log(pc5.gray(` Install the Keyway GitHub App: ${status.installUrl}`));
1300
1271
  throw new Error("GitHub App installation required.");
1301
1272
  }
1302
1273
  const { shouldInstall } = await prompts4({
@@ -1306,50 +1277,50 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
1306
1277
  initial: true
1307
1278
  });
1308
1279
  if (!shouldInstall) {
1309
- console.log(pc4.gray(`
1280
+ console.log(pc5.gray(`
1310
1281
  You can install later: ${status.installUrl}`));
1311
1282
  throw new Error("GitHub App installation required.");
1312
1283
  }
1313
- console.log(pc4.gray("\n Opening browser..."));
1284
+ console.log(pc5.gray("\n Opening browser..."));
1314
1285
  await open2(status.installUrl);
1315
1286
  console.log("");
1316
- console.log(pc4.blue("\u23F3 Waiting for GitHub App installation..."));
1317
- 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"));
1318
1289
  const startTime = Date.now();
1319
1290
  while (Date.now() - startTime < POLL_TIMEOUT_MS) {
1320
1291
  await sleep2(POLL_INTERVAL_MS);
1321
1292
  try {
1322
1293
  const pollStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
1323
1294
  if (pollStatus.installed) {
1324
- console.log(pc4.green("\u2713 GitHub App installed!"));
1295
+ console.log(pc5.green("\u2713 GitHub App installed!"));
1325
1296
  console.log("");
1326
1297
  return accessToken;
1327
1298
  }
1328
- process.stdout.write(pc4.gray("."));
1299
+ process.stdout.write(pc5.gray("."));
1329
1300
  } catch {
1330
1301
  }
1331
1302
  }
1332
1303
  console.log("");
1333
- console.log(pc4.yellow("\u26A0 Timed out waiting for installation."));
1334
- 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}`));
1335
1306
  throw new Error("GitHub App installation timed out.");
1336
1307
  }
1337
1308
  async function initCommand(options = {}) {
1338
1309
  try {
1339
1310
  const repoFullName = getCurrentRepoFullName();
1340
1311
  const dashboardLink = `${DASHBOARD_URL}/${repoFullName}`;
1341
- console.log(pc4.blue("\u{1F510} Initializing Keyway vault...\n"));
1342
- 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)}`);
1343
1314
  const accessToken = await ensureLoginAndGitHubApp(repoFullName, {
1344
1315
  allowPrompt: options.loginPrompt !== false
1345
1316
  });
1346
1317
  trackEvent(AnalyticsEvents.CLI_INIT, { repoFullName, githubAppInstalled: true });
1347
1318
  await initVault(repoFullName, accessToken);
1348
- console.log(pc4.green("\u2713 Vault created!"));
1319
+ console.log(pc5.green("\u2713 Vault created!"));
1349
1320
  try {
1350
1321
  const badgeAdded = await addBadgeToReadme(true);
1351
1322
  if (badgeAdded) {
1352
- console.log(pc4.green("\u2713 Badge added to README.md"));
1323
+ console.log(pc5.green("\u2713 Badge added to README.md"));
1353
1324
  }
1354
1325
  } catch {
1355
1326
  }
@@ -1357,7 +1328,7 @@ async function initCommand(options = {}) {
1357
1328
  const envCandidates = discoverEnvCandidates(process.cwd());
1358
1329
  const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
1359
1330
  if (envCandidates.length > 0 && isInteractive3) {
1360
- 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(", ")}
1361
1332
  `));
1362
1333
  const { shouldPush } = await prompts4({
1363
1334
  type: "confirm",
@@ -1371,25 +1342,25 @@ async function initCommand(options = {}) {
1371
1342
  return;
1372
1343
  }
1373
1344
  }
1374
- console.log(pc4.dim("\u2500".repeat(50)));
1345
+ console.log(pc5.dim("\u2500".repeat(50)));
1375
1346
  console.log("");
1376
1347
  if (envCandidates.length === 0) {
1377
- console.log(` ${pc4.yellow("\u2192")} Create a ${pc4.cyan(".env")} file with your secrets`);
1378
- 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
1379
1350
  `);
1380
1351
  } else {
1381
- 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
1382
1353
  `);
1383
1354
  }
1384
- console.log(` ${pc4.blue("\u2394")} Dashboard: ${pc4.underline(dashboardLink)}`);
1355
+ console.log(` ${pc5.blue("\u2394")} Dashboard: ${pc5.underline(dashboardLink)}`);
1385
1356
  console.log("");
1386
1357
  await shutdownAnalytics();
1387
1358
  } catch (error) {
1388
1359
  if (error instanceof APIError) {
1389
1360
  if (error.statusCode === 409) {
1390
- console.log(pc4.yellow("\n\u26A0 Vault already exists for this repository.\n"));
1391
- console.log(` ${pc4.yellow("\u2192")} Run ${pc4.cyan("keyway push")} to sync your secrets`);
1392
- console.log(` ${pc4.blue("\u2394")} Dashboard: ${pc4.underline(`${DASHBOARD_URL}/${getCurrentRepoFullName()}`)}`);
1361
+ console.log(pc5.yellow("\n\u26A0 Vault already exists for this repository.\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()}`)}`);
1393
1364
  console.log("");
1394
1365
  await shutdownAnalytics();
1395
1366
  return;
@@ -1397,15 +1368,15 @@ async function initCommand(options = {}) {
1397
1368
  if (error.error === "Plan Limit Reached" || error.upgradeUrl) {
1398
1369
  const upgradeUrl = error.upgradeUrl || "https://keyway.sh/pricing";
1399
1370
  console.log("");
1400
- console.log(pc4.dim("\u2500".repeat(50)));
1371
+ console.log(pc5.dim("\u2500".repeat(50)));
1401
1372
  console.log("");
1402
- console.log(` ${pc4.yellow("\u26A1")} ${pc4.bold("Plan Limit Reached")}`);
1373
+ console.log(` ${pc5.yellow("\u26A1")} ${pc5.bold("Plan Limit Reached")}`);
1403
1374
  console.log("");
1404
- console.log(pc4.white(` ${error.message}`));
1375
+ console.log(pc5.white(` ${error.message}`));
1405
1376
  console.log("");
1406
- console.log(` ${pc4.cyan("Upgrade now \u2192")} ${pc4.underline(upgradeUrl)}`);
1377
+ console.log(` ${pc5.cyan("Upgrade now \u2192")} ${pc5.underline(upgradeUrl)}`);
1407
1378
  console.log("");
1408
- console.log(pc4.dim("\u2500".repeat(50)));
1379
+ console.log(pc5.dim("\u2500".repeat(50)));
1409
1380
  console.log("");
1410
1381
  await shutdownAnalytics();
1411
1382
  process.exit(1);
@@ -1417,25 +1388,25 @@ async function initCommand(options = {}) {
1417
1388
  error: message
1418
1389
  });
1419
1390
  await shutdownAnalytics();
1420
- console.error(pc4.red(`
1391
+ console.error(pc5.red(`
1421
1392
  \u2717 ${message}`));
1422
1393
  process.exit(1);
1423
1394
  }
1424
1395
  }
1425
1396
 
1426
1397
  // src/cmds/pull.ts
1427
- import pc5 from "picocolors";
1428
- import fs4 from "fs";
1429
- import path4 from "path";
1398
+ import pc6 from "picocolors";
1399
+ import fs5 from "fs";
1400
+ import path5 from "path";
1430
1401
  import prompts5 from "prompts";
1431
1402
  async function pullCommand(options) {
1432
1403
  try {
1433
1404
  const environment = options.env || "development";
1434
1405
  const envFile = options.file || ".env";
1435
- console.log(pc5.blue("\u{1F510} Pulling secrets from Keyway...\n"));
1436
- 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)}`);
1437
1408
  const repoFullName = getCurrentRepoFullName();
1438
- console.log(`Repository: ${pc5.cyan(repoFullName)}`);
1409
+ console.log(`Repository: ${pc6.cyan(repoFullName)}`);
1439
1410
  const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
1440
1411
  trackEvent(AnalyticsEvents.CLI_PULL, {
1441
1412
  repoFullName,
@@ -1443,11 +1414,11 @@ async function pullCommand(options) {
1443
1414
  });
1444
1415
  console.log("\nDownloading secrets...");
1445
1416
  const response = await pullSecrets(repoFullName, environment, accessToken);
1446
- const envFilePath = path4.resolve(process.cwd(), envFile);
1447
- if (fs4.existsSync(envFilePath)) {
1417
+ const envFilePath = path5.resolve(process.cwd(), envFile);
1418
+ if (fs5.existsSync(envFilePath)) {
1448
1419
  const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
1449
1420
  if (options.yes) {
1450
- console.log(pc5.yellow(`
1421
+ console.log(pc6.yellow(`
1451
1422
  \u26A0 Overwriting existing file: ${envFile}`));
1452
1423
  } else if (!isInteractive3) {
1453
1424
  throw new Error(`File ${envFile} exists. Re-run with --yes to overwrite or choose a different --file.`);
@@ -1466,21 +1437,21 @@ async function pullCommand(options) {
1466
1437
  }
1467
1438
  );
1468
1439
  if (!confirm) {
1469
- console.log(pc5.yellow("Pull aborted."));
1440
+ console.log(pc6.yellow("Pull aborted."));
1470
1441
  return;
1471
1442
  }
1472
1443
  }
1473
1444
  }
1474
- fs4.writeFileSync(envFilePath, response.content, "utf-8");
1445
+ fs5.writeFileSync(envFilePath, response.content, "utf-8");
1475
1446
  const lines = response.content.split("\n").filter((line) => {
1476
1447
  const trimmed = line.trim();
1477
1448
  return trimmed.length > 0 && !trimmed.startsWith("#");
1478
1449
  });
1479
- console.log(pc5.green(`
1450
+ console.log(pc6.green(`
1480
1451
  \u2713 Secrets downloaded successfully`));
1481
1452
  console.log(`
1482
- File: ${pc5.cyan(envFile)}`);
1483
- console.log(`Variables: ${pc5.cyan(lines.length.toString())}`);
1453
+ File: ${pc6.cyan(envFile)}`);
1454
+ console.log(`Variables: ${pc6.cyan(lines.length.toString())}`);
1484
1455
  await shutdownAnalytics();
1485
1456
  } catch (error) {
1486
1457
  const message = error instanceof APIError ? `API ${error.statusCode}: ${error.message}` : error instanceof Error ? truncateMessage(error.message) : "Unknown error";
@@ -1489,20 +1460,20 @@ File: ${pc5.cyan(envFile)}`);
1489
1460
  error: message
1490
1461
  });
1491
1462
  await shutdownAnalytics();
1492
- console.error(pc5.red(`
1463
+ console.error(pc6.red(`
1493
1464
  \u2717 ${message}`));
1494
1465
  process.exit(1);
1495
1466
  }
1496
1467
  }
1497
1468
 
1498
1469
  // src/cmds/doctor.ts
1499
- import pc6 from "picocolors";
1470
+ import pc7 from "picocolors";
1500
1471
 
1501
1472
  // src/core/doctor.ts
1502
1473
  import { execSync as execSync2 } from "child_process";
1503
- import { writeFileSync as writeFileSync2, unlinkSync, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
1474
+ import { writeFileSync, unlinkSync, readFileSync, existsSync } from "fs";
1504
1475
  import { tmpdir } from "os";
1505
- import { join as join2 } from "path";
1476
+ import { join } from "path";
1506
1477
  var API_HEALTH_URL = `${process.env.KEYWAY_API_URL || INTERNAL_API_URL}/v1/health`;
1507
1478
  async function checkNode() {
1508
1479
  const nodeVersion = process.versions.node;
@@ -1619,9 +1590,9 @@ async function checkNetwork() {
1619
1590
  }
1620
1591
  }
1621
1592
  async function checkFileSystem() {
1622
- const testFile = join2(tmpdir(), `.keyway-test-${Date.now()}.tmp`);
1593
+ const testFile = join(tmpdir(), `.keyway-test-${Date.now()}.tmp`);
1623
1594
  try {
1624
- writeFileSync2(testFile, "test");
1595
+ writeFileSync(testFile, "test");
1625
1596
  unlinkSync(testFile);
1626
1597
  return {
1627
1598
  id: "filesystem",
@@ -1640,7 +1611,7 @@ async function checkFileSystem() {
1640
1611
  }
1641
1612
  async function checkGitignore() {
1642
1613
  try {
1643
- if (!existsSync2(".gitignore")) {
1614
+ if (!existsSync(".gitignore")) {
1644
1615
  return {
1645
1616
  id: "gitignore",
1646
1617
  name: ".gitignore configuration",
@@ -1648,7 +1619,7 @@ async function checkGitignore() {
1648
1619
  detail: "No .gitignore file found"
1649
1620
  };
1650
1621
  }
1651
- const gitignoreContent = readFileSync2(".gitignore", "utf-8");
1622
+ const gitignoreContent = readFileSync(".gitignore", "utf-8");
1652
1623
  const hasEnvPattern = gitignoreContent.includes("*.env") || gitignoreContent.includes(".env*");
1653
1624
  const hasDotEnv = gitignoreContent.includes(".env");
1654
1625
  if (hasEnvPattern || hasDotEnv) {
@@ -1750,9 +1721,9 @@ async function runAllChecks(options = {}) {
1750
1721
  // src/cmds/doctor.ts
1751
1722
  function formatSummary(results) {
1752
1723
  const parts = [
1753
- pc6.green(`${results.summary.pass} passed`),
1754
- results.summary.warn > 0 ? pc6.yellow(`${results.summary.warn} warnings`) : null,
1755
- 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
1756
1727
  ].filter(Boolean);
1757
1728
  return parts.join(", ");
1758
1729
  }
@@ -1769,20 +1740,20 @@ async function doctorCommand(options = {}) {
1769
1740
  process.stdout.write(JSON.stringify(results, null, 0) + "\n");
1770
1741
  process.exit(results.exitCode);
1771
1742
  }
1772
- 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"));
1773
1744
  results.checks.forEach((check) => {
1774
- const icon = check.status === "pass" ? pc6.green("\u2713") : check.status === "warn" ? pc6.yellow("!") : pc6.red("\u2717");
1775
- 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}`) : "";
1776
1747
  console.log(` ${icon} ${check.name}${detail}`);
1777
1748
  });
1778
1749
  console.log(`
1779
1750
  Summary: ${formatSummary(results)}`);
1780
1751
  if (results.summary.fail > 0) {
1781
- 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."));
1782
1753
  } else if (results.summary.warn > 0) {
1783
- 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."));
1784
1755
  } else {
1785
- 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."));
1786
1757
  }
1787
1758
  process.exit(results.exitCode);
1788
1759
  } catch (error) {
@@ -1803,7 +1774,7 @@ Summary: ${formatSummary(results)}`);
1803
1774
  };
1804
1775
  process.stdout.write(JSON.stringify(errorResult, null, 0) + "\n");
1805
1776
  } else {
1806
- console.error(pc6.red(`
1777
+ console.error(pc7.red(`
1807
1778
  \u2717 ${message}`));
1808
1779
  }
1809
1780
  process.exit(1);
@@ -1811,7 +1782,7 @@ Summary: ${formatSummary(results)}`);
1811
1782
  }
1812
1783
 
1813
1784
  // src/cmds/connect.ts
1814
- import pc7 from "picocolors";
1785
+ import pc8 from "picocolors";
1815
1786
  import open3 from "open";
1816
1787
  import prompts6 from "prompts";
1817
1788
  async function connectCommand(provider, options = {}) {
@@ -1821,13 +1792,13 @@ async function connectCommand(provider, options = {}) {
1821
1792
  const providerInfo = providers.find((p) => p.name === provider.toLowerCase());
1822
1793
  if (!providerInfo) {
1823
1794
  const available = providers.map((p) => p.name).join(", ");
1824
- console.error(pc7.red(`Unknown provider: ${provider}`));
1825
- 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"}`));
1826
1797
  process.exit(1);
1827
1798
  }
1828
1799
  if (!providerInfo.configured) {
1829
- console.error(pc7.red(`Provider ${providerInfo.displayName} is not configured on the server.`));
1830
- 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."));
1831
1802
  process.exit(1);
1832
1803
  }
1833
1804
  const { connections } = await getConnections(accessToken);
@@ -1840,20 +1811,20 @@ async function connectCommand(provider, options = {}) {
1840
1811
  initial: false
1841
1812
  });
1842
1813
  if (!reconnect) {
1843
- console.log(pc7.gray("Keeping existing connection."));
1814
+ console.log(pc8.gray("Keeping existing connection."));
1844
1815
  return;
1845
1816
  }
1846
1817
  }
1847
- console.log(pc7.blue(`
1818
+ console.log(pc8.blue(`
1848
1819
  Connecting to ${providerInfo.displayName}...
1849
1820
  `));
1850
1821
  const authUrl = getProviderAuthUrl(provider.toLowerCase());
1851
1822
  const startTime = /* @__PURE__ */ new Date();
1852
- console.log(pc7.gray("Opening browser for authorization..."));
1853
- 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}`));
1854
1825
  await open3(authUrl).catch(() => {
1855
1826
  });
1856
- console.log(pc7.gray("Waiting for authorization..."));
1827
+ console.log(pc8.gray("Waiting for authorization..."));
1857
1828
  const maxAttempts = 60;
1858
1829
  let attempts = 0;
1859
1830
  let connected = false;
@@ -1867,7 +1838,7 @@ Connecting to ${providerInfo.displayName}...
1867
1838
  );
1868
1839
  if (newConn) {
1869
1840
  connected = true;
1870
- console.log(pc7.green(`
1841
+ console.log(pc8.green(`
1871
1842
  \u2713 Connected to ${providerInfo.displayName}!`));
1872
1843
  break;
1873
1844
  }
@@ -1875,8 +1846,8 @@ Connecting to ${providerInfo.displayName}...
1875
1846
  }
1876
1847
  }
1877
1848
  if (!connected) {
1878
- console.log(pc7.red("\n\u2717 Authorization timeout."));
1879
- 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."));
1880
1851
  }
1881
1852
  trackEvent(AnalyticsEvents.CLI_CONNECT, {
1882
1853
  provider: provider.toLowerCase(),
@@ -1888,7 +1859,7 @@ Connecting to ${providerInfo.displayName}...
1888
1859
  command: "connect",
1889
1860
  error: truncateMessage(message)
1890
1861
  });
1891
- console.error(pc7.red(`
1862
+ console.error(pc8.red(`
1892
1863
  \u2717 ${message}`));
1893
1864
  process.exit(1);
1894
1865
  }
@@ -1898,24 +1869,24 @@ async function connectionsCommand(options = {}) {
1898
1869
  const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
1899
1870
  const { connections } = await getConnections(accessToken);
1900
1871
  if (connections.length === 0) {
1901
- console.log(pc7.gray("No provider connections found."));
1902
- console.log(pc7.gray("\nConnect to a provider with: keyway connect <provider>"));
1903
- 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"));
1904
1875
  return;
1905
1876
  }
1906
- console.log(pc7.blue("\n\u{1F4E1} Provider Connections\n"));
1877
+ console.log(pc8.blue("\n\u{1F4E1} Provider Connections\n"));
1907
1878
  for (const conn of connections) {
1908
1879
  const providerName = conn.provider.charAt(0).toUpperCase() + conn.provider.slice(1);
1909
- const teamInfo = conn.providerTeamId ? pc7.gray(` (Team: ${conn.providerTeamId})`) : "";
1880
+ const teamInfo = conn.providerTeamId ? pc8.gray(` (Team: ${conn.providerTeamId})`) : "";
1910
1881
  const date = new Date(conn.createdAt).toLocaleDateString();
1911
- console.log(` ${pc7.green("\u25CF")} ${pc7.bold(providerName)}${teamInfo}`);
1912
- console.log(pc7.gray(` Connected: ${date}`));
1913
- 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}`));
1914
1885
  console.log("");
1915
1886
  }
1916
1887
  } catch (error) {
1917
1888
  const message = error instanceof Error ? error.message : "Failed to list connections";
1918
- console.error(pc7.red(`
1889
+ console.error(pc8.red(`
1919
1890
  \u2717 ${message}`));
1920
1891
  process.exit(1);
1921
1892
  }
@@ -1926,7 +1897,7 @@ async function disconnectCommand(provider, options = {}) {
1926
1897
  const { connections } = await getConnections(accessToken);
1927
1898
  const connection = connections.find((c) => c.provider === provider.toLowerCase());
1928
1899
  if (!connection) {
1929
- console.log(pc7.gray(`No connection found for provider: ${provider}`));
1900
+ console.log(pc8.gray(`No connection found for provider: ${provider}`));
1930
1901
  return;
1931
1902
  }
1932
1903
  const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
@@ -1937,11 +1908,11 @@ async function disconnectCommand(provider, options = {}) {
1937
1908
  initial: false
1938
1909
  });
1939
1910
  if (!confirm) {
1940
- console.log(pc7.gray("Cancelled."));
1911
+ console.log(pc8.gray("Cancelled."));
1941
1912
  return;
1942
1913
  }
1943
1914
  await deleteConnection(accessToken, connection.id);
1944
- console.log(pc7.green(`
1915
+ console.log(pc8.green(`
1945
1916
  \u2713 Disconnected from ${providerName}`));
1946
1917
  trackEvent(AnalyticsEvents.CLI_DISCONNECT, {
1947
1918
  provider: provider.toLowerCase()
@@ -1952,15 +1923,24 @@ async function disconnectCommand(provider, options = {}) {
1952
1923
  command: "disconnect",
1953
1924
  error: truncateMessage(message)
1954
1925
  });
1955
- console.error(pc7.red(`
1926
+ console.error(pc8.red(`
1956
1927
  \u2717 ${message}`));
1957
1928
  process.exit(1);
1958
1929
  }
1959
1930
  }
1960
1931
 
1961
1932
  // src/cmds/sync.ts
1962
- import pc8 from "picocolors";
1933
+ import pc9 from "picocolors";
1963
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
+ }
1964
1944
  function findMatchingProject(projects, repoFullName) {
1965
1945
  const repoFullNameLower = repoFullName.toLowerCase();
1966
1946
  const repoName = repoFullName.split("/")[1]?.toLowerCase();
@@ -2000,11 +1980,11 @@ async function promptProjectSelection(projects, repoFullName) {
2000
1980
  let title = p.name;
2001
1981
  const badges = [];
2002
1982
  if (p.linkedRepo?.toLowerCase() === repoFullName.toLowerCase()) {
2003
- badges.push(pc8.green("\u2190 linked"));
1983
+ badges.push(pc9.green("\u2190 linked"));
2004
1984
  } else if (p.name.toLowerCase() === repoName) {
2005
- badges.push(pc8.green("\u2190 same name"));
1985
+ badges.push(pc9.green("\u2190 same name"));
2006
1986
  } else if (p.linkedRepo) {
2007
- badges.push(pc8.gray(`\u2192 ${p.linkedRepo}`));
1987
+ badges.push(pc9.gray(`\u2192 ${p.linkedRepo}`));
2008
1988
  }
2009
1989
  if (badges.length > 0) {
2010
1990
  title = `${p.name} ${badges.join(" ")}`;
@@ -2018,7 +1998,7 @@ async function promptProjectSelection(projects, repoFullName) {
2018
1998
  choices
2019
1999
  });
2020
2000
  if (!projectChoice) {
2021
- console.log(pc8.gray("Cancelled."));
2001
+ console.log(pc9.gray("Cancelled."));
2022
2002
  process.exit(0);
2023
2003
  }
2024
2004
  return projects.find((p) => p.id === projectChoice);
@@ -2026,28 +2006,28 @@ async function promptProjectSelection(projects, repoFullName) {
2026
2006
  async function syncCommand(provider, options = {}) {
2027
2007
  try {
2028
2008
  if (options.pull && options.allowDelete) {
2029
- console.error(pc8.red("Error: --allow-delete cannot be used with --pull"));
2030
- 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."));
2031
2011
  process.exit(1);
2032
2012
  }
2033
2013
  const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
2034
2014
  const repoFullName = detectGitRepo();
2035
2015
  if (!repoFullName) {
2036
- console.error(pc8.red("Could not detect Git repository."));
2037
- 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."));
2038
2018
  process.exit(1);
2039
2019
  }
2040
- console.log(pc8.gray(`Repository: ${repoFullName}`));
2020
+ console.log(pc9.gray(`Repository: ${repoFullName}`));
2041
2021
  const { connections } = await getConnections(accessToken);
2042
2022
  const connection = connections.find((c) => c.provider === provider.toLowerCase());
2043
2023
  if (!connection) {
2044
- console.error(pc8.red(`Not connected to ${provider}.`));
2045
- 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}`));
2046
2026
  process.exit(1);
2047
2027
  }
2048
2028
  const { projects } = await getConnectionProjects(accessToken, connection.id);
2049
2029
  if (projects.length === 0) {
2050
- console.error(pc8.red(`No projects found in your ${provider} account.`));
2030
+ console.error(pc9.red(`No projects found in your ${provider} account.`));
2051
2031
  process.exit(1);
2052
2032
  }
2053
2033
  let selectedProject;
@@ -2056,21 +2036,21 @@ async function syncCommand(provider, options = {}) {
2056
2036
  (p) => p.id === options.project || p.name.toLowerCase() === options.project?.toLowerCase()
2057
2037
  );
2058
2038
  if (!found) {
2059
- console.error(pc8.red(`Project not found: ${options.project}`));
2060
- console.log(pc8.gray("Available projects:"));
2061
- 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}`)));
2062
2042
  process.exit(1);
2063
2043
  }
2064
2044
  selectedProject = found;
2065
2045
  if (!projectMatchesRepo(selectedProject, repoFullName)) {
2066
2046
  console.log("");
2067
- 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"));
2068
- console.log(pc8.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
2069
- 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"));
2070
- console.log(pc8.yellow(` Current repo: ${repoFullName}`));
2071
- 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}`));
2072
2052
  if (selectedProject.linkedRepo) {
2073
- console.log(pc8.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2053
+ console.log(pc9.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2074
2054
  }
2075
2055
  console.log("");
2076
2056
  }
@@ -2079,9 +2059,9 @@ async function syncCommand(provider, options = {}) {
2079
2059
  if (autoMatch && (autoMatch.matchType === "linked_repo" || autoMatch.matchType === "exact_name")) {
2080
2060
  selectedProject = autoMatch.project;
2081
2061
  const matchReason = autoMatch.matchType === "linked_repo" ? `linked to ${repoFullName}` : "exact name match";
2082
- console.log(pc8.green(`\u2713 Auto-selected project: ${selectedProject.name} (${matchReason})`));
2062
+ console.log(pc9.green(`\u2713 Auto-selected project: ${selectedProject.name} (${matchReason})`));
2083
2063
  } else if (autoMatch && autoMatch.matchType === "partial_name") {
2084
- console.log(pc8.yellow(`Detected project: ${autoMatch.project.name} (partial match)`));
2064
+ console.log(pc9.yellow(`Detected project: ${autoMatch.project.name} (partial match)`));
2085
2065
  const { useDetected } = await prompts7({
2086
2066
  type: "confirm",
2087
2067
  name: "useDetected",
@@ -2097,13 +2077,13 @@ async function syncCommand(provider, options = {}) {
2097
2077
  selectedProject = projects[0];
2098
2078
  if (!projectMatchesRepo(selectedProject, repoFullName)) {
2099
2079
  console.log("");
2100
- 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"));
2101
- console.log(pc8.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
2102
- 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"));
2103
- console.log(pc8.yellow(` Current repo: ${repoFullName}`));
2104
- 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}`));
2105
2085
  if (selectedProject.linkedRepo) {
2106
- console.log(pc8.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2086
+ console.log(pc9.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2107
2087
  }
2108
2088
  console.log("");
2109
2089
  const { continueAnyway } = await prompts7({
@@ -2113,14 +2093,14 @@ async function syncCommand(provider, options = {}) {
2113
2093
  initial: false
2114
2094
  });
2115
2095
  if (!continueAnyway) {
2116
- console.log(pc8.gray("Cancelled."));
2096
+ console.log(pc9.gray("Cancelled."));
2117
2097
  process.exit(0);
2118
2098
  }
2119
2099
  }
2120
2100
  } else {
2121
- console.log(pc8.yellow(`
2101
+ console.log(pc9.yellow(`
2122
2102
  \u26A0\uFE0F No matching project found for ${repoFullName}`));
2123
- console.log(pc8.gray("Select a project manually:\n"));
2103
+ console.log(pc9.gray("Select a project manually:\n"));
2124
2104
  selectedProject = await promptProjectSelection(projects, repoFullName);
2125
2105
  }
2126
2106
  }
@@ -2128,13 +2108,13 @@ async function syncCommand(provider, options = {}) {
2128
2108
  const autoMatch = findMatchingProject(projects, repoFullName);
2129
2109
  if (autoMatch && autoMatch.project.id !== selectedProject.id) {
2130
2110
  console.log("");
2131
- 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"));
2132
- console.log(pc8.yellow("\u2502 \u26A0\uFE0F WARNING: You selected a different project \u2502"));
2133
- 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"));
2134
- console.log(pc8.yellow(` Current repo: ${repoFullName}`));
2135
- 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}`));
2136
2116
  if (selectedProject.linkedRepo) {
2137
- console.log(pc8.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2117
+ console.log(pc9.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2138
2118
  }
2139
2119
  console.log("");
2140
2120
  const { continueAnyway } = await prompts7({
@@ -2144,18 +2124,56 @@ async function syncCommand(provider, options = {}) {
2144
2124
  initial: false
2145
2125
  });
2146
2126
  if (!continueAnyway) {
2147
- console.log(pc8.gray("Cancelled."));
2127
+ console.log(pc9.gray("Cancelled."));
2148
2128
  process.exit(0);
2149
2129
  }
2150
2130
  }
2151
2131
  }
2152
- const keywayEnv = options.environment || "production";
2153
- const providerEnv = options.providerEnv || "production";
2154
- const direction = options.pull ? "pull" : "push";
2155
2132
  const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
2156
- console.log(pc8.gray(`Project: ${selectedProject.name}`));
2157
- console.log(pc8.gray(`Environment: ${keywayEnv}${providerEnv !== keywayEnv ? ` \u2192 ${providerEnv}` : ""}`));
2158
- 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";
2159
2177
  const status = await getSyncStatus(
2160
2178
  accessToken,
2161
2179
  repoFullName,
@@ -2163,10 +2181,10 @@ async function syncCommand(provider, options = {}) {
2163
2181
  selectedProject.id,
2164
2182
  keywayEnv
2165
2183
  );
2166
- if (status.isFirstSync && !options.pull && status.vaultIsEmpty && status.providerHasSecrets) {
2167
- console.log(pc8.yellow(`
2184
+ if (status.isFirstSync && direction === "push" && status.vaultIsEmpty && status.providerHasSecrets) {
2185
+ console.log(pc9.yellow(`
2168
2186
  \u26A0\uFE0F Your Keyway vault is empty for "${keywayEnv}", but ${providerName} has ${status.providerSecretCount} secrets.`));
2169
- console.log(pc8.gray(` (Use --environment to sync a different environment)`));
2187
+ console.log(pc9.gray(` (Use --environment to sync a different environment)`));
2170
2188
  const { importFirst } = await prompts7({
2171
2189
  type: "confirm",
2172
2190
  name: "importFirst",
@@ -2208,7 +2226,7 @@ async function syncCommand(provider, options = {}) {
2208
2226
  command: "sync",
2209
2227
  error: truncateMessage(message)
2210
2228
  });
2211
- console.error(pc8.red(`
2229
+ console.error(pc9.red(`
2212
2230
  \u2717 ${message}`));
2213
2231
  process.exit(1);
2214
2232
  }
@@ -2225,33 +2243,33 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
2225
2243
  });
2226
2244
  const totalChanges = preview.toCreate.length + preview.toUpdate.length + preview.toDelete.length;
2227
2245
  if (totalChanges === 0) {
2228
- 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."));
2229
2247
  return;
2230
2248
  }
2231
- console.log(pc8.blue("\n\u{1F4CB} Sync Preview\n"));
2249
+ console.log(pc9.blue("\n\u{1F4CB} Sync Preview\n"));
2232
2250
  if (preview.toCreate.length > 0) {
2233
- console.log(pc8.green(` + ${preview.toCreate.length} to create`));
2234
- 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}`)));
2235
2253
  if (preview.toCreate.length > 5) {
2236
- console.log(pc8.gray(` ... and ${preview.toCreate.length - 5} more`));
2254
+ console.log(pc9.gray(` ... and ${preview.toCreate.length - 5} more`));
2237
2255
  }
2238
2256
  }
2239
2257
  if (preview.toUpdate.length > 0) {
2240
- console.log(pc8.yellow(` ~ ${preview.toUpdate.length} to update`));
2241
- 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}`)));
2242
2260
  if (preview.toUpdate.length > 5) {
2243
- console.log(pc8.gray(` ... and ${preview.toUpdate.length - 5} more`));
2261
+ console.log(pc9.gray(` ... and ${preview.toUpdate.length - 5} more`));
2244
2262
  }
2245
2263
  }
2246
2264
  if (preview.toDelete.length > 0) {
2247
- console.log(pc8.red(` - ${preview.toDelete.length} to delete`));
2248
- 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}`)));
2249
2267
  if (preview.toDelete.length > 5) {
2250
- console.log(pc8.gray(` ... and ${preview.toDelete.length - 5} more`));
2268
+ console.log(pc9.gray(` ... and ${preview.toDelete.length - 5} more`));
2251
2269
  }
2252
2270
  }
2253
2271
  if (preview.toSkip.length > 0) {
2254
- console.log(pc8.gray(` \u25CB ${preview.toSkip.length} unchanged`));
2272
+ console.log(pc9.gray(` \u25CB ${preview.toSkip.length} unchanged`));
2255
2273
  }
2256
2274
  console.log("");
2257
2275
  if (!skipConfirm) {
@@ -2263,11 +2281,11 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
2263
2281
  initial: true
2264
2282
  });
2265
2283
  if (!confirm) {
2266
- console.log(pc8.gray("Cancelled."));
2284
+ console.log(pc9.gray("Cancelled."));
2267
2285
  return;
2268
2286
  }
2269
2287
  }
2270
- console.log(pc8.blue("\n\u23F3 Syncing...\n"));
2288
+ console.log(pc9.blue("\n\u23F3 Syncing...\n"));
2271
2289
  const result = await executeSync(accessToken, repoFullName, {
2272
2290
  connectionId,
2273
2291
  projectId: project.id,
@@ -2277,11 +2295,11 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
2277
2295
  allowDelete
2278
2296
  });
2279
2297
  if (result.success) {
2280
- console.log(pc8.green("\u2713 Sync complete"));
2281
- console.log(pc8.gray(` Created: ${result.stats.created}`));
2282
- 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}`));
2283
2301
  if (result.stats.deleted > 0) {
2284
- console.log(pc8.gray(` Deleted: ${result.stats.deleted}`));
2302
+ console.log(pc9.gray(` Deleted: ${result.stats.deleted}`));
2285
2303
  }
2286
2304
  trackEvent(AnalyticsEvents.CLI_SYNC, {
2287
2305
  provider,
@@ -2291,7 +2309,7 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
2291
2309
  deleted: result.stats.deleted
2292
2310
  });
2293
2311
  } else {
2294
- console.error(pc8.red(`
2312
+ console.error(pc9.red(`
2295
2313
  \u2717 ${result.error}`));
2296
2314
  process.exit(1);
2297
2315
  }
@@ -2301,13 +2319,14 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
2301
2319
  var program = new Command();
2302
2320
  var TAGLINE = "Sync secrets with your team and infra";
2303
2321
  var showBanner = () => {
2304
- const text = pc9.bold(pc9.cyan("Keyway CLI"));
2322
+ const text = pc10.bold(pc10.cyan("Keyway CLI"));
2305
2323
  console.log(`
2306
2324
  ${text}
2307
- ${pc9.gray(TAGLINE)}
2325
+ ${pc10.gray(TAGLINE)}
2308
2326
  `);
2309
2327
  };
2310
2328
  showBanner();
2329
+ warnIfEnvNotGitignored();
2311
2330
  program.name("keyway").description(TAGLINE).version(package_default.version);
2312
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) => {
2313
2332
  await initCommand(options);
@@ -2336,10 +2355,10 @@ program.command("connections").description("List your provider connections").opt
2336
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) => {
2337
2356
  await disconnectCommand(provider, options);
2338
2357
  });
2339
- 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) => {
2340
2359
  await syncCommand(provider, options);
2341
2360
  });
2342
2361
  program.parseAsync().catch((error) => {
2343
- console.error(pc9.red("Error:"), error.message || error);
2362
+ console.error(pc10.red("Error:"), error.message || error);
2344
2363
  process.exit(1);
2345
2364
  });