thinkwork-cli 0.5.1 → 0.5.3

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 CHANGED
@@ -534,24 +534,28 @@ function resolveTerraformDir(stage) {
534
534
  }
535
535
 
536
536
  // src/commands/config.ts
537
- var VALID_MEMORY_ENGINES = ["managed", "hindsight"];
538
537
  function readTfVar(tfvarsPath, key) {
539
538
  if (!existsSync3(tfvarsPath)) return null;
540
539
  const content = readFileSync2(tfvarsPath, "utf-8");
541
- const match = content.match(new RegExp(`^${key}\\s*=\\s*"([^"]*)"`, "m"));
542
- return match ? match[1] : null;
540
+ const quoted = content.match(new RegExp(`^${key}\\s*=\\s*"([^"]*)"`, "m"));
541
+ if (quoted) return quoted[1];
542
+ const bare = content.match(new RegExp(`^${key}\\s*=\\s*([^\\s#]+)`, "m"));
543
+ return bare ? bare[1] : null;
543
544
  }
545
+ var BARE_KEYS = /* @__PURE__ */ new Set(["enable_hindsight"]);
544
546
  function setTfVar(tfvarsPath, key, value) {
545
547
  if (!existsSync3(tfvarsPath)) {
546
548
  throw new Error(`terraform.tfvars not found at ${tfvarsPath}`);
547
549
  }
548
550
  let content = readFileSync2(tfvarsPath, "utf-8");
549
- const regex = new RegExp(`^(${key}\\s*=\\s*)"[^"]*"`, "m");
550
- if (regex.test(content)) {
551
- content = content.replace(regex, `$1"${value}"`);
551
+ const bare = BARE_KEYS.has(key);
552
+ const newLine = bare ? `${key} = ${value}` : `${key} = "${value}"`;
553
+ const existingRegex = new RegExp(`^${key}\\s*=\\s*(?:"[^"]*"|[^\\s#]+)`, "m");
554
+ if (existingRegex.test(content)) {
555
+ content = content.replace(existingRegex, newLine);
552
556
  } else {
553
557
  content += `
554
- ${key} = "${value}"
558
+ ${newLine}
555
559
  `;
556
560
  }
557
561
  writeFileSync2(tfvarsPath, content);
@@ -581,7 +585,7 @@ function registerConfigCommand(program2) {
581
585
  console.log(` ${chalk3.bold("Region:")} ${env.region}`);
582
586
  console.log(` ${chalk3.bold("Account:")} ${env.accountId}`);
583
587
  console.log(` ${chalk3.bold("Database:")} ${env.databaseEngine}`);
584
- console.log(` ${chalk3.bold("Memory:")} ${env.memoryEngine}`);
588
+ console.log(` ${chalk3.bold("Memory:")} managed (always on)${env.enableHindsight ? " + hindsight" : ""}`);
585
589
  console.log(` ${chalk3.bold("Terraform dir:")} ${env.terraformDir}`);
586
590
  console.log(` ${chalk3.bold("Created:")} ${env.createdAt}`);
587
591
  console.log(` ${chalk3.bold("Updated:")} ${env.updatedAt}`);
@@ -622,7 +626,7 @@ function registerConfigCommand(program2) {
622
626
  console.log(chalk3.bold(" Environments"));
623
627
  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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
624
628
  for (const env of envs) {
625
- const memBadge = env.memoryEngine === "hindsight" ? chalk3.magenta("hindsight") : chalk3.dim("managed");
629
+ const memBadge = env.enableHindsight ? chalk3.magenta("managed+hindsight") : chalk3.dim("managed");
626
630
  const dbBadge = env.databaseEngine === "rds-postgres" ? chalk3.yellow("rds") : chalk3.dim("aurora");
627
631
  console.log(
628
632
  ` ${chalk3.bold.cyan(env.stage.padEnd(16))}${env.region.padEnd(14)}${env.accountId.padEnd(16)}${dbBadge.padEnd(20)}${memBadge}`
@@ -634,7 +638,7 @@ function registerConfigCommand(program2) {
634
638
  console.log(` Show details: ${chalk3.cyan("thinkwork config list -s <stage>")}`);
635
639
  console.log("");
636
640
  });
637
- config.command("get <key>").description("Get a configuration value (e.g. memory-engine)").requiredOption("-s, --stage <name>", "Deployment stage").action((key, opts) => {
641
+ config.command("get <key>").description("Get a configuration value (e.g. enable-hindsight)").requiredOption("-s, --stage <name>", "Deployment stage").action((key, opts) => {
638
642
  const stageCheck = validateStage(opts.stage);
639
643
  if (!stageCheck.valid) {
640
644
  printError(stageCheck.error);
@@ -655,17 +659,27 @@ function registerConfigCommand(program2) {
655
659
  printError(stageCheck.error);
656
660
  process.exit(1);
657
661
  }
658
- const tfKey = key.replace(/-/g, "_");
659
- if (tfKey === "memory_engine" && !VALID_MEMORY_ENGINES.includes(value)) {
660
- printError(`Invalid memory engine "${value}". Must be: ${VALID_MEMORY_ENGINES.join(", ")}`);
662
+ let tfKey = key.replace(/-/g, "_");
663
+ let tfValue = value;
664
+ if (tfKey === "memory_engine") {
665
+ if (value !== "managed" && value !== "hindsight") {
666
+ printError(`Invalid memory engine "${value}". Must be 'managed' or 'hindsight'. Note: memory_engine is deprecated \u2014 use enable_hindsight instead.`);
667
+ process.exit(1);
668
+ }
669
+ printWarning("memory_engine is deprecated \u2014 translating to enable_hindsight. Managed memory is always on.");
670
+ tfKey = "enable_hindsight";
671
+ tfValue = value === "hindsight" ? "true" : "false";
672
+ }
673
+ if (tfKey === "enable_hindsight" && tfValue !== "true" && tfValue !== "false") {
674
+ printError(`Invalid enable_hindsight value "${tfValue}". Must be 'true' or 'false'.`);
661
675
  process.exit(1);
662
676
  }
663
677
  const identity = getAwsIdentity();
664
678
  printHeader("config set", opts.stage, identity);
665
679
  const tfvarsPath = resolveTfvarsPath(opts.stage);
666
680
  const oldValue = readTfVar(tfvarsPath, tfKey);
667
- setTfVar(tfvarsPath, tfKey, value);
668
- console.log(` ${key}: ${oldValue ?? "(unset)"} \u2192 ${value}`);
681
+ setTfVar(tfvarsPath, tfKey, tfValue);
682
+ console.log(` ${tfKey}: ${oldValue ?? "(unset)"} \u2192 ${tfValue}`);
669
683
  if (opts.apply) {
670
684
  const tfDir = resolveTerraformDir(opts.stage);
671
685
  if (!tfDir) {
@@ -685,9 +699,9 @@ function registerConfigCommand(program2) {
685
699
  printError(`Deploy failed (exit ${code})`);
686
700
  process.exit(code);
687
701
  }
688
- printSuccess(`Configuration applied: ${key} = ${value}`);
702
+ printSuccess(`Configuration applied: ${tfKey} = ${tfValue}`);
689
703
  } else {
690
- printSuccess(`Configuration updated: ${key} = ${value}`);
704
+ printSuccess(`Configuration updated: ${tfKey} = ${tfValue}`);
691
705
  printWarning("Run with --apply to deploy the change, or run 'thinkwork deploy' separately.");
692
706
  }
693
707
  });
@@ -738,8 +752,8 @@ function registerBootstrapCommand(program2) {
738
752
  bucket = await getTerraformOutput(cwd, "bucket_name");
739
753
  dbEndpoint = await getTerraformOutput(cwd, "db_cluster_endpoint");
740
754
  const secretArn = await getTerraformOutput(cwd, "db_secret_arn");
741
- const { execSync: execSync9 } = await import("child_process");
742
- const secretJson = execSync9(
755
+ const { execSync: execSync10 } = await import("child_process");
756
+ const secretJson = execSync10(
743
757
  `aws secretsmanager get-secret-value --secret-id "${secretArn}" --query SecretString --output text`,
744
758
  { encoding: "utf-8" }
745
759
  ).trim();
@@ -887,10 +901,10 @@ async function ensurePrerequisites() {
887
901
  }
888
902
 
889
903
  // src/commands/login.ts
890
- function ask(prompt) {
904
+ function ask(prompt2) {
891
905
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
892
906
  return new Promise((resolve3) => {
893
- rl.question(prompt, (answer) => {
907
+ rl.question(prompt2, (answer) => {
894
908
  rl.close();
895
909
  resolve3(answer.trim());
896
910
  });
@@ -980,19 +994,19 @@ import { fileURLToPath } from "url";
980
994
  import { createInterface as createInterface3 } from "readline";
981
995
  import chalk5 from "chalk";
982
996
  var __dirname = dirname(fileURLToPath(import.meta.url));
983
- function ask2(prompt, defaultVal = "") {
997
+ function ask2(prompt2, defaultVal = "") {
984
998
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
985
999
  const suffix = defaultVal ? chalk5.dim(` [${defaultVal}]`) : "";
986
1000
  return new Promise((resolve3) => {
987
- rl.question(` ${prompt}${suffix}: `, (answer) => {
1001
+ rl.question(` ${prompt2}${suffix}: `, (answer) => {
988
1002
  rl.close();
989
1003
  resolve3(answer.trim() || defaultVal);
990
1004
  });
991
1005
  });
992
1006
  }
993
- function choose(prompt, options, defaultVal) {
1007
+ function choose(prompt2, options, defaultVal) {
994
1008
  const optStr = options.map((o) => o === defaultVal ? chalk5.bold(o) : chalk5.dim(o)).join(" / ");
995
- return ask2(`${prompt} (${optStr})`, defaultVal);
1009
+ return ask2(`${prompt2} (${optStr})`, defaultVal);
996
1010
  }
997
1011
  function generateSecret(length = 32) {
998
1012
  const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
@@ -1003,9 +1017,9 @@ function generateSecret(length = 32) {
1003
1017
  return result;
1004
1018
  }
1005
1019
  function findBundledTerraform() {
1006
- const bundled = resolve2(__dirname, "..", "terraform");
1020
+ const bundled = resolve2(__dirname, "terraform");
1007
1021
  if (existsSync5(join3(bundled, "modules"))) return bundled;
1008
- const repoTf = resolve2(__dirname, "..", "..", "..", "..", "terraform");
1022
+ const repoTf = resolve2(__dirname, "..", "..", "..", "terraform");
1009
1023
  if (existsSync5(join3(repoTf, "modules"))) return repoTf;
1010
1024
  throw new Error(
1011
1025
  "Terraform modules not found. The CLI package may be incomplete.\nTry reinstalling: npm install -g thinkwork-cli@latest"
@@ -1027,7 +1041,9 @@ function buildTfvars(config) {
1027
1041
  `db_password = "${config.db_password}"`,
1028
1042
  ``,
1029
1043
  `# \u2500\u2500 Memory \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`,
1030
- `memory_engine = "${config.memory_engine}"`,
1044
+ `# AgentCore managed memory is always on (automatic retention).`,
1045
+ `# Hindsight is an optional add-on for semantic + graph retrieval.`,
1046
+ `enable_hindsight = ${config.enable_hindsight === "true"}`,
1031
1047
  ``,
1032
1048
  `# \u2500\u2500 Auth \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`,
1033
1049
  `api_auth_secret = "${config.api_auth_secret}"`
@@ -1094,7 +1110,7 @@ function registerInitCommand(program2) {
1094
1110
  if (opts.defaults) {
1095
1111
  config.region = identity.region !== "unknown" ? identity.region : "us-east-1";
1096
1112
  config.database_engine = "aurora-serverless";
1097
- config.memory_engine = "managed";
1113
+ config.enable_hindsight = "false";
1098
1114
  config.google_oauth_client_id = "";
1099
1115
  config.google_oauth_client_secret = "";
1100
1116
  config.admin_url = "http://localhost:5174";
@@ -1108,9 +1124,11 @@ function registerInitCommand(program2) {
1108
1124
  config.database_engine = await choose("Database engine", ["aurora-serverless", "rds-postgres"], "aurora-serverless");
1109
1125
  console.log("");
1110
1126
  console.log(chalk5.dim(" \u2500\u2500 Memory \u2500\u2500"));
1111
- console.log(chalk5.dim(" managed = Built-in AgentCore memory (remember/recall/forget)"));
1112
- console.log(chalk5.dim(" hindsight = ECS Fargate service with semantic + graph retrieval"));
1113
- config.memory_engine = await choose("Memory engine", ["managed", "hindsight"], "managed");
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)."));
1130
+ const hindsightAnswer = await ask2("Enable Hindsight long-term memory add-on? (y/N)", "N");
1131
+ config.enable_hindsight = hindsightAnswer.toLowerCase() === "y" ? "true" : "false";
1114
1132
  console.log("");
1115
1133
  console.log(chalk5.dim(" \u2500\u2500 Auth \u2500\u2500"));
1116
1134
  const useGoogle = await ask2("Enable Google OAuth login? (y/N)", "N");
@@ -1184,21 +1202,86 @@ provider "aws" {
1184
1202
  region = var.region
1185
1203
  }
1186
1204
 
1187
- variable "stage" { type = string }
1188
- variable "region" { type = string; default = "us-east-1" }
1189
- variable "account_id" { type = string }
1190
- variable "db_password" { type = string; sensitive = true }
1191
- variable "database_engine" { type = string; default = "aurora-serverless" }
1192
- variable "memory_engine" { type = string; default = "managed" }
1193
- variable "google_oauth_client_id" { type = string; default = "" }
1194
- variable "google_oauth_client_secret" { type = string; sensitive = true; default = "" }
1195
- variable "pre_signup_lambda_zip" { type = string; default = "" }
1196
- variable "lambda_zips_dir" { type = string; default = "" }
1197
- variable "api_auth_secret" { type = string; sensitive = true; default = "" }
1198
- variable "admin_callback_urls" { type = list(string); default = ["http://localhost:5174", "http://localhost:5174/auth/callback"] }
1199
- variable "admin_logout_urls" { type = list(string); default = ["http://localhost:5174"] }
1200
- variable "mobile_callback_urls" { type = list(string); default = ["exp://localhost:8081", "thinkwork://", "thinkwork://auth/callback"] }
1201
- variable "mobile_logout_urls" { type = list(string); default = ["exp://localhost:8081", "thinkwork://"] }
1205
+ variable "stage" {
1206
+ type = string
1207
+ }
1208
+
1209
+ variable "region" {
1210
+ type = string
1211
+ default = "us-east-1"
1212
+ }
1213
+
1214
+ variable "account_id" {
1215
+ type = string
1216
+ }
1217
+
1218
+ variable "db_password" {
1219
+ type = string
1220
+ sensitive = true
1221
+ }
1222
+
1223
+ variable "database_engine" {
1224
+ type = string
1225
+ default = "aurora-serverless"
1226
+ }
1227
+
1228
+ variable "enable_hindsight" {
1229
+ type = bool
1230
+ default = false
1231
+ }
1232
+
1233
+ variable "agentcore_memory_id" {
1234
+ type = string
1235
+ default = ""
1236
+ description = "Optional pre-existing Bedrock AgentCore Memory resource ID. Leave empty to auto-provision."
1237
+ }
1238
+
1239
+ variable "google_oauth_client_id" {
1240
+ type = string
1241
+ default = ""
1242
+ }
1243
+
1244
+ variable "google_oauth_client_secret" {
1245
+ type = string
1246
+ sensitive = true
1247
+ default = ""
1248
+ }
1249
+
1250
+ variable "pre_signup_lambda_zip" {
1251
+ type = string
1252
+ default = ""
1253
+ }
1254
+
1255
+ variable "lambda_zips_dir" {
1256
+ type = string
1257
+ default = ""
1258
+ }
1259
+
1260
+ variable "api_auth_secret" {
1261
+ type = string
1262
+ sensitive = true
1263
+ default = ""
1264
+ }
1265
+
1266
+ variable "admin_callback_urls" {
1267
+ type = list(string)
1268
+ default = ["http://localhost:5174", "http://localhost:5174/auth/callback"]
1269
+ }
1270
+
1271
+ variable "admin_logout_urls" {
1272
+ type = list(string)
1273
+ default = ["http://localhost:5174"]
1274
+ }
1275
+
1276
+ variable "mobile_callback_urls" {
1277
+ type = list(string)
1278
+ default = ["exp://localhost:8081", "thinkwork://", "thinkwork://auth/callback"]
1279
+ }
1280
+
1281
+ variable "mobile_logout_urls" {
1282
+ type = list(string)
1283
+ default = ["exp://localhost:8081", "thinkwork://"]
1284
+ }
1202
1285
 
1203
1286
  module "thinkwork" {
1204
1287
  source = "./modules/thinkwork"
@@ -1209,7 +1292,8 @@ module "thinkwork" {
1209
1292
 
1210
1293
  db_password = var.db_password
1211
1294
  database_engine = var.database_engine
1212
- memory_engine = var.memory_engine
1295
+ enable_hindsight = var.enable_hindsight
1296
+ agentcore_memory_id = var.agentcore_memory_id
1213
1297
  google_oauth_client_id = var.google_oauth_client_id
1214
1298
  google_oauth_client_secret = var.google_oauth_client_secret
1215
1299
  pre_signup_lambda_zip = var.pre_signup_lambda_zip
@@ -1221,16 +1305,50 @@ module "thinkwork" {
1221
1305
  mobile_logout_urls = var.mobile_logout_urls
1222
1306
  }
1223
1307
 
1224
- output "api_endpoint" { value = module.thinkwork.api_endpoint }
1225
- output "user_pool_id" { value = module.thinkwork.user_pool_id }
1226
- output "admin_client_id" { value = module.thinkwork.admin_client_id }
1227
- output "mobile_client_id" { value = module.thinkwork.mobile_client_id }
1228
- output "bucket_name" { value = module.thinkwork.bucket_name }
1229
- output "db_cluster_endpoint" { value = module.thinkwork.db_cluster_endpoint }
1230
- output "db_secret_arn" { value = module.thinkwork.db_secret_arn; sensitive = true }
1231
- output "ecr_repository_url" { value = module.thinkwork.ecr_repository_url }
1232
- output "memory_engine" { value = module.thinkwork.memory_engine }
1233
- output "hindsight_endpoint" { value = module.thinkwork.hindsight_endpoint }
1308
+ output "api_endpoint" {
1309
+ value = module.thinkwork.api_endpoint
1310
+ }
1311
+
1312
+ output "user_pool_id" {
1313
+ value = module.thinkwork.user_pool_id
1314
+ }
1315
+
1316
+ output "admin_client_id" {
1317
+ value = module.thinkwork.admin_client_id
1318
+ }
1319
+
1320
+ output "mobile_client_id" {
1321
+ value = module.thinkwork.mobile_client_id
1322
+ }
1323
+
1324
+ output "bucket_name" {
1325
+ value = module.thinkwork.bucket_name
1326
+ }
1327
+
1328
+ output "db_cluster_endpoint" {
1329
+ value = module.thinkwork.db_cluster_endpoint
1330
+ }
1331
+
1332
+ output "db_secret_arn" {
1333
+ value = module.thinkwork.db_secret_arn
1334
+ sensitive = true
1335
+ }
1336
+
1337
+ output "ecr_repository_url" {
1338
+ value = module.thinkwork.ecr_repository_url
1339
+ }
1340
+
1341
+ output "hindsight_enabled" {
1342
+ value = module.thinkwork.hindsight_enabled
1343
+ }
1344
+
1345
+ output "hindsight_endpoint" {
1346
+ value = module.thinkwork.hindsight_endpoint
1347
+ }
1348
+
1349
+ output "agentcore_memory_id" {
1350
+ value = module.thinkwork.agentcore_memory_id
1351
+ }
1234
1352
  `);
1235
1353
  }
1236
1354
  console.log(` Wrote ${chalk5.cyan(tfDir + "/")}`);
@@ -1240,7 +1358,7 @@ output "hindsight_endpoint" { value = module.thinkwork.hindsight_endpoint }
1240
1358
  console.log(` ${chalk5.bold("Region:")} ${config.region}`);
1241
1359
  console.log(` ${chalk5.bold("Account:")} ${config.account_id}`);
1242
1360
  console.log(` ${chalk5.bold("Database:")} ${config.database_engine}`);
1243
- console.log(` ${chalk5.bold("Memory:")} ${config.memory_engine}`);
1361
+ console.log(` ${chalk5.bold("Memory:")} managed (always on)${config.enable_hindsight === "true" ? " + hindsight" : ""}`);
1244
1362
  console.log(` ${chalk5.bold("Google OAuth:")} ${config.google_oauth_client_id ? "enabled" : "disabled"}`);
1245
1363
  console.log(` ${chalk5.bold("Directory:")} ${tfDir}`);
1246
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"));
@@ -1258,7 +1376,7 @@ output "hindsight_endpoint" { value = module.thinkwork.hindsight_endpoint }
1258
1376
  accountId: config.account_id,
1259
1377
  terraformDir: tfDir,
1260
1378
  databaseEngine: config.database_engine,
1261
- memoryEngine: config.memory_engine,
1379
+ enableHindsight: config.enable_hindsight === "true",
1262
1380
  createdAt: now,
1263
1381
  updatedAt: now
1264
1382
  });
@@ -1332,7 +1450,7 @@ function discoverAwsStages(region) {
1332
1450
  `ecs describe-services --cluster thinkwork-${stage}-cluster --services thinkwork-${stage}-hindsight --region ${region} --query "services[0].runningCount" --output text 2>/dev/null`
1333
1451
  );
1334
1452
  if (ecsRaw && ecsRaw !== "None" && ecsRaw !== "0") {
1335
- info.memoryEngine = "hindsight";
1453
+ info.hindsightEnabled = true;
1336
1454
  const albRaw = runAws(
1337
1455
  `elbv2 describe-load-balancers --region ${region} --query "LoadBalancers[?contains(LoadBalancerName, 'tw-${stage}-hindsight')].DNSName|[0]" --output text`
1338
1456
  );
@@ -1346,7 +1464,7 @@ function discoverAwsStages(region) {
1346
1464
  }
1347
1465
  }
1348
1466
  } else {
1349
- info.memoryEngine = "managed";
1467
+ info.hindsightEnabled = false;
1350
1468
  }
1351
1469
  const dbRaw = runAws(
1352
1470
  `rds describe-db-clusters --region ${region} --query "DBClusters[?starts_with(DBClusterIdentifier, 'thinkwork-${stage}')].Endpoint|[0]" --output text`
@@ -1380,8 +1498,9 @@ function printStageDetail(info) {
1380
1498
  console.log(` ${chalk6.bold("Account:")} ${info.accountId}`);
1381
1499
  console.log(` ${chalk6.bold("Lambda fns:")} ${info.lambdaCount || "\u2014"}`);
1382
1500
  console.log(` ${chalk6.bold("AgentCore:")} ${info.agentcoreStatus || "unknown"}`);
1383
- console.log(` ${chalk6.bold("Memory:")} ${info.memoryEngine || "unknown"}`);
1384
- if (info.hindsightHealth) console.log(` ${chalk6.bold("Hindsight:")} ${info.hindsightHealth}`);
1501
+ console.log(` ${chalk6.bold("Memory:")} managed (always on)`);
1502
+ const hindsightLabel = info.hindsightEnabled === void 0 ? "unknown" : info.hindsightEnabled ? info.hindsightHealth || "running" : "disabled";
1503
+ console.log(` ${chalk6.bold("Hindsight:")} ${hindsightLabel}`);
1385
1504
  if (info.bucketName) console.log(` ${chalk6.bold("S3 bucket:")} ${info.bucketName}`);
1386
1505
  if (info.dbEndpoint) console.log(` ${chalk6.bold("Database:")} ${info.dbEndpoint}`);
1387
1506
  if (info.ecrUrl) console.log(` ${chalk6.bold("ECR:")} ${info.ecrUrl}`);
@@ -1671,12 +1790,246 @@ function registerMcpCommand(program2) {
1671
1790
  });
1672
1791
  }
1673
1792
 
1674
- // src/commands/upgrade.ts
1793
+ // src/commands/tools.ts
1794
+ import { readFileSync as readFileSync4, existsSync as existsSync7 } from "fs";
1675
1795
  import { execSync as execSync8 } from "child_process";
1796
+ import { createInterface as createInterface4 } from "readline";
1676
1797
  import chalk8 from "chalk";
1798
+ function readTfVar3(tfvarsPath, key) {
1799
+ if (!existsSync7(tfvarsPath)) return null;
1800
+ const content = readFileSync4(tfvarsPath, "utf-8");
1801
+ const match = content.match(new RegExp(`^${key}\\s*=\\s*"([^"]*)"`, "m"));
1802
+ return match ? match[1] : null;
1803
+ }
1804
+ function resolveTfvarsPath3(stage) {
1805
+ const tfDir = resolveTerraformDir(stage);
1806
+ if (tfDir) {
1807
+ const direct = `${tfDir}/terraform.tfvars`;
1808
+ if (existsSync7(direct)) return direct;
1809
+ }
1810
+ const terraformDir = process.env.THINKWORK_TERRAFORM_DIR || process.cwd();
1811
+ const cwd = resolveTierDir(terraformDir, stage, "app");
1812
+ return `${cwd}/terraform.tfvars`;
1813
+ }
1814
+ function getApiEndpoint2(stage, region) {
1815
+ try {
1816
+ const raw = execSync8(
1817
+ `aws apigatewayv2 get-apis --region ${region} --query "Items[?Name=='thinkwork-${stage}-api'].ApiEndpoint|[0]" --output text`,
1818
+ { encoding: "utf-8", timeout: 15e3, stdio: ["pipe", "pipe", "pipe"] }
1819
+ ).trim();
1820
+ return raw && raw !== "None" ? raw : null;
1821
+ } catch {
1822
+ return null;
1823
+ }
1824
+ }
1825
+ async function apiFetch2(apiUrl, authSecret, path2, options = {}, extraHeaders = {}) {
1826
+ const res = await fetch(`${apiUrl}${path2}`, {
1827
+ ...options,
1828
+ headers: {
1829
+ "Content-Type": "application/json",
1830
+ Authorization: `Bearer ${authSecret}`,
1831
+ ...extraHeaders,
1832
+ ...options.headers
1833
+ }
1834
+ });
1835
+ if (!res.ok) {
1836
+ const body = await res.json().catch(() => ({}));
1837
+ throw new Error(body.error || `HTTP ${res.status}`);
1838
+ }
1839
+ return res.json();
1840
+ }
1841
+ function resolveApiConfig2(stage) {
1842
+ const tfvarsPath = resolveTfvarsPath3(stage);
1843
+ const authSecret = readTfVar3(tfvarsPath, "api_auth_secret");
1844
+ if (!authSecret) {
1845
+ printError(`Cannot read api_auth_secret from ${tfvarsPath}`);
1846
+ return null;
1847
+ }
1848
+ const region = readTfVar3(tfvarsPath, "region") || "us-east-1";
1849
+ const apiUrl = getApiEndpoint2(stage, region);
1850
+ if (!apiUrl) {
1851
+ printError(`Cannot discover API endpoint for stage "${stage}". Is the stack deployed?`);
1852
+ return null;
1853
+ }
1854
+ return { apiUrl, authSecret };
1855
+ }
1856
+ function prompt(question) {
1857
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
1858
+ return new Promise((resolve3) => {
1859
+ rl.question(question, (answer) => {
1860
+ rl.close();
1861
+ resolve3(answer.trim());
1862
+ });
1863
+ });
1864
+ }
1865
+ var TOOL_PROVIDERS = {
1866
+ "web-search": ["exa", "serpapi"]
1867
+ };
1868
+ function registerToolsCommand(program2) {
1869
+ const tools = program2.command("tools").description("Configure built-in agent tools (web_search, \u2026) for your tenant");
1870
+ tools.command("list").description("List configured built-in tools").requiredOption("-s, --stage <name>", "Deployment stage").requiredOption("--tenant <slug>", "Tenant slug").action(async (opts) => {
1871
+ const check = validateStage(opts.stage);
1872
+ if (!check.valid) {
1873
+ printError(check.error);
1874
+ process.exit(1);
1875
+ }
1876
+ const api = resolveApiConfig2(opts.stage);
1877
+ if (!api) process.exit(1);
1878
+ printHeader("tools list", opts.stage);
1879
+ try {
1880
+ const { tools: rows } = await apiFetch2(
1881
+ api.apiUrl,
1882
+ api.authSecret,
1883
+ "/api/skills/builtin-tools",
1884
+ {},
1885
+ { "x-tenant-slug": opts.tenant }
1886
+ );
1887
+ if (!rows || rows.length === 0) {
1888
+ console.log(chalk8.dim(" No built-in tools configured."));
1889
+ console.log(chalk8.dim(" Try: thinkwork tools web-search set --tenant <slug> -s <stage>"));
1890
+ return;
1891
+ }
1892
+ console.log("");
1893
+ for (const r of rows) {
1894
+ const status = r.enabled ? chalk8.green("enabled") : chalk8.dim("disabled");
1895
+ const key = r.hasSecret ? chalk8.green("yes") : chalk8.red("no");
1896
+ const provider = r.provider ?? chalk8.dim("\u2014");
1897
+ console.log(` ${chalk8.bold(r.toolSlug)} ${status}`);
1898
+ console.log(` Provider: ${provider}`);
1899
+ console.log(` Has key: ${key}`);
1900
+ if (r.lastTestedAt) {
1901
+ console.log(` Tested: ${new Date(r.lastTestedAt).toLocaleString()}`);
1902
+ }
1903
+ console.log("");
1904
+ }
1905
+ } catch (err) {
1906
+ printError(err.message);
1907
+ process.exit(1);
1908
+ }
1909
+ });
1910
+ const webSearch = tools.command("web-search").description("Configure the web_search built-in tool");
1911
+ webSearch.command("set").description("Set or update web_search provider + API key (enables the tool)").requiredOption("-s, --stage <name>", "Deployment stage").requiredOption("--tenant <slug>", "Tenant slug").option("--provider <name>", `Provider (${TOOL_PROVIDERS["web-search"].join("|")})`).option("--key <key>", "API key").action(async (opts) => {
1912
+ const check = validateStage(opts.stage);
1913
+ if (!check.valid) {
1914
+ printError(check.error);
1915
+ process.exit(1);
1916
+ }
1917
+ const api = resolveApiConfig2(opts.stage);
1918
+ if (!api) process.exit(1);
1919
+ let provider = opts.provider;
1920
+ if (!provider) {
1921
+ provider = (await prompt(`Provider [${TOOL_PROVIDERS["web-search"].join("/")}]: `)).toLowerCase();
1922
+ }
1923
+ if (!TOOL_PROVIDERS["web-search"].includes(provider)) {
1924
+ printError(`provider must be one of: ${TOOL_PROVIDERS["web-search"].join(", ")}`);
1925
+ process.exit(1);
1926
+ }
1927
+ let apiKey = opts.key;
1928
+ if (!apiKey) {
1929
+ apiKey = await prompt(`${provider} API key: `);
1930
+ }
1931
+ if (!apiKey) {
1932
+ printError("API key is required");
1933
+ process.exit(1);
1934
+ }
1935
+ try {
1936
+ await apiFetch2(
1937
+ api.apiUrl,
1938
+ api.authSecret,
1939
+ "/api/skills/builtin-tools/web-search",
1940
+ {
1941
+ method: "PUT",
1942
+ body: JSON.stringify({ provider, apiKey, enabled: true })
1943
+ },
1944
+ { "x-tenant-slug": opts.tenant }
1945
+ );
1946
+ printSuccess(`web_search configured with provider=${provider}, enabled=true`);
1947
+ printWarning("Run `thinkwork tools web-search test` to verify connectivity.");
1948
+ } catch (err) {
1949
+ printError(err.message);
1950
+ process.exit(1);
1951
+ }
1952
+ });
1953
+ webSearch.command("test").description("Test the stored web_search provider + key against the provider API").requiredOption("-s, --stage <name>", "Deployment stage").requiredOption("--tenant <slug>", "Tenant slug").action(async (opts) => {
1954
+ const check = validateStage(opts.stage);
1955
+ if (!check.valid) {
1956
+ printError(check.error);
1957
+ process.exit(1);
1958
+ }
1959
+ const api = resolveApiConfig2(opts.stage);
1960
+ if (!api) process.exit(1);
1961
+ printHeader("tools web-search test", opts.stage);
1962
+ try {
1963
+ const result = await apiFetch2(
1964
+ api.apiUrl,
1965
+ api.authSecret,
1966
+ "/api/skills/builtin-tools/web-search/test",
1967
+ { method: "POST", body: "{}" },
1968
+ { "x-tenant-slug": opts.tenant }
1969
+ );
1970
+ if (result.ok) {
1971
+ printSuccess(`${result.provider}: ${result.resultCount} result(s) returned.`);
1972
+ } else {
1973
+ printError(`Test failed: ${result.error}`);
1974
+ process.exit(1);
1975
+ }
1976
+ } catch (err) {
1977
+ printError(err.message);
1978
+ process.exit(1);
1979
+ }
1980
+ });
1981
+ webSearch.command("disable").description("Disable web_search without deleting the stored key").requiredOption("-s, --stage <name>", "Deployment stage").requiredOption("--tenant <slug>", "Tenant slug").action(async (opts) => {
1982
+ const check = validateStage(opts.stage);
1983
+ if (!check.valid) {
1984
+ printError(check.error);
1985
+ process.exit(1);
1986
+ }
1987
+ const api = resolveApiConfig2(opts.stage);
1988
+ if (!api) process.exit(1);
1989
+ try {
1990
+ await apiFetch2(
1991
+ api.apiUrl,
1992
+ api.authSecret,
1993
+ "/api/skills/builtin-tools/web-search",
1994
+ { method: "PUT", body: JSON.stringify({ enabled: false }) },
1995
+ { "x-tenant-slug": opts.tenant }
1996
+ );
1997
+ printSuccess("web_search disabled.");
1998
+ } catch (err) {
1999
+ printError(err.message);
2000
+ process.exit(1);
2001
+ }
2002
+ });
2003
+ webSearch.command("clear").description("Remove web_search config entirely (deletes stored key)").requiredOption("-s, --stage <name>", "Deployment stage").requiredOption("--tenant <slug>", "Tenant slug").action(async (opts) => {
2004
+ const check = validateStage(opts.stage);
2005
+ if (!check.valid) {
2006
+ printError(check.error);
2007
+ process.exit(1);
2008
+ }
2009
+ const api = resolveApiConfig2(opts.stage);
2010
+ if (!api) process.exit(1);
2011
+ try {
2012
+ await apiFetch2(
2013
+ api.apiUrl,
2014
+ api.authSecret,
2015
+ "/api/skills/builtin-tools/web-search",
2016
+ { method: "DELETE" },
2017
+ { "x-tenant-slug": opts.tenant }
2018
+ );
2019
+ printSuccess("web_search configuration cleared.");
2020
+ } catch (err) {
2021
+ printError(err.message);
2022
+ process.exit(1);
2023
+ }
2024
+ });
2025
+ }
2026
+
2027
+ // src/commands/update.ts
2028
+ import { execSync as execSync9 } from "child_process";
2029
+ import chalk9 from "chalk";
1677
2030
  function getLatestVersion() {
1678
2031
  try {
1679
- return execSync8("npm view thinkwork-cli version", {
2032
+ return execSync9("npm view thinkwork-cli version", {
1680
2033
  encoding: "utf-8",
1681
2034
  timeout: 1e4,
1682
2035
  stdio: ["pipe", "pipe", "pipe"]
@@ -1687,7 +2040,7 @@ function getLatestVersion() {
1687
2040
  }
1688
2041
  function detectInstallMethod() {
1689
2042
  try {
1690
- const which = execSync8("which thinkwork", {
2043
+ const which = execSync9("which thinkwork", {
1691
2044
  encoding: "utf-8",
1692
2045
  timeout: 5e3,
1693
2046
  stdio: ["pipe", "pipe", "pipe"]
@@ -1708,31 +2061,31 @@ function compareVersions(a, b) {
1708
2061
  }
1709
2062
  return 0;
1710
2063
  }
1711
- function registerUpgradeCommand(program2) {
1712
- program2.command("upgrade").description("Check for and install CLI updates").option("--check", "Only check for updates, don't install").action(async (opts) => {
1713
- printHeader("upgrade", "", null);
1714
- console.log(` Current version: ${chalk8.bold(VERSION)}`);
2064
+ function registerUpdateCommand(program2) {
2065
+ program2.command("update").description("Check for and install CLI updates").option("--check", "Only check for updates, don't install").action(async (opts) => {
2066
+ printHeader("update", "", null);
2067
+ console.log(` Current version: ${chalk9.bold(VERSION)}`);
1715
2068
  const latest = getLatestVersion();
1716
2069
  if (!latest) {
1717
- console.log(chalk8.yellow(" Could not check npm registry for updates."));
2070
+ console.log(chalk9.yellow(" Could not check npm registry for updates."));
1718
2071
  return;
1719
2072
  }
1720
- console.log(` Latest version: ${chalk8.bold(latest)}`);
2073
+ console.log(` Latest version: ${chalk9.bold(latest)}`);
1721
2074
  console.log("");
1722
2075
  const cmp = compareVersions(VERSION, latest);
1723
2076
  if (cmp >= 0) {
1724
- console.log(chalk8.green(" \u2713 You're on the latest version."));
2077
+ console.log(chalk9.green(" \u2713 You're on the latest version."));
1725
2078
  console.log("");
1726
2079
  return;
1727
2080
  }
1728
- console.log(chalk8.cyan(` Update available: ${VERSION} \u2192 ${latest}`));
2081
+ console.log(chalk9.cyan(` Update available: ${VERSION} \u2192 ${latest}`));
1729
2082
  console.log("");
1730
2083
  if (opts.check) {
1731
2084
  const method2 = detectInstallMethod();
1732
2085
  if (method2 === "homebrew") {
1733
- console.log(` Run: ${chalk8.cyan("brew upgrade thinkwork-ai/tap/thinkwork")}`);
2086
+ console.log(` Run: ${chalk9.cyan("brew upgrade thinkwork-ai/tap/thinkwork")}`);
1734
2087
  } else {
1735
- console.log(` Run: ${chalk8.cyan(`npm install -g thinkwork-cli@${latest}`)}`);
2088
+ console.log(` Run: ${chalk9.cyan(`npm install -g thinkwork-cli@${latest}`)}`);
1736
2089
  }
1737
2090
  console.log("");
1738
2091
  return;
@@ -1740,16 +2093,16 @@ function registerUpgradeCommand(program2) {
1740
2093
  const method = detectInstallMethod();
1741
2094
  const cmd = method === "homebrew" ? "brew upgrade thinkwork-ai/tap/thinkwork" : `npm install -g thinkwork-cli@${latest}`;
1742
2095
  console.log(` Installing via ${method}...`);
1743
- console.log(chalk8.dim(` $ ${cmd}`));
2096
+ console.log(chalk9.dim(` $ ${cmd}`));
1744
2097
  console.log("");
1745
2098
  try {
1746
- execSync8(cmd, { stdio: "inherit", timeout: 12e4 });
2099
+ execSync9(cmd, { stdio: "inherit", timeout: 12e4 });
1747
2100
  console.log("");
1748
- console.log(chalk8.green(` \u2713 Upgraded to thinkwork-cli@${latest}`));
2101
+ console.log(chalk9.green(` \u2713 Upgraded to thinkwork-cli@${latest}`));
1749
2102
  } catch {
1750
2103
  console.log("");
1751
- console.log(chalk8.red(` Failed to upgrade. Try manually:`));
1752
- console.log(` ${chalk8.cyan(cmd)}`);
2104
+ console.log(chalk9.red(` Failed to upgrade. Try manually:`));
2105
+ console.log(` ${chalk9.cyan(cmd)}`);
1753
2106
  }
1754
2107
  console.log("");
1755
2108
  });
@@ -1780,5 +2133,6 @@ registerStatusCommand(program);
1780
2133
  registerOutputsCommand(program);
1781
2134
  registerConfigCommand(program);
1782
2135
  registerMcpCommand(program);
1783
- registerUpgradeCommand(program);
2136
+ registerToolsCommand(program);
2137
+ registerUpdateCommand(program);
1784
2138
  program.parse();