@keywaysh/cli 0.0.21 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth-QLPQ24HZ.js +12 -0
- package/dist/chunk-F4C46224.js +102 -0
- package/dist/cli.js +352 -221
- package/package.json +1 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// src/utils/auth.ts
|
|
2
|
+
import Conf from "conf";
|
|
3
|
+
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { homedir } from "os";
|
|
7
|
+
var store = new Conf({
|
|
8
|
+
projectName: "keyway",
|
|
9
|
+
configName: "config",
|
|
10
|
+
fileMode: 384
|
|
11
|
+
});
|
|
12
|
+
var KEY_DIR = join(homedir(), ".keyway");
|
|
13
|
+
var KEY_FILE = join(KEY_DIR, ".key");
|
|
14
|
+
function getOrCreateEncryptionKey() {
|
|
15
|
+
if (!existsSync(KEY_DIR)) {
|
|
16
|
+
mkdirSync(KEY_DIR, { recursive: true, mode: 448 });
|
|
17
|
+
}
|
|
18
|
+
if (existsSync(KEY_FILE)) {
|
|
19
|
+
const keyHex2 = readFileSync(KEY_FILE, "utf-8").trim();
|
|
20
|
+
if (keyHex2.length === 64) {
|
|
21
|
+
return Buffer.from(keyHex2, "hex");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const key = randomBytes(32);
|
|
25
|
+
const keyHex = key.toString("hex");
|
|
26
|
+
writeFileSync(KEY_FILE, keyHex, { mode: 384 });
|
|
27
|
+
try {
|
|
28
|
+
chmodSync(KEY_FILE, 384);
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
return key;
|
|
32
|
+
}
|
|
33
|
+
function encryptToken(token) {
|
|
34
|
+
const key = getOrCreateEncryptionKey();
|
|
35
|
+
const iv = randomBytes(16);
|
|
36
|
+
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
37
|
+
const encrypted = Buffer.concat([cipher.update(token, "utf8"), cipher.final()]);
|
|
38
|
+
const authTag = cipher.getAuthTag();
|
|
39
|
+
return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted.toString("hex")}`;
|
|
40
|
+
}
|
|
41
|
+
function decryptToken(encryptedData) {
|
|
42
|
+
const key = getOrCreateEncryptionKey();
|
|
43
|
+
const parts = encryptedData.split(":");
|
|
44
|
+
if (parts.length !== 3) {
|
|
45
|
+
throw new Error("Invalid encrypted token format");
|
|
46
|
+
}
|
|
47
|
+
const iv = Buffer.from(parts[0], "hex");
|
|
48
|
+
const authTag = Buffer.from(parts[1], "hex");
|
|
49
|
+
const encrypted = Buffer.from(parts[2], "hex");
|
|
50
|
+
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
51
|
+
decipher.setAuthTag(authTag);
|
|
52
|
+
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
53
|
+
return decrypted.toString("utf8");
|
|
54
|
+
}
|
|
55
|
+
function isExpired(auth) {
|
|
56
|
+
if (!auth.expiresAt) return false;
|
|
57
|
+
const expires = Date.parse(auth.expiresAt);
|
|
58
|
+
if (Number.isNaN(expires)) return false;
|
|
59
|
+
return expires <= Date.now();
|
|
60
|
+
}
|
|
61
|
+
async function getStoredAuth() {
|
|
62
|
+
const encryptedData = store.get("auth");
|
|
63
|
+
if (!encryptedData) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const decrypted = decryptToken(encryptedData);
|
|
68
|
+
const auth = JSON.parse(decrypted);
|
|
69
|
+
if (isExpired(auth)) {
|
|
70
|
+
clearAuth();
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
return auth;
|
|
74
|
+
} catch {
|
|
75
|
+
console.error("Failed to decrypt stored auth, clearing...");
|
|
76
|
+
clearAuth();
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function saveAuthToken(token, meta) {
|
|
81
|
+
const auth = {
|
|
82
|
+
keywayToken: token,
|
|
83
|
+
githubLogin: meta?.githubLogin,
|
|
84
|
+
expiresAt: meta?.expiresAt,
|
|
85
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
86
|
+
};
|
|
87
|
+
const encrypted = encryptToken(JSON.stringify(auth));
|
|
88
|
+
store.set("auth", encrypted);
|
|
89
|
+
}
|
|
90
|
+
function clearAuth() {
|
|
91
|
+
store.delete("auth");
|
|
92
|
+
}
|
|
93
|
+
function getAuthFilePath() {
|
|
94
|
+
return store.path;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export {
|
|
98
|
+
getStoredAuth,
|
|
99
|
+
saveAuthToken,
|
|
100
|
+
clearAuth,
|
|
101
|
+
getAuthFilePath
|
|
102
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
clearAuth,
|
|
4
|
+
getAuthFilePath,
|
|
5
|
+
getStoredAuth,
|
|
6
|
+
saveAuthToken
|
|
7
|
+
} from "./chunk-F4C46224.js";
|
|
2
8
|
|
|
3
9
|
// src/cli.ts
|
|
4
10
|
import { Command } from "commander";
|
|
@@ -7,6 +13,7 @@ import pc9 from "picocolors";
|
|
|
7
13
|
// src/cmds/init.ts
|
|
8
14
|
import pc4 from "picocolors";
|
|
9
15
|
import prompts4 from "prompts";
|
|
16
|
+
import open2 from "open";
|
|
10
17
|
|
|
11
18
|
// src/utils/git.ts
|
|
12
19
|
import { execSync } from "child_process";
|
|
@@ -69,7 +76,7 @@ var INTERNAL_POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
|
69
76
|
// package.json
|
|
70
77
|
var package_default = {
|
|
71
78
|
name: "@keywaysh/cli",
|
|
72
|
-
version: "0.
|
|
79
|
+
version: "0.1.1",
|
|
73
80
|
description: "One link to all your secrets",
|
|
74
81
|
type: "module",
|
|
75
82
|
bin: {
|
|
@@ -338,7 +345,8 @@ async function validateToken(token) {
|
|
|
338
345
|
},
|
|
339
346
|
body: JSON.stringify({})
|
|
340
347
|
});
|
|
341
|
-
|
|
348
|
+
const wrapped = await handleResponse(response);
|
|
349
|
+
return wrapped.data;
|
|
342
350
|
}
|
|
343
351
|
async function getProviders() {
|
|
344
352
|
const response = await fetchWithTimeout(`${API_BASE_URL}/v1/integrations`, {
|
|
@@ -347,7 +355,8 @@ async function getProviders() {
|
|
|
347
355
|
"User-Agent": USER_AGENT
|
|
348
356
|
}
|
|
349
357
|
});
|
|
350
|
-
|
|
358
|
+
const wrapped = await handleResponse(response);
|
|
359
|
+
return wrapped.data;
|
|
351
360
|
}
|
|
352
361
|
async function getConnections(accessToken) {
|
|
353
362
|
const response = await fetchWithTimeout(`${API_BASE_URL}/v1/integrations/connections`, {
|
|
@@ -357,7 +366,8 @@ async function getConnections(accessToken) {
|
|
|
357
366
|
Authorization: `Bearer ${accessToken}`
|
|
358
367
|
}
|
|
359
368
|
});
|
|
360
|
-
|
|
369
|
+
const wrapped = await handleResponse(response);
|
|
370
|
+
return wrapped.data;
|
|
361
371
|
}
|
|
362
372
|
async function deleteConnection(accessToken, connectionId) {
|
|
363
373
|
const response = await fetchWithTimeout(`${API_BASE_URL}/v1/integrations/connections/${connectionId}`, {
|
|
@@ -367,7 +377,7 @@ async function deleteConnection(accessToken, connectionId) {
|
|
|
367
377
|
Authorization: `Bearer ${accessToken}`
|
|
368
378
|
}
|
|
369
379
|
});
|
|
370
|
-
|
|
380
|
+
await handleResponse(response);
|
|
371
381
|
}
|
|
372
382
|
function getProviderAuthUrl(provider, redirectUri) {
|
|
373
383
|
const params = redirectUri ? `?redirect_uri=${encodeURIComponent(redirectUri)}` : "";
|
|
@@ -381,7 +391,8 @@ async function getConnectionProjects(accessToken, connectionId) {
|
|
|
381
391
|
Authorization: `Bearer ${accessToken}`
|
|
382
392
|
}
|
|
383
393
|
});
|
|
384
|
-
|
|
394
|
+
const wrapped = await handleResponse(response);
|
|
395
|
+
return wrapped.data;
|
|
385
396
|
}
|
|
386
397
|
async function getSyncStatus(accessToken, repoFullName, connectionId, projectId, environment = "production") {
|
|
387
398
|
const [owner, repo] = repoFullName.split("/");
|
|
@@ -400,7 +411,8 @@ async function getSyncStatus(accessToken, repoFullName, connectionId, projectId,
|
|
|
400
411
|
}
|
|
401
412
|
}
|
|
402
413
|
);
|
|
403
|
-
|
|
414
|
+
const wrapped = await handleResponse(response);
|
|
415
|
+
return wrapped.data;
|
|
404
416
|
}
|
|
405
417
|
async function getSyncPreview(accessToken, repoFullName, options) {
|
|
406
418
|
const [owner, repo] = repoFullName.split("/");
|
|
@@ -424,7 +436,8 @@ async function getSyncPreview(accessToken, repoFullName, options) {
|
|
|
424
436
|
6e4
|
|
425
437
|
// 60 seconds for sync operations
|
|
426
438
|
);
|
|
427
|
-
|
|
439
|
+
const wrapped = await handleResponse(response);
|
|
440
|
+
return wrapped.data;
|
|
428
441
|
}
|
|
429
442
|
async function executeSync(accessToken, repoFullName, options) {
|
|
430
443
|
const [owner, repo] = repoFullName.split("/");
|
|
@@ -449,7 +462,21 @@ async function executeSync(accessToken, repoFullName, options) {
|
|
|
449
462
|
12e4
|
|
450
463
|
// 2 minutes for sync execution
|
|
451
464
|
);
|
|
452
|
-
|
|
465
|
+
const wrapped = await handleResponse(response);
|
|
466
|
+
return wrapped.data;
|
|
467
|
+
}
|
|
468
|
+
async function checkGitHubAppInstallation(repoOwner, repoName, accessToken) {
|
|
469
|
+
const response = await fetchWithTimeout(`${API_BASE_URL}/v1/github/check-installation`, {
|
|
470
|
+
method: "POST",
|
|
471
|
+
headers: {
|
|
472
|
+
"Content-Type": "application/json",
|
|
473
|
+
"User-Agent": USER_AGENT,
|
|
474
|
+
Authorization: `Bearer ${accessToken}`
|
|
475
|
+
},
|
|
476
|
+
body: JSON.stringify({ repoOwner, repoName })
|
|
477
|
+
});
|
|
478
|
+
const wrapped = await handleResponse(response);
|
|
479
|
+
return wrapped.data;
|
|
453
480
|
}
|
|
454
481
|
|
|
455
482
|
// src/utils/analytics.ts
|
|
@@ -541,6 +568,30 @@ async function shutdownAnalytics() {
|
|
|
541
568
|
await posthog.shutdown();
|
|
542
569
|
}
|
|
543
570
|
}
|
|
571
|
+
function identifyUser(userId, properties) {
|
|
572
|
+
try {
|
|
573
|
+
if (TELEMETRY_DISABLED) return;
|
|
574
|
+
if (!posthog) initPostHog();
|
|
575
|
+
if (!posthog) return;
|
|
576
|
+
const sanitizedProperties = properties ? sanitizeProperties(properties) : {};
|
|
577
|
+
posthog.identify({
|
|
578
|
+
distinctId: userId,
|
|
579
|
+
properties: {
|
|
580
|
+
...sanitizedProperties,
|
|
581
|
+
source: "cli"
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
const anonId = getDistinctId();
|
|
585
|
+
if (anonId && anonId !== userId) {
|
|
586
|
+
posthog.alias({
|
|
587
|
+
distinctId: userId,
|
|
588
|
+
alias: anonId
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
} catch (error) {
|
|
592
|
+
console.debug("Analytics identify error:", error);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
544
595
|
var AnalyticsEvents = {
|
|
545
596
|
CLI_INIT: "cli_init",
|
|
546
597
|
CLI_PUSH: "cli_push",
|
|
@@ -554,103 +605,108 @@ var AnalyticsEvents = {
|
|
|
554
605
|
CLI_FEEDBACK: "cli_feedback"
|
|
555
606
|
};
|
|
556
607
|
|
|
557
|
-
// src/cmds/
|
|
558
|
-
import
|
|
559
|
-
import
|
|
560
|
-
import open from "open";
|
|
608
|
+
// src/cmds/readme.ts
|
|
609
|
+
import fs2 from "fs";
|
|
610
|
+
import path2 from "path";
|
|
561
611
|
import prompts from "prompts";
|
|
612
|
+
import pc from "picocolors";
|
|
613
|
+
function generateBadge(repo) {
|
|
614
|
+
return `[](https://www.keyway.sh/vaults/${repo})`;
|
|
615
|
+
}
|
|
616
|
+
function insertBadgeIntoReadme(readmeContent, badge) {
|
|
617
|
+
if (readmeContent.includes("keyway.sh/badge.svg")) {
|
|
618
|
+
return readmeContent;
|
|
619
|
+
}
|
|
620
|
+
const lines = readmeContent.split(/\r?\n/);
|
|
621
|
+
const titleIndex = lines.findIndex((line) => /^#(?!#)\s+/.test(line.trim()));
|
|
622
|
+
if (titleIndex !== -1) {
|
|
623
|
+
const before = lines.slice(0, titleIndex + 1);
|
|
624
|
+
const after = lines.slice(titleIndex + 1);
|
|
625
|
+
while (after.length > 0 && after[0].trim() === "") {
|
|
626
|
+
after.shift();
|
|
627
|
+
}
|
|
628
|
+
const newLines = [...before, "", badge, "", ...after];
|
|
629
|
+
return newLines.join("\n");
|
|
630
|
+
}
|
|
631
|
+
return `${badge}
|
|
562
632
|
|
|
563
|
-
|
|
564
|
-
import Conf from "conf";
|
|
565
|
-
import { createCipheriv, createDecipheriv, randomBytes, scrypt } from "crypto";
|
|
566
|
-
import { promisify } from "util";
|
|
567
|
-
var store = new Conf({
|
|
568
|
-
projectName: "keyway",
|
|
569
|
-
configName: "config",
|
|
570
|
-
fileMode: 384
|
|
571
|
-
});
|
|
572
|
-
var scryptAsync = promisify(scrypt);
|
|
573
|
-
async function getEncryptionKey() {
|
|
574
|
-
const machineId = process.env.USER || process.env.USERNAME || "keyway-user";
|
|
575
|
-
let salt = store.get("salt");
|
|
576
|
-
if (!salt) {
|
|
577
|
-
salt = randomBytes(16).toString("hex");
|
|
578
|
-
store.set("salt", salt);
|
|
579
|
-
}
|
|
580
|
-
const key = await scryptAsync(machineId, salt, 32);
|
|
581
|
-
return key;
|
|
582
|
-
}
|
|
583
|
-
async function encryptToken(token) {
|
|
584
|
-
const key = await getEncryptionKey();
|
|
585
|
-
const iv = randomBytes(16);
|
|
586
|
-
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
587
|
-
const encrypted = Buffer.concat([
|
|
588
|
-
cipher.update(token, "utf8"),
|
|
589
|
-
cipher.final()
|
|
590
|
-
]);
|
|
591
|
-
const authTag = cipher.getAuthTag();
|
|
592
|
-
return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted.toString("hex")}`;
|
|
593
|
-
}
|
|
594
|
-
async function decryptToken(encryptedData) {
|
|
595
|
-
const key = await getEncryptionKey();
|
|
596
|
-
const parts = encryptedData.split(":");
|
|
597
|
-
if (parts.length !== 3) {
|
|
598
|
-
throw new Error("Invalid encrypted token format");
|
|
599
|
-
}
|
|
600
|
-
const iv = Buffer.from(parts[0], "hex");
|
|
601
|
-
const authTag = Buffer.from(parts[1], "hex");
|
|
602
|
-
const encrypted = Buffer.from(parts[2], "hex");
|
|
603
|
-
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
604
|
-
decipher.setAuthTag(authTag);
|
|
605
|
-
const decrypted = Buffer.concat([
|
|
606
|
-
decipher.update(encrypted),
|
|
607
|
-
decipher.final()
|
|
608
|
-
]);
|
|
609
|
-
return decrypted.toString("utf8");
|
|
633
|
+
${readmeContent}`;
|
|
610
634
|
}
|
|
611
|
-
function
|
|
612
|
-
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
|
|
635
|
+
function findReadmePath(cwd) {
|
|
636
|
+
const candidates = ["README.md", "readme.md", "Readme.md"];
|
|
637
|
+
for (const candidate of candidates) {
|
|
638
|
+
const candidatePath = path2.join(cwd, candidate);
|
|
639
|
+
if (fs2.existsSync(candidatePath)) {
|
|
640
|
+
return candidatePath;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return null;
|
|
616
644
|
}
|
|
617
|
-
async function
|
|
618
|
-
const
|
|
619
|
-
if (
|
|
645
|
+
async function ensureReadme(repoName, cwd) {
|
|
646
|
+
const existing = findReadmePath(cwd);
|
|
647
|
+
if (existing) return existing;
|
|
648
|
+
const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
|
|
649
|
+
if (!isInteractive3) {
|
|
650
|
+
console.log(pc.yellow('No README found. Run "keyway readme add-badge" from a repo with a README.'));
|
|
620
651
|
return null;
|
|
621
652
|
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
653
|
+
const { confirm } = await prompts(
|
|
654
|
+
{
|
|
655
|
+
type: "confirm",
|
|
656
|
+
name: "confirm",
|
|
657
|
+
message: "No README found. Create a default README.md?",
|
|
658
|
+
initial: false
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
onCancel: () => ({ confirm: false })
|
|
628
662
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
console.
|
|
632
|
-
clearAuth();
|
|
663
|
+
);
|
|
664
|
+
if (!confirm) {
|
|
665
|
+
console.log(pc.yellow("Skipping badge insertion (no README)."));
|
|
633
666
|
return null;
|
|
634
667
|
}
|
|
668
|
+
const defaultPath = path2.join(cwd, "README.md");
|
|
669
|
+
const content = `# ${repoName}
|
|
670
|
+
|
|
671
|
+
`;
|
|
672
|
+
fs2.writeFileSync(defaultPath, content, "utf-8");
|
|
673
|
+
return defaultPath;
|
|
635
674
|
}
|
|
636
|
-
async function
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
675
|
+
async function addBadgeToReadme(silent = false) {
|
|
676
|
+
const repo = detectGitRepo();
|
|
677
|
+
if (!repo) {
|
|
678
|
+
throw new Error("This directory is not a Git repository.");
|
|
679
|
+
}
|
|
680
|
+
const cwd = process.cwd();
|
|
681
|
+
const readmePath = await ensureReadme(repo, cwd);
|
|
682
|
+
if (!readmePath) return false;
|
|
683
|
+
const badge = generateBadge(repo);
|
|
684
|
+
const content = fs2.readFileSync(readmePath, "utf-8");
|
|
685
|
+
const updated = insertBadgeIntoReadme(content, badge);
|
|
686
|
+
if (updated === content) {
|
|
687
|
+
if (!silent) {
|
|
688
|
+
console.log(pc.gray("Keyway badge already present in README."));
|
|
689
|
+
}
|
|
690
|
+
return false;
|
|
691
|
+
}
|
|
692
|
+
fs2.writeFileSync(readmePath, updated, "utf-8");
|
|
693
|
+
if (!silent) {
|
|
694
|
+
console.log(pc.green(`\u2713 Keyway badge added to ${path2.basename(readmePath)}`));
|
|
695
|
+
}
|
|
696
|
+
return true;
|
|
651
697
|
}
|
|
652
698
|
|
|
699
|
+
// src/cmds/push.ts
|
|
700
|
+
import pc3 from "picocolors";
|
|
701
|
+
import fs3 from "fs";
|
|
702
|
+
import path3 from "path";
|
|
703
|
+
import prompts3 from "prompts";
|
|
704
|
+
|
|
653
705
|
// src/cmds/login.ts
|
|
706
|
+
import pc2 from "picocolors";
|
|
707
|
+
import readline from "readline";
|
|
708
|
+
import open from "open";
|
|
709
|
+
import prompts2 from "prompts";
|
|
654
710
|
function sleep(ms) {
|
|
655
711
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
656
712
|
}
|
|
@@ -674,17 +730,17 @@ async function promptYesNo(question, defaultYes = true) {
|
|
|
674
730
|
});
|
|
675
731
|
}
|
|
676
732
|
async function runLoginFlow() {
|
|
677
|
-
console.log(
|
|
733
|
+
console.log(pc2.blue("\u{1F510} Starting Keyway login...\n"));
|
|
678
734
|
const repoName = detectGitRepo();
|
|
679
735
|
const start = await startDeviceLogin(repoName);
|
|
680
736
|
const verifyUrl = start.verificationUriComplete || start.verificationUri;
|
|
681
737
|
if (!verifyUrl) {
|
|
682
738
|
throw new Error("Missing verification URL from the auth server.");
|
|
683
739
|
}
|
|
684
|
-
console.log(`Code: ${
|
|
740
|
+
console.log(`Code: ${pc2.bold(pc2.green(start.userCode))}`);
|
|
685
741
|
console.log("Waiting for auth...");
|
|
686
742
|
open(verifyUrl).catch(() => {
|
|
687
|
-
console.log(
|
|
743
|
+
console.log(pc2.gray(`Open this URL in your browser: ${verifyUrl}`));
|
|
688
744
|
});
|
|
689
745
|
const pollIntervalMs = (start.interval ?? 5) * 1e3;
|
|
690
746
|
const maxTimeoutMs = Math.min((start.expiresIn ?? 900) * 1e3, 30 * 60 * 1e3);
|
|
@@ -707,9 +763,15 @@ async function runLoginFlow() {
|
|
|
707
763
|
method: "device",
|
|
708
764
|
repo: repoName
|
|
709
765
|
});
|
|
710
|
-
console.log(pc.green("\n\u2713 Login successful"));
|
|
711
766
|
if (result.githubLogin) {
|
|
712
|
-
|
|
767
|
+
identifyUser(result.githubLogin, {
|
|
768
|
+
github_username: result.githubLogin,
|
|
769
|
+
login_method: "device"
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
console.log(pc2.green("\n\u2713 Login successful"));
|
|
773
|
+
if (result.githubLogin) {
|
|
774
|
+
console.log(`Authenticated GitHub user: ${pc2.cyan(result.githubLogin)}`);
|
|
713
775
|
}
|
|
714
776
|
return result.keywayToken;
|
|
715
777
|
}
|
|
@@ -722,7 +784,7 @@ async function ensureLogin(options = {}) {
|
|
|
722
784
|
return envToken;
|
|
723
785
|
}
|
|
724
786
|
if (process.env.GITHUB_TOKEN && !process.env.KEYWAY_TOKEN) {
|
|
725
|
-
console.warn(
|
|
787
|
+
console.warn(pc2.yellow("Note: GITHUB_TOKEN found but not used. Set KEYWAY_TOKEN for Keyway authentication."));
|
|
726
788
|
}
|
|
727
789
|
const stored = await getStoredAuth();
|
|
728
790
|
if (stored?.keywayToken) {
|
|
@@ -742,17 +804,17 @@ async function ensureLogin(options = {}) {
|
|
|
742
804
|
async function runTokenLogin() {
|
|
743
805
|
const repoName = detectGitRepo();
|
|
744
806
|
if (repoName) {
|
|
745
|
-
console.log(`\u{1F4C1} Detected: ${
|
|
807
|
+
console.log(`\u{1F4C1} Detected: ${pc2.cyan(repoName)}`);
|
|
746
808
|
}
|
|
747
809
|
const description = repoName ? `Keyway CLI for ${repoName}` : "Keyway CLI";
|
|
748
810
|
const url = `https://github.com/settings/personal-access-tokens/new?description=${encodeURIComponent(description)}`;
|
|
749
811
|
console.log("Opening GitHub...");
|
|
750
812
|
open(url).catch(() => {
|
|
751
|
-
console.log(
|
|
813
|
+
console.log(pc2.gray(`Open this URL in your browser: ${url}`));
|
|
752
814
|
});
|
|
753
|
-
console.log(
|
|
754
|
-
console.log(
|
|
755
|
-
const { token } = await
|
|
815
|
+
console.log(pc2.gray("Select the detected repo (or scope manually)."));
|
|
816
|
+
console.log(pc2.gray("Permissions: Metadata \u2192 Read-only; Account permissions: None."));
|
|
817
|
+
const { token } = await prompts2(
|
|
756
818
|
{
|
|
757
819
|
type: "password",
|
|
758
820
|
name: "token",
|
|
@@ -784,7 +846,11 @@ async function runTokenLogin() {
|
|
|
784
846
|
method: "pat",
|
|
785
847
|
repo: repoName
|
|
786
848
|
});
|
|
787
|
-
|
|
849
|
+
identifyUser(validation.username, {
|
|
850
|
+
github_username: validation.username,
|
|
851
|
+
login_method: "pat"
|
|
852
|
+
});
|
|
853
|
+
console.log(pc2.green("\u2705 Authenticated"), `as ${pc2.cyan(`@${validation.username}`)}`);
|
|
788
854
|
return trimmedToken;
|
|
789
855
|
}
|
|
790
856
|
async function loginCommand(options = {}) {
|
|
@@ -800,113 +866,18 @@ async function loginCommand(options = {}) {
|
|
|
800
866
|
command: "login",
|
|
801
867
|
error: truncateMessage(message)
|
|
802
868
|
});
|
|
803
|
-
console.error(
|
|
869
|
+
console.error(pc2.red(`
|
|
804
870
|
\u2717 ${message}`));
|
|
805
871
|
process.exit(1);
|
|
806
872
|
}
|
|
807
873
|
}
|
|
808
874
|
async function logoutCommand() {
|
|
809
875
|
clearAuth();
|
|
810
|
-
console.log(
|
|
811
|
-
console.log(
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
// src/cmds/readme.ts
|
|
815
|
-
import fs2 from "fs";
|
|
816
|
-
import path2 from "path";
|
|
817
|
-
import prompts2 from "prompts";
|
|
818
|
-
import pc2 from "picocolors";
|
|
819
|
-
function generateBadge(repo) {
|
|
820
|
-
return `[](https://www.keyway.sh/vaults/${repo})`;
|
|
821
|
-
}
|
|
822
|
-
function insertBadgeIntoReadme(readmeContent, badge) {
|
|
823
|
-
if (readmeContent.includes("keyway.sh/badge.svg")) {
|
|
824
|
-
return readmeContent;
|
|
825
|
-
}
|
|
826
|
-
const lines = readmeContent.split(/\r?\n/);
|
|
827
|
-
const titleIndex = lines.findIndex((line) => /^#(?!#)\s+/.test(line.trim()));
|
|
828
|
-
if (titleIndex !== -1) {
|
|
829
|
-
const before = lines.slice(0, titleIndex + 1);
|
|
830
|
-
const after = lines.slice(titleIndex + 1);
|
|
831
|
-
while (after.length > 0 && after[0].trim() === "") {
|
|
832
|
-
after.shift();
|
|
833
|
-
}
|
|
834
|
-
const newLines = [...before, "", badge, "", ...after];
|
|
835
|
-
return newLines.join("\n");
|
|
836
|
-
}
|
|
837
|
-
return `${badge}
|
|
838
|
-
|
|
839
|
-
${readmeContent}`;
|
|
840
|
-
}
|
|
841
|
-
function findReadmePath(cwd) {
|
|
842
|
-
const candidates = ["README.md", "readme.md", "Readme.md"];
|
|
843
|
-
for (const candidate of candidates) {
|
|
844
|
-
const candidatePath = path2.join(cwd, candidate);
|
|
845
|
-
if (fs2.existsSync(candidatePath)) {
|
|
846
|
-
return candidatePath;
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
return null;
|
|
850
|
-
}
|
|
851
|
-
async function ensureReadme(repoName, cwd) {
|
|
852
|
-
const existing = findReadmePath(cwd);
|
|
853
|
-
if (existing) return existing;
|
|
854
|
-
const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
|
|
855
|
-
if (!isInteractive2) {
|
|
856
|
-
console.log(pc2.yellow('No README found. Run "keyway readme add-badge" from a repo with a README.'));
|
|
857
|
-
return null;
|
|
858
|
-
}
|
|
859
|
-
const { confirm } = await prompts2(
|
|
860
|
-
{
|
|
861
|
-
type: "confirm",
|
|
862
|
-
name: "confirm",
|
|
863
|
-
message: "No README found. Create a default README.md?",
|
|
864
|
-
initial: false
|
|
865
|
-
},
|
|
866
|
-
{
|
|
867
|
-
onCancel: () => ({ confirm: false })
|
|
868
|
-
}
|
|
869
|
-
);
|
|
870
|
-
if (!confirm) {
|
|
871
|
-
console.log(pc2.yellow("Skipping badge insertion (no README)."));
|
|
872
|
-
return null;
|
|
873
|
-
}
|
|
874
|
-
const defaultPath = path2.join(cwd, "README.md");
|
|
875
|
-
const content = `# ${repoName}
|
|
876
|
-
|
|
877
|
-
`;
|
|
878
|
-
fs2.writeFileSync(defaultPath, content, "utf-8");
|
|
879
|
-
return defaultPath;
|
|
880
|
-
}
|
|
881
|
-
async function addBadgeToReadme(silent = false) {
|
|
882
|
-
const repo = detectGitRepo();
|
|
883
|
-
if (!repo) {
|
|
884
|
-
throw new Error("This directory is not a Git repository.");
|
|
885
|
-
}
|
|
886
|
-
const cwd = process.cwd();
|
|
887
|
-
const readmePath = await ensureReadme(repo, cwd);
|
|
888
|
-
if (!readmePath) return false;
|
|
889
|
-
const badge = generateBadge(repo);
|
|
890
|
-
const content = fs2.readFileSync(readmePath, "utf-8");
|
|
891
|
-
const updated = insertBadgeIntoReadme(content, badge);
|
|
892
|
-
if (updated === content) {
|
|
893
|
-
if (!silent) {
|
|
894
|
-
console.log(pc2.gray("Keyway badge already present in README."));
|
|
895
|
-
}
|
|
896
|
-
return false;
|
|
897
|
-
}
|
|
898
|
-
fs2.writeFileSync(readmePath, updated, "utf-8");
|
|
899
|
-
if (!silent) {
|
|
900
|
-
console.log(pc2.green(`\u2713 Keyway badge added to ${path2.basename(readmePath)}`));
|
|
901
|
-
}
|
|
902
|
-
return true;
|
|
876
|
+
console.log(pc2.green("\u2713 Logged out of Keyway"));
|
|
877
|
+
console.log(pc2.gray(`Auth cache cleared: ${getAuthFilePath()}`));
|
|
903
878
|
}
|
|
904
879
|
|
|
905
880
|
// src/cmds/push.ts
|
|
906
|
-
import pc3 from "picocolors";
|
|
907
|
-
import fs3 from "fs";
|
|
908
|
-
import path3 from "path";
|
|
909
|
-
import prompts3 from "prompts";
|
|
910
881
|
function deriveEnvFromFile(file) {
|
|
911
882
|
const base = path3.basename(file);
|
|
912
883
|
const match = base.match(/\.env(?:\.(.+))?$/);
|
|
@@ -947,7 +918,7 @@ function discoverEnvCandidates(cwd) {
|
|
|
947
918
|
async function pushCommand(options) {
|
|
948
919
|
try {
|
|
949
920
|
console.log(pc3.blue("\u{1F510} Pushing secrets to Keyway...\n"));
|
|
950
|
-
const
|
|
921
|
+
const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
|
|
951
922
|
let environment = options.env;
|
|
952
923
|
let envFile = options.file;
|
|
953
924
|
const candidates = discoverEnvCandidates(process.cwd());
|
|
@@ -957,7 +928,7 @@ async function pushCommand(options) {
|
|
|
957
928
|
envFile = match.file;
|
|
958
929
|
}
|
|
959
930
|
}
|
|
960
|
-
if (!environment && !envFile &&
|
|
931
|
+
if (!environment && !envFile && isInteractive3 && candidates.length > 0) {
|
|
961
932
|
const { choice } = await prompts3(
|
|
962
933
|
{
|
|
963
934
|
type: "select",
|
|
@@ -1011,7 +982,7 @@ async function pushCommand(options) {
|
|
|
1011
982
|
}
|
|
1012
983
|
let envFilePath = path3.resolve(process.cwd(), envFile);
|
|
1013
984
|
if (!fs3.existsSync(envFilePath)) {
|
|
1014
|
-
if (!
|
|
985
|
+
if (!isInteractive3) {
|
|
1015
986
|
throw new Error(`File not found: ${envFile}. Provide --file <path> or run interactively to choose a file.`);
|
|
1016
987
|
}
|
|
1017
988
|
const { newPath } = await prompts3(
|
|
@@ -1052,8 +1023,8 @@ async function pushCommand(options) {
|
|
|
1052
1023
|
const repoFullName = getCurrentRepoFullName();
|
|
1053
1024
|
console.log(`Repository: ${pc3.cyan(repoFullName)}`);
|
|
1054
1025
|
if (!options.yes) {
|
|
1055
|
-
const
|
|
1056
|
-
if (!
|
|
1026
|
+
const isInteractive4 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1027
|
+
if (!isInteractive4) {
|
|
1057
1028
|
throw new Error("Confirmation required. Re-run with --yes in non-interactive environments.");
|
|
1058
1029
|
}
|
|
1059
1030
|
const { confirm } = await prompts3(
|
|
@@ -1110,6 +1081,12 @@ Your secrets are now encrypted and stored securely.`);
|
|
|
1110
1081
|
hint = `Available environments: ${availableEnvs}
|
|
1111
1082
|
Use ${pc3.cyan(`keyway push --env <environment>`)} to specify one, or create '${requestedEnv}' via the dashboard.`;
|
|
1112
1083
|
}
|
|
1084
|
+
if (error.statusCode === 403 && error.upgradeUrl) {
|
|
1085
|
+
hint = `${pc3.yellow("\u26A1")} Upgrade to Pro: ${pc3.cyan(error.upgradeUrl)}`;
|
|
1086
|
+
} else if (error.statusCode === 403 && message.toLowerCase().includes("read-only")) {
|
|
1087
|
+
message = "This vault is read-only on your current plan.";
|
|
1088
|
+
hint = `Upgrade to Pro to unlock editing: ${pc3.cyan("https://keyway.sh/settings")}`;
|
|
1089
|
+
}
|
|
1113
1090
|
} else if (error instanceof Error) {
|
|
1114
1091
|
message = truncateMessage(error.message);
|
|
1115
1092
|
} else {
|
|
@@ -1132,15 +1109,168 @@ ${hint}`));
|
|
|
1132
1109
|
|
|
1133
1110
|
// src/cmds/init.ts
|
|
1134
1111
|
var DASHBOARD_URL = "https://www.keyway.sh/dashboard/vaults";
|
|
1112
|
+
var POLL_INTERVAL_MS = 3e3;
|
|
1113
|
+
var POLL_TIMEOUT_MS = 12e4;
|
|
1114
|
+
function sleep2(ms) {
|
|
1115
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1116
|
+
}
|
|
1117
|
+
function isInteractive2() {
|
|
1118
|
+
return Boolean(process.stdout.isTTY && process.stdin.isTTY && !process.env.CI);
|
|
1119
|
+
}
|
|
1120
|
+
async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
1121
|
+
const [repoOwner, repoName] = repoFullName.split("/");
|
|
1122
|
+
const envToken = process.env.KEYWAY_TOKEN;
|
|
1123
|
+
if (envToken) {
|
|
1124
|
+
const result = await ensureGitHubAppInstalledOnly(repoFullName, envToken);
|
|
1125
|
+
if (result === null) {
|
|
1126
|
+
throw new Error("KEYWAY_TOKEN is invalid or expired. Please update the token.");
|
|
1127
|
+
}
|
|
1128
|
+
return result;
|
|
1129
|
+
}
|
|
1130
|
+
const stored = await getStoredAuth();
|
|
1131
|
+
if (stored?.keywayToken) {
|
|
1132
|
+
const result = await ensureGitHubAppInstalledOnly(repoFullName, stored.keywayToken);
|
|
1133
|
+
if (result !== null) {
|
|
1134
|
+
return result;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
const allowPrompt = options.allowPrompt !== false;
|
|
1138
|
+
if (!allowPrompt || !isInteractive2()) {
|
|
1139
|
+
throw new Error('No Keyway session found. Run "keyway login" to authenticate.');
|
|
1140
|
+
}
|
|
1141
|
+
console.log("");
|
|
1142
|
+
console.log(pc4.gray(" Keyway uses a GitHub App for secure access."));
|
|
1143
|
+
console.log(pc4.gray(" Installing the app will also log you in."));
|
|
1144
|
+
console.log("");
|
|
1145
|
+
const { shouldProceed } = await prompts4({
|
|
1146
|
+
type: "confirm",
|
|
1147
|
+
name: "shouldProceed",
|
|
1148
|
+
message: "Open browser to install Keyway & sign in?",
|
|
1149
|
+
initial: true
|
|
1150
|
+
});
|
|
1151
|
+
if (!shouldProceed) {
|
|
1152
|
+
throw new Error('Setup required. Run "keyway init" when ready.');
|
|
1153
|
+
}
|
|
1154
|
+
const deviceStart = await startDeviceLogin(repoFullName);
|
|
1155
|
+
const installUrl = deviceStart.githubAppInstallUrl || "https://github.com/apps/keyway/installations/new";
|
|
1156
|
+
console.log(pc4.gray("\n Opening browser..."));
|
|
1157
|
+
await open2(installUrl);
|
|
1158
|
+
console.log("");
|
|
1159
|
+
console.log(pc4.blue("\u23F3 Waiting for installation & authorization..."));
|
|
1160
|
+
console.log(pc4.gray(" (Press Ctrl+C to cancel)\n"));
|
|
1161
|
+
const pollIntervalMs = Math.max((deviceStart.interval ?? 5) * 1e3, POLL_INTERVAL_MS);
|
|
1162
|
+
const startTime = Date.now();
|
|
1163
|
+
let accessToken = null;
|
|
1164
|
+
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
1165
|
+
await sleep2(pollIntervalMs);
|
|
1166
|
+
try {
|
|
1167
|
+
if (!accessToken) {
|
|
1168
|
+
const result = await pollDeviceLogin(deviceStart.deviceCode);
|
|
1169
|
+
if (result.status === "approved" && result.keywayToken) {
|
|
1170
|
+
accessToken = result.keywayToken;
|
|
1171
|
+
await saveAuthToken(result.keywayToken, {
|
|
1172
|
+
githubLogin: result.githubLogin,
|
|
1173
|
+
expiresAt: result.expiresAt
|
|
1174
|
+
});
|
|
1175
|
+
console.log(pc4.green("\u2713 Signed in!"));
|
|
1176
|
+
if (result.githubLogin) {
|
|
1177
|
+
identifyUser(result.githubLogin, {
|
|
1178
|
+
github_username: result.githubLogin,
|
|
1179
|
+
login_method: "github_app"
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
if (accessToken) {
|
|
1185
|
+
const installStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1186
|
+
if (installStatus.installed) {
|
|
1187
|
+
console.log(pc4.green("\u2713 GitHub App installed!"));
|
|
1188
|
+
console.log("");
|
|
1189
|
+
return accessToken;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
process.stdout.write(pc4.gray("."));
|
|
1193
|
+
} catch {
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
console.log("");
|
|
1197
|
+
console.log(pc4.yellow("\u26A0 Timed out waiting for setup."));
|
|
1198
|
+
console.log(pc4.gray(` Install the GitHub App: ${installUrl}`));
|
|
1199
|
+
throw new Error("Setup timed out. Please try again.");
|
|
1200
|
+
}
|
|
1201
|
+
async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
1202
|
+
const [repoOwner, repoName] = repoFullName.split("/");
|
|
1203
|
+
let status;
|
|
1204
|
+
try {
|
|
1205
|
+
status = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1206
|
+
} catch (error) {
|
|
1207
|
+
if (error instanceof APIError && error.statusCode === 401) {
|
|
1208
|
+
console.log(pc4.yellow("\n\u26A0 Session expired or invalid. Clearing credentials..."));
|
|
1209
|
+
const { clearAuth: clearAuth2 } = await import("./auth-QLPQ24HZ.js");
|
|
1210
|
+
clearAuth2();
|
|
1211
|
+
return null;
|
|
1212
|
+
}
|
|
1213
|
+
throw error;
|
|
1214
|
+
}
|
|
1215
|
+
if (status.installed) {
|
|
1216
|
+
return accessToken;
|
|
1217
|
+
}
|
|
1218
|
+
console.log("");
|
|
1219
|
+
console.log(pc4.yellow("\u26A0 GitHub App not installed for this repository"));
|
|
1220
|
+
console.log("");
|
|
1221
|
+
console.log(pc4.gray(" The Keyway GitHub App is required to securely manage secrets."));
|
|
1222
|
+
console.log(pc4.gray(" It only requests minimal permissions (repository metadata)."));
|
|
1223
|
+
console.log("");
|
|
1224
|
+
if (!isInteractive2()) {
|
|
1225
|
+
console.log(pc4.gray(` Install the Keyway GitHub App: ${status.installUrl}`));
|
|
1226
|
+
throw new Error("GitHub App installation required.");
|
|
1227
|
+
}
|
|
1228
|
+
const { shouldInstall } = await prompts4({
|
|
1229
|
+
type: "confirm",
|
|
1230
|
+
name: "shouldInstall",
|
|
1231
|
+
message: "Open browser to install Keyway GitHub App?",
|
|
1232
|
+
initial: true
|
|
1233
|
+
});
|
|
1234
|
+
if (!shouldInstall) {
|
|
1235
|
+
console.log(pc4.gray(`
|
|
1236
|
+
You can install later: ${status.installUrl}`));
|
|
1237
|
+
throw new Error("GitHub App installation required.");
|
|
1238
|
+
}
|
|
1239
|
+
console.log(pc4.gray("\n Opening browser..."));
|
|
1240
|
+
await open2(status.installUrl);
|
|
1241
|
+
console.log("");
|
|
1242
|
+
console.log(pc4.blue("\u23F3 Waiting for GitHub App installation..."));
|
|
1243
|
+
console.log(pc4.gray(" (Press Ctrl+C to cancel)\n"));
|
|
1244
|
+
const startTime = Date.now();
|
|
1245
|
+
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
1246
|
+
await sleep2(POLL_INTERVAL_MS);
|
|
1247
|
+
try {
|
|
1248
|
+
const pollStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1249
|
+
if (pollStatus.installed) {
|
|
1250
|
+
console.log(pc4.green("\u2713 GitHub App installed!"));
|
|
1251
|
+
console.log("");
|
|
1252
|
+
return accessToken;
|
|
1253
|
+
}
|
|
1254
|
+
process.stdout.write(pc4.gray("."));
|
|
1255
|
+
} catch {
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
console.log("");
|
|
1259
|
+
console.log(pc4.yellow("\u26A0 Timed out waiting for installation."));
|
|
1260
|
+
console.log(pc4.gray(` You can install the GitHub App later: ${status.installUrl}`));
|
|
1261
|
+
throw new Error("GitHub App installation timed out.");
|
|
1262
|
+
}
|
|
1135
1263
|
async function initCommand(options = {}) {
|
|
1136
1264
|
try {
|
|
1137
1265
|
const repoFullName = getCurrentRepoFullName();
|
|
1138
1266
|
const dashboardLink = `${DASHBOARD_URL}/${repoFullName}`;
|
|
1139
1267
|
console.log(pc4.blue("\u{1F510} Initializing Keyway vault...\n"));
|
|
1140
1268
|
console.log(` ${pc4.gray("Repository:")} ${pc4.white(repoFullName)}`);
|
|
1141
|
-
const accessToken = await
|
|
1142
|
-
|
|
1143
|
-
|
|
1269
|
+
const accessToken = await ensureLoginAndGitHubApp(repoFullName, {
|
|
1270
|
+
allowPrompt: options.loginPrompt !== false
|
|
1271
|
+
});
|
|
1272
|
+
trackEvent(AnalyticsEvents.CLI_INIT, { repoFullName, githubAppInstalled: true });
|
|
1273
|
+
await initVault(repoFullName, accessToken);
|
|
1144
1274
|
console.log(pc4.green("\u2713 Vault created!"));
|
|
1145
1275
|
try {
|
|
1146
1276
|
const badgeAdded = await addBadgeToReadme(true);
|
|
@@ -1151,8 +1281,8 @@ async function initCommand(options = {}) {
|
|
|
1151
1281
|
}
|
|
1152
1282
|
console.log("");
|
|
1153
1283
|
const envCandidates = discoverEnvCandidates(process.cwd());
|
|
1154
|
-
const
|
|
1155
|
-
if (envCandidates.length > 0 &&
|
|
1284
|
+
const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1285
|
+
if (envCandidates.length > 0 && isInteractive3) {
|
|
1156
1286
|
console.log(pc4.gray(` Found ${envCandidates.length} env file(s): ${envCandidates.map((c) => c.file).join(", ")}
|
|
1157
1287
|
`));
|
|
1158
1288
|
const { shouldPush } = await prompts4({
|
|
@@ -1190,15 +1320,16 @@ async function initCommand(options = {}) {
|
|
|
1190
1320
|
await shutdownAnalytics();
|
|
1191
1321
|
return;
|
|
1192
1322
|
}
|
|
1193
|
-
if (error.error === "
|
|
1323
|
+
if (error.error === "Plan Limit Reached" || error.upgradeUrl) {
|
|
1324
|
+
const upgradeUrl = error.upgradeUrl || "https://keyway.sh/pricing";
|
|
1194
1325
|
console.log("");
|
|
1195
1326
|
console.log(pc4.dim("\u2500".repeat(50)));
|
|
1196
1327
|
console.log("");
|
|
1197
|
-
console.log(` ${pc4.yellow("\u26A1")} ${pc4.bold("
|
|
1328
|
+
console.log(` ${pc4.yellow("\u26A1")} ${pc4.bold("Plan Limit Reached")}`);
|
|
1198
1329
|
console.log("");
|
|
1199
|
-
console.log(pc4.
|
|
1330
|
+
console.log(pc4.white(` ${error.message}`));
|
|
1200
1331
|
console.log("");
|
|
1201
|
-
console.log(` ${pc4.cyan("\u2192")} ${pc4.underline(
|
|
1332
|
+
console.log(` ${pc4.cyan("Upgrade now \u2192")} ${pc4.underline(upgradeUrl)}`);
|
|
1202
1333
|
console.log("");
|
|
1203
1334
|
console.log(pc4.dim("\u2500".repeat(50)));
|
|
1204
1335
|
console.log("");
|
|
@@ -1240,11 +1371,11 @@ async function pullCommand(options) {
|
|
|
1240
1371
|
const response = await pullSecrets(repoFullName, environment, accessToken);
|
|
1241
1372
|
const envFilePath = path4.resolve(process.cwd(), envFile);
|
|
1242
1373
|
if (fs4.existsSync(envFilePath)) {
|
|
1243
|
-
const
|
|
1374
|
+
const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1244
1375
|
if (options.yes) {
|
|
1245
1376
|
console.log(pc5.yellow(`
|
|
1246
1377
|
\u26A0 Overwriting existing file: ${envFile}`));
|
|
1247
|
-
} else if (!
|
|
1378
|
+
} else if (!isInteractive3) {
|
|
1248
1379
|
throw new Error(`File ${envFile} exists. Re-run with --yes to overwrite or choose a different --file.`);
|
|
1249
1380
|
} else {
|
|
1250
1381
|
const { confirm } = await prompts5(
|
|
@@ -1607,7 +1738,7 @@ Summary: ${formatSummary(results)}`);
|
|
|
1607
1738
|
|
|
1608
1739
|
// src/cmds/connect.ts
|
|
1609
1740
|
import pc7 from "picocolors";
|
|
1610
|
-
import
|
|
1741
|
+
import open3 from "open";
|
|
1611
1742
|
import prompts6 from "prompts";
|
|
1612
1743
|
async function connectCommand(provider, options = {}) {
|
|
1613
1744
|
try {
|
|
@@ -1646,7 +1777,7 @@ Connecting to ${providerInfo.displayName}...
|
|
|
1646
1777
|
const startTime = /* @__PURE__ */ new Date();
|
|
1647
1778
|
console.log(pc7.gray("Opening browser for authorization..."));
|
|
1648
1779
|
console.log(pc7.gray(`If the browser doesn't open, visit: ${authUrl}`));
|
|
1649
|
-
await
|
|
1780
|
+
await open3(authUrl).catch(() => {
|
|
1650
1781
|
});
|
|
1651
1782
|
console.log(pc7.gray("Waiting for authorization..."));
|
|
1652
1783
|
const maxAttempts = 60;
|