@keywaysh/cli 0.1.14 → 0.1.15

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 +365 -163
  2. package/package.json +4 -1
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
 
9
9
  // src/cli.ts
10
10
  import { Command } from "commander";
11
- import pc12 from "picocolors";
11
+ import pc13 from "picocolors";
12
12
 
13
13
  // src/cmds/init.ts
14
14
  import pc7 from "picocolors";
@@ -140,7 +140,7 @@ var INTERNAL_POSTHOG_HOST = "https://eu.i.posthog.com";
140
140
  // package.json
141
141
  var package_default = {
142
142
  name: "@keywaysh/cli",
143
- version: "0.1.14",
143
+ version: "0.1.15",
144
144
  description: "One link to all your secrets",
145
145
  type: "module",
146
146
  bin: {
@@ -185,9 +185,11 @@ var package_default = {
185
185
  node: ">=18.0.0"
186
186
  },
187
187
  dependencies: {
188
+ "@octokit/rest": "^22.0.1",
188
189
  "balanced-match": "^3.0.1",
189
190
  commander: "^14.0.0",
190
191
  conf: "^15.0.2",
192
+ "libsodium-wrappers": "^0.7.15",
191
193
  open: "^11.0.0",
192
194
  picocolors: "^1.1.1",
193
195
  "posthog-node": "^3.5.0",
@@ -195,6 +197,7 @@ var package_default = {
195
197
  },
196
198
  devDependencies: {
197
199
  "@types/balanced-match": "^3.0.2",
200
+ "@types/libsodium-wrappers": "^0.7.14",
198
201
  "@types/node": "^24.2.0",
199
202
  "@types/prompts": "^2.4.9",
200
203
  "@vitest/coverage-v8": "^3.0.0",
@@ -453,8 +456,8 @@ function getProviderAuthUrl(provider, accessToken, redirectUri) {
453
456
  if (redirectUri) params.set("redirect_uri", redirectUri);
454
457
  return `${API_BASE_URL}/v1/integrations/${provider}/authorize?${params}`;
455
458
  }
456
- async function getConnectionProjects(accessToken, connectionId) {
457
- const response = await fetchWithTimeout(`${API_BASE_URL}/v1/integrations/connections/${connectionId}/projects`, {
459
+ async function getAllProviderProjects(accessToken, provider) {
460
+ const response = await fetchWithTimeout(`${API_BASE_URL}/v1/integrations/providers/${provider}/all-projects`, {
458
461
  method: "GET",
459
462
  headers: {
460
463
  "User-Agent": USER_AGENT,
@@ -2043,9 +2046,139 @@ Summary: ${formatSummary(results)}`);
2043
2046
  }
2044
2047
  }
2045
2048
 
2046
- // src/cmds/connect.ts
2049
+ // src/cmds/ci.ts
2050
+ import { execSync as execSync3 } from "child_process";
2051
+ import { Octokit } from "@octokit/rest";
2047
2052
  import pc10 from "picocolors";
2048
2053
  import prompts8 from "prompts";
2054
+ function isGhAvailable() {
2055
+ try {
2056
+ execSync3("gh auth status", { stdio: "ignore" });
2057
+ return true;
2058
+ } catch {
2059
+ return false;
2060
+ }
2061
+ }
2062
+ function addSecretWithGh(repo, secretName, secretValue) {
2063
+ execSync3(`gh secret set ${secretName} --repo ${repo}`, {
2064
+ input: secretValue,
2065
+ stdio: ["pipe", "ignore", "ignore"]
2066
+ });
2067
+ }
2068
+ async function ciSetupCommand(options) {
2069
+ const repo = options.repo || detectGitRepo();
2070
+ if (!repo) {
2071
+ console.error(pc10.red("Not in a git repository. Use --repo owner/repo"));
2072
+ process.exit(1);
2073
+ }
2074
+ console.log(pc10.bold(`
2075
+ \u{1F510} Setting up GitHub Actions for ${repo}
2076
+ `));
2077
+ console.log(pc10.dim("Step 1: Keyway Authentication"));
2078
+ let keywayToken;
2079
+ try {
2080
+ keywayToken = await ensureLogin({ allowPrompt: true });
2081
+ console.log(pc10.green(" \u2713 Authenticated with Keyway\n"));
2082
+ } catch {
2083
+ console.error(pc10.red(" \u2717 Failed to authenticate with Keyway"));
2084
+ console.error(pc10.dim(" Run `keyway login` first"));
2085
+ process.exit(1);
2086
+ }
2087
+ const useGh = isGhAvailable();
2088
+ if (useGh) {
2089
+ console.log(pc10.dim("Step 2: Adding secret via GitHub CLI"));
2090
+ try {
2091
+ addSecretWithGh(repo, "KEYWAY_TOKEN", keywayToken);
2092
+ console.log(pc10.green(` \u2713 Secret KEYWAY_TOKEN added to ${repo}
2093
+ `));
2094
+ } catch (error) {
2095
+ const message = error instanceof Error ? error.message : String(error);
2096
+ console.error(pc10.red(` \u2717 Failed to add secret: ${message}`));
2097
+ console.error(pc10.dim(" Try running: gh auth login"));
2098
+ process.exit(1);
2099
+ }
2100
+ } else {
2101
+ console.log(pc10.dim("Step 2: Temporary GitHub PAT"));
2102
+ console.log(" gh CLI not found. We need a one-time GitHub PAT.");
2103
+ console.log(pc10.dim(" You can delete it immediately after setup.\n"));
2104
+ const patUrl = "https://github.com/settings/tokens/new?scopes=repo&description=Keyway%20CI%20Setup%20(temporary)";
2105
+ await openUrl(patUrl);
2106
+ const { githubToken } = await prompts8({
2107
+ type: "password",
2108
+ name: "githubToken",
2109
+ message: "Paste your GitHub PAT:"
2110
+ });
2111
+ if (!githubToken) {
2112
+ console.error(pc10.red("\n \u2717 GitHub PAT is required"));
2113
+ process.exit(1);
2114
+ }
2115
+ const octokit = new Octokit({ auth: githubToken });
2116
+ try {
2117
+ await octokit.users.getAuthenticated();
2118
+ console.log(pc10.green(" \u2713 GitHub PAT validated\n"));
2119
+ } catch {
2120
+ console.error(pc10.red(" \u2717 Invalid GitHub PAT"));
2121
+ process.exit(1);
2122
+ }
2123
+ console.log(pc10.dim("Step 3: Adding secret to repository"));
2124
+ const [owner, repoName] = repo.split("/");
2125
+ try {
2126
+ await addRepoSecret(octokit, owner, repoName, "KEYWAY_TOKEN", keywayToken);
2127
+ console.log(pc10.green(` \u2713 Secret KEYWAY_TOKEN added to ${repo}
2128
+ `));
2129
+ } catch (error) {
2130
+ const message = error instanceof Error ? error.message : String(error);
2131
+ if (message.includes("Not Found")) {
2132
+ console.error(pc10.red(` \u2717 Repository not found or no access: ${repo}`));
2133
+ console.error(pc10.dim(" Make sure the PAT has access to this repository"));
2134
+ } else {
2135
+ console.error(pc10.red(` \u2717 Failed to add secret: ${message}`));
2136
+ }
2137
+ process.exit(1);
2138
+ }
2139
+ }
2140
+ console.log(pc10.green(pc10.bold("\u2713 Setup complete!\n")));
2141
+ console.log("Add this to your workflow (.github/workflows/*.yml):\n");
2142
+ console.log(
2143
+ pc10.cyan(` - uses: keywaysh/keyway-action@v1
2144
+ with:
2145
+ token: \${{ secrets.KEYWAY_TOKEN }}
2146
+ environment: production`)
2147
+ );
2148
+ console.log();
2149
+ if (!useGh) {
2150
+ console.log(`\u{1F5D1}\uFE0F Delete the temporary PAT: ${pc10.underline("https://github.com/settings/tokens")}`);
2151
+ }
2152
+ console.log(pc10.dim(`\u{1F4D6} Docs: ${pc10.underline("https://docs.keyway.sh/ci")}
2153
+ `));
2154
+ }
2155
+ async function addRepoSecret(octokit, owner, repo, secretName, secretValue) {
2156
+ const { data: publicKey } = await octokit.rest.actions.getRepoPublicKey({
2157
+ owner,
2158
+ repo
2159
+ });
2160
+ const encryptedValue = await encryptSecret(publicKey.key, secretValue);
2161
+ await octokit.rest.actions.createOrUpdateRepoSecret({
2162
+ owner,
2163
+ repo,
2164
+ secret_name: secretName,
2165
+ encrypted_value: encryptedValue,
2166
+ key_id: publicKey.key_id
2167
+ });
2168
+ }
2169
+ async function encryptSecret(publicKey, secret) {
2170
+ const sodiumModule = await import("libsodium-wrappers");
2171
+ const sodium = sodiumModule.default || sodiumModule;
2172
+ await sodium.ready;
2173
+ const binkey = sodium.from_base64(publicKey, sodium.base64_variants.ORIGINAL);
2174
+ const binsec = sodium.from_string(secret);
2175
+ const encBytes = sodium.crypto_box_seal(binsec, binkey);
2176
+ return sodium.to_base64(encBytes, sodium.base64_variants.ORIGINAL);
2177
+ }
2178
+
2179
+ // src/cmds/connect.ts
2180
+ import pc11 from "picocolors";
2181
+ import prompts9 from "prompts";
2049
2182
  var TOKEN_AUTH_PROVIDERS = ["railway"];
2050
2183
  function getTokenCreationUrl(provider) {
2051
2184
  switch (provider) {
@@ -2058,37 +2191,37 @@ function getTokenCreationUrl(provider) {
2058
2191
  async function connectWithTokenFlow(accessToken, provider, displayName) {
2059
2192
  const tokenUrl = getTokenCreationUrl(provider);
2060
2193
  if (provider === "railway") {
2061
- console.log(pc10.yellow("\nTip: Select the workspace containing your projects."));
2062
- console.log(pc10.yellow(` Do NOT use "No workspace" - it won't have access to your projects.`));
2194
+ console.log(pc11.yellow("\nTip: Select the workspace containing your projects."));
2195
+ console.log(pc11.yellow(` Do NOT use "No workspace" - it won't have access to your projects.`));
2063
2196
  }
2064
2197
  await openUrl(tokenUrl);
2065
- const { token } = await prompts8({
2198
+ const { token } = await prompts9({
2066
2199
  type: "password",
2067
2200
  name: "token",
2068
2201
  message: `${displayName} API Token:`
2069
2202
  });
2070
2203
  if (!token) {
2071
- console.log(pc10.gray("Cancelled."));
2204
+ console.log(pc11.gray("Cancelled."));
2072
2205
  return false;
2073
2206
  }
2074
- console.log(pc10.gray("\nValidating token..."));
2207
+ console.log(pc11.gray("\nValidating token..."));
2075
2208
  try {
2076
2209
  const result = await connectWithToken(accessToken, provider, token);
2077
2210
  if (result.success) {
2078
- console.log(pc10.green(`
2211
+ console.log(pc11.green(`
2079
2212
  \u2713 Connected to ${displayName}!`));
2080
- console.log(pc10.gray(` Account: ${result.user.username}`));
2213
+ console.log(pc11.gray(` Account: ${result.user.username}`));
2081
2214
  if (result.user.teamName) {
2082
- console.log(pc10.gray(` Team: ${result.user.teamName}`));
2215
+ console.log(pc11.gray(` Team: ${result.user.teamName}`));
2083
2216
  }
2084
2217
  return true;
2085
2218
  } else {
2086
- console.log(pc10.red("\n\u2717 Connection failed."));
2219
+ console.log(pc11.red("\n\u2717 Connection failed."));
2087
2220
  return false;
2088
2221
  }
2089
2222
  } catch (error) {
2090
2223
  const message = error instanceof Error ? error.message : "Token validation failed";
2091
- console.log(pc10.red(`
2224
+ console.log(pc11.red(`
2092
2225
  \u2717 ${message}`));
2093
2226
  return false;
2094
2227
  }
@@ -2097,7 +2230,7 @@ async function connectWithOAuthFlow(accessToken, provider, displayName) {
2097
2230
  const authUrl = getProviderAuthUrl(provider, accessToken);
2098
2231
  const startTime = /* @__PURE__ */ new Date();
2099
2232
  await openUrl(authUrl);
2100
- console.log(pc10.gray("Waiting for authorization..."));
2233
+ console.log(pc11.gray("Waiting for authorization..."));
2101
2234
  const maxAttempts = 60;
2102
2235
  let attempts = 0;
2103
2236
  while (attempts < maxAttempts) {
@@ -2109,15 +2242,15 @@ async function connectWithOAuthFlow(accessToken, provider, displayName) {
2109
2242
  (c) => c.provider === provider && new Date(c.createdAt) > startTime
2110
2243
  );
2111
2244
  if (newConn) {
2112
- console.log(pc10.green(`
2245
+ console.log(pc11.green(`
2113
2246
  \u2713 Connected to ${displayName}!`));
2114
2247
  return true;
2115
2248
  }
2116
2249
  } catch {
2117
2250
  }
2118
2251
  }
2119
- console.log(pc10.red("\n\u2717 Authorization timeout."));
2120
- console.log(pc10.gray("Run `keyway connections` to check if the connection was established."));
2252
+ console.log(pc11.red("\n\u2717 Authorization timeout."));
2253
+ console.log(pc11.gray("Run `keyway connections` to check if the connection was established."));
2121
2254
  return false;
2122
2255
  }
2123
2256
  async function connectCommand(provider, options = {}) {
@@ -2127,30 +2260,40 @@ async function connectCommand(provider, options = {}) {
2127
2260
  const providerInfo = providers.find((p) => p.name === provider.toLowerCase());
2128
2261
  if (!providerInfo) {
2129
2262
  const available = providers.map((p) => p.name).join(", ");
2130
- console.error(pc10.red(`Unknown provider: ${provider}`));
2131
- console.log(pc10.gray(`Available providers: ${available || "none"}`));
2263
+ console.error(pc11.red(`Unknown provider: ${provider}`));
2264
+ console.log(pc11.gray(`Available providers: ${available || "none"}`));
2132
2265
  process.exit(1);
2133
2266
  }
2134
2267
  if (!providerInfo.configured) {
2135
- console.error(pc10.red(`Provider ${providerInfo.displayName} is not configured on the server.`));
2136
- console.log(pc10.gray("Contact your administrator to enable this integration."));
2268
+ console.error(pc11.red(`Provider ${providerInfo.displayName} is not configured on the server.`));
2269
+ console.log(pc11.gray("Contact your administrator to enable this integration."));
2137
2270
  process.exit(1);
2138
2271
  }
2139
2272
  const { connections } = await getConnections(accessToken);
2140
- const existingConnection = connections.find((c) => c.provider === provider.toLowerCase());
2141
- if (existingConnection) {
2142
- const { reconnect } = await prompts8({
2143
- type: "confirm",
2144
- name: "reconnect",
2145
- message: `You're already connected to ${providerInfo.displayName}. Reconnect?`,
2146
- initial: false
2273
+ const existingConnections = connections.filter((c) => c.provider === provider.toLowerCase());
2274
+ if (existingConnections.length > 0) {
2275
+ console.log(pc11.gray(`
2276
+ You have ${existingConnections.length} ${providerInfo.displayName} connection(s):`));
2277
+ for (const conn of existingConnections) {
2278
+ const teamInfo = conn.providerTeamId ? `(Team: ${conn.providerTeamId})` : "(Personal)";
2279
+ console.log(pc11.gray(` - ${teamInfo}`));
2280
+ }
2281
+ console.log("");
2282
+ const { action } = await prompts9({
2283
+ type: "select",
2284
+ name: "action",
2285
+ message: "What would you like to do?",
2286
+ choices: [
2287
+ { title: "Add another account/team", value: "add" },
2288
+ { title: "Cancel", value: "cancel" }
2289
+ ]
2147
2290
  });
2148
- if (!reconnect) {
2149
- console.log(pc10.gray("Keeping existing connection."));
2291
+ if (action !== "add") {
2292
+ console.log(pc11.gray("Keeping existing connections."));
2150
2293
  return;
2151
2294
  }
2152
2295
  }
2153
- console.log(pc10.blue(`
2296
+ console.log(pc11.blue(`
2154
2297
  Connecting to ${providerInfo.displayName}...
2155
2298
  `));
2156
2299
  let connected = false;
@@ -2169,7 +2312,7 @@ Connecting to ${providerInfo.displayName}...
2169
2312
  command: "connect",
2170
2313
  error: truncateMessage(message)
2171
2314
  });
2172
- console.error(pc10.red(`
2315
+ console.error(pc11.red(`
2173
2316
  \u2717 ${message}`));
2174
2317
  process.exit(1);
2175
2318
  }
@@ -2179,24 +2322,24 @@ async function connectionsCommand(options = {}) {
2179
2322
  const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
2180
2323
  const { connections } = await getConnections(accessToken);
2181
2324
  if (connections.length === 0) {
2182
- console.log(pc10.gray("No provider connections found."));
2183
- console.log(pc10.gray("\nConnect to a provider with: keyway connect <provider>"));
2184
- console.log(pc10.gray("Available providers: vercel, railway"));
2325
+ console.log(pc11.gray("No provider connections found."));
2326
+ console.log(pc11.gray("\nConnect to a provider with: keyway connect <provider>"));
2327
+ console.log(pc11.gray("Available providers: vercel, railway"));
2185
2328
  return;
2186
2329
  }
2187
- console.log(pc10.blue("\n\u{1F4E1} Provider Connections\n"));
2330
+ console.log(pc11.blue("\n\u{1F4E1} Provider Connections\n"));
2188
2331
  for (const conn of connections) {
2189
2332
  const providerName = conn.provider.charAt(0).toUpperCase() + conn.provider.slice(1);
2190
- const teamInfo = conn.providerTeamId ? pc10.gray(` (Team: ${conn.providerTeamId})`) : "";
2333
+ const teamInfo = conn.providerTeamId ? pc11.gray(` (Team: ${conn.providerTeamId})`) : "";
2191
2334
  const date = new Date(conn.createdAt).toLocaleDateString();
2192
- console.log(` ${pc10.green("\u25CF")} ${pc10.bold(providerName)}${teamInfo}`);
2193
- console.log(pc10.gray(` Connected: ${date}`));
2194
- console.log(pc10.gray(` ID: ${conn.id}`));
2335
+ console.log(` ${pc11.green("\u25CF")} ${pc11.bold(providerName)}${teamInfo}`);
2336
+ console.log(pc11.gray(` Connected: ${date}`));
2337
+ console.log(pc11.gray(` ID: ${conn.id}`));
2195
2338
  console.log("");
2196
2339
  }
2197
2340
  } catch (error) {
2198
2341
  const message = error instanceof Error ? error.message : "Failed to list connections";
2199
- console.error(pc10.red(`
2342
+ console.error(pc11.red(`
2200
2343
  \u2717 ${message}`));
2201
2344
  process.exit(1);
2202
2345
  }
@@ -2207,22 +2350,22 @@ async function disconnectCommand(provider, options = {}) {
2207
2350
  const { connections } = await getConnections(accessToken);
2208
2351
  const connection = connections.find((c) => c.provider === provider.toLowerCase());
2209
2352
  if (!connection) {
2210
- console.log(pc10.gray(`No connection found for provider: ${provider}`));
2353
+ console.log(pc11.gray(`No connection found for provider: ${provider}`));
2211
2354
  return;
2212
2355
  }
2213
2356
  const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
2214
- const { confirm } = await prompts8({
2357
+ const { confirm } = await prompts9({
2215
2358
  type: "confirm",
2216
2359
  name: "confirm",
2217
2360
  message: `Disconnect from ${providerName}?`,
2218
2361
  initial: false
2219
2362
  });
2220
2363
  if (!confirm) {
2221
- console.log(pc10.gray("Cancelled."));
2364
+ console.log(pc11.gray("Cancelled."));
2222
2365
  return;
2223
2366
  }
2224
2367
  await deleteConnection(accessToken, connection.id);
2225
- console.log(pc10.green(`
2368
+ console.log(pc11.green(`
2226
2369
  \u2713 Disconnected from ${providerName}`));
2227
2370
  trackEvent(AnalyticsEvents.CLI_DISCONNECT, {
2228
2371
  provider: provider.toLowerCase()
@@ -2233,15 +2376,15 @@ async function disconnectCommand(provider, options = {}) {
2233
2376
  command: "disconnect",
2234
2377
  error: truncateMessage(message)
2235
2378
  });
2236
- console.error(pc10.red(`
2379
+ console.error(pc11.red(`
2237
2380
  \u2717 ${message}`));
2238
2381
  process.exit(1);
2239
2382
  }
2240
2383
  }
2241
2384
 
2242
2385
  // src/cmds/sync.ts
2243
- import pc11 from "picocolors";
2244
- import prompts9 from "prompts";
2386
+ import pc12 from "picocolors";
2387
+ import prompts10 from "prompts";
2245
2388
  function mapToVercelEnvironment(keywayEnv) {
2246
2389
  const mapping = {
2247
2390
  production: "production",
@@ -2285,36 +2428,36 @@ function mapToProviderEnvironment(provider, keywayEnv) {
2285
2428
  function displayDiffSummary(diff, providerName) {
2286
2429
  const totalDiff = diff.onlyInKeyway.length + diff.onlyInProvider.length + diff.different.length;
2287
2430
  if (totalDiff === 0 && diff.same.length > 0) {
2288
- console.log(pc11.green(`
2431
+ console.log(pc12.green(`
2289
2432
  \u2713 Already in sync (${diff.same.length} secrets)`));
2290
2433
  return;
2291
2434
  }
2292
- console.log(pc11.blue("\n\u{1F4CA} Comparison Summary\n"));
2293
- console.log(pc11.gray(` Keyway: ${diff.keywayCount} secrets | ${providerName}: ${diff.providerCount} secrets
2435
+ console.log(pc12.blue("\n\u{1F4CA} Comparison Summary\n"));
2436
+ console.log(pc12.gray(` Keyway: ${diff.keywayCount} secrets | ${providerName}: ${diff.providerCount} secrets
2294
2437
  `));
2295
2438
  if (diff.onlyInKeyway.length > 0) {
2296
- console.log(pc11.cyan(` \u2192 ${diff.onlyInKeyway.length} only in Keyway`));
2297
- diff.onlyInKeyway.slice(0, 3).forEach((key) => console.log(pc11.gray(` ${key}`)));
2439
+ console.log(pc12.cyan(` \u2192 ${diff.onlyInKeyway.length} only in Keyway`));
2440
+ diff.onlyInKeyway.slice(0, 3).forEach((key) => console.log(pc12.gray(` ${key}`)));
2298
2441
  if (diff.onlyInKeyway.length > 3) {
2299
- console.log(pc11.gray(` ... and ${diff.onlyInKeyway.length - 3} more`));
2442
+ console.log(pc12.gray(` ... and ${diff.onlyInKeyway.length - 3} more`));
2300
2443
  }
2301
2444
  }
2302
2445
  if (diff.onlyInProvider.length > 0) {
2303
- console.log(pc11.magenta(` \u2190 ${diff.onlyInProvider.length} only in ${providerName}`));
2304
- diff.onlyInProvider.slice(0, 3).forEach((key) => console.log(pc11.gray(` ${key}`)));
2446
+ console.log(pc12.magenta(` \u2190 ${diff.onlyInProvider.length} only in ${providerName}`));
2447
+ diff.onlyInProvider.slice(0, 3).forEach((key) => console.log(pc12.gray(` ${key}`)));
2305
2448
  if (diff.onlyInProvider.length > 3) {
2306
- console.log(pc11.gray(` ... and ${diff.onlyInProvider.length - 3} more`));
2449
+ console.log(pc12.gray(` ... and ${diff.onlyInProvider.length - 3} more`));
2307
2450
  }
2308
2451
  }
2309
2452
  if (diff.different.length > 0) {
2310
- console.log(pc11.yellow(` \u2260 ${diff.different.length} with different values`));
2311
- diff.different.slice(0, 3).forEach((key) => console.log(pc11.gray(` ${key}`)));
2453
+ console.log(pc12.yellow(` \u2260 ${diff.different.length} with different values`));
2454
+ diff.different.slice(0, 3).forEach((key) => console.log(pc12.gray(` ${key}`)));
2312
2455
  if (diff.different.length > 3) {
2313
- console.log(pc11.gray(` ... and ${diff.different.length - 3} more`));
2456
+ console.log(pc12.gray(` ... and ${diff.different.length - 3} more`));
2314
2457
  }
2315
2458
  }
2316
2459
  if (diff.same.length > 0) {
2317
- console.log(pc11.gray(` = ${diff.same.length} identical`));
2460
+ console.log(pc12.gray(` = ${diff.same.length} identical`));
2318
2461
  }
2319
2462
  console.log("");
2320
2463
  }
@@ -2356,30 +2499,42 @@ function projectMatchesRepo(project, repoFullName) {
2356
2499
  }
2357
2500
  async function promptProjectSelection(projects, repoFullName) {
2358
2501
  const repoName = repoFullName.split("/")[1]?.toLowerCase() || "";
2502
+ const uniqueTeams = new Set(projects.map((p) => p.teamId || "personal"));
2503
+ const hasMultipleAccounts = uniqueTeams.size > 1;
2359
2504
  const choices = projects.map((p) => {
2360
2505
  const displayName = getProjectDisplayName(p);
2361
2506
  let title = displayName;
2362
2507
  const badges = [];
2508
+ if (hasMultipleAccounts) {
2509
+ if (p.teamName) {
2510
+ badges.push(pc12.cyan(`[${p.teamName}]`));
2511
+ } else if (p.teamId) {
2512
+ const shortTeamId = p.teamId.length > 12 ? p.teamId.slice(0, 12) + "..." : p.teamId;
2513
+ badges.push(pc12.cyan(`[team:${shortTeamId}]`));
2514
+ } else {
2515
+ badges.push(pc12.cyan("[personal]"));
2516
+ }
2517
+ }
2363
2518
  if (p.linkedRepo?.toLowerCase() === repoFullName.toLowerCase()) {
2364
- badges.push(pc11.green("\u2190 linked"));
2519
+ badges.push(pc12.green("\u2190 linked"));
2365
2520
  } else if (p.name.toLowerCase() === repoName || p.serviceName?.toLowerCase() === repoName) {
2366
- badges.push(pc11.green("\u2190 same name"));
2521
+ badges.push(pc12.green("\u2190 same name"));
2367
2522
  } else if (p.linkedRepo) {
2368
- badges.push(pc11.gray(`\u2192 ${p.linkedRepo}`));
2523
+ badges.push(pc12.gray(`\u2192 ${p.linkedRepo}`));
2369
2524
  }
2370
2525
  if (badges.length > 0) {
2371
2526
  title = `${displayName} ${badges.join(" ")}`;
2372
2527
  }
2373
2528
  return { title, value: p.id };
2374
2529
  });
2375
- const { projectChoice } = await prompts9({
2530
+ const { projectChoice } = await prompts10({
2376
2531
  type: "select",
2377
2532
  name: "projectChoice",
2378
2533
  message: "Select a project:",
2379
2534
  choices
2380
2535
  });
2381
2536
  if (!projectChoice) {
2382
- console.log(pc11.gray("Cancelled."));
2537
+ console.log(pc12.gray("Cancelled."));
2383
2538
  process.exit(0);
2384
2539
  }
2385
2540
  return projects.find((p) => p.id === projectChoice);
@@ -2387,97 +2542,133 @@ async function promptProjectSelection(projects, repoFullName) {
2387
2542
  async function syncCommand(provider, options = {}) {
2388
2543
  try {
2389
2544
  if (options.pull && options.allowDelete) {
2390
- console.error(pc11.red("Error: --allow-delete cannot be used with --pull"));
2391
- console.log(pc11.gray("The --allow-delete flag is only for push operations."));
2545
+ console.error(pc12.red("Error: --allow-delete cannot be used with --pull"));
2546
+ console.log(pc12.gray("The --allow-delete flag is only for push operations."));
2392
2547
  process.exit(1);
2393
2548
  }
2394
2549
  const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
2395
2550
  const repoFullName = detectGitRepo();
2396
2551
  if (!repoFullName) {
2397
- console.error(pc11.red("Could not detect Git repository."));
2398
- console.log(pc11.gray("Run this command from a Git repository directory."));
2552
+ console.error(pc12.red("Could not detect Git repository."));
2553
+ console.log(pc12.gray("Run this command from a Git repository directory."));
2399
2554
  process.exit(1);
2400
2555
  }
2401
- console.log(pc11.gray(`Repository: ${repoFullName}`));
2556
+ console.log(pc12.gray(`Repository: ${repoFullName}`));
2402
2557
  const vaultExists = await checkVaultExists(accessToken, repoFullName);
2403
2558
  if (!vaultExists) {
2404
- console.log(pc11.yellow(`
2559
+ console.log(pc12.yellow(`
2405
2560
  No vault found for ${repoFullName}.`));
2406
- const { shouldCreate } = await prompts9({
2561
+ const { shouldCreate } = await prompts10({
2407
2562
  type: "confirm",
2408
2563
  name: "shouldCreate",
2409
2564
  message: "Create vault now?",
2410
2565
  initial: true
2411
2566
  });
2412
2567
  if (!shouldCreate) {
2413
- console.log(pc11.gray("Cancelled. Run `keyway init` to create a vault first."));
2568
+ console.log(pc12.gray("Cancelled. Run `keyway init` to create a vault first."));
2414
2569
  process.exit(0);
2415
2570
  }
2416
- console.log(pc11.gray("\nCreating vault..."));
2571
+ console.log(pc12.gray("\nCreating vault..."));
2417
2572
  try {
2418
2573
  await initVault(repoFullName, accessToken);
2419
- console.log(pc11.green(`\u2713 Vault created for ${repoFullName}
2574
+ console.log(pc12.green(`\u2713 Vault created for ${repoFullName}
2420
2575
  `));
2421
2576
  } catch (error) {
2422
2577
  const message = error instanceof Error ? error.message : "Failed to create vault";
2423
- console.error(pc11.red(`
2578
+ console.error(pc12.red(`
2424
2579
  \u2717 ${message}`));
2425
2580
  process.exit(1);
2426
2581
  }
2427
2582
  }
2428
- let { connections } = await getConnections(accessToken);
2429
- let connection = connections.find((c) => c.provider === provider.toLowerCase());
2430
- if (!connection) {
2431
- const providerDisplayName = provider.charAt(0).toUpperCase() + provider.slice(1);
2432
- console.log(pc11.yellow(`
2583
+ const providerDisplayName = provider.charAt(0).toUpperCase() + provider.slice(1);
2584
+ let { projects: allProjects, connections } = await getAllProviderProjects(accessToken, provider.toLowerCase());
2585
+ if (connections.length === 0) {
2586
+ console.log(pc12.yellow(`
2433
2587
  Not connected to ${providerDisplayName}.`));
2434
- const { shouldConnect } = await prompts9({
2588
+ const { shouldConnect } = await prompts10({
2435
2589
  type: "confirm",
2436
2590
  name: "shouldConnect",
2437
2591
  message: `Connect to ${providerDisplayName} now?`,
2438
2592
  initial: true
2439
2593
  });
2440
2594
  if (!shouldConnect) {
2441
- console.log(pc11.gray("Cancelled."));
2595
+ console.log(pc12.gray("Cancelled."));
2442
2596
  process.exit(0);
2443
2597
  }
2444
2598
  await connectCommand(provider, { loginPrompt: false });
2445
- const refreshed = await getConnections(accessToken);
2599
+ const refreshed = await getAllProviderProjects(accessToken, provider.toLowerCase());
2600
+ allProjects = refreshed.projects;
2446
2601
  connections = refreshed.connections;
2447
- connection = connections.find((c) => c.provider === provider.toLowerCase());
2448
- if (!connection) {
2449
- console.error(pc11.red(`
2602
+ if (connections.length === 0) {
2603
+ console.error(pc12.red(`
2450
2604
  Connection to ${providerDisplayName} failed.`));
2451
2605
  process.exit(1);
2452
2606
  }
2453
2607
  console.log("");
2454
2608
  }
2455
- const { projects } = await getConnectionProjects(accessToken, connection.id);
2609
+ let projects = allProjects.map((p) => ({
2610
+ id: p.id,
2611
+ name: p.name,
2612
+ serviceId: p.serviceId,
2613
+ serviceName: p.serviceName,
2614
+ linkedRepo: p.linkedRepo,
2615
+ environments: p.environments,
2616
+ connectionId: p.connectionId,
2617
+ teamId: p.teamId,
2618
+ teamName: p.teamName
2619
+ }));
2620
+ if (options.team) {
2621
+ const teamFilter = options.team.toLowerCase();
2622
+ const filteredProjects = projects.filter(
2623
+ (p) => p.teamId?.toLowerCase() === teamFilter || p.teamName?.toLowerCase() === teamFilter || // Match "personal" for null teamId
2624
+ teamFilter === "personal" && !p.teamId
2625
+ );
2626
+ if (filteredProjects.length === 0) {
2627
+ console.error(pc12.red(`No projects found for team: ${options.team}`));
2628
+ console.log(pc12.gray("Available teams:"));
2629
+ const teams = /* @__PURE__ */ new Set();
2630
+ projects.forEach((p) => {
2631
+ if (p.teamName) teams.add(p.teamName);
2632
+ else if (p.teamId) teams.add(p.teamId);
2633
+ else teams.add("personal");
2634
+ });
2635
+ teams.forEach((t) => console.log(pc12.gray(` - ${t}`)));
2636
+ process.exit(1);
2637
+ }
2638
+ projects = filteredProjects;
2639
+ console.log(pc12.gray(`Filtered to ${projects.length} projects in team: ${options.team}`));
2640
+ }
2456
2641
  if (projects.length === 0) {
2457
- console.error(pc11.red(`No projects found in your ${provider} account.`));
2642
+ console.error(pc12.red(`No projects found in your ${providerDisplayName} account(s).`));
2643
+ if (connections.length > 1) {
2644
+ console.log(pc12.gray(`Checked ${connections.length} connected accounts.`));
2645
+ }
2458
2646
  process.exit(1);
2459
2647
  }
2648
+ if (connections.length > 1 && !options.team) {
2649
+ console.log(pc12.gray(`Searching ${projects.length} projects across ${connections.length} ${providerDisplayName} accounts...`));
2650
+ }
2460
2651
  let selectedProject;
2461
2652
  if (options.project) {
2462
2653
  const found = projects.find(
2463
2654
  (p) => p.id === options.project || p.name.toLowerCase() === options.project?.toLowerCase() || p.serviceName?.toLowerCase() === options.project?.toLowerCase()
2464
2655
  );
2465
2656
  if (!found) {
2466
- console.error(pc11.red(`Project not found: ${options.project}`));
2467
- console.log(pc11.gray("Available projects:"));
2468
- projects.forEach((p) => console.log(pc11.gray(` - ${getProjectDisplayName(p)}`)));
2657
+ console.error(pc12.red(`Project not found: ${options.project}`));
2658
+ console.log(pc12.gray("Available projects:"));
2659
+ projects.forEach((p) => console.log(pc12.gray(` - ${getProjectDisplayName(p)}`)));
2469
2660
  process.exit(1);
2470
2661
  }
2471
2662
  selectedProject = found;
2472
2663
  if (!projectMatchesRepo(selectedProject, repoFullName)) {
2473
2664
  console.log("");
2474
- console.log(pc11.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"));
2475
- console.log(pc11.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
2476
- console.log(pc11.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"));
2477
- console.log(pc11.yellow(` Current repo: ${repoFullName}`));
2478
- console.log(pc11.yellow(` Selected project: ${getProjectDisplayName(selectedProject)}`));
2665
+ console.log(pc12.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"));
2666
+ console.log(pc12.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
2667
+ console.log(pc12.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"));
2668
+ console.log(pc12.yellow(` Current repo: ${repoFullName}`));
2669
+ console.log(pc12.yellow(` Selected project: ${getProjectDisplayName(selectedProject)}`));
2479
2670
  if (selectedProject.linkedRepo) {
2480
- console.log(pc11.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2671
+ console.log(pc12.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2481
2672
  }
2482
2673
  console.log("");
2483
2674
  }
@@ -2486,11 +2677,18 @@ Connection to ${providerDisplayName} failed.`));
2486
2677
  if (autoMatch && (autoMatch.matchType === "linked_repo" || autoMatch.matchType === "exact_name")) {
2487
2678
  selectedProject = autoMatch.project;
2488
2679
  const matchReason = autoMatch.matchType === "linked_repo" ? `linked to ${repoFullName}` : "exact name match";
2489
- console.log(pc11.green(`\u2713 Auto-selected project: ${getProjectDisplayName(selectedProject)} (${matchReason})`));
2680
+ let teamInfo = "";
2681
+ if (selectedProject.teamName) {
2682
+ teamInfo = pc12.gray(` (${selectedProject.teamName})`);
2683
+ } else if (selectedProject.teamId && connections.length > 1) {
2684
+ const shortTeamId = selectedProject.teamId.length > 12 ? selectedProject.teamId.slice(0, 12) + "..." : selectedProject.teamId;
2685
+ teamInfo = pc12.gray(` (team:${shortTeamId})`);
2686
+ }
2687
+ console.log(pc12.green(`\u2713 Auto-selected project: ${getProjectDisplayName(selectedProject)}${teamInfo} (${matchReason})`));
2490
2688
  } else if (autoMatch && autoMatch.matchType === "partial_name") {
2491
2689
  const partialDisplayName = getProjectDisplayName(autoMatch.project);
2492
- console.log(pc11.yellow(`Detected project: ${partialDisplayName} (partial match)`));
2493
- const { useDetected } = await prompts9({
2690
+ console.log(pc12.yellow(`Detected project: ${partialDisplayName} (partial match)`));
2691
+ const { useDetected } = await prompts10({
2494
2692
  type: "confirm",
2495
2693
  name: "useDetected",
2496
2694
  message: `Use ${partialDisplayName}?`,
@@ -2505,30 +2703,30 @@ Connection to ${providerDisplayName} failed.`));
2505
2703
  selectedProject = projects[0];
2506
2704
  if (!projectMatchesRepo(selectedProject, repoFullName)) {
2507
2705
  console.log("");
2508
- console.log(pc11.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"));
2509
- console.log(pc11.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
2510
- console.log(pc11.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"));
2511
- console.log(pc11.yellow(` Current repo: ${repoFullName}`));
2512
- console.log(pc11.yellow(` Only project: ${getProjectDisplayName(selectedProject)}`));
2706
+ console.log(pc12.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"));
2707
+ console.log(pc12.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
2708
+ console.log(pc12.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"));
2709
+ console.log(pc12.yellow(` Current repo: ${repoFullName}`));
2710
+ console.log(pc12.yellow(` Only project: ${getProjectDisplayName(selectedProject)}`));
2513
2711
  if (selectedProject.linkedRepo) {
2514
- console.log(pc11.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2712
+ console.log(pc12.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2515
2713
  }
2516
2714
  console.log("");
2517
- const { continueAnyway } = await prompts9({
2715
+ const { continueAnyway } = await prompts10({
2518
2716
  type: "confirm",
2519
2717
  name: "continueAnyway",
2520
2718
  message: "Continue anyway?",
2521
2719
  initial: false
2522
2720
  });
2523
2721
  if (!continueAnyway) {
2524
- console.log(pc11.gray("Cancelled."));
2722
+ console.log(pc12.gray("Cancelled."));
2525
2723
  process.exit(0);
2526
2724
  }
2527
2725
  }
2528
2726
  } else {
2529
- console.log(pc11.yellow(`
2727
+ console.log(pc12.yellow(`
2530
2728
  \u26A0\uFE0F No matching project found for ${repoFullName}`));
2531
- console.log(pc11.gray("Select a project manually:\n"));
2729
+ console.log(pc12.gray("Select a project manually:\n"));
2532
2730
  selectedProject = await promptProjectSelection(projects, repoFullName);
2533
2731
  }
2534
2732
  }
@@ -2536,23 +2734,23 @@ Connection to ${providerDisplayName} failed.`));
2536
2734
  const autoMatch = findMatchingProject(projects, repoFullName);
2537
2735
  if (autoMatch && autoMatch.project.id !== selectedProject.id) {
2538
2736
  console.log("");
2539
- console.log(pc11.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"));
2540
- console.log(pc11.yellow("\u2502 \u26A0\uFE0F WARNING: You selected a different project \u2502"));
2541
- console.log(pc11.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"));
2542
- console.log(pc11.yellow(` Current repo: ${repoFullName}`));
2543
- console.log(pc11.yellow(` Selected project: ${getProjectDisplayName(selectedProject)}`));
2737
+ console.log(pc12.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"));
2738
+ console.log(pc12.yellow("\u2502 \u26A0\uFE0F WARNING: You selected a different project \u2502"));
2739
+ console.log(pc12.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"));
2740
+ console.log(pc12.yellow(` Current repo: ${repoFullName}`));
2741
+ console.log(pc12.yellow(` Selected project: ${getProjectDisplayName(selectedProject)}`));
2544
2742
  if (selectedProject.linkedRepo) {
2545
- console.log(pc11.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2743
+ console.log(pc12.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2546
2744
  }
2547
2745
  console.log("");
2548
- const { continueAnyway } = await prompts9({
2746
+ const { continueAnyway } = await prompts10({
2549
2747
  type: "confirm",
2550
2748
  name: "continueAnyway",
2551
2749
  message: "Are you sure you want to sync with this project?",
2552
2750
  initial: false
2553
2751
  });
2554
2752
  if (!continueAnyway) {
2555
- console.log(pc11.gray("Cancelled."));
2753
+ console.log(pc12.gray("Cancelled."));
2556
2754
  process.exit(0);
2557
2755
  }
2558
2756
  }
@@ -2566,7 +2764,7 @@ Connection to ${providerDisplayName} failed.`));
2566
2764
  if (needsEnvPrompt || needsDirectionPrompt) {
2567
2765
  if (needsEnvPrompt) {
2568
2766
  const vaultEnvs = await getVaultEnvironments(accessToken, repoFullName);
2569
- const { selectedEnv } = await prompts9({
2767
+ const { selectedEnv } = await prompts10({
2570
2768
  type: "select",
2571
2769
  name: "selectedEnv",
2572
2770
  message: "Keyway environment:",
@@ -2574,7 +2772,7 @@ Connection to ${providerDisplayName} failed.`));
2574
2772
  initial: Math.max(0, vaultEnvs.indexOf("production"))
2575
2773
  });
2576
2774
  if (!selectedEnv) {
2577
- console.log(pc11.gray("Cancelled."));
2775
+ console.log(pc12.gray("Cancelled."));
2578
2776
  process.exit(0);
2579
2777
  }
2580
2778
  keywayEnv = selectedEnv;
@@ -2588,9 +2786,9 @@ Connection to ${providerDisplayName} failed.`));
2588
2786
  providerEnv = mappedEnv;
2589
2787
  } else if (selectedProject.environments.length === 1) {
2590
2788
  providerEnv = selectedProject.environments[0];
2591
- console.log(pc11.gray(`Using ${providerName} environment: ${providerEnv}`));
2789
+ console.log(pc12.gray(`Using ${providerName} environment: ${providerEnv}`));
2592
2790
  } else {
2593
- const { selectedProviderEnv } = await prompts9({
2791
+ const { selectedProviderEnv } = await prompts10({
2594
2792
  type: "select",
2595
2793
  name: "selectedProviderEnv",
2596
2794
  message: `${providerName} environment:`,
@@ -2600,7 +2798,7 @@ Connection to ${providerDisplayName} failed.`));
2600
2798
  ))
2601
2799
  });
2602
2800
  if (!selectedProviderEnv) {
2603
- console.log(pc11.gray("Cancelled."));
2801
+ console.log(pc12.gray("Cancelled."));
2604
2802
  process.exit(0);
2605
2803
  }
2606
2804
  providerEnv = selectedProviderEnv;
@@ -2614,9 +2812,9 @@ Connection to ${providerDisplayName} failed.`));
2614
2812
  if (needsDirectionPrompt) {
2615
2813
  const effectiveKeywayEnv = keywayEnv || "production";
2616
2814
  const effectiveProviderEnv = providerEnv || mapToProviderEnvironment(provider, effectiveKeywayEnv);
2617
- console.log(pc11.gray("\nComparing secrets..."));
2815
+ console.log(pc12.gray("\nComparing secrets..."));
2618
2816
  diff = await getSyncDiff(accessToken, repoFullName, {
2619
- connectionId: connection.id,
2817
+ connectionId: selectedProject.connectionId,
2620
2818
  projectId: selectedProject.id,
2621
2819
  serviceId: selectedProject.serviceId,
2622
2820
  // Railway: service ID for service-specific variables
@@ -2636,7 +2834,7 @@ Connection to ${providerDisplayName} failed.`));
2636
2834
  } else if (diff.providerCount === 0 && diff.keywayCount > 0) {
2637
2835
  defaultDirection = 0;
2638
2836
  }
2639
- const { selectedDirection } = await prompts9({
2837
+ const { selectedDirection } = await prompts10({
2640
2838
  type: "select",
2641
2839
  name: "selectedDirection",
2642
2840
  message: "Sync direction:",
@@ -2647,7 +2845,7 @@ Connection to ${providerDisplayName} failed.`));
2647
2845
  initial: defaultDirection
2648
2846
  });
2649
2847
  if (!selectedDirection) {
2650
- console.log(pc11.gray("Cancelled."));
2848
+ console.log(pc12.gray("Cancelled."));
2651
2849
  process.exit(0);
2652
2850
  }
2653
2851
  direction = selectedDirection;
@@ -2659,15 +2857,15 @@ Connection to ${providerDisplayName} failed.`));
2659
2857
  const status = await getSyncStatus(
2660
2858
  accessToken,
2661
2859
  repoFullName,
2662
- connection.id,
2860
+ selectedProject.connectionId,
2663
2861
  selectedProject.id,
2664
2862
  keywayEnv
2665
2863
  );
2666
2864
  if (status.isFirstSync && direction === "push" && status.vaultIsEmpty && status.providerHasSecrets) {
2667
- console.log(pc11.yellow(`
2865
+ console.log(pc12.yellow(`
2668
2866
  \u26A0\uFE0F Your Keyway vault is empty for "${keywayEnv}", but ${providerName} has ${status.providerSecretCount} secrets.`));
2669
- console.log(pc11.gray(` (Use --environment to sync a different environment)`));
2670
- const { importFirst } = await prompts9({
2867
+ console.log(pc12.gray(` (Use --environment to sync a different environment)`));
2868
+ const { importFirst } = await prompts10({
2671
2869
  type: "confirm",
2672
2870
  name: "importFirst",
2673
2871
  message: `Import secrets from ${providerName} first?`,
@@ -2677,7 +2875,7 @@ Connection to ${providerDisplayName} failed.`));
2677
2875
  await executeSyncOperation(
2678
2876
  accessToken,
2679
2877
  repoFullName,
2680
- connection.id,
2878
+ selectedProject.connectionId,
2681
2879
  selectedProject,
2682
2880
  keywayEnv,
2683
2881
  providerEnv,
@@ -2693,7 +2891,7 @@ Connection to ${providerDisplayName} failed.`));
2693
2891
  await executeSyncOperation(
2694
2892
  accessToken,
2695
2893
  repoFullName,
2696
- connection.id,
2894
+ selectedProject.connectionId,
2697
2895
  selectedProject,
2698
2896
  keywayEnv,
2699
2897
  providerEnv,
@@ -2708,7 +2906,7 @@ Connection to ${providerDisplayName} failed.`));
2708
2906
  command: "sync",
2709
2907
  error: truncateMessage(message)
2710
2908
  });
2711
- console.error(pc11.red(`
2909
+ console.error(pc12.red(`
2712
2910
  \u2717 ${message}`));
2713
2911
  process.exit(1);
2714
2912
  }
@@ -2727,49 +2925,49 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
2727
2925
  });
2728
2926
  const totalChanges = preview.toCreate.length + preview.toUpdate.length + preview.toDelete.length;
2729
2927
  if (totalChanges === 0) {
2730
- console.log(pc11.green("\n\u2713 Already in sync. No changes needed."));
2928
+ console.log(pc12.green("\n\u2713 Already in sync. No changes needed."));
2731
2929
  return;
2732
2930
  }
2733
- console.log(pc11.blue("\n\u{1F4CB} Sync Preview\n"));
2931
+ console.log(pc12.blue("\n\u{1F4CB} Sync Preview\n"));
2734
2932
  if (preview.toCreate.length > 0) {
2735
- console.log(pc11.green(` + ${preview.toCreate.length} to create`));
2736
- preview.toCreate.slice(0, 5).forEach((key) => console.log(pc11.gray(` ${key}`)));
2933
+ console.log(pc12.green(` + ${preview.toCreate.length} to create`));
2934
+ preview.toCreate.slice(0, 5).forEach((key) => console.log(pc12.gray(` ${key}`)));
2737
2935
  if (preview.toCreate.length > 5) {
2738
- console.log(pc11.gray(` ... and ${preview.toCreate.length - 5} more`));
2936
+ console.log(pc12.gray(` ... and ${preview.toCreate.length - 5} more`));
2739
2937
  }
2740
2938
  }
2741
2939
  if (preview.toUpdate.length > 0) {
2742
- console.log(pc11.yellow(` ~ ${preview.toUpdate.length} to update`));
2743
- preview.toUpdate.slice(0, 5).forEach((key) => console.log(pc11.gray(` ${key}`)));
2940
+ console.log(pc12.yellow(` ~ ${preview.toUpdate.length} to update`));
2941
+ preview.toUpdate.slice(0, 5).forEach((key) => console.log(pc12.gray(` ${key}`)));
2744
2942
  if (preview.toUpdate.length > 5) {
2745
- console.log(pc11.gray(` ... and ${preview.toUpdate.length - 5} more`));
2943
+ console.log(pc12.gray(` ... and ${preview.toUpdate.length - 5} more`));
2746
2944
  }
2747
2945
  }
2748
2946
  if (preview.toDelete.length > 0) {
2749
- console.log(pc11.red(` - ${preview.toDelete.length} to delete`));
2750
- preview.toDelete.slice(0, 5).forEach((key) => console.log(pc11.gray(` ${key}`)));
2947
+ console.log(pc12.red(` - ${preview.toDelete.length} to delete`));
2948
+ preview.toDelete.slice(0, 5).forEach((key) => console.log(pc12.gray(` ${key}`)));
2751
2949
  if (preview.toDelete.length > 5) {
2752
- console.log(pc11.gray(` ... and ${preview.toDelete.length - 5} more`));
2950
+ console.log(pc12.gray(` ... and ${preview.toDelete.length - 5} more`));
2753
2951
  }
2754
2952
  }
2755
2953
  if (preview.toSkip.length > 0) {
2756
- console.log(pc11.gray(` \u25CB ${preview.toSkip.length} unchanged`));
2954
+ console.log(pc12.gray(` \u25CB ${preview.toSkip.length} unchanged`));
2757
2955
  }
2758
2956
  console.log("");
2759
2957
  if (!skipConfirm) {
2760
2958
  const target = direction === "push" ? providerName : "Keyway";
2761
- const { confirm } = await prompts9({
2959
+ const { confirm } = await prompts10({
2762
2960
  type: "confirm",
2763
2961
  name: "confirm",
2764
2962
  message: `Apply ${totalChanges} changes to ${target}?`,
2765
2963
  initial: true
2766
2964
  });
2767
2965
  if (!confirm) {
2768
- console.log(pc11.gray("Cancelled."));
2966
+ console.log(pc12.gray("Cancelled."));
2769
2967
  return;
2770
2968
  }
2771
2969
  }
2772
- console.log(pc11.blue("\n\u23F3 Syncing...\n"));
2970
+ console.log(pc12.blue("\n\u23F3 Syncing...\n"));
2773
2971
  const result = await executeSync(accessToken, repoFullName, {
2774
2972
  connectionId,
2775
2973
  projectId: project.id,
@@ -2781,11 +2979,11 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
2781
2979
  allowDelete
2782
2980
  });
2783
2981
  if (result.success) {
2784
- console.log(pc11.green("\u2713 Sync complete"));
2785
- console.log(pc11.gray(` Created: ${result.stats.created}`));
2786
- console.log(pc11.gray(` Updated: ${result.stats.updated}`));
2982
+ console.log(pc12.green("\u2713 Sync complete"));
2983
+ console.log(pc12.gray(` Created: ${result.stats.created}`));
2984
+ console.log(pc12.gray(` Updated: ${result.stats.updated}`));
2787
2985
  if (result.stats.deleted > 0) {
2788
- console.log(pc11.gray(` Deleted: ${result.stats.deleted}`));
2986
+ console.log(pc12.gray(` Deleted: ${result.stats.deleted}`));
2789
2987
  }
2790
2988
  trackEvent(AnalyticsEvents.CLI_SYNC, {
2791
2989
  provider,
@@ -2795,7 +2993,7 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
2795
2993
  deleted: result.stats.deleted
2796
2994
  });
2797
2995
  } else {
2798
- console.error(pc11.red(`
2996
+ console.error(pc12.red(`
2799
2997
  \u2717 ${result.error}`));
2800
2998
  process.exit(1);
2801
2999
  }
@@ -2803,16 +3001,16 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
2803
3001
 
2804
3002
  // src/cli.ts
2805
3003
  process.on("unhandledRejection", (reason) => {
2806
- console.error(pc12.red("Unhandled error:"), reason);
3004
+ console.error(pc13.red("Unhandled error:"), reason);
2807
3005
  process.exit(1);
2808
3006
  });
2809
3007
  var program = new Command();
2810
3008
  var TAGLINE = "Sync secrets with your team and infra";
2811
3009
  var showBanner = () => {
2812
- const text = pc12.bold(pc12.cyan("Keyway CLI"));
3010
+ const text = pc13.bold(pc13.cyan("Keyway CLI"));
2813
3011
  console.log(`
2814
3012
  ${text}
2815
- ${pc12.gray(TAGLINE)}
3013
+ ${pc13.gray(TAGLINE)}
2816
3014
  `);
2817
3015
  };
2818
3016
  showBanner();
@@ -2844,13 +3042,17 @@ program.command("connections").description("List your provider connections").opt
2844
3042
  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) => {
2845
3043
  await disconnectCommand(provider, options);
2846
3044
  });
2847
- 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) => {
3045
+ 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("--team <team>", "Team/org name or ID (for multi-account)").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) => {
2848
3046
  await syncCommand(provider, options);
2849
3047
  });
3048
+ var ci = program.command("ci").description("CI/CD integration commands");
3049
+ ci.command("setup").description("Setup GitHub Actions integration (adds KEYWAY_TOKEN secret)").option("--repo <repo>", "Repository in owner/repo format (auto-detected)").action(async (options) => {
3050
+ await ciSetupCommand(options);
3051
+ });
2850
3052
  (async () => {
2851
3053
  await warnIfEnvNotGitignored();
2852
3054
  await program.parseAsync();
2853
3055
  })().catch((error) => {
2854
- console.error(pc12.red("Error:"), error.message || error);
3056
+ console.error(pc13.red("Error:"), error.message || error);
2855
3057
  process.exit(1);
2856
3058
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keywaysh/cli",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "One link to all your secrets",
5
5
  "type": "module",
6
6
  "bin": {
@@ -45,9 +45,11 @@
45
45
  "node": ">=18.0.0"
46
46
  },
47
47
  "dependencies": {
48
+ "@octokit/rest": "^22.0.1",
48
49
  "balanced-match": "^3.0.1",
49
50
  "commander": "^14.0.0",
50
51
  "conf": "^15.0.2",
52
+ "libsodium-wrappers": "^0.7.15",
51
53
  "open": "^11.0.0",
52
54
  "picocolors": "^1.1.1",
53
55
  "posthog-node": "^3.5.0",
@@ -55,6 +57,7 @@
55
57
  },
56
58
  "devDependencies": {
57
59
  "@types/balanced-match": "^3.0.2",
60
+ "@types/libsodium-wrappers": "^0.7.14",
58
61
  "@types/node": "^24.2.0",
59
62
  "@types/prompts": "^2.4.9",
60
63
  "@vitest/coverage-v8": "^3.0.0",