@keywaysh/cli 0.1.13 → 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 (3) hide show
  1. package/README.md +19 -0
  2. package/dist/cli.js +522 -276
  3. package/package.json +4 -1
package/dist/cli.js CHANGED
@@ -8,11 +8,11 @@ import {
8
8
 
9
9
  // src/cli.ts
10
10
  import { Command } from "commander";
11
- import pc11 from "picocolors";
11
+ import pc13 from "picocolors";
12
12
 
13
13
  // src/cmds/init.ts
14
- import pc6 from "picocolors";
15
- import prompts5 from "prompts";
14
+ import pc7 from "picocolors";
15
+ import prompts6 from "prompts";
16
16
 
17
17
  // src/utils/git.ts
18
18
  import { execSync } from "child_process";
@@ -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.13",
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,
@@ -900,10 +903,10 @@ async function addBadgeToReadme(silent = false) {
900
903
  }
901
904
 
902
905
  // src/cmds/push.ts
903
- import pc5 from "picocolors";
904
- import fs4 from "fs";
905
- import path4 from "path";
906
- import prompts4 from "prompts";
906
+ import pc6 from "picocolors";
907
+ import fs5 from "fs";
908
+ import path5 from "path";
909
+ import prompts5 from "prompts";
907
910
 
908
911
  // src/cmds/login.ts
909
912
  import pc4 from "picocolors";
@@ -1112,9 +1115,34 @@ async function logoutCommand() {
1112
1115
  console.log(pc4.gray(`Auth cache cleared: ${getAuthFilePath()}`));
1113
1116
  }
1114
1117
 
1118
+ // src/utils/env.ts
1119
+ import fs4 from "fs";
1120
+ import path4 from "path";
1121
+ import pc5 from "picocolors";
1122
+ import prompts4 from "prompts";
1123
+ async function promptCreateEnvFile() {
1124
+ const { createEnv } = await prompts4({
1125
+ type: "confirm",
1126
+ name: "createEnv",
1127
+ message: "No .env file found. Create one?",
1128
+ initial: true
1129
+ }, {
1130
+ onCancel: () => {
1131
+ throw new Error("Cancelled by user.");
1132
+ }
1133
+ });
1134
+ if (!createEnv) {
1135
+ return false;
1136
+ }
1137
+ const envFilePath = path4.join(process.cwd(), ".env");
1138
+ fs4.writeFileSync(envFilePath, "# Add your environment variables here\n# Example: API_KEY=your-api-key\n");
1139
+ console.log(pc5.green("\u2713 Created .env file"));
1140
+ return true;
1141
+ }
1142
+
1115
1143
  // src/cmds/push.ts
1116
1144
  function deriveEnvFromFile(file) {
1117
- const base = path4.basename(file);
1145
+ const base = path5.basename(file);
1118
1146
  const match = base.match(/\.env(?:\.(.+))?$/);
1119
1147
  if (match) {
1120
1148
  return match[1] || "development";
@@ -1123,15 +1151,15 @@ function deriveEnvFromFile(file) {
1123
1151
  }
1124
1152
  function discoverEnvCandidates(cwd) {
1125
1153
  try {
1126
- const entries = fs4.readdirSync(cwd);
1154
+ const entries = fs5.readdirSync(cwd);
1127
1155
  const hasEnvLocal = entries.includes(".env.local");
1128
1156
  if (hasEnvLocal) {
1129
- console.log(pc5.gray("\u2139\uFE0F Detected .env.local \u2014 not synced by design (machine-specific secrets)"));
1157
+ console.log(pc6.gray("\u2139\uFE0F Detected .env.local \u2014 not synced by design (machine-specific secrets)"));
1130
1158
  }
1131
1159
  const candidates = entries.filter((name) => name.startsWith(".env") && name !== ".env.local").map((name) => {
1132
- const fullPath = path4.join(cwd, name);
1160
+ const fullPath = path5.join(cwd, name);
1133
1161
  try {
1134
- const stat = fs4.statSync(fullPath);
1162
+ const stat = fs5.statSync(fullPath);
1135
1163
  if (!stat.isFile()) return null;
1136
1164
  return { file: name, env: deriveEnvFromFile(name) };
1137
1165
  } catch {
@@ -1152,13 +1180,21 @@ function discoverEnvCandidates(cwd) {
1152
1180
  }
1153
1181
  async function pushCommand(options) {
1154
1182
  try {
1155
- console.log(pc5.blue("\u{1F510} Pushing secrets to Keyway...\n"));
1183
+ console.log(pc6.blue("\u{1F510} Pushing secrets to Keyway...\n"));
1156
1184
  const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
1157
1185
  let environment = options.env;
1158
1186
  let envFile = options.file;
1159
1187
  const candidates = discoverEnvCandidates(process.cwd());
1160
1188
  if (candidates.length === 0 && !envFile) {
1161
- throw new Error("No .env file found. Create a .env file first, or use --file <path> to specify one.");
1189
+ if (!isInteractive2) {
1190
+ throw new Error("No .env file found. Create a .env file first, or use --file <path> to specify one.");
1191
+ }
1192
+ const created = await promptCreateEnvFile();
1193
+ if (!created) {
1194
+ throw new Error("No .env file found.");
1195
+ }
1196
+ console.log(pc6.gray(" Add your variables and run keyway push again\n"));
1197
+ return;
1162
1198
  }
1163
1199
  if (environment && !envFile) {
1164
1200
  const match = candidates.find((c) => c.env === environment);
@@ -1167,7 +1203,7 @@ async function pushCommand(options) {
1167
1203
  }
1168
1204
  }
1169
1205
  if (!environment && !envFile && isInteractive2 && candidates.length > 0) {
1170
- const { choice } = await prompts4(
1206
+ const { choice } = await prompts5(
1171
1207
  {
1172
1208
  type: "select",
1173
1209
  name: "choice",
@@ -1190,15 +1226,15 @@ async function pushCommand(options) {
1190
1226
  envFile = choice.file;
1191
1227
  environment = choice.env;
1192
1228
  } else if (choice === "custom") {
1193
- const { fileInput } = await prompts4(
1229
+ const { fileInput } = await prompts5(
1194
1230
  {
1195
1231
  type: "text",
1196
1232
  name: "fileInput",
1197
1233
  message: "Path to env file:",
1198
1234
  validate: (value) => {
1199
1235
  if (!value) return "Path is required";
1200
- const resolved = path4.resolve(process.cwd(), value);
1201
- if (!fs4.existsSync(resolved)) return `File not found: ${value}`;
1236
+ const resolved = path5.resolve(process.cwd(), value);
1237
+ if (!fs5.existsSync(resolved)) return `File not found: ${value}`;
1202
1238
  return true;
1203
1239
  }
1204
1240
  },
@@ -1218,11 +1254,11 @@ async function pushCommand(options) {
1218
1254
  if (!envFile) {
1219
1255
  envFile = ".env";
1220
1256
  }
1221
- const envFilePath = path4.resolve(process.cwd(), envFile);
1222
- if (!fs4.existsSync(envFilePath)) {
1257
+ const envFilePath = path5.resolve(process.cwd(), envFile);
1258
+ if (!fs5.existsSync(envFilePath)) {
1223
1259
  throw new Error(`File not found: ${envFile}`);
1224
1260
  }
1225
- const content = fs4.readFileSync(envFilePath, "utf-8");
1261
+ const content = fs5.readFileSync(envFilePath, "utf-8");
1226
1262
  if (content.trim().length === 0) {
1227
1263
  throw new Error(`File is empty: ${envFile}`);
1228
1264
  }
@@ -1230,17 +1266,17 @@ async function pushCommand(options) {
1230
1266
  const trimmed = line.trim();
1231
1267
  return trimmed.length > 0 && !trimmed.startsWith("#");
1232
1268
  });
1233
- console.log(`File: ${pc5.cyan(envFile)}`);
1234
- console.log(`Environment: ${pc5.cyan(environment)}`);
1235
- console.log(`Variables: ${pc5.cyan(lines.length.toString())}`);
1269
+ console.log(`File: ${pc6.cyan(envFile)}`);
1270
+ console.log(`Environment: ${pc6.cyan(environment)}`);
1271
+ console.log(`Variables: ${pc6.cyan(lines.length.toString())}`);
1236
1272
  const repoFullName = getCurrentRepoFullName();
1237
- console.log(`Repository: ${pc5.cyan(repoFullName)}`);
1273
+ console.log(`Repository: ${pc6.cyan(repoFullName)}`);
1238
1274
  if (!options.yes) {
1239
1275
  const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
1240
1276
  if (!isInteractive3) {
1241
1277
  throw new Error("Confirmation required. Re-run with --yes in non-interactive environments.");
1242
1278
  }
1243
- const { confirm } = await prompts4(
1279
+ const { confirm } = await prompts5(
1244
1280
  {
1245
1281
  type: "confirm",
1246
1282
  name: "confirm",
@@ -1254,7 +1290,7 @@ async function pushCommand(options) {
1254
1290
  }
1255
1291
  );
1256
1292
  if (!confirm) {
1257
- console.log(pc5.yellow("Push aborted."));
1293
+ console.log(pc6.yellow("Push aborted."));
1258
1294
  return;
1259
1295
  }
1260
1296
  }
@@ -1266,13 +1302,13 @@ async function pushCommand(options) {
1266
1302
  });
1267
1303
  console.log("\nUploading secrets...");
1268
1304
  const response = await pushSecrets(repoFullName, environment, content, accessToken);
1269
- console.log(pc5.green("\n\u2713 " + response.message));
1305
+ console.log(pc6.green("\n\u2713 " + response.message));
1270
1306
  if (response.stats) {
1271
1307
  const { created, updated, deleted } = response.stats;
1272
1308
  const parts = [];
1273
- if (created > 0) parts.push(pc5.green(`+${created} created`));
1274
- if (updated > 0) parts.push(pc5.yellow(`~${updated} updated`));
1275
- if (deleted > 0) parts.push(pc5.red(`-${deleted} deleted`));
1309
+ if (created > 0) parts.push(pc6.green(`+${created} created`));
1310
+ if (updated > 0) parts.push(pc6.yellow(`~${updated} updated`));
1311
+ if (deleted > 0) parts.push(pc6.red(`-${deleted} deleted`));
1276
1312
  if (parts.length > 0) {
1277
1313
  console.log(`Stats: ${parts.join(", ")}`);
1278
1314
  }
@@ -1281,7 +1317,7 @@ async function pushCommand(options) {
1281
1317
  Your secrets are now encrypted and stored securely.`);
1282
1318
  const dashboardLink = `https://www.keyway.sh/dashboard/vaults/${repoFullName}`;
1283
1319
  console.log(`
1284
- ${pc5.blue("\u2394")} Dashboard: ${pc5.underline(dashboardLink)}`);
1320
+ ${pc6.blue("\u2394")} Dashboard: ${pc6.underline(dashboardLink)}`);
1285
1321
  await shutdownAnalytics();
1286
1322
  } catch (error) {
1287
1323
  let message;
@@ -1294,7 +1330,7 @@ ${pc5.blue("\u2394")} Dashboard: ${pc5.underline(dashboardLink)}`);
1294
1330
  const availableEnvs = envNotFoundMatch[2];
1295
1331
  message = `Environment '${requestedEnv}' does not exist in this vault.`;
1296
1332
  hint = `Available environments: ${availableEnvs}
1297
- Use ${pc5.cyan(`keyway push --env <environment>`)} to specify one, or create '${requestedEnv}' via the dashboard.`;
1333
+ Use ${pc6.cyan(`keyway push --env <environment>`)} to specify one, or create '${requestedEnv}' via the dashboard.`;
1298
1334
  }
1299
1335
  if (error.statusCode === 403 && (error.upgradeUrl || message.toLowerCase().includes("read-only"))) {
1300
1336
  const upgradeMessage = message.toLowerCase().includes("read-only") ? "This vault is read-only on your current plan." : message;
@@ -1317,10 +1353,10 @@ Use ${pc5.cyan(`keyway push --env <environment>`)} to specify one, or create '${
1317
1353
  error: message
1318
1354
  });
1319
1355
  await shutdownAnalytics();
1320
- console.error(pc5.red(`
1356
+ console.error(pc6.red(`
1321
1357
  \u2717 ${message}`));
1322
1358
  if (hint) {
1323
- console.error(pc5.gray(`
1359
+ console.error(pc6.gray(`
1324
1360
  ${hint}`));
1325
1361
  }
1326
1362
  process.exit(1);
@@ -1355,7 +1391,7 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
1355
1391
  const deviceStart = await startDeviceLogin(repoFullName);
1356
1392
  const installUrl = deviceStart.githubAppInstallUrl || "https://github.com/apps/keyway/installations/new";
1357
1393
  console.log("");
1358
- const { shouldProceed } = await prompts5({
1394
+ const { shouldProceed } = await prompts6({
1359
1395
  type: "confirm",
1360
1396
  name: "shouldProceed",
1361
1397
  message: "Open browser to sign in?",
@@ -1365,8 +1401,8 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
1365
1401
  throw new Error('Setup required. Run "keyway init" when ready.');
1366
1402
  }
1367
1403
  await openUrl(deviceStart.verificationUriComplete);
1368
- console.log(pc6.blue("\u23F3 Waiting for authorization..."));
1369
- console.log(pc6.gray(" (Press Ctrl+C to cancel)\n"));
1404
+ console.log(pc7.blue("\u23F3 Waiting for authorization..."));
1405
+ console.log(pc7.gray(" (Press Ctrl+C to cancel)\n"));
1370
1406
  const pollIntervalMs = Math.max((deviceStart.interval ?? 5) * 1e3, POLL_INTERVAL_MS);
1371
1407
  const startTime = Date.now();
1372
1408
  let accessToken = null;
@@ -1381,7 +1417,7 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
1381
1417
  githubLogin: result.githubLogin,
1382
1418
  expiresAt: result.expiresAt
1383
1419
  });
1384
- console.log(pc6.green("\u2713 Signed in!"));
1420
+ console.log(pc7.green("\u2713 Signed in!"));
1385
1421
  if (result.githubLogin) {
1386
1422
  identifyUser(result.githubLogin, {
1387
1423
  github_username: result.githubLogin,
@@ -1391,7 +1427,7 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
1391
1427
  break;
1392
1428
  }
1393
1429
  consecutiveErrors = 0;
1394
- process.stdout.write(pc6.gray("."));
1430
+ process.stdout.write(pc7.gray("."));
1395
1431
  } catch (error) {
1396
1432
  consecutiveErrors++;
1397
1433
  if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
@@ -1402,35 +1438,35 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
1402
1438
  }
1403
1439
  if (!accessToken) {
1404
1440
  console.log("");
1405
- console.log(pc6.yellow("\u26A0 Timed out waiting for sign in."));
1441
+ console.log(pc7.yellow("\u26A0 Timed out waiting for sign in."));
1406
1442
  throw new Error("Sign in timed out. Please try again.");
1407
1443
  }
1408
1444
  const installStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
1409
1445
  if (installStatus.installed) {
1410
- console.log(pc6.green("\u2713 GitHub App installed"));
1446
+ console.log(pc7.green("\u2713 GitHub App installed"));
1411
1447
  console.log("");
1412
1448
  return accessToken;
1413
1449
  }
1414
1450
  console.log("");
1415
- console.log(pc6.yellow("\u26A0 GitHub App not installed on this repository"));
1416
- console.log(pc6.gray(" The Keyway GitHub App is required for secure access."));
1451
+ console.log(pc7.yellow("\u26A0 GitHub App not installed on this repository"));
1452
+ console.log(pc7.gray(" The Keyway GitHub App is required for secure access."));
1417
1453
  console.log("");
1418
- const { shouldInstall } = await prompts5({
1454
+ const { shouldInstall } = await prompts6({
1419
1455
  type: "confirm",
1420
1456
  name: "shouldInstall",
1421
1457
  message: "Open browser to install GitHub App?",
1422
1458
  initial: true
1423
1459
  });
1424
1460
  if (!shouldInstall) {
1425
- console.log(pc6.gray(`
1461
+ console.log(pc7.gray(`
1426
1462
  Install later: ${installUrl}`));
1427
1463
  throw new Error("GitHub App installation required.");
1428
1464
  }
1429
1465
  await openUrl(installUrl);
1430
- console.log(pc6.blue("\u23F3 Waiting for GitHub App installation..."));
1431
- console.log(pc6.gray(' Add this repository and click "Install"'));
1432
- console.log(pc6.gray(" Then return here - the CLI will detect it automatically"));
1433
- console.log(pc6.gray(" (Press Ctrl+C to cancel)\n"));
1466
+ console.log(pc7.blue("\u23F3 Waiting for GitHub App installation..."));
1467
+ console.log(pc7.gray(' Add this repository and click "Install"'));
1468
+ console.log(pc7.gray(" Then return here - the CLI will detect it automatically"));
1469
+ console.log(pc7.gray(" (Press Ctrl+C to cancel)\n"));
1434
1470
  const installStartTime = Date.now();
1435
1471
  consecutiveErrors = 0;
1436
1472
  while (Date.now() - installStartTime < POLL_TIMEOUT_MS) {
@@ -1438,12 +1474,12 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
1438
1474
  try {
1439
1475
  const pollStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
1440
1476
  if (pollStatus.installed) {
1441
- console.log(pc6.green("\u2713 GitHub App installed!"));
1477
+ console.log(pc7.green("\u2713 GitHub App installed!"));
1442
1478
  console.log("");
1443
1479
  return accessToken;
1444
1480
  }
1445
1481
  consecutiveErrors = 0;
1446
- process.stdout.write(pc6.gray("."));
1482
+ process.stdout.write(pc7.gray("."));
1447
1483
  } catch (error) {
1448
1484
  consecutiveErrors++;
1449
1485
  if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
@@ -1453,8 +1489,8 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
1453
1489
  }
1454
1490
  }
1455
1491
  console.log("");
1456
- console.log(pc6.yellow("\u26A0 Timed out waiting for installation."));
1457
- console.log(pc6.gray(` Install the GitHub App: ${installUrl}`));
1492
+ console.log(pc7.yellow("\u26A0 Timed out waiting for installation."));
1493
+ console.log(pc7.gray(` Install the GitHub App: ${installUrl}`));
1458
1494
  throw new Error("GitHub App installation timed out.");
1459
1495
  }
1460
1496
  async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
@@ -1464,7 +1500,7 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
1464
1500
  status = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
1465
1501
  } catch (error) {
1466
1502
  if (error instanceof APIError && error.statusCode === 401) {
1467
- console.log(pc6.yellow("\n\u26A0 Session expired or invalid. Clearing credentials..."));
1503
+ console.log(pc7.yellow("\n\u26A0 Session expired or invalid. Clearing credentials..."));
1468
1504
  const { clearAuth: clearAuth2 } = await import("./auth-QLPQ24HZ.js");
1469
1505
  clearAuth2();
1470
1506
  return null;
@@ -1475,29 +1511,29 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
1475
1511
  return accessToken;
1476
1512
  }
1477
1513
  console.log("");
1478
- console.log(pc6.yellow("\u26A0 GitHub App not installed for this repository"));
1514
+ console.log(pc7.yellow("\u26A0 GitHub App not installed for this repository"));
1479
1515
  console.log("");
1480
- console.log(pc6.gray(" The Keyway GitHub App is required to securely manage secrets."));
1481
- console.log(pc6.gray(" It only requests minimal permissions (repository metadata)."));
1516
+ console.log(pc7.gray(" The Keyway GitHub App is required to securely manage secrets."));
1517
+ console.log(pc7.gray(" It only requests minimal permissions (repository metadata)."));
1482
1518
  console.log("");
1483
1519
  if (!isInteractive()) {
1484
- console.log(pc6.gray(` Install the Keyway GitHub App: ${status.installUrl}`));
1520
+ console.log(pc7.gray(` Install the Keyway GitHub App: ${status.installUrl}`));
1485
1521
  throw new Error("GitHub App installation required.");
1486
1522
  }
1487
- const { shouldInstall } = await prompts5({
1523
+ const { shouldInstall } = await prompts6({
1488
1524
  type: "confirm",
1489
1525
  name: "shouldInstall",
1490
1526
  message: "Open browser to install Keyway GitHub App?",
1491
1527
  initial: true
1492
1528
  });
1493
1529
  if (!shouldInstall) {
1494
- console.log(pc6.gray(`
1530
+ console.log(pc7.gray(`
1495
1531
  You can install later: ${status.installUrl}`));
1496
1532
  throw new Error("GitHub App installation required.");
1497
1533
  }
1498
1534
  await openUrl(status.installUrl);
1499
- console.log(pc6.blue("\u23F3 Waiting for GitHub App installation..."));
1500
- console.log(pc6.gray(" (Press Ctrl+C to cancel)\n"));
1535
+ console.log(pc7.blue("\u23F3 Waiting for GitHub App installation..."));
1536
+ console.log(pc7.gray(" (Press Ctrl+C to cancel)\n"));
1501
1537
  const startTime = Date.now();
1502
1538
  let consecutiveErrors = 0;
1503
1539
  while (Date.now() - startTime < POLL_TIMEOUT_MS) {
@@ -1505,12 +1541,12 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
1505
1541
  try {
1506
1542
  const pollStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
1507
1543
  if (pollStatus.installed) {
1508
- console.log(pc6.green("\u2713 GitHub App installed!"));
1544
+ console.log(pc7.green("\u2713 GitHub App installed!"));
1509
1545
  console.log("");
1510
1546
  return accessToken;
1511
1547
  }
1512
1548
  consecutiveErrors = 0;
1513
- process.stdout.write(pc6.gray("."));
1549
+ process.stdout.write(pc7.gray("."));
1514
1550
  } catch (error) {
1515
1551
  consecutiveErrors++;
1516
1552
  if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
@@ -1520,35 +1556,35 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
1520
1556
  }
1521
1557
  }
1522
1558
  console.log("");
1523
- console.log(pc6.yellow("\u26A0 Timed out waiting for installation."));
1524
- console.log(pc6.gray(` You can install the GitHub App later: ${status.installUrl}`));
1559
+ console.log(pc7.yellow("\u26A0 Timed out waiting for installation."));
1560
+ console.log(pc7.gray(` You can install the GitHub App later: ${status.installUrl}`));
1525
1561
  throw new Error("GitHub App installation timed out.");
1526
1562
  }
1527
1563
  async function initCommand(options = {}) {
1528
1564
  try {
1529
1565
  const repoFullName = getCurrentRepoFullName();
1530
1566
  const dashboardLink = `${DASHBOARD_URL}/${repoFullName}`;
1531
- console.log(pc6.blue("\u{1F510} Initializing Keyway vault...\n"));
1532
- console.log(` ${pc6.gray("Repository:")} ${pc6.white(repoFullName)}`);
1567
+ console.log(pc7.blue("\u{1F510} Initializing Keyway vault...\n"));
1568
+ console.log(` ${pc7.gray("Repository:")} ${pc7.white(repoFullName)}`);
1533
1569
  const accessToken = await ensureLoginAndGitHubApp(repoFullName, {
1534
1570
  allowPrompt: options.loginPrompt !== false
1535
1571
  });
1536
1572
  trackEvent(AnalyticsEvents.CLI_INIT, { repoFullName, githubAppInstalled: true });
1537
1573
  const vaultExists = await checkVaultExists(accessToken, repoFullName);
1538
1574
  if (vaultExists) {
1539
- console.log(pc6.green("\n\u2713 Already initialized!\n"));
1540
- console.log(` ${pc6.yellow("\u2192")} Run ${pc6.cyan("keyway push")} to sync your secrets`);
1541
- console.log(` ${pc6.blue("\u2394")} Dashboard: ${pc6.underline(dashboardLink)}`);
1575
+ console.log(pc7.green("\n\u2713 Already initialized!\n"));
1576
+ console.log(` ${pc7.yellow("\u2192")} Run ${pc7.cyan("keyway push")} to sync your secrets`);
1577
+ console.log(` ${pc7.blue("\u2394")} Dashboard: ${pc7.underline(dashboardLink)}`);
1542
1578
  console.log("");
1543
1579
  await shutdownAnalytics();
1544
1580
  return;
1545
1581
  }
1546
1582
  await initVault(repoFullName, accessToken);
1547
- console.log(pc6.green("\u2713 Vault created!"));
1583
+ console.log(pc7.green("\u2713 Vault created!"));
1548
1584
  try {
1549
1585
  const badgeAdded = await addBadgeToReadme(true);
1550
1586
  if (badgeAdded) {
1551
- console.log(pc6.green("\u2713 Badge added to README.md"));
1587
+ console.log(pc7.green("\u2713 Badge added to README.md"));
1552
1588
  }
1553
1589
  } catch {
1554
1590
  }
@@ -1556,9 +1592,9 @@ async function initCommand(options = {}) {
1556
1592
  const envCandidates = discoverEnvCandidates(process.cwd());
1557
1593
  const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
1558
1594
  if (envCandidates.length > 0 && isInteractive2) {
1559
- console.log(pc6.gray(` Found ${envCandidates.length} env file(s): ${envCandidates.map((c) => c.file).join(", ")}
1595
+ console.log(pc7.gray(` Found ${envCandidates.length} env file(s): ${envCandidates.map((c) => c.file).join(", ")}
1560
1596
  `));
1561
- const { shouldPush } = await prompts5({
1597
+ const { shouldPush } = await prompts6({
1562
1598
  type: "confirm",
1563
1599
  name: "shouldPush",
1564
1600
  message: "Push secrets now?",
@@ -1570,25 +1606,36 @@ async function initCommand(options = {}) {
1570
1606
  return;
1571
1607
  }
1572
1608
  }
1573
- console.log(pc6.dim("\u2500".repeat(50)));
1609
+ console.log(pc7.dim("\u2500".repeat(50)));
1574
1610
  console.log("");
1575
1611
  if (envCandidates.length === 0) {
1576
- console.log(`${pc6.yellow("\u26A0")} No .env file found - your vault is empty`);
1577
- console.log(` Next: Create ${pc6.cyan(".env")} and run ${pc6.cyan("keyway push")}
1612
+ if (isInteractive2) {
1613
+ const created = await promptCreateEnvFile();
1614
+ if (created) {
1615
+ console.log(` Add your variables and run ${pc7.cyan("keyway push")}
1616
+ `);
1617
+ } else {
1618
+ console.log(` Next: Create ${pc7.cyan(".env")} and run ${pc7.cyan("keyway push")}
1619
+ `);
1620
+ }
1621
+ } else {
1622
+ console.log(`${pc7.yellow("\u26A0")} No .env file found - your vault is empty`);
1623
+ console.log(` Next: Create ${pc7.cyan(".env")} and run ${pc7.cyan("keyway push")}
1578
1624
  `);
1625
+ }
1579
1626
  } else {
1580
- console.log(` ${pc6.yellow("\u2192")} Run ${pc6.cyan("keyway push")} to sync your secrets
1627
+ console.log(` ${pc7.yellow("\u2192")} Run ${pc7.cyan("keyway push")} to sync your secrets
1581
1628
  `);
1582
1629
  }
1583
- console.log(` ${pc6.blue("\u2394")} Dashboard: ${pc6.underline(dashboardLink)}`);
1630
+ console.log(` ${pc7.blue("\u2394")} Dashboard: ${pc7.underline(dashboardLink)}`);
1584
1631
  console.log("");
1585
1632
  await shutdownAnalytics();
1586
1633
  } catch (error) {
1587
1634
  if (error instanceof APIError) {
1588
1635
  if (error.statusCode === 409) {
1589
- console.log(pc6.green("\n\u2713 Already initialized!\n"));
1590
- console.log(` ${pc6.yellow("\u2192")} Run ${pc6.cyan("keyway push")} to sync your secrets`);
1591
- console.log(` ${pc6.blue("\u2394")} Dashboard: ${pc6.underline(`${DASHBOARD_URL}/${getCurrentRepoFullName()}`)}`);
1636
+ console.log(pc7.green("\n\u2713 Already initialized!\n"));
1637
+ console.log(` ${pc7.yellow("\u2192")} Run ${pc7.cyan("keyway push")} to sync your secrets`);
1638
+ console.log(` ${pc7.blue("\u2394")} Dashboard: ${pc7.underline(`${DASHBOARD_URL}/${getCurrentRepoFullName()}`)}`);
1592
1639
  console.log("");
1593
1640
  await shutdownAnalytics();
1594
1641
  return;
@@ -1606,25 +1653,25 @@ async function initCommand(options = {}) {
1606
1653
  error: message
1607
1654
  });
1608
1655
  await shutdownAnalytics();
1609
- console.error(pc6.red(`
1656
+ console.error(pc7.red(`
1610
1657
  \u2717 ${message}`));
1611
1658
  process.exit(1);
1612
1659
  }
1613
1660
  }
1614
1661
 
1615
1662
  // src/cmds/pull.ts
1616
- import pc7 from "picocolors";
1617
- import fs5 from "fs";
1618
- import path5 from "path";
1619
- import prompts6 from "prompts";
1663
+ import pc8 from "picocolors";
1664
+ import fs6 from "fs";
1665
+ import path6 from "path";
1666
+ import prompts7 from "prompts";
1620
1667
  async function pullCommand(options) {
1621
1668
  try {
1622
1669
  const environment = options.env || "development";
1623
1670
  const envFile = options.file || ".env";
1624
- console.log(pc7.blue("\u{1F510} Pulling secrets from Keyway...\n"));
1625
- console.log(`Environment: ${pc7.cyan(environment)}`);
1671
+ console.log(pc8.blue("\u{1F510} Pulling secrets from Keyway...\n"));
1672
+ console.log(`Environment: ${pc8.cyan(environment)}`);
1626
1673
  const repoFullName = getCurrentRepoFullName();
1627
- console.log(`Repository: ${pc7.cyan(repoFullName)}`);
1674
+ console.log(`Repository: ${pc8.cyan(repoFullName)}`);
1628
1675
  const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
1629
1676
  trackEvent(AnalyticsEvents.CLI_PULL, {
1630
1677
  repoFullName,
@@ -1632,16 +1679,16 @@ async function pullCommand(options) {
1632
1679
  });
1633
1680
  console.log("\nDownloading secrets...");
1634
1681
  const response = await pullSecrets(repoFullName, environment, accessToken);
1635
- const envFilePath = path5.resolve(process.cwd(), envFile);
1636
- if (fs5.existsSync(envFilePath)) {
1682
+ const envFilePath = path6.resolve(process.cwd(), envFile);
1683
+ if (fs6.existsSync(envFilePath)) {
1637
1684
  const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
1638
1685
  if (options.yes) {
1639
- console.log(pc7.yellow(`
1686
+ console.log(pc8.yellow(`
1640
1687
  \u26A0 Overwriting existing file: ${envFile}`));
1641
1688
  } else if (!isInteractive2) {
1642
1689
  throw new Error(`File ${envFile} exists. Re-run with --yes to overwrite or choose a different --file.`);
1643
1690
  } else {
1644
- const { confirm } = await prompts6(
1691
+ const { confirm } = await prompts7(
1645
1692
  {
1646
1693
  type: "confirm",
1647
1694
  name: "confirm",
@@ -1655,21 +1702,21 @@ async function pullCommand(options) {
1655
1702
  }
1656
1703
  );
1657
1704
  if (!confirm) {
1658
- console.log(pc7.yellow("Pull aborted."));
1705
+ console.log(pc8.yellow("Pull aborted."));
1659
1706
  return;
1660
1707
  }
1661
1708
  }
1662
1709
  }
1663
- fs5.writeFileSync(envFilePath, response.content, "utf-8");
1710
+ fs6.writeFileSync(envFilePath, response.content, "utf-8");
1664
1711
  const lines = response.content.split("\n").filter((line) => {
1665
1712
  const trimmed = line.trim();
1666
1713
  return trimmed.length > 0 && !trimmed.startsWith("#");
1667
1714
  });
1668
- console.log(pc7.green(`
1715
+ console.log(pc8.green(`
1669
1716
  \u2713 Secrets downloaded successfully`));
1670
1717
  console.log(`
1671
- File: ${pc7.cyan(envFile)}`);
1672
- console.log(`Variables: ${pc7.cyan(lines.length.toString())}`);
1718
+ File: ${pc8.cyan(envFile)}`);
1719
+ console.log(`Variables: ${pc8.cyan(lines.length.toString())}`);
1673
1720
  await shutdownAnalytics();
1674
1721
  } catch (error) {
1675
1722
  const message = error instanceof APIError ? `API ${error.statusCode}: ${error.message}` : error instanceof Error ? truncateMessage(error.message) : "Unknown error";
@@ -1678,14 +1725,14 @@ File: ${pc7.cyan(envFile)}`);
1678
1725
  error: message
1679
1726
  });
1680
1727
  await shutdownAnalytics();
1681
- console.error(pc7.red(`
1728
+ console.error(pc8.red(`
1682
1729
  \u2717 ${message}`));
1683
1730
  process.exit(1);
1684
1731
  }
1685
1732
  }
1686
1733
 
1687
1734
  // src/cmds/doctor.ts
1688
- import pc8 from "picocolors";
1735
+ import pc9 from "picocolors";
1689
1736
 
1690
1737
  // src/core/doctor.ts
1691
1738
  import { execSync as execSync2 } from "child_process";
@@ -1939,9 +1986,9 @@ async function runAllChecks(options = {}) {
1939
1986
  // src/cmds/doctor.ts
1940
1987
  function formatSummary(results) {
1941
1988
  const parts = [
1942
- pc8.green(`${results.summary.pass} passed`),
1943
- results.summary.warn > 0 ? pc8.yellow(`${results.summary.warn} warnings`) : null,
1944
- results.summary.fail > 0 ? pc8.red(`${results.summary.fail} failed`) : null
1989
+ pc9.green(`${results.summary.pass} passed`),
1990
+ results.summary.warn > 0 ? pc9.yellow(`${results.summary.warn} warnings`) : null,
1991
+ results.summary.fail > 0 ? pc9.red(`${results.summary.fail} failed`) : null
1945
1992
  ].filter(Boolean);
1946
1993
  return parts.join(", ");
1947
1994
  }
@@ -1958,20 +2005,20 @@ async function doctorCommand(options = {}) {
1958
2005
  process.stdout.write(JSON.stringify(results, null, 0) + "\n");
1959
2006
  process.exit(results.exitCode);
1960
2007
  }
1961
- console.log(pc8.cyan("\n\u{1F50D} Keyway Doctor - Environment Check\n"));
2008
+ console.log(pc9.cyan("\n\u{1F50D} Keyway Doctor - Environment Check\n"));
1962
2009
  results.checks.forEach((check) => {
1963
- const icon = check.status === "pass" ? pc8.green("\u2713") : check.status === "warn" ? pc8.yellow("!") : pc8.red("\u2717");
1964
- const detail = check.detail ? pc8.dim(` \u2014 ${check.detail}`) : "";
2010
+ const icon = check.status === "pass" ? pc9.green("\u2713") : check.status === "warn" ? pc9.yellow("!") : pc9.red("\u2717");
2011
+ const detail = check.detail ? pc9.dim(` \u2014 ${check.detail}`) : "";
1965
2012
  console.log(` ${icon} ${check.name}${detail}`);
1966
2013
  });
1967
2014
  console.log(`
1968
2015
  Summary: ${formatSummary(results)}`);
1969
2016
  if (results.summary.fail > 0) {
1970
- console.log(pc8.red("\u26A0 Some checks failed. Please resolve the issues above before using Keyway."));
2017
+ console.log(pc9.red("\u26A0 Some checks failed. Please resolve the issues above before using Keyway."));
1971
2018
  } else if (results.summary.warn > 0) {
1972
- console.log(pc8.yellow("\u26A0 Some warnings detected. Keyway should work but consider addressing them."));
2019
+ console.log(pc9.yellow("\u26A0 Some warnings detected. Keyway should work but consider addressing them."));
1973
2020
  } else {
1974
- console.log(pc8.green("\u2728 All checks passed! Your environment is ready for Keyway."));
2021
+ console.log(pc9.green("\u2728 All checks passed! Your environment is ready for Keyway."));
1975
2022
  }
1976
2023
  process.exit(results.exitCode);
1977
2024
  } catch (error) {
@@ -1992,16 +2039,146 @@ Summary: ${formatSummary(results)}`);
1992
2039
  };
1993
2040
  process.stdout.write(JSON.stringify(errorResult, null, 0) + "\n");
1994
2041
  } else {
1995
- console.error(pc8.red(`
2042
+ console.error(pc9.red(`
1996
2043
  \u2717 ${message}`));
1997
2044
  }
1998
2045
  process.exit(1);
1999
2046
  }
2000
2047
  }
2001
2048
 
2049
+ // src/cmds/ci.ts
2050
+ import { execSync as execSync3 } from "child_process";
2051
+ import { Octokit } from "@octokit/rest";
2052
+ import pc10 from "picocolors";
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
+
2002
2179
  // src/cmds/connect.ts
2003
- import pc9 from "picocolors";
2004
- import prompts7 from "prompts";
2180
+ import pc11 from "picocolors";
2181
+ import prompts9 from "prompts";
2005
2182
  var TOKEN_AUTH_PROVIDERS = ["railway"];
2006
2183
  function getTokenCreationUrl(provider) {
2007
2184
  switch (provider) {
@@ -2014,37 +2191,37 @@ function getTokenCreationUrl(provider) {
2014
2191
  async function connectWithTokenFlow(accessToken, provider, displayName) {
2015
2192
  const tokenUrl = getTokenCreationUrl(provider);
2016
2193
  if (provider === "railway") {
2017
- console.log(pc9.yellow("\nTip: Select the workspace containing your projects."));
2018
- console.log(pc9.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.`));
2019
2196
  }
2020
2197
  await openUrl(tokenUrl);
2021
- const { token } = await prompts7({
2198
+ const { token } = await prompts9({
2022
2199
  type: "password",
2023
2200
  name: "token",
2024
2201
  message: `${displayName} API Token:`
2025
2202
  });
2026
2203
  if (!token) {
2027
- console.log(pc9.gray("Cancelled."));
2204
+ console.log(pc11.gray("Cancelled."));
2028
2205
  return false;
2029
2206
  }
2030
- console.log(pc9.gray("\nValidating token..."));
2207
+ console.log(pc11.gray("\nValidating token..."));
2031
2208
  try {
2032
2209
  const result = await connectWithToken(accessToken, provider, token);
2033
2210
  if (result.success) {
2034
- console.log(pc9.green(`
2211
+ console.log(pc11.green(`
2035
2212
  \u2713 Connected to ${displayName}!`));
2036
- console.log(pc9.gray(` Account: ${result.user.username}`));
2213
+ console.log(pc11.gray(` Account: ${result.user.username}`));
2037
2214
  if (result.user.teamName) {
2038
- console.log(pc9.gray(` Team: ${result.user.teamName}`));
2215
+ console.log(pc11.gray(` Team: ${result.user.teamName}`));
2039
2216
  }
2040
2217
  return true;
2041
2218
  } else {
2042
- console.log(pc9.red("\n\u2717 Connection failed."));
2219
+ console.log(pc11.red("\n\u2717 Connection failed."));
2043
2220
  return false;
2044
2221
  }
2045
2222
  } catch (error) {
2046
2223
  const message = error instanceof Error ? error.message : "Token validation failed";
2047
- console.log(pc9.red(`
2224
+ console.log(pc11.red(`
2048
2225
  \u2717 ${message}`));
2049
2226
  return false;
2050
2227
  }
@@ -2053,7 +2230,7 @@ async function connectWithOAuthFlow(accessToken, provider, displayName) {
2053
2230
  const authUrl = getProviderAuthUrl(provider, accessToken);
2054
2231
  const startTime = /* @__PURE__ */ new Date();
2055
2232
  await openUrl(authUrl);
2056
- console.log(pc9.gray("Waiting for authorization..."));
2233
+ console.log(pc11.gray("Waiting for authorization..."));
2057
2234
  const maxAttempts = 60;
2058
2235
  let attempts = 0;
2059
2236
  while (attempts < maxAttempts) {
@@ -2065,15 +2242,15 @@ async function connectWithOAuthFlow(accessToken, provider, displayName) {
2065
2242
  (c) => c.provider === provider && new Date(c.createdAt) > startTime
2066
2243
  );
2067
2244
  if (newConn) {
2068
- console.log(pc9.green(`
2245
+ console.log(pc11.green(`
2069
2246
  \u2713 Connected to ${displayName}!`));
2070
2247
  return true;
2071
2248
  }
2072
2249
  } catch {
2073
2250
  }
2074
2251
  }
2075
- console.log(pc9.red("\n\u2717 Authorization timeout."));
2076
- console.log(pc9.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."));
2077
2254
  return false;
2078
2255
  }
2079
2256
  async function connectCommand(provider, options = {}) {
@@ -2083,30 +2260,40 @@ async function connectCommand(provider, options = {}) {
2083
2260
  const providerInfo = providers.find((p) => p.name === provider.toLowerCase());
2084
2261
  if (!providerInfo) {
2085
2262
  const available = providers.map((p) => p.name).join(", ");
2086
- console.error(pc9.red(`Unknown provider: ${provider}`));
2087
- console.log(pc9.gray(`Available providers: ${available || "none"}`));
2263
+ console.error(pc11.red(`Unknown provider: ${provider}`));
2264
+ console.log(pc11.gray(`Available providers: ${available || "none"}`));
2088
2265
  process.exit(1);
2089
2266
  }
2090
2267
  if (!providerInfo.configured) {
2091
- console.error(pc9.red(`Provider ${providerInfo.displayName} is not configured on the server.`));
2092
- console.log(pc9.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."));
2093
2270
  process.exit(1);
2094
2271
  }
2095
2272
  const { connections } = await getConnections(accessToken);
2096
- const existingConnection = connections.find((c) => c.provider === provider.toLowerCase());
2097
- if (existingConnection) {
2098
- const { reconnect } = await prompts7({
2099
- type: "confirm",
2100
- name: "reconnect",
2101
- message: `You're already connected to ${providerInfo.displayName}. Reconnect?`,
2102
- 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
+ ]
2103
2290
  });
2104
- if (!reconnect) {
2105
- console.log(pc9.gray("Keeping existing connection."));
2291
+ if (action !== "add") {
2292
+ console.log(pc11.gray("Keeping existing connections."));
2106
2293
  return;
2107
2294
  }
2108
2295
  }
2109
- console.log(pc9.blue(`
2296
+ console.log(pc11.blue(`
2110
2297
  Connecting to ${providerInfo.displayName}...
2111
2298
  `));
2112
2299
  let connected = false;
@@ -2125,7 +2312,7 @@ Connecting to ${providerInfo.displayName}...
2125
2312
  command: "connect",
2126
2313
  error: truncateMessage(message)
2127
2314
  });
2128
- console.error(pc9.red(`
2315
+ console.error(pc11.red(`
2129
2316
  \u2717 ${message}`));
2130
2317
  process.exit(1);
2131
2318
  }
@@ -2135,24 +2322,24 @@ async function connectionsCommand(options = {}) {
2135
2322
  const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
2136
2323
  const { connections } = await getConnections(accessToken);
2137
2324
  if (connections.length === 0) {
2138
- console.log(pc9.gray("No provider connections found."));
2139
- console.log(pc9.gray("\nConnect to a provider with: keyway connect <provider>"));
2140
- console.log(pc9.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"));
2141
2328
  return;
2142
2329
  }
2143
- console.log(pc9.blue("\n\u{1F4E1} Provider Connections\n"));
2330
+ console.log(pc11.blue("\n\u{1F4E1} Provider Connections\n"));
2144
2331
  for (const conn of connections) {
2145
2332
  const providerName = conn.provider.charAt(0).toUpperCase() + conn.provider.slice(1);
2146
- const teamInfo = conn.providerTeamId ? pc9.gray(` (Team: ${conn.providerTeamId})`) : "";
2333
+ const teamInfo = conn.providerTeamId ? pc11.gray(` (Team: ${conn.providerTeamId})`) : "";
2147
2334
  const date = new Date(conn.createdAt).toLocaleDateString();
2148
- console.log(` ${pc9.green("\u25CF")} ${pc9.bold(providerName)}${teamInfo}`);
2149
- console.log(pc9.gray(` Connected: ${date}`));
2150
- console.log(pc9.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}`));
2151
2338
  console.log("");
2152
2339
  }
2153
2340
  } catch (error) {
2154
2341
  const message = error instanceof Error ? error.message : "Failed to list connections";
2155
- console.error(pc9.red(`
2342
+ console.error(pc11.red(`
2156
2343
  \u2717 ${message}`));
2157
2344
  process.exit(1);
2158
2345
  }
@@ -2163,22 +2350,22 @@ async function disconnectCommand(provider, options = {}) {
2163
2350
  const { connections } = await getConnections(accessToken);
2164
2351
  const connection = connections.find((c) => c.provider === provider.toLowerCase());
2165
2352
  if (!connection) {
2166
- console.log(pc9.gray(`No connection found for provider: ${provider}`));
2353
+ console.log(pc11.gray(`No connection found for provider: ${provider}`));
2167
2354
  return;
2168
2355
  }
2169
2356
  const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
2170
- const { confirm } = await prompts7({
2357
+ const { confirm } = await prompts9({
2171
2358
  type: "confirm",
2172
2359
  name: "confirm",
2173
2360
  message: `Disconnect from ${providerName}?`,
2174
2361
  initial: false
2175
2362
  });
2176
2363
  if (!confirm) {
2177
- console.log(pc9.gray("Cancelled."));
2364
+ console.log(pc11.gray("Cancelled."));
2178
2365
  return;
2179
2366
  }
2180
2367
  await deleteConnection(accessToken, connection.id);
2181
- console.log(pc9.green(`
2368
+ console.log(pc11.green(`
2182
2369
  \u2713 Disconnected from ${providerName}`));
2183
2370
  trackEvent(AnalyticsEvents.CLI_DISCONNECT, {
2184
2371
  provider: provider.toLowerCase()
@@ -2189,15 +2376,15 @@ async function disconnectCommand(provider, options = {}) {
2189
2376
  command: "disconnect",
2190
2377
  error: truncateMessage(message)
2191
2378
  });
2192
- console.error(pc9.red(`
2379
+ console.error(pc11.red(`
2193
2380
  \u2717 ${message}`));
2194
2381
  process.exit(1);
2195
2382
  }
2196
2383
  }
2197
2384
 
2198
2385
  // src/cmds/sync.ts
2199
- import pc10 from "picocolors";
2200
- import prompts8 from "prompts";
2386
+ import pc12 from "picocolors";
2387
+ import prompts10 from "prompts";
2201
2388
  function mapToVercelEnvironment(keywayEnv) {
2202
2389
  const mapping = {
2203
2390
  production: "production",
@@ -2241,36 +2428,36 @@ function mapToProviderEnvironment(provider, keywayEnv) {
2241
2428
  function displayDiffSummary(diff, providerName) {
2242
2429
  const totalDiff = diff.onlyInKeyway.length + diff.onlyInProvider.length + diff.different.length;
2243
2430
  if (totalDiff === 0 && diff.same.length > 0) {
2244
- console.log(pc10.green(`
2431
+ console.log(pc12.green(`
2245
2432
  \u2713 Already in sync (${diff.same.length} secrets)`));
2246
2433
  return;
2247
2434
  }
2248
- console.log(pc10.blue("\n\u{1F4CA} Comparison Summary\n"));
2249
- console.log(pc10.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
2250
2437
  `));
2251
2438
  if (diff.onlyInKeyway.length > 0) {
2252
- console.log(pc10.cyan(` \u2192 ${diff.onlyInKeyway.length} only in Keyway`));
2253
- diff.onlyInKeyway.slice(0, 3).forEach((key) => console.log(pc10.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}`)));
2254
2441
  if (diff.onlyInKeyway.length > 3) {
2255
- console.log(pc10.gray(` ... and ${diff.onlyInKeyway.length - 3} more`));
2442
+ console.log(pc12.gray(` ... and ${diff.onlyInKeyway.length - 3} more`));
2256
2443
  }
2257
2444
  }
2258
2445
  if (diff.onlyInProvider.length > 0) {
2259
- console.log(pc10.magenta(` \u2190 ${diff.onlyInProvider.length} only in ${providerName}`));
2260
- diff.onlyInProvider.slice(0, 3).forEach((key) => console.log(pc10.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}`)));
2261
2448
  if (diff.onlyInProvider.length > 3) {
2262
- console.log(pc10.gray(` ... and ${diff.onlyInProvider.length - 3} more`));
2449
+ console.log(pc12.gray(` ... and ${diff.onlyInProvider.length - 3} more`));
2263
2450
  }
2264
2451
  }
2265
2452
  if (diff.different.length > 0) {
2266
- console.log(pc10.yellow(` \u2260 ${diff.different.length} with different values`));
2267
- diff.different.slice(0, 3).forEach((key) => console.log(pc10.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}`)));
2268
2455
  if (diff.different.length > 3) {
2269
- console.log(pc10.gray(` ... and ${diff.different.length - 3} more`));
2456
+ console.log(pc12.gray(` ... and ${diff.different.length - 3} more`));
2270
2457
  }
2271
2458
  }
2272
2459
  if (diff.same.length > 0) {
2273
- console.log(pc10.gray(` = ${diff.same.length} identical`));
2460
+ console.log(pc12.gray(` = ${diff.same.length} identical`));
2274
2461
  }
2275
2462
  console.log("");
2276
2463
  }
@@ -2312,30 +2499,42 @@ function projectMatchesRepo(project, repoFullName) {
2312
2499
  }
2313
2500
  async function promptProjectSelection(projects, repoFullName) {
2314
2501
  const repoName = repoFullName.split("/")[1]?.toLowerCase() || "";
2502
+ const uniqueTeams = new Set(projects.map((p) => p.teamId || "personal"));
2503
+ const hasMultipleAccounts = uniqueTeams.size > 1;
2315
2504
  const choices = projects.map((p) => {
2316
2505
  const displayName = getProjectDisplayName(p);
2317
2506
  let title = displayName;
2318
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
+ }
2319
2518
  if (p.linkedRepo?.toLowerCase() === repoFullName.toLowerCase()) {
2320
- badges.push(pc10.green("\u2190 linked"));
2519
+ badges.push(pc12.green("\u2190 linked"));
2321
2520
  } else if (p.name.toLowerCase() === repoName || p.serviceName?.toLowerCase() === repoName) {
2322
- badges.push(pc10.green("\u2190 same name"));
2521
+ badges.push(pc12.green("\u2190 same name"));
2323
2522
  } else if (p.linkedRepo) {
2324
- badges.push(pc10.gray(`\u2192 ${p.linkedRepo}`));
2523
+ badges.push(pc12.gray(`\u2192 ${p.linkedRepo}`));
2325
2524
  }
2326
2525
  if (badges.length > 0) {
2327
2526
  title = `${displayName} ${badges.join(" ")}`;
2328
2527
  }
2329
2528
  return { title, value: p.id };
2330
2529
  });
2331
- const { projectChoice } = await prompts8({
2530
+ const { projectChoice } = await prompts10({
2332
2531
  type: "select",
2333
2532
  name: "projectChoice",
2334
2533
  message: "Select a project:",
2335
2534
  choices
2336
2535
  });
2337
2536
  if (!projectChoice) {
2338
- console.log(pc10.gray("Cancelled."));
2537
+ console.log(pc12.gray("Cancelled."));
2339
2538
  process.exit(0);
2340
2539
  }
2341
2540
  return projects.find((p) => p.id === projectChoice);
@@ -2343,97 +2542,133 @@ async function promptProjectSelection(projects, repoFullName) {
2343
2542
  async function syncCommand(provider, options = {}) {
2344
2543
  try {
2345
2544
  if (options.pull && options.allowDelete) {
2346
- console.error(pc10.red("Error: --allow-delete cannot be used with --pull"));
2347
- console.log(pc10.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."));
2348
2547
  process.exit(1);
2349
2548
  }
2350
2549
  const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
2351
2550
  const repoFullName = detectGitRepo();
2352
2551
  if (!repoFullName) {
2353
- console.error(pc10.red("Could not detect Git repository."));
2354
- console.log(pc10.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."));
2355
2554
  process.exit(1);
2356
2555
  }
2357
- console.log(pc10.gray(`Repository: ${repoFullName}`));
2556
+ console.log(pc12.gray(`Repository: ${repoFullName}`));
2358
2557
  const vaultExists = await checkVaultExists(accessToken, repoFullName);
2359
2558
  if (!vaultExists) {
2360
- console.log(pc10.yellow(`
2559
+ console.log(pc12.yellow(`
2361
2560
  No vault found for ${repoFullName}.`));
2362
- const { shouldCreate } = await prompts8({
2561
+ const { shouldCreate } = await prompts10({
2363
2562
  type: "confirm",
2364
2563
  name: "shouldCreate",
2365
2564
  message: "Create vault now?",
2366
2565
  initial: true
2367
2566
  });
2368
2567
  if (!shouldCreate) {
2369
- console.log(pc10.gray("Cancelled. Run `keyway init` to create a vault first."));
2568
+ console.log(pc12.gray("Cancelled. Run `keyway init` to create a vault first."));
2370
2569
  process.exit(0);
2371
2570
  }
2372
- console.log(pc10.gray("\nCreating vault..."));
2571
+ console.log(pc12.gray("\nCreating vault..."));
2373
2572
  try {
2374
2573
  await initVault(repoFullName, accessToken);
2375
- console.log(pc10.green(`\u2713 Vault created for ${repoFullName}
2574
+ console.log(pc12.green(`\u2713 Vault created for ${repoFullName}
2376
2575
  `));
2377
2576
  } catch (error) {
2378
2577
  const message = error instanceof Error ? error.message : "Failed to create vault";
2379
- console.error(pc10.red(`
2578
+ console.error(pc12.red(`
2380
2579
  \u2717 ${message}`));
2381
2580
  process.exit(1);
2382
2581
  }
2383
2582
  }
2384
- let { connections } = await getConnections(accessToken);
2385
- let connection = connections.find((c) => c.provider === provider.toLowerCase());
2386
- if (!connection) {
2387
- const providerDisplayName = provider.charAt(0).toUpperCase() + provider.slice(1);
2388
- console.log(pc10.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(`
2389
2587
  Not connected to ${providerDisplayName}.`));
2390
- const { shouldConnect } = await prompts8({
2588
+ const { shouldConnect } = await prompts10({
2391
2589
  type: "confirm",
2392
2590
  name: "shouldConnect",
2393
2591
  message: `Connect to ${providerDisplayName} now?`,
2394
2592
  initial: true
2395
2593
  });
2396
2594
  if (!shouldConnect) {
2397
- console.log(pc10.gray("Cancelled."));
2595
+ console.log(pc12.gray("Cancelled."));
2398
2596
  process.exit(0);
2399
2597
  }
2400
2598
  await connectCommand(provider, { loginPrompt: false });
2401
- const refreshed = await getConnections(accessToken);
2599
+ const refreshed = await getAllProviderProjects(accessToken, provider.toLowerCase());
2600
+ allProjects = refreshed.projects;
2402
2601
  connections = refreshed.connections;
2403
- connection = connections.find((c) => c.provider === provider.toLowerCase());
2404
- if (!connection) {
2405
- console.error(pc10.red(`
2602
+ if (connections.length === 0) {
2603
+ console.error(pc12.red(`
2406
2604
  Connection to ${providerDisplayName} failed.`));
2407
2605
  process.exit(1);
2408
2606
  }
2409
2607
  console.log("");
2410
2608
  }
2411
- 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
+ }
2412
2641
  if (projects.length === 0) {
2413
- console.error(pc10.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
+ }
2414
2646
  process.exit(1);
2415
2647
  }
2648
+ if (connections.length > 1 && !options.team) {
2649
+ console.log(pc12.gray(`Searching ${projects.length} projects across ${connections.length} ${providerDisplayName} accounts...`));
2650
+ }
2416
2651
  let selectedProject;
2417
2652
  if (options.project) {
2418
2653
  const found = projects.find(
2419
2654
  (p) => p.id === options.project || p.name.toLowerCase() === options.project?.toLowerCase() || p.serviceName?.toLowerCase() === options.project?.toLowerCase()
2420
2655
  );
2421
2656
  if (!found) {
2422
- console.error(pc10.red(`Project not found: ${options.project}`));
2423
- console.log(pc10.gray("Available projects:"));
2424
- projects.forEach((p) => console.log(pc10.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)}`)));
2425
2660
  process.exit(1);
2426
2661
  }
2427
2662
  selectedProject = found;
2428
2663
  if (!projectMatchesRepo(selectedProject, repoFullName)) {
2429
2664
  console.log("");
2430
- console.log(pc10.yellow("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
2431
- console.log(pc10.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
2432
- console.log(pc10.yellow("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
2433
- console.log(pc10.yellow(` Current repo: ${repoFullName}`));
2434
- console.log(pc10.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)}`));
2435
2670
  if (selectedProject.linkedRepo) {
2436
- console.log(pc10.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2671
+ console.log(pc12.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2437
2672
  }
2438
2673
  console.log("");
2439
2674
  }
@@ -2442,11 +2677,18 @@ Connection to ${providerDisplayName} failed.`));
2442
2677
  if (autoMatch && (autoMatch.matchType === "linked_repo" || autoMatch.matchType === "exact_name")) {
2443
2678
  selectedProject = autoMatch.project;
2444
2679
  const matchReason = autoMatch.matchType === "linked_repo" ? `linked to ${repoFullName}` : "exact name match";
2445
- console.log(pc10.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})`));
2446
2688
  } else if (autoMatch && autoMatch.matchType === "partial_name") {
2447
2689
  const partialDisplayName = getProjectDisplayName(autoMatch.project);
2448
- console.log(pc10.yellow(`Detected project: ${partialDisplayName} (partial match)`));
2449
- const { useDetected } = await prompts8({
2690
+ console.log(pc12.yellow(`Detected project: ${partialDisplayName} (partial match)`));
2691
+ const { useDetected } = await prompts10({
2450
2692
  type: "confirm",
2451
2693
  name: "useDetected",
2452
2694
  message: `Use ${partialDisplayName}?`,
@@ -2461,30 +2703,30 @@ Connection to ${providerDisplayName} failed.`));
2461
2703
  selectedProject = projects[0];
2462
2704
  if (!projectMatchesRepo(selectedProject, repoFullName)) {
2463
2705
  console.log("");
2464
- console.log(pc10.yellow("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
2465
- console.log(pc10.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
2466
- console.log(pc10.yellow("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
2467
- console.log(pc10.yellow(` Current repo: ${repoFullName}`));
2468
- console.log(pc10.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)}`));
2469
2711
  if (selectedProject.linkedRepo) {
2470
- console.log(pc10.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2712
+ console.log(pc12.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2471
2713
  }
2472
2714
  console.log("");
2473
- const { continueAnyway } = await prompts8({
2715
+ const { continueAnyway } = await prompts10({
2474
2716
  type: "confirm",
2475
2717
  name: "continueAnyway",
2476
2718
  message: "Continue anyway?",
2477
2719
  initial: false
2478
2720
  });
2479
2721
  if (!continueAnyway) {
2480
- console.log(pc10.gray("Cancelled."));
2722
+ console.log(pc12.gray("Cancelled."));
2481
2723
  process.exit(0);
2482
2724
  }
2483
2725
  }
2484
2726
  } else {
2485
- console.log(pc10.yellow(`
2727
+ console.log(pc12.yellow(`
2486
2728
  \u26A0\uFE0F No matching project found for ${repoFullName}`));
2487
- console.log(pc10.gray("Select a project manually:\n"));
2729
+ console.log(pc12.gray("Select a project manually:\n"));
2488
2730
  selectedProject = await promptProjectSelection(projects, repoFullName);
2489
2731
  }
2490
2732
  }
@@ -2492,23 +2734,23 @@ Connection to ${providerDisplayName} failed.`));
2492
2734
  const autoMatch = findMatchingProject(projects, repoFullName);
2493
2735
  if (autoMatch && autoMatch.project.id !== selectedProject.id) {
2494
2736
  console.log("");
2495
- console.log(pc10.yellow("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
2496
- console.log(pc10.yellow("\u2502 \u26A0\uFE0F WARNING: You selected a different project \u2502"));
2497
- console.log(pc10.yellow("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
2498
- console.log(pc10.yellow(` Current repo: ${repoFullName}`));
2499
- console.log(pc10.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)}`));
2500
2742
  if (selectedProject.linkedRepo) {
2501
- console.log(pc10.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2743
+ console.log(pc12.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
2502
2744
  }
2503
2745
  console.log("");
2504
- const { continueAnyway } = await prompts8({
2746
+ const { continueAnyway } = await prompts10({
2505
2747
  type: "confirm",
2506
2748
  name: "continueAnyway",
2507
2749
  message: "Are you sure you want to sync with this project?",
2508
2750
  initial: false
2509
2751
  });
2510
2752
  if (!continueAnyway) {
2511
- console.log(pc10.gray("Cancelled."));
2753
+ console.log(pc12.gray("Cancelled."));
2512
2754
  process.exit(0);
2513
2755
  }
2514
2756
  }
@@ -2522,7 +2764,7 @@ Connection to ${providerDisplayName} failed.`));
2522
2764
  if (needsEnvPrompt || needsDirectionPrompt) {
2523
2765
  if (needsEnvPrompt) {
2524
2766
  const vaultEnvs = await getVaultEnvironments(accessToken, repoFullName);
2525
- const { selectedEnv } = await prompts8({
2767
+ const { selectedEnv } = await prompts10({
2526
2768
  type: "select",
2527
2769
  name: "selectedEnv",
2528
2770
  message: "Keyway environment:",
@@ -2530,7 +2772,7 @@ Connection to ${providerDisplayName} failed.`));
2530
2772
  initial: Math.max(0, vaultEnvs.indexOf("production"))
2531
2773
  });
2532
2774
  if (!selectedEnv) {
2533
- console.log(pc10.gray("Cancelled."));
2775
+ console.log(pc12.gray("Cancelled."));
2534
2776
  process.exit(0);
2535
2777
  }
2536
2778
  keywayEnv = selectedEnv;
@@ -2544,9 +2786,9 @@ Connection to ${providerDisplayName} failed.`));
2544
2786
  providerEnv = mappedEnv;
2545
2787
  } else if (selectedProject.environments.length === 1) {
2546
2788
  providerEnv = selectedProject.environments[0];
2547
- console.log(pc10.gray(`Using ${providerName} environment: ${providerEnv}`));
2789
+ console.log(pc12.gray(`Using ${providerName} environment: ${providerEnv}`));
2548
2790
  } else {
2549
- const { selectedProviderEnv } = await prompts8({
2791
+ const { selectedProviderEnv } = await prompts10({
2550
2792
  type: "select",
2551
2793
  name: "selectedProviderEnv",
2552
2794
  message: `${providerName} environment:`,
@@ -2556,7 +2798,7 @@ Connection to ${providerDisplayName} failed.`));
2556
2798
  ))
2557
2799
  });
2558
2800
  if (!selectedProviderEnv) {
2559
- console.log(pc10.gray("Cancelled."));
2801
+ console.log(pc12.gray("Cancelled."));
2560
2802
  process.exit(0);
2561
2803
  }
2562
2804
  providerEnv = selectedProviderEnv;
@@ -2570,9 +2812,9 @@ Connection to ${providerDisplayName} failed.`));
2570
2812
  if (needsDirectionPrompt) {
2571
2813
  const effectiveKeywayEnv = keywayEnv || "production";
2572
2814
  const effectiveProviderEnv = providerEnv || mapToProviderEnvironment(provider, effectiveKeywayEnv);
2573
- console.log(pc10.gray("\nComparing secrets..."));
2815
+ console.log(pc12.gray("\nComparing secrets..."));
2574
2816
  diff = await getSyncDiff(accessToken, repoFullName, {
2575
- connectionId: connection.id,
2817
+ connectionId: selectedProject.connectionId,
2576
2818
  projectId: selectedProject.id,
2577
2819
  serviceId: selectedProject.serviceId,
2578
2820
  // Railway: service ID for service-specific variables
@@ -2592,7 +2834,7 @@ Connection to ${providerDisplayName} failed.`));
2592
2834
  } else if (diff.providerCount === 0 && diff.keywayCount > 0) {
2593
2835
  defaultDirection = 0;
2594
2836
  }
2595
- const { selectedDirection } = await prompts8({
2837
+ const { selectedDirection } = await prompts10({
2596
2838
  type: "select",
2597
2839
  name: "selectedDirection",
2598
2840
  message: "Sync direction:",
@@ -2603,7 +2845,7 @@ Connection to ${providerDisplayName} failed.`));
2603
2845
  initial: defaultDirection
2604
2846
  });
2605
2847
  if (!selectedDirection) {
2606
- console.log(pc10.gray("Cancelled."));
2848
+ console.log(pc12.gray("Cancelled."));
2607
2849
  process.exit(0);
2608
2850
  }
2609
2851
  direction = selectedDirection;
@@ -2615,15 +2857,15 @@ Connection to ${providerDisplayName} failed.`));
2615
2857
  const status = await getSyncStatus(
2616
2858
  accessToken,
2617
2859
  repoFullName,
2618
- connection.id,
2860
+ selectedProject.connectionId,
2619
2861
  selectedProject.id,
2620
2862
  keywayEnv
2621
2863
  );
2622
2864
  if (status.isFirstSync && direction === "push" && status.vaultIsEmpty && status.providerHasSecrets) {
2623
- console.log(pc10.yellow(`
2865
+ console.log(pc12.yellow(`
2624
2866
  \u26A0\uFE0F Your Keyway vault is empty for "${keywayEnv}", but ${providerName} has ${status.providerSecretCount} secrets.`));
2625
- console.log(pc10.gray(` (Use --environment to sync a different environment)`));
2626
- const { importFirst } = await prompts8({
2867
+ console.log(pc12.gray(` (Use --environment to sync a different environment)`));
2868
+ const { importFirst } = await prompts10({
2627
2869
  type: "confirm",
2628
2870
  name: "importFirst",
2629
2871
  message: `Import secrets from ${providerName} first?`,
@@ -2633,7 +2875,7 @@ Connection to ${providerDisplayName} failed.`));
2633
2875
  await executeSyncOperation(
2634
2876
  accessToken,
2635
2877
  repoFullName,
2636
- connection.id,
2878
+ selectedProject.connectionId,
2637
2879
  selectedProject,
2638
2880
  keywayEnv,
2639
2881
  providerEnv,
@@ -2649,7 +2891,7 @@ Connection to ${providerDisplayName} failed.`));
2649
2891
  await executeSyncOperation(
2650
2892
  accessToken,
2651
2893
  repoFullName,
2652
- connection.id,
2894
+ selectedProject.connectionId,
2653
2895
  selectedProject,
2654
2896
  keywayEnv,
2655
2897
  providerEnv,
@@ -2664,7 +2906,7 @@ Connection to ${providerDisplayName} failed.`));
2664
2906
  command: "sync",
2665
2907
  error: truncateMessage(message)
2666
2908
  });
2667
- console.error(pc10.red(`
2909
+ console.error(pc12.red(`
2668
2910
  \u2717 ${message}`));
2669
2911
  process.exit(1);
2670
2912
  }
@@ -2683,49 +2925,49 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
2683
2925
  });
2684
2926
  const totalChanges = preview.toCreate.length + preview.toUpdate.length + preview.toDelete.length;
2685
2927
  if (totalChanges === 0) {
2686
- console.log(pc10.green("\n\u2713 Already in sync. No changes needed."));
2928
+ console.log(pc12.green("\n\u2713 Already in sync. No changes needed."));
2687
2929
  return;
2688
2930
  }
2689
- console.log(pc10.blue("\n\u{1F4CB} Sync Preview\n"));
2931
+ console.log(pc12.blue("\n\u{1F4CB} Sync Preview\n"));
2690
2932
  if (preview.toCreate.length > 0) {
2691
- console.log(pc10.green(` + ${preview.toCreate.length} to create`));
2692
- preview.toCreate.slice(0, 5).forEach((key) => console.log(pc10.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}`)));
2693
2935
  if (preview.toCreate.length > 5) {
2694
- console.log(pc10.gray(` ... and ${preview.toCreate.length - 5} more`));
2936
+ console.log(pc12.gray(` ... and ${preview.toCreate.length - 5} more`));
2695
2937
  }
2696
2938
  }
2697
2939
  if (preview.toUpdate.length > 0) {
2698
- console.log(pc10.yellow(` ~ ${preview.toUpdate.length} to update`));
2699
- preview.toUpdate.slice(0, 5).forEach((key) => console.log(pc10.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}`)));
2700
2942
  if (preview.toUpdate.length > 5) {
2701
- console.log(pc10.gray(` ... and ${preview.toUpdate.length - 5} more`));
2943
+ console.log(pc12.gray(` ... and ${preview.toUpdate.length - 5} more`));
2702
2944
  }
2703
2945
  }
2704
2946
  if (preview.toDelete.length > 0) {
2705
- console.log(pc10.red(` - ${preview.toDelete.length} to delete`));
2706
- preview.toDelete.slice(0, 5).forEach((key) => console.log(pc10.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}`)));
2707
2949
  if (preview.toDelete.length > 5) {
2708
- console.log(pc10.gray(` ... and ${preview.toDelete.length - 5} more`));
2950
+ console.log(pc12.gray(` ... and ${preview.toDelete.length - 5} more`));
2709
2951
  }
2710
2952
  }
2711
2953
  if (preview.toSkip.length > 0) {
2712
- console.log(pc10.gray(` \u25CB ${preview.toSkip.length} unchanged`));
2954
+ console.log(pc12.gray(` \u25CB ${preview.toSkip.length} unchanged`));
2713
2955
  }
2714
2956
  console.log("");
2715
2957
  if (!skipConfirm) {
2716
2958
  const target = direction === "push" ? providerName : "Keyway";
2717
- const { confirm } = await prompts8({
2959
+ const { confirm } = await prompts10({
2718
2960
  type: "confirm",
2719
2961
  name: "confirm",
2720
2962
  message: `Apply ${totalChanges} changes to ${target}?`,
2721
2963
  initial: true
2722
2964
  });
2723
2965
  if (!confirm) {
2724
- console.log(pc10.gray("Cancelled."));
2966
+ console.log(pc12.gray("Cancelled."));
2725
2967
  return;
2726
2968
  }
2727
2969
  }
2728
- console.log(pc10.blue("\n\u23F3 Syncing...\n"));
2970
+ console.log(pc12.blue("\n\u23F3 Syncing...\n"));
2729
2971
  const result = await executeSync(accessToken, repoFullName, {
2730
2972
  connectionId,
2731
2973
  projectId: project.id,
@@ -2737,11 +2979,11 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
2737
2979
  allowDelete
2738
2980
  });
2739
2981
  if (result.success) {
2740
- console.log(pc10.green("\u2713 Sync complete"));
2741
- console.log(pc10.gray(` Created: ${result.stats.created}`));
2742
- console.log(pc10.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}`));
2743
2985
  if (result.stats.deleted > 0) {
2744
- console.log(pc10.gray(` Deleted: ${result.stats.deleted}`));
2986
+ console.log(pc12.gray(` Deleted: ${result.stats.deleted}`));
2745
2987
  }
2746
2988
  trackEvent(AnalyticsEvents.CLI_SYNC, {
2747
2989
  provider,
@@ -2751,7 +2993,7 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
2751
2993
  deleted: result.stats.deleted
2752
2994
  });
2753
2995
  } else {
2754
- console.error(pc10.red(`
2996
+ console.error(pc12.red(`
2755
2997
  \u2717 ${result.error}`));
2756
2998
  process.exit(1);
2757
2999
  }
@@ -2759,16 +3001,16 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
2759
3001
 
2760
3002
  // src/cli.ts
2761
3003
  process.on("unhandledRejection", (reason) => {
2762
- console.error(pc11.red("Unhandled error:"), reason);
3004
+ console.error(pc13.red("Unhandled error:"), reason);
2763
3005
  process.exit(1);
2764
3006
  });
2765
3007
  var program = new Command();
2766
3008
  var TAGLINE = "Sync secrets with your team and infra";
2767
3009
  var showBanner = () => {
2768
- const text = pc11.bold(pc11.cyan("Keyway CLI"));
3010
+ const text = pc13.bold(pc13.cyan("Keyway CLI"));
2769
3011
  console.log(`
2770
3012
  ${text}
2771
- ${pc11.gray(TAGLINE)}
3013
+ ${pc13.gray(TAGLINE)}
2772
3014
  `);
2773
3015
  };
2774
3016
  showBanner();
@@ -2800,13 +3042,17 @@ program.command("connections").description("List your provider connections").opt
2800
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) => {
2801
3043
  await disconnectCommand(provider, options);
2802
3044
  });
2803
- 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) => {
2804
3046
  await syncCommand(provider, options);
2805
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
+ });
2806
3052
  (async () => {
2807
3053
  await warnIfEnvNotGitignored();
2808
3054
  await program.parseAsync();
2809
3055
  })().catch((error) => {
2810
- console.error(pc11.red("Error:"), error.message || error);
3056
+ console.error(pc13.red("Error:"), error.message || error);
2811
3057
  process.exit(1);
2812
3058
  });