thinkwork-cli 0.6.0 → 0.6.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.
Files changed (2) hide show
  1. package/dist/cli.js +433 -209
  2. package/package.json +1 -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 (existsSync(envDir)) {
107
+ if (existsSync2(envDir)) {
84
108
  return envDir;
85
109
  }
86
110
  const greenfield = path.join(terraformDir, "examples", "greenfield");
87
- if (existsSync(greenfield)) {
111
+ if (existsSync2(greenfield)) {
88
112
  return greenfield;
89
113
  }
90
114
  const flat = path.join(terraformDir);
91
- if (existsSync(path.join(flat, "main.tf"))) {
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 (existsSync(path.join(cwdTf, "main.tf"))) {
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 (!existsSync(dotTerraform)) {
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 readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3 } from "fs";
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 existsSync2, mkdirSync, writeFileSync, readFileSync, readdirSync } from "fs";
493
- import { join } from "path";
494
- import { homedir } from "os";
495
- var THINKWORK_HOME = join(homedir(), ".thinkwork");
496
- var ENVIRONMENTS_DIR = join(THINKWORK_HOME, "environments");
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 (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
522
+ if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
499
523
  }
500
524
  function saveEnvironment(config) {
501
525
  ensureDir(ENVIRONMENTS_DIR);
502
- const envDir = join(ENVIRONMENTS_DIR, config.stage);
526
+ const envDir = join2(ENVIRONMENTS_DIR, config.stage);
503
527
  ensureDir(envDir);
504
- writeFileSync(
505
- join(envDir, "config.json"),
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 = join(ENVIRONMENTS_DIR, stage, "config.json");
511
- if (!existsSync2(configPath)) return null;
512
- return JSON.parse(readFileSync(configPath, "utf-8"));
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 (!existsSync2(ENVIRONMENTS_DIR)) return [];
539
+ if (!existsSync3(ENVIRONMENTS_DIR)) return [];
516
540
  return readdirSync(ENVIRONMENTS_DIR).filter((name) => {
517
- return existsSync2(join(ENVIRONMENTS_DIR, name, "config.json"));
541
+ return existsSync3(join2(ENVIRONMENTS_DIR, name, "config.json"));
518
542
  }).map((name) => {
519
543
  return JSON.parse(
520
- readFileSync(join(ENVIRONMENTS_DIR, name, "config.json"), "utf-8")
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 && existsSync2(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 && existsSync2(envVar)) return envVar;
531
- const cwdTf = join(process.cwd(), "terraform");
532
- if (existsSync2(join(cwdTf, "main.tf"))) return cwdTf;
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 (!existsSync3(tfvarsPath)) return null;
539
- const content = readFileSync2(tfvarsPath, "utf-8");
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 (!existsSync3(tfvarsPath)) {
571
+ if (!existsSync4(tfvarsPath)) {
548
572
  throw new Error(`terraform.tfvars not found at ${tfvarsPath}`);
549
573
  }
550
- let content = readFileSync2(tfvarsPath, "utf-8");
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
- writeFileSync2(tfvarsPath, content);
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 (existsSync3(direct)) return direct;
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 (existsSync3(tfvarsPath)) {
618
+ if (existsSync4(tfvarsPath)) {
595
619
  console.log("");
596
620
  console.log(chalk3.dim(" terraform.tfvars:"));
597
- const content = readFileSync2(tfvarsPath, "utf-8");
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,89 @@ 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 chalk5 from "chalk";
806
+
807
+ // src/aws-profiles.ts
808
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
809
+ import { homedir as homedir3 } from "os";
810
+ import { join as join3 } from "path";
811
+ var CREDENTIALS_PATH = join3(homedir3(), ".aws", "credentials");
812
+ var CONFIG_PATH = join3(homedir3(), ".aws", "config");
813
+ function parseIni(content) {
814
+ const sections = {};
815
+ let current = null;
816
+ for (const rawLine of content.split(/\r?\n/)) {
817
+ const line = rawLine.trim();
818
+ if (!line || line.startsWith("#") || line.startsWith(";")) continue;
819
+ const header = line.match(/^\[(.+)\]$/);
820
+ if (header) {
821
+ current = header[1].trim();
822
+ sections[current] ??= {};
823
+ continue;
824
+ }
825
+ if (!current) continue;
826
+ const eq = line.indexOf("=");
827
+ if (eq === -1) continue;
828
+ const key = line.slice(0, eq).trim();
829
+ const value = line.slice(eq + 1).trim();
830
+ sections[current][key] = value;
831
+ }
832
+ return sections;
833
+ }
834
+ function normalizeConfigSection(section) {
835
+ if (section === "default") return "default";
836
+ if (section.startsWith("profile ")) return section.slice("profile ".length).trim();
837
+ return null;
838
+ }
839
+ function classify(fields) {
840
+ if (fields.aws_access_key_id) return "keys";
841
+ if (fields.sso_start_url || fields.sso_session || fields.sso_account_id || fields.sso_role_name) {
842
+ return "sso";
843
+ }
844
+ if (fields.role_arn || fields.source_profile || fields.credential_source) {
845
+ return "role";
846
+ }
847
+ return "other";
848
+ }
849
+ function listAwsProfiles() {
850
+ const byName = /* @__PURE__ */ new Map();
851
+ if (existsSync5(CREDENTIALS_PATH)) {
852
+ const sections = parseIni(readFileSync4(CREDENTIALS_PATH, "utf-8"));
853
+ for (const [section, fields] of Object.entries(sections)) {
854
+ byName.set(section, {
855
+ name: section,
856
+ source: "credentials",
857
+ type: classify(fields)
858
+ });
859
+ }
860
+ }
861
+ if (existsSync5(CONFIG_PATH)) {
862
+ const sections = parseIni(readFileSync4(CONFIG_PATH, "utf-8"));
863
+ for (const [section, fields] of Object.entries(sections)) {
864
+ const name = normalizeConfigSection(section);
865
+ if (!name) continue;
866
+ const existing = byName.get(name);
867
+ const type = classify(fields);
868
+ if (existing) {
869
+ byName.set(name, {
870
+ ...existing,
871
+ source: "both",
872
+ // Prefer the more specific type if one side says "other".
873
+ type: existing.type === "other" ? type : existing.type
874
+ });
875
+ } else {
876
+ byName.set(name, { name, source: "config", type });
877
+ }
878
+ }
879
+ }
880
+ return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
881
+ }
781
882
 
782
883
  // src/prerequisites.ts
783
884
  import { execSync as execSync3 } from "child_process";
784
- import { mkdirSync as mkdirSync2, createWriteStream, chmodSync } from "fs";
785
- import { join as join2 } from "path";
786
- import { homedir as homedir2, platform, arch } from "os";
885
+ import { mkdirSync as mkdirSync3, createWriteStream, chmodSync } from "fs";
886
+ import { join as join4 } from "path";
887
+ import { homedir as homedir4, platform, arch } from "os";
787
888
  import chalk4 from "chalk";
788
889
  function run(cmd, opts) {
789
890
  try {
@@ -815,14 +916,14 @@ async function ensureAwsCli() {
815
916
  }
816
917
  if (os === "linux") {
817
918
  try {
818
- const tmpDir = join2(homedir2(), ".thinkwork", "tmp");
819
- mkdirSync2(tmpDir, { recursive: true });
820
- const zipPath = join2(tmpDir, "awscliv2.zip");
919
+ const tmpDir = join4(homedir4(), ".thinkwork", "tmp");
920
+ mkdirSync3(tmpDir, { recursive: true });
921
+ const zipPath = join4(tmpDir, "awscliv2.zip");
821
922
  console.log(" Downloading AWS CLI...");
822
923
  run(`curl -sL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "${zipPath}"`);
823
924
  run(`cd "${tmpDir}" && unzip -qo "${zipPath}"`);
824
- run(`"${tmpDir}/aws/install" --install-dir "${homedir2()}/.thinkwork/aws-cli" --bin-dir "${homedir2()}/.local/bin" --update`);
825
- process.env.PATH = `${homedir2()}/.local/bin:${process.env.PATH}`;
925
+ run(`"${tmpDir}/aws/install" --install-dir "${homedir4()}/.thinkwork/aws-cli" --bin-dir "${homedir4()}/.local/bin" --update`);
926
+ process.env.PATH = `${homedir4()}/.local/bin:${process.env.PATH}`;
826
927
  if (isInstalled("aws")) {
827
928
  console.log(` ${chalk4.green("\u2713")} AWS CLI installed to ~/.local/bin/aws`);
828
929
  return true;
@@ -832,9 +933,9 @@ async function ensureAwsCli() {
832
933
  }
833
934
  if (os === "darwin") {
834
935
  try {
835
- const tmpDir = join2(homedir2(), ".thinkwork", "tmp");
836
- mkdirSync2(tmpDir, { recursive: true });
837
- const pkgPath = join2(tmpDir, "AWSCLIV2.pkg");
936
+ const tmpDir = join4(homedir4(), ".thinkwork", "tmp");
937
+ mkdirSync3(tmpDir, { recursive: true });
938
+ const pkgPath = join4(tmpDir, "AWSCLIV2.pkg");
838
939
  console.log(" Downloading AWS CLI...");
839
940
  run(`curl -sL "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "${pkgPath}"`);
840
941
  run(`installer -pkg "${pkgPath}" -target CurrentUserHomeDirectory 2>/dev/null || sudo installer -pkg "${pkgPath}" -target /`);
@@ -865,15 +966,15 @@ async function ensureTerraform() {
865
966
  const archName = arch() === "arm64" ? "arm64" : "amd64";
866
967
  const url = `https://releases.hashicorp.com/terraform/${tfVersion}/terraform_${tfVersion}_${osName}_${archName}.zip`;
867
968
  try {
868
- const tmpDir = join2(homedir2(), ".thinkwork", "tmp");
869
- const binDir = join2(homedir2(), ".local", "bin");
870
- mkdirSync2(tmpDir, { recursive: true });
871
- mkdirSync2(binDir, { recursive: true });
872
- const zipPath = join2(tmpDir, "terraform.zip");
969
+ const tmpDir = join4(homedir4(), ".thinkwork", "tmp");
970
+ const binDir = join4(homedir4(), ".local", "bin");
971
+ mkdirSync3(tmpDir, { recursive: true });
972
+ mkdirSync3(binDir, { recursive: true });
973
+ const zipPath = join4(tmpDir, "terraform.zip");
873
974
  console.log(` Downloading Terraform ${tfVersion}...`);
874
975
  run(`curl -sL "${url}" -o "${zipPath}"`);
875
976
  run(`unzip -qo "${zipPath}" -d "${binDir}"`);
876
- chmodSync(join2(binDir, "terraform"), 493);
977
+ chmodSync(join4(binDir, "terraform"), 493);
877
978
  if (!process.env.PATH?.includes(binDir)) {
878
979
  process.env.PATH = `${binDir}:${process.env.PATH}`;
879
980
  }
@@ -910,93 +1011,212 @@ function ask(prompt2) {
910
1011
  });
911
1012
  });
912
1013
  }
1014
+ function verifyProfile(profile) {
1015
+ try {
1016
+ const raw = execSync4(
1017
+ `aws sts get-caller-identity --profile ${profile} --output json`,
1018
+ { encoding: "utf-8", timeout: 15e3, stdio: ["pipe", "pipe", "pipe"] }
1019
+ );
1020
+ const parsed = JSON.parse(raw);
1021
+ return { account: parsed.Account, arn: parsed.Arn };
1022
+ } catch {
1023
+ return null;
1024
+ }
1025
+ }
1026
+ function describeType(type) {
1027
+ switch (type) {
1028
+ case "keys":
1029
+ return "access keys";
1030
+ case "sso":
1031
+ return "SSO";
1032
+ case "role":
1033
+ return "assumed role";
1034
+ default:
1035
+ return "config only";
1036
+ }
1037
+ }
1038
+ async function pickProfile(profiles) {
1039
+ console.log("");
1040
+ console.log(chalk5.bold(" Found these profiles in ~/.aws:"));
1041
+ profiles.forEach((p, i) => {
1042
+ const idx = String(i + 1).padStart(2, " ");
1043
+ console.log(
1044
+ ` ${chalk5.cyan(idx)}. ${chalk5.bold(p.name)} ${chalk5.dim(`(${describeType(p.type)})`)}`
1045
+ );
1046
+ });
1047
+ const newIdx = profiles.length + 1;
1048
+ const ssoIdx = profiles.length + 2;
1049
+ console.log(
1050
+ ` ${chalk5.cyan(String(newIdx).padStart(2, " "))}. Enter new access keys`
1051
+ );
1052
+ console.log(
1053
+ ` ${chalk5.cyan(String(ssoIdx).padStart(2, " "))}. Log in via AWS SSO`
1054
+ );
1055
+ console.log("");
1056
+ const answer = await ask(` Pick a profile [1-${ssoIdx}] (Enter to cancel): `);
1057
+ if (!answer) return { kind: "cancel" };
1058
+ const n = Number.parseInt(answer, 10);
1059
+ if (Number.isNaN(n) || n < 1 || n > ssoIdx) {
1060
+ printError(`"${answer}" is not a valid option.`);
1061
+ return { kind: "cancel" };
1062
+ }
1063
+ if (n === newIdx) return { kind: "keys" };
1064
+ if (n === ssoIdx) return { kind: "sso" };
1065
+ return { kind: "existing", name: profiles[n - 1].name };
1066
+ }
1067
+ async function runKeyEntry(targetProfile) {
1068
+ console.log("");
1069
+ console.log(" Enter your AWS credentials. These will be saved to the");
1070
+ console.log(` AWS CLI profile "${targetProfile}".`);
1071
+ console.log("");
1072
+ const accessKeyId = await ask(" AWS Access Key ID: ");
1073
+ if (!accessKeyId) {
1074
+ printError("Access Key ID is required");
1075
+ return false;
1076
+ }
1077
+ const secretAccessKey = await ask(" AWS Secret Access Key: ");
1078
+ if (!secretAccessKey) {
1079
+ printError("Secret Access Key is required");
1080
+ return false;
1081
+ }
1082
+ const region = await ask(" Default region [us-east-1]: ");
1083
+ const finalRegion = region || "us-east-1";
1084
+ try {
1085
+ execSync4(
1086
+ `aws configure set aws_access_key_id "${accessKeyId}" --profile ${targetProfile}`,
1087
+ { stdio: "pipe" }
1088
+ );
1089
+ execSync4(
1090
+ `aws configure set aws_secret_access_key "${secretAccessKey}" --profile ${targetProfile}`,
1091
+ { stdio: "pipe" }
1092
+ );
1093
+ execSync4(
1094
+ `aws configure set region "${finalRegion}" --profile ${targetProfile}`,
1095
+ { stdio: "pipe" }
1096
+ );
1097
+ return true;
1098
+ } catch (err) {
1099
+ printError(`Failed to save credentials: ${err}`);
1100
+ return false;
1101
+ }
1102
+ }
1103
+ function runSsoLogin(targetProfile) {
1104
+ console.log(" Launching AWS SSO login...");
1105
+ console.log("");
1106
+ try {
1107
+ execSync4(`aws sso login --profile ${targetProfile}`, { stdio: "inherit" });
1108
+ return true;
1109
+ } catch {
1110
+ printError(
1111
+ `SSO login failed. Run \`aws configure sso --profile ${targetProfile}\` first to set up the profile.`
1112
+ );
1113
+ return false;
1114
+ }
1115
+ }
1116
+ function finalize(profile, mode) {
1117
+ const identity = getAwsIdentity();
1118
+ if (!identity) {
1119
+ printError(
1120
+ `Credentials saved but could not verify with profile "${profile}". Try \`aws sts get-caller-identity --profile ${profile}\`.`
1121
+ );
1122
+ process.exit(1);
1123
+ }
1124
+ saveCliConfig({ defaultProfile: profile });
1125
+ printSuccess(
1126
+ `Logged in via ${mode} (account: ${identity.account}, region: ${identity.region})`
1127
+ );
1128
+ console.log("");
1129
+ console.log(
1130
+ ` Profile "${profile}" saved as your Thinkwork default. Subsequent commands`
1131
+ );
1132
+ console.log(
1133
+ ` (\`thinkwork list\`, \`thinkwork deploy\`, \u2026) will use it automatically.`
1134
+ );
1135
+ console.log(
1136
+ chalk5.dim(
1137
+ ` Override per-command with --profile <other>, or unset with \`rm ~/.thinkwork/config.json\`.`
1138
+ )
1139
+ );
1140
+ }
913
1141
  function registerLoginCommand(program2) {
914
- program2.command("login").description("Configure AWS credentials for Thinkwork deployments").option("--profile <name>", "AWS profile name to configure", "thinkwork").option("--sso", "Use AWS SSO (Identity Center) login").action(async (opts) => {
1142
+ program2.command("login").description(
1143
+ "Configure AWS credentials for Thinkwork. Picks from existing ~/.aws profiles by default; falls back to new keys or SSO."
1144
+ ).option(
1145
+ "--profile <name>",
1146
+ "AWS profile name to configure (used when entering new keys or SSO)",
1147
+ "thinkwork"
1148
+ ).option("--sso", "Skip the picker and go straight to SSO login").option("--keys", "Skip the picker and go straight to access-key entry").action(async (opts) => {
915
1149
  printHeader("login", opts.profile);
916
1150
  const awsOk = await ensureAwsCli();
917
- if (!awsOk) {
918
- process.exit(1);
1151
+ if (!awsOk) process.exit(1);
1152
+ if (opts.sso) {
1153
+ if (!runSsoLogin(opts.profile)) process.exit(1);
1154
+ process.env.AWS_PROFILE = opts.profile;
1155
+ finalize(opts.profile, "SSO");
1156
+ return;
1157
+ }
1158
+ if (opts.keys) {
1159
+ if (!await runKeyEntry(opts.profile)) process.exit(1);
1160
+ process.env.AWS_PROFILE = opts.profile;
1161
+ finalize(opts.profile, "access keys");
1162
+ return;
919
1163
  }
920
- const existing = getAwsIdentity();
921
- if (existing) {
922
- console.log(` Already authenticated:`);
923
- console.log(` Account: ${existing.account}`);
924
- console.log(` Region: ${existing.region}`);
925
- console.log(` ARN: ${existing.arn}`);
1164
+ const profiles = listAwsProfiles();
1165
+ if (profiles.length === 0) {
926
1166
  console.log("");
927
- const reauth = await ask(" Re-authenticate? [y/N] ");
928
- if (reauth.toLowerCase() !== "y") {
929
- printSuccess("Using existing credentials");
930
- return;
931
- }
1167
+ console.log(chalk5.dim(" No AWS profiles found in ~/.aws/."));
1168
+ console.log(
1169
+ chalk5.dim(" Falling through to access-key entry for a new profile.")
1170
+ );
1171
+ if (!await runKeyEntry(opts.profile)) process.exit(1);
1172
+ process.env.AWS_PROFILE = opts.profile;
1173
+ finalize(opts.profile, "access keys");
1174
+ return;
932
1175
  }
933
- if (opts.sso) {
934
- console.log(" Launching AWS SSO login...");
1176
+ const choice = await pickProfile(profiles);
1177
+ if (choice.kind === "cancel") {
935
1178
  console.log("");
936
- try {
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
- }
1179
+ console.log(chalk5.dim(" Cancelled. No changes made."));
950
1180
  return;
951
1181
  }
952
- console.log(" Enter your AWS credentials. These will be saved to the");
953
- console.log(` AWS CLI profile "${opts.profile}".`);
954
- console.log("");
955
- const accessKeyId = await ask(" AWS Access Key ID: ");
956
- if (!accessKeyId) {
957
- printError("Access Key ID is required");
958
- process.exit(1);
1182
+ if (choice.kind === "keys") {
1183
+ if (!await runKeyEntry(opts.profile)) process.exit(1);
1184
+ process.env.AWS_PROFILE = opts.profile;
1185
+ finalize(opts.profile, "access keys");
1186
+ return;
959
1187
  }
960
- const secretAccessKey = await ask(" AWS Secret Access Key: ");
961
- if (!secretAccessKey) {
962
- printError("Secret Access Key is required");
963
- process.exit(1);
1188
+ if (choice.kind === "sso") {
1189
+ if (!runSsoLogin(opts.profile)) process.exit(1);
1190
+ process.env.AWS_PROFILE = opts.profile;
1191
+ finalize(opts.profile, "SSO");
1192
+ return;
964
1193
  }
965
- const region = await ask(" Default region [us-east-1]: ");
966
- const finalRegion = region || "us-east-1";
967
- try {
968
- execSync4(`aws configure set aws_access_key_id "${accessKeyId}" --profile ${opts.profile}`, { stdio: "pipe" });
969
- execSync4(`aws configure set aws_secret_access_key "${secretAccessKey}" --profile ${opts.profile}`, { stdio: "pipe" });
970
- execSync4(`aws configure set region "${finalRegion}" --profile ${opts.profile}`, { stdio: "pipe" });
971
- } catch (err) {
972
- printError(`Failed to save credentials: ${err}`);
1194
+ const picked = choice.name;
1195
+ console.log("");
1196
+ console.log(` Verifying "${picked}"...`);
1197
+ const identity = verifyProfile(picked);
1198
+ if (!identity) {
1199
+ printError(
1200
+ `Could not authenticate with profile "${picked}". If it's an SSO profile, try \`aws sso login --profile ${picked}\` first.`
1201
+ );
973
1202
  process.exit(1);
974
1203
  }
975
- process.env.AWS_PROFILE = opts.profile;
976
- const identity = getAwsIdentity();
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
- }
1204
+ process.env.AWS_PROFILE = picked;
1205
+ finalize(picked, "existing profile");
986
1206
  });
987
1207
  }
988
1208
 
989
1209
  // src/commands/init.ts
990
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, cpSync } from "fs";
991
- import { resolve as resolve2, join as join3, dirname } from "path";
1210
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, cpSync } from "fs";
1211
+ import { resolve as resolve2, join as join5, dirname as dirname2 } from "path";
992
1212
  import { execSync as execSync5 } from "child_process";
993
1213
  import { fileURLToPath } from "url";
994
1214
  import { createInterface as createInterface3 } from "readline";
995
- import chalk5 from "chalk";
996
- var __dirname = dirname(fileURLToPath(import.meta.url));
1215
+ import chalk6 from "chalk";
1216
+ var __dirname = dirname2(fileURLToPath(import.meta.url));
997
1217
  function ask2(prompt2, defaultVal = "") {
998
1218
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
999
- const suffix = defaultVal ? chalk5.dim(` [${defaultVal}]`) : "";
1219
+ const suffix = defaultVal ? chalk6.dim(` [${defaultVal}]`) : "";
1000
1220
  return new Promise((resolve3) => {
1001
1221
  rl.question(` ${prompt2}${suffix}: `, (answer) => {
1002
1222
  rl.close();
@@ -1005,7 +1225,7 @@ function ask2(prompt2, defaultVal = "") {
1005
1225
  });
1006
1226
  }
1007
1227
  function choose(prompt2, options, defaultVal) {
1008
- const optStr = options.map((o) => o === defaultVal ? chalk5.bold(o) : chalk5.dim(o)).join(" / ");
1228
+ const optStr = options.map((o) => o === defaultVal ? chalk6.bold(o) : chalk6.dim(o)).join(" / ");
1009
1229
  return ask2(`${prompt2} (${optStr})`, defaultVal);
1010
1230
  }
1011
1231
  function generateSecret(length = 32) {
@@ -1018,9 +1238,9 @@ function generateSecret(length = 32) {
1018
1238
  }
1019
1239
  function findBundledTerraform() {
1020
1240
  const bundled = resolve2(__dirname, "terraform");
1021
- if (existsSync5(join3(bundled, "modules"))) return bundled;
1241
+ if (existsSync7(join5(bundled, "modules"))) return bundled;
1022
1242
  const repoTf = resolve2(__dirname, "..", "..", "..", "terraform");
1023
- if (existsSync5(join3(repoTf, "modules"))) return repoTf;
1243
+ if (existsSync7(join5(repoTf, "modules"))) return repoTf;
1024
1244
  throw new Error(
1025
1245
  "Terraform modules not found. The CLI package may be incomplete.\nTry reinstalling: npm install -g thinkwork-cli@latest"
1026
1246
  );
@@ -1090,9 +1310,9 @@ function registerInitCommand(program2) {
1090
1310
  process.exit(1);
1091
1311
  }
1092
1312
  const targetDir = resolve2(opts.dir);
1093
- const tfDir = join3(targetDir, "terraform");
1094
- const tfvarsPath = join3(tfDir, "terraform.tfvars");
1095
- if (existsSync5(tfvarsPath)) {
1313
+ const tfDir = join5(targetDir, "terraform");
1314
+ const tfvarsPath = join5(tfDir, "terraform.tfvars");
1315
+ if (existsSync7(tfvarsPath)) {
1096
1316
  printWarning(`terraform.tfvars already exists at ${tfvarsPath}`);
1097
1317
  const overwrite = await ask2("Overwrite?", "N");
1098
1318
  if (overwrite.toLowerCase() !== "y") {
@@ -1116,21 +1336,21 @@ function registerInitCommand(program2) {
1116
1336
  config.admin_url = "http://localhost:5174";
1117
1337
  config.mobile_scheme = "thinkwork";
1118
1338
  } else {
1119
- console.log(chalk5.bold(" Configure your Thinkwork environment\n"));
1339
+ console.log(chalk6.bold(" Configure your Thinkwork environment\n"));
1120
1340
  const defaultRegion = identity.region !== "unknown" ? identity.region : "us-east-1";
1121
1341
  config.region = await ask2("AWS Region", defaultRegion);
1122
1342
  console.log("");
1123
- console.log(chalk5.dim(" \u2500\u2500 Database \u2500\u2500"));
1343
+ console.log(chalk6.dim(" \u2500\u2500 Database \u2500\u2500"));
1124
1344
  config.database_engine = await choose("Database engine", ["aurora-serverless", "rds-postgres"], "aurora-serverless");
1125
1345
  console.log("");
1126
- console.log(chalk5.dim(" \u2500\u2500 Memory \u2500\u2500"));
1127
- console.log(chalk5.dim(" AgentCore managed memory is always on (automatic retention)."));
1128
- console.log(chalk5.dim(" Hindsight is an optional add-on: ECS Fargate service for"));
1129
- console.log(chalk5.dim(" semantic + entity-graph retrieval (~$75/mo)."));
1346
+ console.log(chalk6.dim(" \u2500\u2500 Memory \u2500\u2500"));
1347
+ console.log(chalk6.dim(" AgentCore managed memory is always on (automatic retention)."));
1348
+ console.log(chalk6.dim(" Hindsight is an optional add-on: ECS Fargate service for"));
1349
+ console.log(chalk6.dim(" semantic + entity-graph retrieval (~$75/mo)."));
1130
1350
  const hindsightAnswer = await ask2("Enable Hindsight long-term memory add-on? (y/N)", "N");
1131
1351
  config.enable_hindsight = hindsightAnswer.toLowerCase() === "y" ? "true" : "false";
1132
1352
  console.log("");
1133
- console.log(chalk5.dim(" \u2500\u2500 Auth \u2500\u2500"));
1353
+ console.log(chalk6.dim(" \u2500\u2500 Auth \u2500\u2500"));
1134
1354
  const useGoogle = await ask2("Enable Google OAuth login? (y/N)", "N");
1135
1355
  if (useGoogle.toLowerCase() === "y") {
1136
1356
  config.google_oauth_client_id = await ask2("Google OAuth Client ID");
@@ -1140,13 +1360,13 @@ function registerInitCommand(program2) {
1140
1360
  config.google_oauth_client_secret = "";
1141
1361
  }
1142
1362
  console.log("");
1143
- console.log(chalk5.dim(" \u2500\u2500 Frontend URLs \u2500\u2500"));
1363
+ console.log(chalk6.dim(" \u2500\u2500 Frontend URLs \u2500\u2500"));
1144
1364
  config.admin_url = await ask2("Admin UI URL", "http://localhost:5174");
1145
1365
  config.mobile_scheme = await ask2("Mobile app URL scheme", "thinkwork");
1146
1366
  console.log("");
1147
- console.log(chalk5.dim(" \u2500\u2500 Secrets (auto-generated) \u2500\u2500"));
1148
- console.log(chalk5.dim(` DB password: ${config.db_password.slice(0, 8)}...`));
1149
- console.log(chalk5.dim(` API auth secret: ${config.api_auth_secret.slice(0, 16)}...`));
1367
+ console.log(chalk6.dim(" \u2500\u2500 Secrets (auto-generated) \u2500\u2500"));
1368
+ console.log(chalk6.dim(` DB password: ${config.db_password.slice(0, 8)}...`));
1369
+ console.log(chalk6.dim(` API auth secret: ${config.api_auth_secret.slice(0, 16)}...`));
1150
1370
  }
1151
1371
  console.log("");
1152
1372
  console.log(" Scaffolding Terraform modules...");
@@ -1157,24 +1377,24 @@ function registerInitCommand(program2) {
1157
1377
  printError(String(err));
1158
1378
  process.exit(1);
1159
1379
  }
1160
- mkdirSync3(tfDir, { recursive: true });
1380
+ mkdirSync4(tfDir, { recursive: true });
1161
1381
  const copyDirs = ["modules", "examples"];
1162
1382
  for (const dir of copyDirs) {
1163
- const src = join3(bundledTf, dir);
1164
- const dst = join3(tfDir, dir);
1165
- if (existsSync5(src) && !existsSync5(dst)) {
1383
+ const src = join5(bundledTf, dir);
1384
+ const dst = join5(tfDir, dir);
1385
+ if (existsSync7(src) && !existsSync7(dst)) {
1166
1386
  cpSync(src, dst, { recursive: true });
1167
1387
  }
1168
1388
  }
1169
- const schemaPath = join3(bundledTf, "schema.graphql");
1170
- if (existsSync5(schemaPath) && !existsSync5(join3(tfDir, "schema.graphql"))) {
1171
- cpSync(schemaPath, join3(tfDir, "schema.graphql"));
1389
+ const schemaPath = join5(bundledTf, "schema.graphql");
1390
+ if (existsSync7(schemaPath) && !existsSync7(join5(tfDir, "schema.graphql"))) {
1391
+ cpSync(schemaPath, join5(tfDir, "schema.graphql"));
1172
1392
  }
1173
1393
  const tfvars = buildTfvars(config);
1174
- writeFileSync3(tfvarsPath, tfvars);
1175
- const mainTfPath = join3(tfDir, "main.tf");
1176
- if (!existsSync5(mainTfPath)) {
1177
- writeFileSync3(mainTfPath, `################################################################################
1394
+ writeFileSync4(tfvarsPath, tfvars);
1395
+ const mainTfPath = join5(tfDir, "main.tf");
1396
+ if (!existsSync7(mainTfPath)) {
1397
+ writeFileSync4(mainTfPath, `################################################################################
1178
1398
  # Thinkwork \u2014 ${config.stage}
1179
1399
  # Generated by: thinkwork init -s ${config.stage}
1180
1400
  ################################################################################
@@ -1351,17 +1571,17 @@ output "agentcore_memory_id" {
1351
1571
  }
1352
1572
  `);
1353
1573
  }
1354
- console.log(` Wrote ${chalk5.cyan(tfDir + "/")}`);
1574
+ console.log(` Wrote ${chalk6.cyan(tfDir + "/")}`);
1355
1575
  console.log("");
1356
- console.log(chalk5.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"));
1357
- console.log(` ${chalk5.bold("Stage:")} ${config.stage}`);
1358
- console.log(` ${chalk5.bold("Region:")} ${config.region}`);
1359
- console.log(` ${chalk5.bold("Account:")} ${config.account_id}`);
1360
- console.log(` ${chalk5.bold("Database:")} ${config.database_engine}`);
1361
- console.log(` ${chalk5.bold("Memory:")} managed (always on)${config.enable_hindsight === "true" ? " + hindsight" : ""}`);
1362
- console.log(` ${chalk5.bold("Google OAuth:")} ${config.google_oauth_client_id ? "enabled" : "disabled"}`);
1363
- console.log(` ${chalk5.bold("Directory:")} ${tfDir}`);
1364
- console.log(chalk5.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"));
1576
+ 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"));
1577
+ console.log(` ${chalk6.bold("Stage:")} ${config.stage}`);
1578
+ console.log(` ${chalk6.bold("Region:")} ${config.region}`);
1579
+ console.log(` ${chalk6.bold("Account:")} ${config.account_id}`);
1580
+ console.log(` ${chalk6.bold("Database:")} ${config.database_engine}`);
1581
+ console.log(` ${chalk6.bold("Memory:")} managed (always on)${config.enable_hindsight === "true" ? " + hindsight" : ""}`);
1582
+ console.log(` ${chalk6.bold("Google OAuth:")} ${config.google_oauth_client_id ? "enabled" : "disabled"}`);
1583
+ console.log(` ${chalk6.bold("Directory:")} ${tfDir}`);
1584
+ 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
1585
  console.log("\n Initializing Terraform...\n");
1366
1586
  try {
1367
1587
  execSync5("terraform init", { cwd: tfDir, stdio: "inherit" });
@@ -1383,17 +1603,17 @@ output "agentcore_memory_id" {
1383
1603
  printSuccess(`Environment "${opts.stage}" initialized`);
1384
1604
  console.log("");
1385
1605
  console.log(" Next steps:");
1386
- console.log(` ${chalk5.cyan("1.")} thinkwork plan -s ${opts.stage} ${chalk5.dim("# Review infrastructure plan")}`);
1387
- console.log(` ${chalk5.cyan("2.")} thinkwork deploy -s ${opts.stage} ${chalk5.dim("# Deploy to AWS (~5 min)")}`);
1388
- console.log(` ${chalk5.cyan("3.")} thinkwork bootstrap -s ${opts.stage} ${chalk5.dim("# Seed workspace files + skills")}`);
1389
- console.log(` ${chalk5.cyan("4.")} thinkwork outputs -s ${opts.stage} ${chalk5.dim("# Show API URL, Cognito IDs, etc.")}`);
1606
+ console.log(` ${chalk6.cyan("1.")} thinkwork plan -s ${opts.stage} ${chalk6.dim("# Review infrastructure plan")}`);
1607
+ console.log(` ${chalk6.cyan("2.")} thinkwork deploy -s ${opts.stage} ${chalk6.dim("# Deploy to AWS (~5 min)")}`);
1608
+ console.log(` ${chalk6.cyan("3.")} thinkwork bootstrap -s ${opts.stage} ${chalk6.dim("# Seed workspace files + skills")}`);
1609
+ console.log(` ${chalk6.cyan("4.")} thinkwork outputs -s ${opts.stage} ${chalk6.dim("# Show API URL, Cognito IDs, etc.")}`);
1390
1610
  console.log("");
1391
1611
  });
1392
1612
  }
1393
1613
 
1394
1614
  // src/commands/status.ts
1395
1615
  import { execSync as execSync6 } from "child_process";
1396
- import chalk6 from "chalk";
1616
+ import chalk7 from "chalk";
1397
1617
  function link(url, label) {
1398
1618
  const text = label || url;
1399
1619
  return `\x1B]8;;${url}\x1B\\${text}\x1B]8;;\x1B\\`;
@@ -1491,33 +1711,33 @@ function discoverAwsStages(region) {
1491
1711
  return stages;
1492
1712
  }
1493
1713
  function printStageDetail(info) {
1494
- console.log(chalk6.bold.cyan(` \u2B21 ${info.stage}`));
1495
- 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\u2500\u2500\u2500\u2500"));
1496
- console.log(` ${chalk6.bold("Source:")} ${info.source === "both" ? "AWS + local config" : info.source === "aws" ? "AWS (no local config)" : "local only (not in AWS)"}`);
1497
- console.log(` ${chalk6.bold("Region:")} ${info.region}`);
1498
- console.log(` ${chalk6.bold("Account:")} ${info.accountId}`);
1499
- console.log(` ${chalk6.bold("Lambda fns:")} ${info.lambdaCount || "\u2014"}`);
1500
- console.log(` ${chalk6.bold("AgentCore:")} ${info.agentcoreStatus || "unknown"}`);
1501
- console.log(` ${chalk6.bold("Memory:")} managed (always on)`);
1714
+ console.log(chalk7.bold.cyan(` \u2B21 ${info.stage}`));
1715
+ 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"));
1716
+ console.log(` ${chalk7.bold("Source:")} ${info.source === "both" ? "AWS + local config" : info.source === "aws" ? "AWS (no local config)" : "local only (not in AWS)"}`);
1717
+ console.log(` ${chalk7.bold("Region:")} ${info.region}`);
1718
+ console.log(` ${chalk7.bold("Account:")} ${info.accountId}`);
1719
+ console.log(` ${chalk7.bold("Lambda fns:")} ${info.lambdaCount || "\u2014"}`);
1720
+ console.log(` ${chalk7.bold("AgentCore:")} ${info.agentcoreStatus || "unknown"}`);
1721
+ console.log(` ${chalk7.bold("Memory:")} managed (always on)`);
1502
1722
  const hindsightLabel = info.hindsightEnabled === void 0 ? "unknown" : info.hindsightEnabled ? info.hindsightHealth || "running" : "disabled";
1503
- console.log(` ${chalk6.bold("Hindsight:")} ${hindsightLabel}`);
1504
- if (info.bucketName) console.log(` ${chalk6.bold("S3 bucket:")} ${info.bucketName}`);
1505
- if (info.dbEndpoint) console.log(` ${chalk6.bold("Database:")} ${info.dbEndpoint}`);
1506
- if (info.ecrUrl) console.log(` ${chalk6.bold("ECR:")} ${info.ecrUrl}`);
1723
+ console.log(` ${chalk7.bold("Hindsight:")} ${hindsightLabel}`);
1724
+ if (info.bucketName) console.log(` ${chalk7.bold("S3 bucket:")} ${info.bucketName}`);
1725
+ if (info.dbEndpoint) console.log(` ${chalk7.bold("Database:")} ${info.dbEndpoint}`);
1726
+ if (info.ecrUrl) console.log(` ${chalk7.bold("ECR:")} ${info.ecrUrl}`);
1507
1727
  console.log("");
1508
- console.log(chalk6.bold(" URLs:"));
1728
+ console.log(chalk7.bold(" URLs:"));
1509
1729
  if (info.adminUrl) console.log(` Admin: ${link(info.adminUrl)}`);
1510
1730
  if (info.docsUrl) console.log(` Docs: ${link(info.docsUrl)}`);
1511
1731
  if (info.apiEndpoint) console.log(` API: ${link(info.apiEndpoint)}`);
1512
1732
  if (info.appsyncApiUrl) console.log(` AppSync: ${link(info.appsyncApiUrl)}`);
1513
1733
  if (info.appsyncUrl) console.log(` WebSocket: ${link(info.appsyncUrl)}`);
1514
1734
  if (info.hindsightEndpoint) console.log(` Hindsight: ${link(info.hindsightEndpoint)}`);
1515
- 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\u2500\u2500\u2500\u2500"));
1735
+ 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
1736
  const local = loadEnvironment(info.stage);
1517
1737
  if (local) {
1518
- console.log(chalk6.dim(` Terraform dir: ${local.terraformDir}`));
1738
+ console.log(chalk7.dim(` Terraform dir: ${local.terraformDir}`));
1519
1739
  } else {
1520
- console.log(chalk6.dim(` No local config. Run: thinkwork init -s ${info.stage}`));
1740
+ console.log(chalk7.dim(` No local config. Run: thinkwork init -s ${info.stage}`));
1521
1741
  }
1522
1742
  console.log("");
1523
1743
  }
@@ -1531,7 +1751,7 @@ function registerStatusCommand(program2) {
1531
1751
  printError("AWS credentials not configured. Run `thinkwork login` first.");
1532
1752
  process.exit(1);
1533
1753
  }
1534
- console.log(chalk6.dim(" Scanning AWS account for Thinkwork deployments...\n"));
1754
+ console.log(chalk7.dim(" Scanning AWS account for Thinkwork deployments...\n"));
1535
1755
  const awsStages = discoverAwsStages(opts.region);
1536
1756
  const localEnvs = listEnvironments();
1537
1757
  const merged = /* @__PURE__ */ new Map();
@@ -1566,7 +1786,7 @@ function registerStatusCommand(program2) {
1566
1786
  }
1567
1787
  if (merged.size === 0) {
1568
1788
  console.log(" No Thinkwork environments found.");
1569
- console.log(` Run ${chalk6.cyan("thinkwork init -s <stage>")} to create one.`);
1789
+ console.log(` Run ${chalk7.cyan("thinkwork init -s <stage>")} to create one.`);
1570
1790
  console.log("");
1571
1791
  return;
1572
1792
  }
@@ -1577,14 +1797,14 @@ function registerStatusCommand(program2) {
1577
1797
  }
1578
1798
 
1579
1799
  // src/commands/mcp.ts
1580
- import chalk7 from "chalk";
1800
+ import chalk8 from "chalk";
1581
1801
 
1582
1802
  // src/api-client.ts
1583
- import { readFileSync as readFileSync3, existsSync as existsSync6 } from "fs";
1803
+ import { readFileSync as readFileSync5, existsSync as existsSync8 } from "fs";
1584
1804
  import { execSync as execSync7 } from "child_process";
1585
1805
  function readTfVar2(tfvarsPath, key) {
1586
- if (!existsSync6(tfvarsPath)) return null;
1587
- const content = readFileSync3(tfvarsPath, "utf-8");
1806
+ if (!existsSync8(tfvarsPath)) return null;
1807
+ const content = readFileSync5(tfvarsPath, "utf-8");
1588
1808
  const match = content.match(new RegExp(`^${key}\\s*=\\s*"([^"]*)"`, "m"));
1589
1809
  return match ? match[1] : null;
1590
1810
  }
@@ -1592,7 +1812,7 @@ function resolveTfvarsPath2(stage) {
1592
1812
  const tfDir = resolveTerraformDir(stage);
1593
1813
  if (tfDir) {
1594
1814
  const direct = `${tfDir}/terraform.tfvars`;
1595
- if (existsSync6(direct)) return direct;
1815
+ if (existsSync8(direct)) return direct;
1596
1816
  }
1597
1817
  const terraformDir = process.env.THINKWORK_TERRAFORM_DIR || process.cwd();
1598
1818
  const cwd = resolveTierDir(terraformDir, stage, "app");
@@ -1671,14 +1891,14 @@ function registerMcpCommand(program2) {
1671
1891
  try {
1672
1892
  const { servers } = await apiFetch(api.apiUrl, api.authSecret, "/api/skills/mcp-servers", {}, { "x-tenant-slug": opts.tenant });
1673
1893
  if (!servers || servers.length === 0) {
1674
- console.log(chalk7.dim(" No MCP servers registered."));
1894
+ console.log(chalk8.dim(" No MCP servers registered."));
1675
1895
  return;
1676
1896
  }
1677
1897
  console.log("");
1678
1898
  for (const s of servers) {
1679
- const status = s.enabled ? chalk7.green("enabled") : chalk7.dim("disabled");
1899
+ const status = s.enabled ? chalk8.green("enabled") : chalk8.dim("disabled");
1680
1900
  const authLabel = s.authType === "per_user_oauth" ? `OAuth (${s.oauthProvider})` : s.authType === "tenant_api_key" ? "API Key" : "none";
1681
- console.log(` ${chalk7.bold(s.name)} ${chalk7.dim(s.slug)} ${status}`);
1901
+ console.log(` ${chalk8.bold(s.name)} ${chalk8.dim(s.slug)} ${status}`);
1682
1902
  console.log(` URL: ${s.url}`);
1683
1903
  console.log(` Transport: ${s.transport}`);
1684
1904
  console.log(` Auth: ${authLabel}`);
@@ -1753,11 +1973,11 @@ function registerMcpCommand(program2) {
1753
1973
  if (result.ok) {
1754
1974
  printSuccess("Connection successful.");
1755
1975
  if (result.tools?.length) {
1756
- console.log(chalk7.bold(`
1976
+ console.log(chalk8.bold(`
1757
1977
  Discovered tools (${result.tools.length}):
1758
1978
  `));
1759
1979
  for (const t of result.tools) {
1760
- console.log(` ${chalk7.cyan(t.name)}${t.description ? chalk7.dim(` - ${t.description}`) : ""}`);
1980
+ console.log(` ${chalk8.cyan(t.name)}${t.description ? chalk8.dim(` - ${t.description}`) : ""}`);
1761
1981
  }
1762
1982
  console.log("");
1763
1983
  } else {
@@ -1813,7 +2033,7 @@ function registerMcpCommand(program2) {
1813
2033
 
1814
2034
  // src/commands/tools.ts
1815
2035
  import { createInterface as createInterface4 } from "readline";
1816
- import chalk8 from "chalk";
2036
+ import chalk9 from "chalk";
1817
2037
  function prompt(question) {
1818
2038
  const rl = createInterface4({ input: process.stdin, output: process.stdout });
1819
2039
  return new Promise((resolve3) => {
@@ -1846,16 +2066,16 @@ function registerToolsCommand(program2) {
1846
2066
  { "x-tenant-slug": opts.tenant }
1847
2067
  );
1848
2068
  if (!rows || rows.length === 0) {
1849
- console.log(chalk8.dim(" No built-in tools configured."));
1850
- console.log(chalk8.dim(" Try: thinkwork tools web-search set --tenant <slug> -s <stage>"));
2069
+ console.log(chalk9.dim(" No built-in tools configured."));
2070
+ console.log(chalk9.dim(" Try: thinkwork tools web-search set --tenant <slug> -s <stage>"));
1851
2071
  return;
1852
2072
  }
1853
2073
  console.log("");
1854
2074
  for (const r of rows) {
1855
- const status = r.enabled ? chalk8.green("enabled") : chalk8.dim("disabled");
1856
- const key = r.hasSecret ? chalk8.green("yes") : chalk8.red("no");
1857
- const provider = r.provider ?? chalk8.dim("\u2014");
1858
- console.log(` ${chalk8.bold(r.toolSlug)} ${status}`);
2075
+ const status = r.enabled ? chalk9.green("enabled") : chalk9.dim("disabled");
2076
+ const key = r.hasSecret ? chalk9.green("yes") : chalk9.red("no");
2077
+ const provider = r.provider ?? chalk9.dim("\u2014");
2078
+ console.log(` ${chalk9.bold(r.toolSlug)} ${status}`);
1859
2079
  console.log(` Provider: ${provider}`);
1860
2080
  console.log(` Has key: ${key}`);
1861
2081
  if (r.lastTestedAt) {
@@ -1988,7 +2208,7 @@ function registerToolsCommand(program2) {
1988
2208
  // src/commands/update.ts
1989
2209
  import { execSync as execSync8 } from "child_process";
1990
2210
  import { realpathSync } from "fs";
1991
- import chalk9 from "chalk";
2211
+ import chalk10 from "chalk";
1992
2212
  function getLatestVersion() {
1993
2213
  try {
1994
2214
  return execSync8("npm view thinkwork-cli version", {
@@ -2030,28 +2250,28 @@ function compareVersions(a, b) {
2030
2250
  function registerUpdateCommand(program2) {
2031
2251
  program2.command("update").description("Check for and install CLI updates").option("--check", "Only check for updates, don't install").action(async (opts) => {
2032
2252
  printHeader("update", "", null);
2033
- console.log(` Current version: ${chalk9.bold(VERSION)}`);
2253
+ console.log(` Current version: ${chalk10.bold(VERSION)}`);
2034
2254
  const latest = getLatestVersion();
2035
2255
  if (!latest) {
2036
- console.log(chalk9.yellow(" Could not check npm registry for updates."));
2256
+ console.log(chalk10.yellow(" Could not check npm registry for updates."));
2037
2257
  return;
2038
2258
  }
2039
- console.log(` Latest version: ${chalk9.bold(latest)}`);
2259
+ console.log(` Latest version: ${chalk10.bold(latest)}`);
2040
2260
  console.log("");
2041
2261
  const cmp = compareVersions(VERSION, latest);
2042
2262
  if (cmp >= 0) {
2043
- console.log(chalk9.green(" \u2713 You're on the latest version."));
2263
+ console.log(chalk10.green(" \u2713 You're on the latest version."));
2044
2264
  console.log("");
2045
2265
  return;
2046
2266
  }
2047
- console.log(chalk9.cyan(` Update available: ${VERSION} \u2192 ${latest}`));
2267
+ console.log(chalk10.cyan(` Update available: ${VERSION} \u2192 ${latest}`));
2048
2268
  console.log("");
2049
2269
  if (opts.check) {
2050
2270
  const method2 = detectInstallMethod();
2051
2271
  if (method2 === "homebrew") {
2052
- console.log(` Run: ${chalk9.cyan("brew upgrade thinkwork-ai/tap/thinkwork")}`);
2272
+ console.log(` Run: ${chalk10.cyan("brew upgrade thinkwork-ai/tap/thinkwork")}`);
2053
2273
  } else {
2054
- console.log(` Run: ${chalk9.cyan(`npm install -g thinkwork-cli@${latest}`)}`);
2274
+ console.log(` Run: ${chalk10.cyan(`npm install -g thinkwork-cli@${latest}`)}`);
2055
2275
  }
2056
2276
  console.log("");
2057
2277
  return;
@@ -2059,16 +2279,16 @@ function registerUpdateCommand(program2) {
2059
2279
  const method = detectInstallMethod();
2060
2280
  const cmd = method === "homebrew" ? "brew upgrade thinkwork-ai/tap/thinkwork" : `npm install -g thinkwork-cli@${latest}`;
2061
2281
  console.log(` Installing via ${method}...`);
2062
- console.log(chalk9.dim(` $ ${cmd}`));
2282
+ console.log(chalk10.dim(` $ ${cmd}`));
2063
2283
  console.log("");
2064
2284
  try {
2065
2285
  execSync8(cmd, { stdio: "inherit", timeout: 12e4 });
2066
2286
  console.log("");
2067
- console.log(chalk9.green(` \u2713 Upgraded to thinkwork-cli@${latest}`));
2287
+ console.log(chalk10.green(` \u2713 Upgraded to thinkwork-cli@${latest}`));
2068
2288
  } catch {
2069
2289
  console.log("");
2070
- console.log(chalk9.red(` Failed to upgrade. Try manually:`));
2071
- console.log(` ${chalk9.cyan(cmd)}`);
2290
+ console.log(chalk10.red(` Failed to upgrade. Try manually:`));
2291
+ console.log(` ${chalk10.cyan(cmd)}`);
2072
2292
  }
2073
2293
  console.log("");
2074
2294
  });
@@ -2255,10 +2475,14 @@ program.name("thinkwork").description(
2255
2475
  "AWS profile to use (sets AWS_PROFILE for Terraform and AWS CLI)"
2256
2476
  );
2257
2477
  program.hook("preAction", (_thisCommand, actionCommand) => {
2258
- const profile = actionCommand.opts().profile ?? program.opts().profile;
2259
- if (profile) {
2260
- process.env.AWS_PROFILE = profile;
2478
+ const explicit = actionCommand.opts().profile ?? program.opts().profile;
2479
+ if (explicit) {
2480
+ process.env.AWS_PROFILE = explicit;
2481
+ return;
2261
2482
  }
2483
+ if (process.env.AWS_PROFILE) return;
2484
+ const fallback = loadCliConfig().defaultProfile;
2485
+ if (fallback) process.env.AWS_PROFILE = fallback;
2262
2486
  });
2263
2487
  registerLoginCommand(program);
2264
2488
  registerInitCommand(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkwork-cli",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Thinkwork CLI — deploy, manage, and interact with your Thinkwork stack",
5
5
  "license": "MIT",
6
6
  "type": "module",