@keywaysh/cli 0.0.19 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +131 -28
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -69,7 +69,7 @@ var INTERNAL_POSTHOG_HOST = "https://eu.i.posthog.com";
69
69
  // package.json
70
70
  var package_default = {
71
71
  name: "@keywaysh/cli",
72
- version: "0.0.19",
72
+ version: "0.0.21",
73
73
  description: "One link to all your secrets",
74
74
  type: "module",
75
75
  bin: {
@@ -550,7 +550,8 @@ var AnalyticsEvents = {
550
550
  CLI_DOCTOR: "cli_doctor",
551
551
  CLI_CONNECT: "cli_connect",
552
552
  CLI_DISCONNECT: "cli_disconnect",
553
- CLI_SYNC: "cli_sync"
553
+ CLI_SYNC: "cli_sync",
554
+ CLI_FEEDBACK: "cli_feedback"
554
555
  };
555
556
 
556
557
  // src/cmds/login.ts
@@ -1756,14 +1757,66 @@ async function disconnectCommand(provider, options = {}) {
1756
1757
  import pc8 from "picocolors";
1757
1758
  import prompts7 from "prompts";
1758
1759
  function findMatchingProject(projects, repoFullName) {
1760
+ const repoFullNameLower = repoFullName.toLowerCase();
1759
1761
  const repoName = repoFullName.split("/")[1]?.toLowerCase();
1760
1762
  if (!repoName) return void 0;
1761
- const exact = projects.find((p) => p.name.toLowerCase() === repoName);
1762
- if (exact) return exact;
1763
- const partial = projects.filter(
1763
+ const linkedMatch = projects.find(
1764
+ (p) => p.linkedRepo?.toLowerCase() === repoFullNameLower
1765
+ );
1766
+ if (linkedMatch) {
1767
+ return { project: linkedMatch, matchType: "linked_repo" };
1768
+ }
1769
+ const exactNameMatch = projects.find((p) => p.name.toLowerCase() === repoName);
1770
+ if (exactNameMatch) {
1771
+ return { project: exactNameMatch, matchType: "exact_name" };
1772
+ }
1773
+ const partialMatches = projects.filter(
1764
1774
  (p) => p.name.toLowerCase().includes(repoName) || repoName.includes(p.name.toLowerCase())
1765
1775
  );
1766
- return partial.length === 1 ? partial[0] : void 0;
1776
+ if (partialMatches.length === 1) {
1777
+ return { project: partialMatches[0], matchType: "partial_name" };
1778
+ }
1779
+ return void 0;
1780
+ }
1781
+ function projectMatchesRepo(project, repoFullName) {
1782
+ const repoFullNameLower = repoFullName.toLowerCase();
1783
+ const repoName = repoFullName.split("/")[1]?.toLowerCase();
1784
+ if (project.linkedRepo?.toLowerCase() === repoFullNameLower) {
1785
+ return true;
1786
+ }
1787
+ if (repoName && project.name.toLowerCase() === repoName) {
1788
+ return true;
1789
+ }
1790
+ return false;
1791
+ }
1792
+ async function promptProjectSelection(projects, repoFullName) {
1793
+ const repoName = repoFullName.split("/")[1]?.toLowerCase() || "";
1794
+ const choices = projects.map((p) => {
1795
+ let title = p.name;
1796
+ const badges = [];
1797
+ if (p.linkedRepo?.toLowerCase() === repoFullName.toLowerCase()) {
1798
+ badges.push(pc8.green("\u2190 linked"));
1799
+ } else if (p.name.toLowerCase() === repoName) {
1800
+ badges.push(pc8.green("\u2190 same name"));
1801
+ } else if (p.linkedRepo) {
1802
+ badges.push(pc8.gray(`\u2192 ${p.linkedRepo}`));
1803
+ }
1804
+ if (badges.length > 0) {
1805
+ title = `${p.name} ${badges.join(" ")}`;
1806
+ }
1807
+ return { title, value: p.id };
1808
+ });
1809
+ const { projectChoice } = await prompts7({
1810
+ type: "select",
1811
+ name: "projectChoice",
1812
+ message: "Select a project:",
1813
+ choices
1814
+ });
1815
+ if (!projectChoice) {
1816
+ console.log(pc8.gray("Cancelled."));
1817
+ process.exit(0);
1818
+ }
1819
+ return projects.find((p) => p.id === projectChoice);
1767
1820
  }
1768
1821
  async function syncCommand(provider, options = {}) {
1769
1822
  try {
@@ -1804,43 +1857,91 @@ async function syncCommand(provider, options = {}) {
1804
1857
  process.exit(1);
1805
1858
  }
1806
1859
  selectedProject = found;
1860
+ if (!projectMatchesRepo(selectedProject, repoFullName)) {
1861
+ console.log("");
1862
+ 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"));
1863
+ console.log(pc8.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
1864
+ 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"));
1865
+ console.log(pc8.yellow(` Current repo: ${repoFullName}`));
1866
+ console.log(pc8.yellow(` Selected project: ${selectedProject.name}`));
1867
+ if (selectedProject.linkedRepo) {
1868
+ console.log(pc8.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
1869
+ }
1870
+ console.log("");
1871
+ }
1807
1872
  } else {
1808
1873
  const autoMatch = findMatchingProject(projects, repoFullName);
1809
- if (autoMatch && projects.length > 1) {
1810
- console.log(pc8.gray(`Detected project: ${autoMatch.name}`));
1874
+ if (autoMatch && (autoMatch.matchType === "linked_repo" || autoMatch.matchType === "exact_name")) {
1875
+ selectedProject = autoMatch.project;
1876
+ const matchReason = autoMatch.matchType === "linked_repo" ? `linked to ${repoFullName}` : "exact name match";
1877
+ console.log(pc8.green(`\u2713 Auto-selected project: ${selectedProject.name} (${matchReason})`));
1878
+ } else if (autoMatch && autoMatch.matchType === "partial_name") {
1879
+ console.log(pc8.yellow(`Detected project: ${autoMatch.project.name} (partial match)`));
1811
1880
  const { useDetected } = await prompts7({
1812
1881
  type: "confirm",
1813
1882
  name: "useDetected",
1814
- message: `Use ${autoMatch.name}?`,
1883
+ message: `Use ${autoMatch.project.name}?`,
1815
1884
  initial: true
1816
1885
  });
1817
1886
  if (useDetected) {
1818
- selectedProject = autoMatch;
1887
+ selectedProject = autoMatch.project;
1819
1888
  } else {
1820
- const { projectChoice } = await prompts7({
1821
- type: "select",
1822
- name: "projectChoice",
1823
- message: "Select a project:",
1824
- choices: projects.map((p) => ({ title: p.name, value: p.id }))
1825
- });
1826
- selectedProject = projects.find((p) => p.id === projectChoice);
1889
+ selectedProject = await promptProjectSelection(projects, repoFullName);
1827
1890
  }
1828
- } else if (autoMatch) {
1829
- selectedProject = autoMatch;
1830
1891
  } else if (projects.length === 1) {
1831
1892
  selectedProject = projects[0];
1893
+ if (!projectMatchesRepo(selectedProject, repoFullName)) {
1894
+ console.log("");
1895
+ 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"));
1896
+ console.log(pc8.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
1897
+ 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"));
1898
+ console.log(pc8.yellow(` Current repo: ${repoFullName}`));
1899
+ console.log(pc8.yellow(` Only project: ${selectedProject.name}`));
1900
+ if (selectedProject.linkedRepo) {
1901
+ console.log(pc8.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
1902
+ }
1903
+ console.log("");
1904
+ const { continueAnyway } = await prompts7({
1905
+ type: "confirm",
1906
+ name: "continueAnyway",
1907
+ message: "Continue anyway?",
1908
+ initial: false
1909
+ });
1910
+ if (!continueAnyway) {
1911
+ console.log(pc8.gray("Cancelled."));
1912
+ process.exit(0);
1913
+ }
1914
+ }
1832
1915
  } else {
1833
- const { projectChoice } = await prompts7({
1834
- type: "select",
1835
- name: "projectChoice",
1836
- message: "Select a project:",
1837
- choices: projects.map((p) => ({ title: p.name, value: p.id }))
1916
+ console.log(pc8.yellow(`
1917
+ \u26A0\uFE0F No matching project found for ${repoFullName}`));
1918
+ console.log(pc8.gray("Select a project manually:\n"));
1919
+ selectedProject = await promptProjectSelection(projects, repoFullName);
1920
+ }
1921
+ }
1922
+ if (!options.project && !projectMatchesRepo(selectedProject, repoFullName)) {
1923
+ const autoMatch = findMatchingProject(projects, repoFullName);
1924
+ if (autoMatch && autoMatch.project.id !== selectedProject.id) {
1925
+ console.log("");
1926
+ 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"));
1927
+ console.log(pc8.yellow("\u2502 \u26A0\uFE0F WARNING: You selected a different project \u2502"));
1928
+ 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"));
1929
+ console.log(pc8.yellow(` Current repo: ${repoFullName}`));
1930
+ console.log(pc8.yellow(` Selected project: ${selectedProject.name}`));
1931
+ if (selectedProject.linkedRepo) {
1932
+ console.log(pc8.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
1933
+ }
1934
+ console.log("");
1935
+ const { continueAnyway } = await prompts7({
1936
+ type: "confirm",
1937
+ name: "continueAnyway",
1938
+ message: "Are you sure you want to sync with this project?",
1939
+ initial: false
1838
1940
  });
1839
- if (!projectChoice) {
1941
+ if (!continueAnyway) {
1840
1942
  console.log(pc8.gray("Cancelled."));
1841
1943
  process.exit(0);
1842
1944
  }
1843
- selectedProject = projects.find((p) => p.id === projectChoice);
1844
1945
  }
1845
1946
  }
1846
1947
  const keywayEnv = options.environment || "production";
@@ -1848,6 +1949,7 @@ async function syncCommand(provider, options = {}) {
1848
1949
  const direction = options.pull ? "pull" : "push";
1849
1950
  const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
1850
1951
  console.log(pc8.gray(`Project: ${selectedProject.name}`));
1952
+ console.log(pc8.gray(`Environment: ${keywayEnv}${providerEnv !== keywayEnv ? ` \u2192 ${providerEnv}` : ""}`));
1851
1953
  console.log(pc8.gray(`Direction: ${direction === "push" ? "Keyway \u2192 " + providerName : providerName + " \u2192 Keyway"}`));
1852
1954
  const status = await getSyncStatus(
1853
1955
  accessToken,
@@ -1858,7 +1960,8 @@ async function syncCommand(provider, options = {}) {
1858
1960
  );
1859
1961
  if (status.isFirstSync && !options.pull && status.vaultIsEmpty && status.providerHasSecrets) {
1860
1962
  console.log(pc8.yellow(`
1861
- \u26A0\uFE0F Your Keyway vault is empty, but ${providerName} has ${status.providerSecretCount} secrets.`));
1963
+ \u26A0\uFE0F Your Keyway vault is empty for "${keywayEnv}", but ${providerName} has ${status.providerSecretCount} secrets.`));
1964
+ console.log(pc8.gray(` (Use --environment to sync a different environment)`));
1862
1965
  const { importFirst } = await prompts7({
1863
1966
  type: "confirm",
1864
1967
  name: "importFirst",
@@ -2028,7 +2131,7 @@ program.command("connections").description("List your provider connections").opt
2028
2131
  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) => {
2029
2132
  await disconnectCommand(provider, options);
2030
2133
  });
2031
- 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", "production").option("--provider-env <env>", "Provider environment", "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) => {
2134
+ 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) => {
2032
2135
  await syncCommand(provider, options);
2033
2136
  });
2034
2137
  program.parseAsync().catch((error) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keywaysh/cli",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "description": "One link to all your secrets",
5
5
  "type": "module",
6
6
  "bin": {