thinkwork-cli 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +533 -212
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,30 @@ var require2 = createRequire(import.meta.url);
|
|
|
9
9
|
var pkg = require2("../package.json");
|
|
10
10
|
var VERSION = pkg.version;
|
|
11
11
|
|
|
12
|
+
// src/cli-config.ts
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
14
|
+
import { homedir } from "os";
|
|
15
|
+
import { dirname, join } from "path";
|
|
16
|
+
function getCliConfigPath(override) {
|
|
17
|
+
return override ?? join(homedir(), ".thinkwork", "config.json");
|
|
18
|
+
}
|
|
19
|
+
function loadCliConfig(pathOverride) {
|
|
20
|
+
const path2 = getCliConfigPath(pathOverride);
|
|
21
|
+
if (!existsSync(path2)) return {};
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(readFileSync(path2, "utf-8"));
|
|
24
|
+
} catch {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function saveCliConfig(next, pathOverride) {
|
|
29
|
+
const path2 = getCliConfigPath(pathOverride);
|
|
30
|
+
const dir = dirname(path2);
|
|
31
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
32
|
+
const merged = { ...loadCliConfig(pathOverride), ...next };
|
|
33
|
+
writeFileSync(path2, JSON.stringify(merged, null, 2) + "\n");
|
|
34
|
+
}
|
|
35
|
+
|
|
12
36
|
// src/config.ts
|
|
13
37
|
var VALID_COMPONENTS = ["foundation", "data", "app", "all"];
|
|
14
38
|
var PROD_LIKE_STAGES = ["main", "prod", "production", "staging"];
|
|
@@ -76,23 +100,23 @@ function getAwsIdentity() {
|
|
|
76
100
|
|
|
77
101
|
// src/terraform.ts
|
|
78
102
|
import { spawn } from "child_process";
|
|
79
|
-
import { existsSync } from "fs";
|
|
103
|
+
import { existsSync as existsSync2 } from "fs";
|
|
80
104
|
import path from "path";
|
|
81
105
|
function resolveTierDir(terraformDir, stage, tier) {
|
|
82
106
|
const envDir = path.join(terraformDir, "environments", stage, tier);
|
|
83
|
-
if (
|
|
107
|
+
if (existsSync2(envDir)) {
|
|
84
108
|
return envDir;
|
|
85
109
|
}
|
|
86
110
|
const greenfield = path.join(terraformDir, "examples", "greenfield");
|
|
87
|
-
if (
|
|
111
|
+
if (existsSync2(greenfield)) {
|
|
88
112
|
return greenfield;
|
|
89
113
|
}
|
|
90
114
|
const flat = path.join(terraformDir);
|
|
91
|
-
if (
|
|
115
|
+
if (existsSync2(path.join(flat, "main.tf"))) {
|
|
92
116
|
return flat;
|
|
93
117
|
}
|
|
94
118
|
const cwdTf = path.join(process.cwd(), "terraform");
|
|
95
|
-
if (
|
|
119
|
+
if (existsSync2(path.join(cwdTf, "main.tf"))) {
|
|
96
120
|
return cwdTf;
|
|
97
121
|
}
|
|
98
122
|
return terraformDir;
|
|
@@ -136,7 +160,7 @@ function runTerraform(cwd, args) {
|
|
|
136
160
|
}
|
|
137
161
|
async function ensureInit(cwd) {
|
|
138
162
|
const dotTerraform = path.join(cwd, ".terraform");
|
|
139
|
-
if (!
|
|
163
|
+
if (!existsSync2(dotTerraform)) {
|
|
140
164
|
const code = await runTerraform(cwd, ["init"]);
|
|
141
165
|
if (code !== 0) {
|
|
142
166
|
throw new Error("terraform init failed");
|
|
@@ -485,58 +509,58 @@ function registerOutputsCommand(program2) {
|
|
|
485
509
|
}
|
|
486
510
|
|
|
487
511
|
// src/commands/config.ts
|
|
488
|
-
import { readFileSync as
|
|
512
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync4 } from "fs";
|
|
489
513
|
import chalk3 from "chalk";
|
|
490
514
|
|
|
491
515
|
// src/environments.ts
|
|
492
|
-
import { existsSync as
|
|
493
|
-
import { join } from "path";
|
|
494
|
-
import { homedir } from "os";
|
|
495
|
-
var THINKWORK_HOME =
|
|
496
|
-
var ENVIRONMENTS_DIR =
|
|
516
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync2, readdirSync } from "fs";
|
|
517
|
+
import { join as join2 } from "path";
|
|
518
|
+
import { homedir as homedir2 } from "os";
|
|
519
|
+
var THINKWORK_HOME = join2(homedir2(), ".thinkwork");
|
|
520
|
+
var ENVIRONMENTS_DIR = join2(THINKWORK_HOME, "environments");
|
|
497
521
|
function ensureDir(dir) {
|
|
498
|
-
if (!
|
|
522
|
+
if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
|
|
499
523
|
}
|
|
500
524
|
function saveEnvironment(config) {
|
|
501
525
|
ensureDir(ENVIRONMENTS_DIR);
|
|
502
|
-
const envDir =
|
|
526
|
+
const envDir = join2(ENVIRONMENTS_DIR, config.stage);
|
|
503
527
|
ensureDir(envDir);
|
|
504
|
-
|
|
505
|
-
|
|
528
|
+
writeFileSync2(
|
|
529
|
+
join2(envDir, "config.json"),
|
|
506
530
|
JSON.stringify(config, null, 2) + "\n"
|
|
507
531
|
);
|
|
508
532
|
}
|
|
509
533
|
function loadEnvironment(stage) {
|
|
510
|
-
const configPath =
|
|
511
|
-
if (!
|
|
512
|
-
return JSON.parse(
|
|
534
|
+
const configPath = join2(ENVIRONMENTS_DIR, stage, "config.json");
|
|
535
|
+
if (!existsSync3(configPath)) return null;
|
|
536
|
+
return JSON.parse(readFileSync2(configPath, "utf-8"));
|
|
513
537
|
}
|
|
514
538
|
function listEnvironments() {
|
|
515
|
-
if (!
|
|
539
|
+
if (!existsSync3(ENVIRONMENTS_DIR)) return [];
|
|
516
540
|
return readdirSync(ENVIRONMENTS_DIR).filter((name) => {
|
|
517
|
-
return
|
|
541
|
+
return existsSync3(join2(ENVIRONMENTS_DIR, name, "config.json"));
|
|
518
542
|
}).map((name) => {
|
|
519
543
|
return JSON.parse(
|
|
520
|
-
|
|
544
|
+
readFileSync2(join2(ENVIRONMENTS_DIR, name, "config.json"), "utf-8")
|
|
521
545
|
);
|
|
522
546
|
}).sort((a, b) => a.stage.localeCompare(b.stage));
|
|
523
547
|
}
|
|
524
548
|
function resolveTerraformDir(stage) {
|
|
525
549
|
const env = loadEnvironment(stage);
|
|
526
|
-
if (env?.terraformDir &&
|
|
550
|
+
if (env?.terraformDir && existsSync3(env.terraformDir)) {
|
|
527
551
|
return env.terraformDir;
|
|
528
552
|
}
|
|
529
553
|
const envVar = process.env.THINKWORK_TERRAFORM_DIR;
|
|
530
|
-
if (envVar &&
|
|
531
|
-
const cwdTf =
|
|
532
|
-
if (
|
|
554
|
+
if (envVar && existsSync3(envVar)) return envVar;
|
|
555
|
+
const cwdTf = join2(process.cwd(), "terraform");
|
|
556
|
+
if (existsSync3(join2(cwdTf, "main.tf"))) return cwdTf;
|
|
533
557
|
return null;
|
|
534
558
|
}
|
|
535
559
|
|
|
536
560
|
// src/commands/config.ts
|
|
537
561
|
function readTfVar(tfvarsPath, key) {
|
|
538
|
-
if (!
|
|
539
|
-
const content =
|
|
562
|
+
if (!existsSync4(tfvarsPath)) return null;
|
|
563
|
+
const content = readFileSync3(tfvarsPath, "utf-8");
|
|
540
564
|
const quoted = content.match(new RegExp(`^${key}\\s*=\\s*"([^"]*)"`, "m"));
|
|
541
565
|
if (quoted) return quoted[1];
|
|
542
566
|
const bare = content.match(new RegExp(`^${key}\\s*=\\s*([^\\s#]+)`, "m"));
|
|
@@ -544,10 +568,10 @@ function readTfVar(tfvarsPath, key) {
|
|
|
544
568
|
}
|
|
545
569
|
var BARE_KEYS = /* @__PURE__ */ new Set(["enable_hindsight"]);
|
|
546
570
|
function setTfVar(tfvarsPath, key, value) {
|
|
547
|
-
if (!
|
|
571
|
+
if (!existsSync4(tfvarsPath)) {
|
|
548
572
|
throw new Error(`terraform.tfvars not found at ${tfvarsPath}`);
|
|
549
573
|
}
|
|
550
|
-
let content =
|
|
574
|
+
let content = readFileSync3(tfvarsPath, "utf-8");
|
|
551
575
|
const bare = BARE_KEYS.has(key);
|
|
552
576
|
const newLine = bare ? `${key} = ${value}` : `${key} = "${value}"`;
|
|
553
577
|
const existingRegex = new RegExp(`^${key}\\s*=\\s*(?:"[^"]*"|[^\\s#]+)`, "m");
|
|
@@ -558,13 +582,13 @@ function setTfVar(tfvarsPath, key, value) {
|
|
|
558
582
|
${newLine}
|
|
559
583
|
`;
|
|
560
584
|
}
|
|
561
|
-
|
|
585
|
+
writeFileSync3(tfvarsPath, content);
|
|
562
586
|
}
|
|
563
587
|
function resolveTfvarsPath(stage) {
|
|
564
588
|
const tfDir = resolveTerraformDir(stage);
|
|
565
589
|
if (tfDir) {
|
|
566
590
|
const direct = `${tfDir}/terraform.tfvars`;
|
|
567
|
-
if (
|
|
591
|
+
if (existsSync4(direct)) return direct;
|
|
568
592
|
}
|
|
569
593
|
const terraformDir = process.env.THINKWORK_TERRAFORM_DIR || process.cwd();
|
|
570
594
|
const cwd = resolveTierDir(terraformDir, stage, "app");
|
|
@@ -591,10 +615,10 @@ function registerConfigCommand(program2) {
|
|
|
591
615
|
console.log(` ${chalk3.bold("Updated:")} ${env.updatedAt}`);
|
|
592
616
|
console.log(chalk3.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
593
617
|
const tfvarsPath = `${env.terraformDir}/terraform.tfvars`;
|
|
594
|
-
if (
|
|
618
|
+
if (existsSync4(tfvarsPath)) {
|
|
595
619
|
console.log("");
|
|
596
620
|
console.log(chalk3.dim(" terraform.tfvars:"));
|
|
597
|
-
const content =
|
|
621
|
+
const content = readFileSync3(tfvarsPath, "utf-8");
|
|
598
622
|
for (const line of content.split("\n")) {
|
|
599
623
|
if (line.trim() && !line.trim().startsWith("#")) {
|
|
600
624
|
const masked = line.replace(
|
|
@@ -778,12 +802,90 @@ function registerBootstrapCommand(program2) {
|
|
|
778
802
|
// src/commands/login.ts
|
|
779
803
|
import { execSync as execSync4 } from "child_process";
|
|
780
804
|
import { createInterface as createInterface2 } from "readline";
|
|
805
|
+
import { select, Separator } from "@inquirer/prompts";
|
|
806
|
+
import chalk5 from "chalk";
|
|
807
|
+
|
|
808
|
+
// src/aws-profiles.ts
|
|
809
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
810
|
+
import { homedir as homedir3 } from "os";
|
|
811
|
+
import { join as join3 } from "path";
|
|
812
|
+
var CREDENTIALS_PATH = join3(homedir3(), ".aws", "credentials");
|
|
813
|
+
var CONFIG_PATH = join3(homedir3(), ".aws", "config");
|
|
814
|
+
function parseIni(content) {
|
|
815
|
+
const sections = {};
|
|
816
|
+
let current = null;
|
|
817
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
818
|
+
const line = rawLine.trim();
|
|
819
|
+
if (!line || line.startsWith("#") || line.startsWith(";")) continue;
|
|
820
|
+
const header = line.match(/^\[(.+)\]$/);
|
|
821
|
+
if (header) {
|
|
822
|
+
current = header[1].trim();
|
|
823
|
+
sections[current] ??= {};
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
if (!current) continue;
|
|
827
|
+
const eq = line.indexOf("=");
|
|
828
|
+
if (eq === -1) continue;
|
|
829
|
+
const key = line.slice(0, eq).trim();
|
|
830
|
+
const value = line.slice(eq + 1).trim();
|
|
831
|
+
sections[current][key] = value;
|
|
832
|
+
}
|
|
833
|
+
return sections;
|
|
834
|
+
}
|
|
835
|
+
function normalizeConfigSection(section) {
|
|
836
|
+
if (section === "default") return "default";
|
|
837
|
+
if (section.startsWith("profile ")) return section.slice("profile ".length).trim();
|
|
838
|
+
return null;
|
|
839
|
+
}
|
|
840
|
+
function classify(fields) {
|
|
841
|
+
if (fields.aws_access_key_id) return "keys";
|
|
842
|
+
if (fields.sso_start_url || fields.sso_session || fields.sso_account_id || fields.sso_role_name) {
|
|
843
|
+
return "sso";
|
|
844
|
+
}
|
|
845
|
+
if (fields.role_arn || fields.source_profile || fields.credential_source) {
|
|
846
|
+
return "role";
|
|
847
|
+
}
|
|
848
|
+
return "other";
|
|
849
|
+
}
|
|
850
|
+
function listAwsProfiles() {
|
|
851
|
+
const byName = /* @__PURE__ */ new Map();
|
|
852
|
+
if (existsSync5(CREDENTIALS_PATH)) {
|
|
853
|
+
const sections = parseIni(readFileSync4(CREDENTIALS_PATH, "utf-8"));
|
|
854
|
+
for (const [section, fields] of Object.entries(sections)) {
|
|
855
|
+
byName.set(section, {
|
|
856
|
+
name: section,
|
|
857
|
+
source: "credentials",
|
|
858
|
+
type: classify(fields)
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
if (existsSync5(CONFIG_PATH)) {
|
|
863
|
+
const sections = parseIni(readFileSync4(CONFIG_PATH, "utf-8"));
|
|
864
|
+
for (const [section, fields] of Object.entries(sections)) {
|
|
865
|
+
const name = normalizeConfigSection(section);
|
|
866
|
+
if (!name) continue;
|
|
867
|
+
const existing = byName.get(name);
|
|
868
|
+
const type = classify(fields);
|
|
869
|
+
if (existing) {
|
|
870
|
+
byName.set(name, {
|
|
871
|
+
...existing,
|
|
872
|
+
source: "both",
|
|
873
|
+
// Prefer the more specific type if one side says "other".
|
|
874
|
+
type: existing.type === "other" ? type : existing.type
|
|
875
|
+
});
|
|
876
|
+
} else {
|
|
877
|
+
byName.set(name, { name, source: "config", type });
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
882
|
+
}
|
|
781
883
|
|
|
782
884
|
// src/prerequisites.ts
|
|
783
885
|
import { execSync as execSync3 } from "child_process";
|
|
784
|
-
import { mkdirSync as
|
|
785
|
-
import { join as
|
|
786
|
-
import { homedir as
|
|
886
|
+
import { mkdirSync as mkdirSync3, createWriteStream, chmodSync } from "fs";
|
|
887
|
+
import { join as join4 } from "path";
|
|
888
|
+
import { homedir as homedir4, platform, arch } from "os";
|
|
787
889
|
import chalk4 from "chalk";
|
|
788
890
|
function run(cmd, opts) {
|
|
789
891
|
try {
|
|
@@ -815,14 +917,14 @@ async function ensureAwsCli() {
|
|
|
815
917
|
}
|
|
816
918
|
if (os === "linux") {
|
|
817
919
|
try {
|
|
818
|
-
const tmpDir =
|
|
819
|
-
|
|
820
|
-
const zipPath =
|
|
920
|
+
const tmpDir = join4(homedir4(), ".thinkwork", "tmp");
|
|
921
|
+
mkdirSync3(tmpDir, { recursive: true });
|
|
922
|
+
const zipPath = join4(tmpDir, "awscliv2.zip");
|
|
821
923
|
console.log(" Downloading AWS CLI...");
|
|
822
924
|
run(`curl -sL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "${zipPath}"`);
|
|
823
925
|
run(`cd "${tmpDir}" && unzip -qo "${zipPath}"`);
|
|
824
|
-
run(`"${tmpDir}/aws/install" --install-dir "${
|
|
825
|
-
process.env.PATH = `${
|
|
926
|
+
run(`"${tmpDir}/aws/install" --install-dir "${homedir4()}/.thinkwork/aws-cli" --bin-dir "${homedir4()}/.local/bin" --update`);
|
|
927
|
+
process.env.PATH = `${homedir4()}/.local/bin:${process.env.PATH}`;
|
|
826
928
|
if (isInstalled("aws")) {
|
|
827
929
|
console.log(` ${chalk4.green("\u2713")} AWS CLI installed to ~/.local/bin/aws`);
|
|
828
930
|
return true;
|
|
@@ -832,9 +934,9 @@ async function ensureAwsCli() {
|
|
|
832
934
|
}
|
|
833
935
|
if (os === "darwin") {
|
|
834
936
|
try {
|
|
835
|
-
const tmpDir =
|
|
836
|
-
|
|
837
|
-
const pkgPath =
|
|
937
|
+
const tmpDir = join4(homedir4(), ".thinkwork", "tmp");
|
|
938
|
+
mkdirSync3(tmpDir, { recursive: true });
|
|
939
|
+
const pkgPath = join4(tmpDir, "AWSCLIV2.pkg");
|
|
838
940
|
console.log(" Downloading AWS CLI...");
|
|
839
941
|
run(`curl -sL "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "${pkgPath}"`);
|
|
840
942
|
run(`installer -pkg "${pkgPath}" -target CurrentUserHomeDirectory 2>/dev/null || sudo installer -pkg "${pkgPath}" -target /`);
|
|
@@ -865,15 +967,15 @@ async function ensureTerraform() {
|
|
|
865
967
|
const archName = arch() === "arm64" ? "arm64" : "amd64";
|
|
866
968
|
const url = `https://releases.hashicorp.com/terraform/${tfVersion}/terraform_${tfVersion}_${osName}_${archName}.zip`;
|
|
867
969
|
try {
|
|
868
|
-
const tmpDir =
|
|
869
|
-
const binDir =
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
const zipPath =
|
|
970
|
+
const tmpDir = join4(homedir4(), ".thinkwork", "tmp");
|
|
971
|
+
const binDir = join4(homedir4(), ".local", "bin");
|
|
972
|
+
mkdirSync3(tmpDir, { recursive: true });
|
|
973
|
+
mkdirSync3(binDir, { recursive: true });
|
|
974
|
+
const zipPath = join4(tmpDir, "terraform.zip");
|
|
873
975
|
console.log(` Downloading Terraform ${tfVersion}...`);
|
|
874
976
|
run(`curl -sL "${url}" -o "${zipPath}"`);
|
|
875
977
|
run(`unzip -qo "${zipPath}" -d "${binDir}"`);
|
|
876
|
-
chmodSync(
|
|
978
|
+
chmodSync(join4(binDir, "terraform"), 493);
|
|
877
979
|
if (!process.env.PATH?.includes(binDir)) {
|
|
878
980
|
process.env.PATH = `${binDir}:${process.env.PATH}`;
|
|
879
981
|
}
|
|
@@ -910,93 +1012,239 @@ function ask(prompt2) {
|
|
|
910
1012
|
});
|
|
911
1013
|
});
|
|
912
1014
|
}
|
|
1015
|
+
function verifyProfile(profile) {
|
|
1016
|
+
try {
|
|
1017
|
+
const raw = execSync4(
|
|
1018
|
+
`aws sts get-caller-identity --profile ${profile} --output json`,
|
|
1019
|
+
{ encoding: "utf-8", timeout: 15e3, stdio: ["pipe", "pipe", "pipe"] }
|
|
1020
|
+
);
|
|
1021
|
+
const parsed = JSON.parse(raw);
|
|
1022
|
+
return { account: parsed.Account, arn: parsed.Arn };
|
|
1023
|
+
} catch {
|
|
1024
|
+
return null;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
function describeType(type) {
|
|
1028
|
+
switch (type) {
|
|
1029
|
+
case "keys":
|
|
1030
|
+
return "access keys";
|
|
1031
|
+
case "sso":
|
|
1032
|
+
return "SSO";
|
|
1033
|
+
case "role":
|
|
1034
|
+
return "assumed role";
|
|
1035
|
+
default:
|
|
1036
|
+
return "config only";
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
async function pickProfile(profiles) {
|
|
1040
|
+
if (!process.stdin.isTTY) {
|
|
1041
|
+
printError(
|
|
1042
|
+
"The profile picker needs an interactive terminal. Re-run with --keys, --sso, or --profile <name>."
|
|
1043
|
+
);
|
|
1044
|
+
return { kind: "cancel" };
|
|
1045
|
+
}
|
|
1046
|
+
const choices = profiles.map((p) => ({
|
|
1047
|
+
name: `${p.name} ${chalk5.dim(`(${describeType(p.type)})`)}`,
|
|
1048
|
+
value: { kind: "existing", name: p.name }
|
|
1049
|
+
}));
|
|
1050
|
+
choices.push(new Separator());
|
|
1051
|
+
choices.push({
|
|
1052
|
+
name: "Enter new access keys",
|
|
1053
|
+
value: { kind: "keys" },
|
|
1054
|
+
description: "Paste an AWS Access Key ID and Secret Access Key; saved to a new profile."
|
|
1055
|
+
});
|
|
1056
|
+
choices.push({
|
|
1057
|
+
name: "Log in via AWS SSO",
|
|
1058
|
+
value: { kind: "sso" },
|
|
1059
|
+
description: "Run `aws sso login` against the configured SSO profile."
|
|
1060
|
+
});
|
|
1061
|
+
try {
|
|
1062
|
+
const picked = await select({
|
|
1063
|
+
message: "Pick an AWS profile for Thinkwork:",
|
|
1064
|
+
choices,
|
|
1065
|
+
loop: false,
|
|
1066
|
+
pageSize: Math.max(profiles.length + 2, 10)
|
|
1067
|
+
});
|
|
1068
|
+
return picked;
|
|
1069
|
+
} catch (err) {
|
|
1070
|
+
if (err instanceof Error && err.name === "ExitPromptError") {
|
|
1071
|
+
return { kind: "cancel" };
|
|
1072
|
+
}
|
|
1073
|
+
throw err;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
async function runKeyEntry(targetProfile) {
|
|
1077
|
+
console.log("");
|
|
1078
|
+
console.log(" Enter your AWS credentials. These will be saved to the");
|
|
1079
|
+
console.log(` AWS CLI profile "${targetProfile}".`);
|
|
1080
|
+
console.log("");
|
|
1081
|
+
const accessKeyId = await ask(" AWS Access Key ID: ");
|
|
1082
|
+
if (!accessKeyId) {
|
|
1083
|
+
printError("Access Key ID is required");
|
|
1084
|
+
return false;
|
|
1085
|
+
}
|
|
1086
|
+
const secretAccessKey = await ask(" AWS Secret Access Key: ");
|
|
1087
|
+
if (!secretAccessKey) {
|
|
1088
|
+
printError("Secret Access Key is required");
|
|
1089
|
+
return false;
|
|
1090
|
+
}
|
|
1091
|
+
const region = await ask(" Default region [us-east-1]: ");
|
|
1092
|
+
const finalRegion = region || "us-east-1";
|
|
1093
|
+
try {
|
|
1094
|
+
execSync4(
|
|
1095
|
+
`aws configure set aws_access_key_id "${accessKeyId}" --profile ${targetProfile}`,
|
|
1096
|
+
{ stdio: "pipe" }
|
|
1097
|
+
);
|
|
1098
|
+
execSync4(
|
|
1099
|
+
`aws configure set aws_secret_access_key "${secretAccessKey}" --profile ${targetProfile}`,
|
|
1100
|
+
{ stdio: "pipe" }
|
|
1101
|
+
);
|
|
1102
|
+
execSync4(
|
|
1103
|
+
`aws configure set region "${finalRegion}" --profile ${targetProfile}`,
|
|
1104
|
+
{ stdio: "pipe" }
|
|
1105
|
+
);
|
|
1106
|
+
return true;
|
|
1107
|
+
} catch (err) {
|
|
1108
|
+
printError(`Failed to save credentials: ${err}`);
|
|
1109
|
+
return false;
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
function runSsoLogin(targetProfile) {
|
|
1113
|
+
console.log(" Launching AWS SSO login...");
|
|
1114
|
+
console.log("");
|
|
1115
|
+
try {
|
|
1116
|
+
execSync4(`aws sso login --profile ${targetProfile}`, { stdio: "inherit" });
|
|
1117
|
+
return true;
|
|
1118
|
+
} catch {
|
|
1119
|
+
printError(
|
|
1120
|
+
`SSO login failed. Run \`aws configure sso --profile ${targetProfile}\` first to set up the profile.`
|
|
1121
|
+
);
|
|
1122
|
+
return false;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
function finalize(profile, mode) {
|
|
1126
|
+
const identity = getAwsIdentity();
|
|
1127
|
+
if (!identity) {
|
|
1128
|
+
printError(
|
|
1129
|
+
`Credentials saved but could not verify with profile "${profile}". Try \`aws sts get-caller-identity --profile ${profile}\`.`
|
|
1130
|
+
);
|
|
1131
|
+
process.exit(1);
|
|
1132
|
+
}
|
|
1133
|
+
saveCliConfig({ defaultProfile: profile });
|
|
1134
|
+
printSuccess(
|
|
1135
|
+
`Logged in via ${mode} (account: ${identity.account}, region: ${identity.region})`
|
|
1136
|
+
);
|
|
1137
|
+
console.log("");
|
|
1138
|
+
console.log(
|
|
1139
|
+
` Profile "${profile}" saved as your Thinkwork default. Subsequent commands`
|
|
1140
|
+
);
|
|
1141
|
+
console.log(
|
|
1142
|
+
` (\`thinkwork list\`, \`thinkwork deploy\`, \u2026) will use it automatically.`
|
|
1143
|
+
);
|
|
1144
|
+
console.log(
|
|
1145
|
+
chalk5.dim(
|
|
1146
|
+
` Override per-command with --profile <other>, or unset with \`rm ~/.thinkwork/config.json\`.`
|
|
1147
|
+
)
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
913
1150
|
function registerLoginCommand(program2) {
|
|
914
|
-
program2.command("login").description(
|
|
1151
|
+
program2.command("login").description(
|
|
1152
|
+
"Configure AWS credentials for Thinkwork. Picks from existing ~/.aws profiles by default; falls back to new keys or SSO."
|
|
1153
|
+
).option(
|
|
1154
|
+
"--profile <name>",
|
|
1155
|
+
"AWS profile name to configure (used when entering new keys or SSO)",
|
|
1156
|
+
"thinkwork"
|
|
1157
|
+
).option("--sso", "Skip the picker and go straight to SSO login").option("--keys", "Skip the picker and go straight to access-key entry").addHelpText(
|
|
1158
|
+
"after",
|
|
1159
|
+
`
|
|
1160
|
+
Examples:
|
|
1161
|
+
# Interactive picker \u2014 lists profiles from ~/.aws, verifies the one you pick,
|
|
1162
|
+
# and saves it as your Thinkwork default.
|
|
1163
|
+
$ thinkwork login
|
|
1164
|
+
|
|
1165
|
+
# Skip the picker, enter fresh access keys into a named profile
|
|
1166
|
+
$ thinkwork login --keys --profile thinkwork
|
|
1167
|
+
|
|
1168
|
+
# Skip the picker, log in via AWS SSO
|
|
1169
|
+
$ thinkwork login --sso --profile work-sso
|
|
1170
|
+
|
|
1171
|
+
After login, commands resolve the AWS profile in this order:
|
|
1172
|
+
1. --profile <name> (per-command override)
|
|
1173
|
+
2. $AWS_PROFILE env var
|
|
1174
|
+
3. defaultProfile from ~/.thinkwork/config.json (set by this command)
|
|
1175
|
+
`
|
|
1176
|
+
).action(async (opts) => {
|
|
915
1177
|
printHeader("login", opts.profile);
|
|
916
1178
|
const awsOk = await ensureAwsCli();
|
|
917
|
-
if (!awsOk)
|
|
918
|
-
|
|
1179
|
+
if (!awsOk) process.exit(1);
|
|
1180
|
+
if (opts.sso) {
|
|
1181
|
+
if (!runSsoLogin(opts.profile)) process.exit(1);
|
|
1182
|
+
process.env.AWS_PROFILE = opts.profile;
|
|
1183
|
+
finalize(opts.profile, "SSO");
|
|
1184
|
+
return;
|
|
919
1185
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1186
|
+
if (opts.keys) {
|
|
1187
|
+
if (!await runKeyEntry(opts.profile)) process.exit(1);
|
|
1188
|
+
process.env.AWS_PROFILE = opts.profile;
|
|
1189
|
+
finalize(opts.profile, "access keys");
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
const profiles = listAwsProfiles();
|
|
1193
|
+
if (profiles.length === 0) {
|
|
926
1194
|
console.log("");
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1195
|
+
console.log(chalk5.dim(" No AWS profiles found in ~/.aws/."));
|
|
1196
|
+
console.log(
|
|
1197
|
+
chalk5.dim(" Falling through to access-key entry for a new profile.")
|
|
1198
|
+
);
|
|
1199
|
+
if (!await runKeyEntry(opts.profile)) process.exit(1);
|
|
1200
|
+
process.env.AWS_PROFILE = opts.profile;
|
|
1201
|
+
finalize(opts.profile, "access keys");
|
|
1202
|
+
return;
|
|
932
1203
|
}
|
|
933
|
-
|
|
934
|
-
|
|
1204
|
+
const choice = await pickProfile(profiles);
|
|
1205
|
+
if (choice.kind === "cancel") {
|
|
935
1206
|
console.log("");
|
|
936
|
-
|
|
937
|
-
execSync4(`aws sso login --profile ${opts.profile}`, {
|
|
938
|
-
stdio: "inherit"
|
|
939
|
-
});
|
|
940
|
-
process.env.AWS_PROFILE = opts.profile;
|
|
941
|
-
const identity2 = getAwsIdentity();
|
|
942
|
-
if (identity2) {
|
|
943
|
-
printSuccess(`Logged in via SSO (account: ${identity2.account}, region: ${identity2.region})`);
|
|
944
|
-
} else {
|
|
945
|
-
printError("SSO login succeeded but could not verify identity");
|
|
946
|
-
}
|
|
947
|
-
} catch {
|
|
948
|
-
printError("SSO login failed. Run `aws configure sso` first to set up your SSO profile.");
|
|
949
|
-
}
|
|
1207
|
+
console.log(chalk5.dim(" Cancelled. No changes made."));
|
|
950
1208
|
return;
|
|
951
1209
|
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
printError("Access Key ID is required");
|
|
958
|
-
process.exit(1);
|
|
1210
|
+
if (choice.kind === "keys") {
|
|
1211
|
+
if (!await runKeyEntry(opts.profile)) process.exit(1);
|
|
1212
|
+
process.env.AWS_PROFILE = opts.profile;
|
|
1213
|
+
finalize(opts.profile, "access keys");
|
|
1214
|
+
return;
|
|
959
1215
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1216
|
+
if (choice.kind === "sso") {
|
|
1217
|
+
if (!runSsoLogin(opts.profile)) process.exit(1);
|
|
1218
|
+
process.env.AWS_PROFILE = opts.profile;
|
|
1219
|
+
finalize(opts.profile, "SSO");
|
|
1220
|
+
return;
|
|
964
1221
|
}
|
|
965
|
-
const
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1222
|
+
const picked = choice.name;
|
|
1223
|
+
console.log("");
|
|
1224
|
+
console.log(` Verifying "${picked}"...`);
|
|
1225
|
+
const identity = verifyProfile(picked);
|
|
1226
|
+
if (!identity) {
|
|
1227
|
+
printError(
|
|
1228
|
+
`Could not authenticate with profile "${picked}". If it's an SSO profile, try \`aws sso login --profile ${picked}\` first.`
|
|
1229
|
+
);
|
|
973
1230
|
process.exit(1);
|
|
974
1231
|
}
|
|
975
|
-
process.env.AWS_PROFILE =
|
|
976
|
-
|
|
977
|
-
if (identity) {
|
|
978
|
-
printSuccess(`Logged in (account: ${identity.account}, region: ${identity.region})`);
|
|
979
|
-
console.log("");
|
|
980
|
-
console.log(` Profile saved as "${opts.profile}". Use it with:`);
|
|
981
|
-
console.log(` thinkwork deploy -s dev --profile ${opts.profile}`);
|
|
982
|
-
console.log(` export AWS_PROFILE=${opts.profile}`);
|
|
983
|
-
} else {
|
|
984
|
-
printError("Credentials saved but could not verify. Check your Access Key ID and Secret.");
|
|
985
|
-
}
|
|
1232
|
+
process.env.AWS_PROFILE = picked;
|
|
1233
|
+
finalize(picked, "existing profile");
|
|
986
1234
|
});
|
|
987
1235
|
}
|
|
988
1236
|
|
|
989
1237
|
// src/commands/init.ts
|
|
990
|
-
import { existsSync as
|
|
991
|
-
import { resolve as resolve2, join as
|
|
1238
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, cpSync } from "fs";
|
|
1239
|
+
import { resolve as resolve2, join as join5, dirname as dirname2 } from "path";
|
|
992
1240
|
import { execSync as execSync5 } from "child_process";
|
|
993
1241
|
import { fileURLToPath } from "url";
|
|
994
1242
|
import { createInterface as createInterface3 } from "readline";
|
|
995
|
-
import
|
|
996
|
-
var __dirname =
|
|
1243
|
+
import chalk6 from "chalk";
|
|
1244
|
+
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
997
1245
|
function ask2(prompt2, defaultVal = "") {
|
|
998
1246
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
999
|
-
const suffix = defaultVal ?
|
|
1247
|
+
const suffix = defaultVal ? chalk6.dim(` [${defaultVal}]`) : "";
|
|
1000
1248
|
return new Promise((resolve3) => {
|
|
1001
1249
|
rl.question(` ${prompt2}${suffix}: `, (answer) => {
|
|
1002
1250
|
rl.close();
|
|
@@ -1005,7 +1253,7 @@ function ask2(prompt2, defaultVal = "") {
|
|
|
1005
1253
|
});
|
|
1006
1254
|
}
|
|
1007
1255
|
function choose(prompt2, options, defaultVal) {
|
|
1008
|
-
const optStr = options.map((o) => o === defaultVal ?
|
|
1256
|
+
const optStr = options.map((o) => o === defaultVal ? chalk6.bold(o) : chalk6.dim(o)).join(" / ");
|
|
1009
1257
|
return ask2(`${prompt2} (${optStr})`, defaultVal);
|
|
1010
1258
|
}
|
|
1011
1259
|
function generateSecret(length = 32) {
|
|
@@ -1018,9 +1266,9 @@ function generateSecret(length = 32) {
|
|
|
1018
1266
|
}
|
|
1019
1267
|
function findBundledTerraform() {
|
|
1020
1268
|
const bundled = resolve2(__dirname, "terraform");
|
|
1021
|
-
if (
|
|
1269
|
+
if (existsSync7(join5(bundled, "modules"))) return bundled;
|
|
1022
1270
|
const repoTf = resolve2(__dirname, "..", "..", "..", "terraform");
|
|
1023
|
-
if (
|
|
1271
|
+
if (existsSync7(join5(repoTf, "modules"))) return repoTf;
|
|
1024
1272
|
throw new Error(
|
|
1025
1273
|
"Terraform modules not found. The CLI package may be incomplete.\nTry reinstalling: npm install -g thinkwork-cli@latest"
|
|
1026
1274
|
);
|
|
@@ -1090,9 +1338,9 @@ function registerInitCommand(program2) {
|
|
|
1090
1338
|
process.exit(1);
|
|
1091
1339
|
}
|
|
1092
1340
|
const targetDir = resolve2(opts.dir);
|
|
1093
|
-
const tfDir =
|
|
1094
|
-
const tfvarsPath =
|
|
1095
|
-
if (
|
|
1341
|
+
const tfDir = join5(targetDir, "terraform");
|
|
1342
|
+
const tfvarsPath = join5(tfDir, "terraform.tfvars");
|
|
1343
|
+
if (existsSync7(tfvarsPath)) {
|
|
1096
1344
|
printWarning(`terraform.tfvars already exists at ${tfvarsPath}`);
|
|
1097
1345
|
const overwrite = await ask2("Overwrite?", "N");
|
|
1098
1346
|
if (overwrite.toLowerCase() !== "y") {
|
|
@@ -1116,21 +1364,21 @@ function registerInitCommand(program2) {
|
|
|
1116
1364
|
config.admin_url = "http://localhost:5174";
|
|
1117
1365
|
config.mobile_scheme = "thinkwork";
|
|
1118
1366
|
} else {
|
|
1119
|
-
console.log(
|
|
1367
|
+
console.log(chalk6.bold(" Configure your Thinkwork environment\n"));
|
|
1120
1368
|
const defaultRegion = identity.region !== "unknown" ? identity.region : "us-east-1";
|
|
1121
1369
|
config.region = await ask2("AWS Region", defaultRegion);
|
|
1122
1370
|
console.log("");
|
|
1123
|
-
console.log(
|
|
1371
|
+
console.log(chalk6.dim(" \u2500\u2500 Database \u2500\u2500"));
|
|
1124
1372
|
config.database_engine = await choose("Database engine", ["aurora-serverless", "rds-postgres"], "aurora-serverless");
|
|
1125
1373
|
console.log("");
|
|
1126
|
-
console.log(
|
|
1127
|
-
console.log(
|
|
1128
|
-
console.log(
|
|
1129
|
-
console.log(
|
|
1374
|
+
console.log(chalk6.dim(" \u2500\u2500 Memory \u2500\u2500"));
|
|
1375
|
+
console.log(chalk6.dim(" AgentCore managed memory is always on (automatic retention)."));
|
|
1376
|
+
console.log(chalk6.dim(" Hindsight is an optional add-on: ECS Fargate service for"));
|
|
1377
|
+
console.log(chalk6.dim(" semantic + entity-graph retrieval (~$75/mo)."));
|
|
1130
1378
|
const hindsightAnswer = await ask2("Enable Hindsight long-term memory add-on? (y/N)", "N");
|
|
1131
1379
|
config.enable_hindsight = hindsightAnswer.toLowerCase() === "y" ? "true" : "false";
|
|
1132
1380
|
console.log("");
|
|
1133
|
-
console.log(
|
|
1381
|
+
console.log(chalk6.dim(" \u2500\u2500 Auth \u2500\u2500"));
|
|
1134
1382
|
const useGoogle = await ask2("Enable Google OAuth login? (y/N)", "N");
|
|
1135
1383
|
if (useGoogle.toLowerCase() === "y") {
|
|
1136
1384
|
config.google_oauth_client_id = await ask2("Google OAuth Client ID");
|
|
@@ -1140,13 +1388,13 @@ function registerInitCommand(program2) {
|
|
|
1140
1388
|
config.google_oauth_client_secret = "";
|
|
1141
1389
|
}
|
|
1142
1390
|
console.log("");
|
|
1143
|
-
console.log(
|
|
1391
|
+
console.log(chalk6.dim(" \u2500\u2500 Frontend URLs \u2500\u2500"));
|
|
1144
1392
|
config.admin_url = await ask2("Admin UI URL", "http://localhost:5174");
|
|
1145
1393
|
config.mobile_scheme = await ask2("Mobile app URL scheme", "thinkwork");
|
|
1146
1394
|
console.log("");
|
|
1147
|
-
console.log(
|
|
1148
|
-
console.log(
|
|
1149
|
-
console.log(
|
|
1395
|
+
console.log(chalk6.dim(" \u2500\u2500 Secrets (auto-generated) \u2500\u2500"));
|
|
1396
|
+
console.log(chalk6.dim(` DB password: ${config.db_password.slice(0, 8)}...`));
|
|
1397
|
+
console.log(chalk6.dim(` API auth secret: ${config.api_auth_secret.slice(0, 16)}...`));
|
|
1150
1398
|
}
|
|
1151
1399
|
console.log("");
|
|
1152
1400
|
console.log(" Scaffolding Terraform modules...");
|
|
@@ -1157,24 +1405,24 @@ function registerInitCommand(program2) {
|
|
|
1157
1405
|
printError(String(err));
|
|
1158
1406
|
process.exit(1);
|
|
1159
1407
|
}
|
|
1160
|
-
|
|
1408
|
+
mkdirSync4(tfDir, { recursive: true });
|
|
1161
1409
|
const copyDirs = ["modules", "examples"];
|
|
1162
1410
|
for (const dir of copyDirs) {
|
|
1163
|
-
const src =
|
|
1164
|
-
const dst =
|
|
1165
|
-
if (
|
|
1411
|
+
const src = join5(bundledTf, dir);
|
|
1412
|
+
const dst = join5(tfDir, dir);
|
|
1413
|
+
if (existsSync7(src) && !existsSync7(dst)) {
|
|
1166
1414
|
cpSync(src, dst, { recursive: true });
|
|
1167
1415
|
}
|
|
1168
1416
|
}
|
|
1169
|
-
const schemaPath =
|
|
1170
|
-
if (
|
|
1171
|
-
cpSync(schemaPath,
|
|
1417
|
+
const schemaPath = join5(bundledTf, "schema.graphql");
|
|
1418
|
+
if (existsSync7(schemaPath) && !existsSync7(join5(tfDir, "schema.graphql"))) {
|
|
1419
|
+
cpSync(schemaPath, join5(tfDir, "schema.graphql"));
|
|
1172
1420
|
}
|
|
1173
1421
|
const tfvars = buildTfvars(config);
|
|
1174
|
-
|
|
1175
|
-
const mainTfPath =
|
|
1176
|
-
if (!
|
|
1177
|
-
|
|
1422
|
+
writeFileSync4(tfvarsPath, tfvars);
|
|
1423
|
+
const mainTfPath = join5(tfDir, "main.tf");
|
|
1424
|
+
if (!existsSync7(mainTfPath)) {
|
|
1425
|
+
writeFileSync4(mainTfPath, `################################################################################
|
|
1178
1426
|
# Thinkwork \u2014 ${config.stage}
|
|
1179
1427
|
# Generated by: thinkwork init -s ${config.stage}
|
|
1180
1428
|
################################################################################
|
|
@@ -1351,17 +1599,17 @@ output "agentcore_memory_id" {
|
|
|
1351
1599
|
}
|
|
1352
1600
|
`);
|
|
1353
1601
|
}
|
|
1354
|
-
console.log(` Wrote ${
|
|
1602
|
+
console.log(` Wrote ${chalk6.cyan(tfDir + "/")}`);
|
|
1355
1603
|
console.log("");
|
|
1356
|
-
console.log(
|
|
1357
|
-
console.log(` ${
|
|
1358
|
-
console.log(` ${
|
|
1359
|
-
console.log(` ${
|
|
1360
|
-
console.log(` ${
|
|
1361
|
-
console.log(` ${
|
|
1362
|
-
console.log(` ${
|
|
1363
|
-
console.log(` ${
|
|
1364
|
-
console.log(
|
|
1604
|
+
console.log(chalk6.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1605
|
+
console.log(` ${chalk6.bold("Stage:")} ${config.stage}`);
|
|
1606
|
+
console.log(` ${chalk6.bold("Region:")} ${config.region}`);
|
|
1607
|
+
console.log(` ${chalk6.bold("Account:")} ${config.account_id}`);
|
|
1608
|
+
console.log(` ${chalk6.bold("Database:")} ${config.database_engine}`);
|
|
1609
|
+
console.log(` ${chalk6.bold("Memory:")} managed (always on)${config.enable_hindsight === "true" ? " + hindsight" : ""}`);
|
|
1610
|
+
console.log(` ${chalk6.bold("Google OAuth:")} ${config.google_oauth_client_id ? "enabled" : "disabled"}`);
|
|
1611
|
+
console.log(` ${chalk6.bold("Directory:")} ${tfDir}`);
|
|
1612
|
+
console.log(chalk6.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1365
1613
|
console.log("\n Initializing Terraform...\n");
|
|
1366
1614
|
try {
|
|
1367
1615
|
execSync5("terraform init", { cwd: tfDir, stdio: "inherit" });
|
|
@@ -1383,17 +1631,17 @@ output "agentcore_memory_id" {
|
|
|
1383
1631
|
printSuccess(`Environment "${opts.stage}" initialized`);
|
|
1384
1632
|
console.log("");
|
|
1385
1633
|
console.log(" Next steps:");
|
|
1386
|
-
console.log(` ${
|
|
1387
|
-
console.log(` ${
|
|
1388
|
-
console.log(` ${
|
|
1389
|
-
console.log(` ${
|
|
1634
|
+
console.log(` ${chalk6.cyan("1.")} thinkwork plan -s ${opts.stage} ${chalk6.dim("# Review infrastructure plan")}`);
|
|
1635
|
+
console.log(` ${chalk6.cyan("2.")} thinkwork deploy -s ${opts.stage} ${chalk6.dim("# Deploy to AWS (~5 min)")}`);
|
|
1636
|
+
console.log(` ${chalk6.cyan("3.")} thinkwork bootstrap -s ${opts.stage} ${chalk6.dim("# Seed workspace files + skills")}`);
|
|
1637
|
+
console.log(` ${chalk6.cyan("4.")} thinkwork outputs -s ${opts.stage} ${chalk6.dim("# Show API URL, Cognito IDs, etc.")}`);
|
|
1390
1638
|
console.log("");
|
|
1391
1639
|
});
|
|
1392
1640
|
}
|
|
1393
1641
|
|
|
1394
1642
|
// src/commands/status.ts
|
|
1395
1643
|
import { execSync as execSync6 } from "child_process";
|
|
1396
|
-
import
|
|
1644
|
+
import chalk7 from "chalk";
|
|
1397
1645
|
function link(url, label) {
|
|
1398
1646
|
const text = label || url;
|
|
1399
1647
|
return `\x1B]8;;${url}\x1B\\${text}\x1B]8;;\x1B\\`;
|
|
@@ -1491,47 +1739,70 @@ function discoverAwsStages(region) {
|
|
|
1491
1739
|
return stages;
|
|
1492
1740
|
}
|
|
1493
1741
|
function printStageDetail(info) {
|
|
1494
|
-
console.log(
|
|
1495
|
-
console.log(
|
|
1496
|
-
console.log(` ${
|
|
1497
|
-
console.log(` ${
|
|
1498
|
-
console.log(` ${
|
|
1499
|
-
console.log(` ${
|
|
1500
|
-
console.log(` ${
|
|
1501
|
-
console.log(` ${
|
|
1742
|
+
console.log(chalk7.bold.cyan(` \u2B21 ${info.stage}`));
|
|
1743
|
+
console.log(chalk7.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1744
|
+
console.log(` ${chalk7.bold("Source:")} ${info.source === "both" ? "AWS + local config" : info.source === "aws" ? "AWS (no local config)" : "local only (not in AWS)"}`);
|
|
1745
|
+
console.log(` ${chalk7.bold("Region:")} ${info.region}`);
|
|
1746
|
+
console.log(` ${chalk7.bold("Account:")} ${info.accountId}`);
|
|
1747
|
+
console.log(` ${chalk7.bold("Lambda fns:")} ${info.lambdaCount || "\u2014"}`);
|
|
1748
|
+
console.log(` ${chalk7.bold("AgentCore:")} ${info.agentcoreStatus || "unknown"}`);
|
|
1749
|
+
console.log(` ${chalk7.bold("Memory:")} managed (always on)`);
|
|
1502
1750
|
const hindsightLabel = info.hindsightEnabled === void 0 ? "unknown" : info.hindsightEnabled ? info.hindsightHealth || "running" : "disabled";
|
|
1503
|
-
console.log(` ${
|
|
1504
|
-
if (info.bucketName) console.log(` ${
|
|
1505
|
-
if (info.dbEndpoint) console.log(` ${
|
|
1506
|
-
if (info.ecrUrl) console.log(` ${
|
|
1751
|
+
console.log(` ${chalk7.bold("Hindsight:")} ${hindsightLabel}`);
|
|
1752
|
+
if (info.bucketName) console.log(` ${chalk7.bold("S3 bucket:")} ${info.bucketName}`);
|
|
1753
|
+
if (info.dbEndpoint) console.log(` ${chalk7.bold("Database:")} ${info.dbEndpoint}`);
|
|
1754
|
+
if (info.ecrUrl) console.log(` ${chalk7.bold("ECR:")} ${info.ecrUrl}`);
|
|
1507
1755
|
console.log("");
|
|
1508
|
-
console.log(
|
|
1756
|
+
console.log(chalk7.bold(" URLs:"));
|
|
1509
1757
|
if (info.adminUrl) console.log(` Admin: ${link(info.adminUrl)}`);
|
|
1510
1758
|
if (info.docsUrl) console.log(` Docs: ${link(info.docsUrl)}`);
|
|
1511
1759
|
if (info.apiEndpoint) console.log(` API: ${link(info.apiEndpoint)}`);
|
|
1512
1760
|
if (info.appsyncApiUrl) console.log(` AppSync: ${link(info.appsyncApiUrl)}`);
|
|
1513
1761
|
if (info.appsyncUrl) console.log(` WebSocket: ${link(info.appsyncUrl)}`);
|
|
1514
1762
|
if (info.hindsightEndpoint) console.log(` Hindsight: ${link(info.hindsightEndpoint)}`);
|
|
1515
|
-
console.log(
|
|
1763
|
+
console.log(chalk7.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1516
1764
|
const local = loadEnvironment(info.stage);
|
|
1517
1765
|
if (local) {
|
|
1518
|
-
console.log(
|
|
1766
|
+
console.log(chalk7.dim(` Terraform dir: ${local.terraformDir}`));
|
|
1519
1767
|
} else {
|
|
1520
|
-
console.log(
|
|
1768
|
+
console.log(chalk7.dim(` No local config. Run: thinkwork init -s ${info.stage}`));
|
|
1521
1769
|
}
|
|
1522
1770
|
console.log("");
|
|
1523
1771
|
}
|
|
1524
1772
|
function registerStatusCommand(program2) {
|
|
1525
1773
|
program2.command("status").alias("list").alias("ls").description(
|
|
1526
1774
|
"Show all Thinkwork environments / deployments (AWS + local). Aliases: list, ls"
|
|
1527
|
-
).option("-s, --stage <name>", "Show details for a specific stage").option("--region <region>", "AWS region to scan", "us-east-1").
|
|
1775
|
+
).option("-s, --stage <name>", "Show details for a specific stage").option("--region <region>", "AWS region to scan", "us-east-1").addHelpText(
|
|
1776
|
+
"after",
|
|
1777
|
+
`
|
|
1778
|
+
Examples:
|
|
1779
|
+
# List every deployment in the current AWS account (us-east-1)
|
|
1780
|
+
$ thinkwork list
|
|
1781
|
+
|
|
1782
|
+
# Same thing, tighter verb
|
|
1783
|
+
$ thinkwork ls
|
|
1784
|
+
|
|
1785
|
+
# Deep-dive on one stage (same info but scoped)
|
|
1786
|
+
$ thinkwork list -s dev
|
|
1787
|
+
|
|
1788
|
+
# Scan a different region
|
|
1789
|
+
$ thinkwork list --region us-west-2
|
|
1790
|
+
|
|
1791
|
+
# Use a specific AWS profile for this call only
|
|
1792
|
+
$ thinkwork --profile work-sso list
|
|
1793
|
+
|
|
1794
|
+
Discovers stages by looking for \`thinkwork-<stage>-api-graphql-http\`
|
|
1795
|
+
Lambdas and fans out to API Gateway, AppSync, S3, RDS, ECS, CloudFront,
|
|
1796
|
+
and AgentCore for per-stage detail.
|
|
1797
|
+
`
|
|
1798
|
+
).action(async (opts) => {
|
|
1528
1799
|
const identity = getAwsIdentity();
|
|
1529
1800
|
printHeader("status", opts.stage || "all", identity);
|
|
1530
1801
|
if (!identity) {
|
|
1531
1802
|
printError("AWS credentials not configured. Run `thinkwork login` first.");
|
|
1532
1803
|
process.exit(1);
|
|
1533
1804
|
}
|
|
1534
|
-
console.log(
|
|
1805
|
+
console.log(chalk7.dim(" Scanning AWS account for Thinkwork deployments...\n"));
|
|
1535
1806
|
const awsStages = discoverAwsStages(opts.region);
|
|
1536
1807
|
const localEnvs = listEnvironments();
|
|
1537
1808
|
const merged = /* @__PURE__ */ new Map();
|
|
@@ -1566,7 +1837,7 @@ function registerStatusCommand(program2) {
|
|
|
1566
1837
|
}
|
|
1567
1838
|
if (merged.size === 0) {
|
|
1568
1839
|
console.log(" No Thinkwork environments found.");
|
|
1569
|
-
console.log(` Run ${
|
|
1840
|
+
console.log(` Run ${chalk7.cyan("thinkwork init -s <stage>")} to create one.`);
|
|
1570
1841
|
console.log("");
|
|
1571
1842
|
return;
|
|
1572
1843
|
}
|
|
@@ -1577,14 +1848,14 @@ function registerStatusCommand(program2) {
|
|
|
1577
1848
|
}
|
|
1578
1849
|
|
|
1579
1850
|
// src/commands/mcp.ts
|
|
1580
|
-
import
|
|
1851
|
+
import chalk8 from "chalk";
|
|
1581
1852
|
|
|
1582
1853
|
// src/api-client.ts
|
|
1583
|
-
import { readFileSync as
|
|
1854
|
+
import { readFileSync as readFileSync5, existsSync as existsSync8 } from "fs";
|
|
1584
1855
|
import { execSync as execSync7 } from "child_process";
|
|
1585
1856
|
function readTfVar2(tfvarsPath, key) {
|
|
1586
|
-
if (!
|
|
1587
|
-
const content =
|
|
1857
|
+
if (!existsSync8(tfvarsPath)) return null;
|
|
1858
|
+
const content = readFileSync5(tfvarsPath, "utf-8");
|
|
1588
1859
|
const match = content.match(new RegExp(`^${key}\\s*=\\s*"([^"]*)"`, "m"));
|
|
1589
1860
|
return match ? match[1] : null;
|
|
1590
1861
|
}
|
|
@@ -1592,7 +1863,7 @@ function resolveTfvarsPath2(stage) {
|
|
|
1592
1863
|
const tfDir = resolveTerraformDir(stage);
|
|
1593
1864
|
if (tfDir) {
|
|
1594
1865
|
const direct = `${tfDir}/terraform.tfvars`;
|
|
1595
|
-
if (
|
|
1866
|
+
if (existsSync8(direct)) return direct;
|
|
1596
1867
|
}
|
|
1597
1868
|
const terraformDir = process.env.THINKWORK_TERRAFORM_DIR || process.cwd();
|
|
1598
1869
|
const cwd = resolveTierDir(terraformDir, stage, "app");
|
|
@@ -1671,14 +1942,14 @@ function registerMcpCommand(program2) {
|
|
|
1671
1942
|
try {
|
|
1672
1943
|
const { servers } = await apiFetch(api.apiUrl, api.authSecret, "/api/skills/mcp-servers", {}, { "x-tenant-slug": opts.tenant });
|
|
1673
1944
|
if (!servers || servers.length === 0) {
|
|
1674
|
-
console.log(
|
|
1945
|
+
console.log(chalk8.dim(" No MCP servers registered."));
|
|
1675
1946
|
return;
|
|
1676
1947
|
}
|
|
1677
1948
|
console.log("");
|
|
1678
1949
|
for (const s of servers) {
|
|
1679
|
-
const status = s.enabled ?
|
|
1950
|
+
const status = s.enabled ? chalk8.green("enabled") : chalk8.dim("disabled");
|
|
1680
1951
|
const authLabel = s.authType === "per_user_oauth" ? `OAuth (${s.oauthProvider})` : s.authType === "tenant_api_key" ? "API Key" : "none";
|
|
1681
|
-
console.log(` ${
|
|
1952
|
+
console.log(` ${chalk8.bold(s.name)} ${chalk8.dim(s.slug)} ${status}`);
|
|
1682
1953
|
console.log(` URL: ${s.url}`);
|
|
1683
1954
|
console.log(` Transport: ${s.transport}`);
|
|
1684
1955
|
console.log(` Auth: ${authLabel}`);
|
|
@@ -1753,11 +2024,11 @@ function registerMcpCommand(program2) {
|
|
|
1753
2024
|
if (result.ok) {
|
|
1754
2025
|
printSuccess("Connection successful.");
|
|
1755
2026
|
if (result.tools?.length) {
|
|
1756
|
-
console.log(
|
|
2027
|
+
console.log(chalk8.bold(`
|
|
1757
2028
|
Discovered tools (${result.tools.length}):
|
|
1758
2029
|
`));
|
|
1759
2030
|
for (const t of result.tools) {
|
|
1760
|
-
console.log(` ${
|
|
2031
|
+
console.log(` ${chalk8.cyan(t.name)}${t.description ? chalk8.dim(` - ${t.description}`) : ""}`);
|
|
1761
2032
|
}
|
|
1762
2033
|
console.log("");
|
|
1763
2034
|
} else {
|
|
@@ -1813,7 +2084,7 @@ function registerMcpCommand(program2) {
|
|
|
1813
2084
|
|
|
1814
2085
|
// src/commands/tools.ts
|
|
1815
2086
|
import { createInterface as createInterface4 } from "readline";
|
|
1816
|
-
import
|
|
2087
|
+
import chalk9 from "chalk";
|
|
1817
2088
|
function prompt(question) {
|
|
1818
2089
|
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
1819
2090
|
return new Promise((resolve3) => {
|
|
@@ -1846,16 +2117,16 @@ function registerToolsCommand(program2) {
|
|
|
1846
2117
|
{ "x-tenant-slug": opts.tenant }
|
|
1847
2118
|
);
|
|
1848
2119
|
if (!rows || rows.length === 0) {
|
|
1849
|
-
console.log(
|
|
1850
|
-
console.log(
|
|
2120
|
+
console.log(chalk9.dim(" No built-in tools configured."));
|
|
2121
|
+
console.log(chalk9.dim(" Try: thinkwork tools web-search set --tenant <slug> -s <stage>"));
|
|
1851
2122
|
return;
|
|
1852
2123
|
}
|
|
1853
2124
|
console.log("");
|
|
1854
2125
|
for (const r of rows) {
|
|
1855
|
-
const status = r.enabled ?
|
|
1856
|
-
const key = r.hasSecret ?
|
|
1857
|
-
const provider = r.provider ??
|
|
1858
|
-
console.log(` ${
|
|
2126
|
+
const status = r.enabled ? chalk9.green("enabled") : chalk9.dim("disabled");
|
|
2127
|
+
const key = r.hasSecret ? chalk9.green("yes") : chalk9.red("no");
|
|
2128
|
+
const provider = r.provider ?? chalk9.dim("\u2014");
|
|
2129
|
+
console.log(` ${chalk9.bold(r.toolSlug)} ${status}`);
|
|
1859
2130
|
console.log(` Provider: ${provider}`);
|
|
1860
2131
|
console.log(` Has key: ${key}`);
|
|
1861
2132
|
if (r.lastTestedAt) {
|
|
@@ -1988,7 +2259,7 @@ function registerToolsCommand(program2) {
|
|
|
1988
2259
|
// src/commands/update.ts
|
|
1989
2260
|
import { execSync as execSync8 } from "child_process";
|
|
1990
2261
|
import { realpathSync } from "fs";
|
|
1991
|
-
import
|
|
2262
|
+
import chalk10 from "chalk";
|
|
1992
2263
|
function getLatestVersion() {
|
|
1993
2264
|
try {
|
|
1994
2265
|
return execSync8("npm view thinkwork-cli version", {
|
|
@@ -2030,28 +2301,28 @@ function compareVersions(a, b) {
|
|
|
2030
2301
|
function registerUpdateCommand(program2) {
|
|
2031
2302
|
program2.command("update").description("Check for and install CLI updates").option("--check", "Only check for updates, don't install").action(async (opts) => {
|
|
2032
2303
|
printHeader("update", "", null);
|
|
2033
|
-
console.log(` Current version: ${
|
|
2304
|
+
console.log(` Current version: ${chalk10.bold(VERSION)}`);
|
|
2034
2305
|
const latest = getLatestVersion();
|
|
2035
2306
|
if (!latest) {
|
|
2036
|
-
console.log(
|
|
2307
|
+
console.log(chalk10.yellow(" Could not check npm registry for updates."));
|
|
2037
2308
|
return;
|
|
2038
2309
|
}
|
|
2039
|
-
console.log(` Latest version: ${
|
|
2310
|
+
console.log(` Latest version: ${chalk10.bold(latest)}`);
|
|
2040
2311
|
console.log("");
|
|
2041
2312
|
const cmp = compareVersions(VERSION, latest);
|
|
2042
2313
|
if (cmp >= 0) {
|
|
2043
|
-
console.log(
|
|
2314
|
+
console.log(chalk10.green(" \u2713 You're on the latest version."));
|
|
2044
2315
|
console.log("");
|
|
2045
2316
|
return;
|
|
2046
2317
|
}
|
|
2047
|
-
console.log(
|
|
2318
|
+
console.log(chalk10.cyan(` Update available: ${VERSION} \u2192 ${latest}`));
|
|
2048
2319
|
console.log("");
|
|
2049
2320
|
if (opts.check) {
|
|
2050
2321
|
const method2 = detectInstallMethod();
|
|
2051
2322
|
if (method2 === "homebrew") {
|
|
2052
|
-
console.log(` Run: ${
|
|
2323
|
+
console.log(` Run: ${chalk10.cyan("brew upgrade thinkwork-ai/tap/thinkwork")}`);
|
|
2053
2324
|
} else {
|
|
2054
|
-
console.log(` Run: ${
|
|
2325
|
+
console.log(` Run: ${chalk10.cyan(`npm install -g thinkwork-cli@${latest}`)}`);
|
|
2055
2326
|
}
|
|
2056
2327
|
console.log("");
|
|
2057
2328
|
return;
|
|
@@ -2059,16 +2330,16 @@ function registerUpdateCommand(program2) {
|
|
|
2059
2330
|
const method = detectInstallMethod();
|
|
2060
2331
|
const cmd = method === "homebrew" ? "brew upgrade thinkwork-ai/tap/thinkwork" : `npm install -g thinkwork-cli@${latest}`;
|
|
2061
2332
|
console.log(` Installing via ${method}...`);
|
|
2062
|
-
console.log(
|
|
2333
|
+
console.log(chalk10.dim(` $ ${cmd}`));
|
|
2063
2334
|
console.log("");
|
|
2064
2335
|
try {
|
|
2065
2336
|
execSync8(cmd, { stdio: "inherit", timeout: 12e4 });
|
|
2066
2337
|
console.log("");
|
|
2067
|
-
console.log(
|
|
2338
|
+
console.log(chalk10.green(` \u2713 Upgraded to thinkwork-cli@${latest}`));
|
|
2068
2339
|
} catch {
|
|
2069
2340
|
console.log("");
|
|
2070
|
-
console.log(
|
|
2071
|
-
console.log(` ${
|
|
2341
|
+
console.log(chalk10.red(` Failed to upgrade. Try manually:`));
|
|
2342
|
+
console.log(` ${chalk10.cyan(cmd)}`);
|
|
2072
2343
|
}
|
|
2073
2344
|
console.log("");
|
|
2074
2345
|
});
|
|
@@ -2122,7 +2393,38 @@ function registerUserCommand(program2) {
|
|
|
2122
2393
|
const user = program2.command("user").description("User-management utilities for a deployed Thinkwork stack");
|
|
2123
2394
|
user.command("invite <email>").description(
|
|
2124
2395
|
"Invite a teammate to a tenant. Creates the Cognito user (Cognito emails a temporary password) and adds them as a tenant member."
|
|
2125
|
-
).requiredOption("-s, --stage <name>", "Deployment stage
|
|
2396
|
+
).requiredOption("-s, --stage <name>", "Deployment stage (e.g. dev, prod)").requiredOption(
|
|
2397
|
+
"--tenant <slug>",
|
|
2398
|
+
"Tenant slug (the URL-safe tenant id, e.g. acme)"
|
|
2399
|
+
).option("--name <name>", "Display name for the invited user").option(
|
|
2400
|
+
"--role <role>",
|
|
2401
|
+
'Tenant member role: "member", "admin", or "owner"',
|
|
2402
|
+
"member"
|
|
2403
|
+
).addHelpText(
|
|
2404
|
+
"after",
|
|
2405
|
+
`
|
|
2406
|
+
Examples:
|
|
2407
|
+
# Invite a teammate as a regular member
|
|
2408
|
+
$ thinkwork user invite alice@example.com --tenant acme -s dev
|
|
2409
|
+
|
|
2410
|
+
# Invite with a display name and admin role
|
|
2411
|
+
$ thinkwork user invite bob@example.com --tenant acme -s dev \\
|
|
2412
|
+
--name "Bob Smith" --role admin
|
|
2413
|
+
|
|
2414
|
+
# Re-inviting someone who's already a member is a no-op (no second email)
|
|
2415
|
+
$ thinkwork user invite alice@example.com --tenant acme -s dev
|
|
2416
|
+
\u26A0 alice@example.com is already a member of "acme" (role: member). No email sent.
|
|
2417
|
+
|
|
2418
|
+
What happens:
|
|
2419
|
+
1. A Cognito user is created (or reused if the email already exists).
|
|
2420
|
+
2. Cognito emails the user a temporary password.
|
|
2421
|
+
3. The user is added to the tenant with the given role.
|
|
2422
|
+
4. On first sign-in they're prompted to set a real password.
|
|
2423
|
+
|
|
2424
|
+
Requires the stack to be deployed (the CLI discovers the API Gateway URL
|
|
2425
|
+
and reads api_auth_secret from terraform.tfvars for the stage).
|
|
2426
|
+
`
|
|
2427
|
+
).action(
|
|
2126
2428
|
async (email, opts) => {
|
|
2127
2429
|
const stageCheck = validateStage(opts.stage);
|
|
2128
2430
|
if (!stageCheck.valid) {
|
|
@@ -2182,9 +2484,24 @@ function registerUserCommand(program2) {
|
|
|
2182
2484
|
);
|
|
2183
2485
|
user.command("reset-password <email>").description(
|
|
2184
2486
|
"Trigger Cognito's forgot-password flow for a user (admin-initiated). Sends them a verification code email."
|
|
2185
|
-
).option("-p, --profile <name>", "AWS profile").requiredOption("-s, --stage <name>", "Deployment stage").option(
|
|
2487
|
+
).option("-p, --profile <name>", "AWS profile").requiredOption("-s, --stage <name>", "Deployment stage (e.g. dev, prod)").option(
|
|
2186
2488
|
"-r, --region <name>",
|
|
2187
2489
|
"AWS region (defaults to AWS CLI default / AWS_REGION)"
|
|
2490
|
+
).addHelpText(
|
|
2491
|
+
"after",
|
|
2492
|
+
`
|
|
2493
|
+
Examples:
|
|
2494
|
+
# Admin-triggered password reset \u2014 works even if the account is locked
|
|
2495
|
+
$ thinkwork user reset-password alice@example.com -s dev
|
|
2496
|
+
|
|
2497
|
+
# Target a specific AWS profile + region
|
|
2498
|
+
$ thinkwork user reset-password alice@example.com -s prod \\
|
|
2499
|
+
--profile thinkwork --region us-east-1
|
|
2500
|
+
|
|
2501
|
+
Cognito emails the user a verification code; they set a new password on
|
|
2502
|
+
next sign-in. Use this instead of \`forgot-password\` when the user is in
|
|
2503
|
+
FORCE_CHANGE_PASSWORD or has been disabled.
|
|
2504
|
+
`
|
|
2188
2505
|
).action(
|
|
2189
2506
|
async (email, opts) => {
|
|
2190
2507
|
const stageCheck = validateStage(opts.stage);
|
|
@@ -2255,10 +2572,14 @@ program.name("thinkwork").description(
|
|
|
2255
2572
|
"AWS profile to use (sets AWS_PROFILE for Terraform and AWS CLI)"
|
|
2256
2573
|
);
|
|
2257
2574
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
2258
|
-
const
|
|
2259
|
-
if (
|
|
2260
|
-
process.env.AWS_PROFILE =
|
|
2575
|
+
const explicit = actionCommand.opts().profile ?? program.opts().profile;
|
|
2576
|
+
if (explicit) {
|
|
2577
|
+
process.env.AWS_PROFILE = explicit;
|
|
2578
|
+
return;
|
|
2261
2579
|
}
|
|
2580
|
+
if (process.env.AWS_PROFILE) return;
|
|
2581
|
+
const fallback = loadCliConfig().defaultProfile;
|
|
2582
|
+
if (fallback) process.env.AWS_PROFILE = fallback;
|
|
2262
2583
|
});
|
|
2263
2584
|
registerLoginCommand(program);
|
|
2264
2585
|
registerInitCommand(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thinkwork-cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Thinkwork CLI — deploy, manage, and interact with your Thinkwork stack",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"prepublishOnly": "npm run build && npm run typecheck"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
+
"@inquirer/prompts": "^8.4.1",
|
|
22
23
|
"chalk": "^5.6.2",
|
|
23
24
|
"commander": "^12.0.0",
|
|
24
25
|
"ora": "^9.3.0"
|