thinkwork-cli 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -738,8 +738,8 @@ function registerBootstrapCommand(program2) {
738
738
  bucket = await getTerraformOutput(cwd, "bucket_name");
739
739
  dbEndpoint = await getTerraformOutput(cwd, "db_cluster_endpoint");
740
740
  const secretArn = await getTerraformOutput(cwd, "db_secret_arn");
741
- const { execSync: execSync6 } = await import("child_process");
742
- const secretJson = execSync6(
741
+ const { execSync: execSync7 } = await import("child_process");
742
+ const secretJson = execSync7(
743
743
  `aws secretsmanager get-secret-value --secret-id "${secretArn}" --query SecretString --output text`,
744
744
  { encoding: "utf-8" }
745
745
  ).trim();
@@ -1273,6 +1273,160 @@ output "hindsight_endpoint" { value = module.thinkwork.hindsight_endpoint }
1273
1273
  });
1274
1274
  }
1275
1275
 
1276
+ // src/commands/status.ts
1277
+ import { execSync as execSync6 } from "child_process";
1278
+ import chalk6 from "chalk";
1279
+ function runAws(cmd) {
1280
+ try {
1281
+ return execSync6(`aws ${cmd}`, {
1282
+ encoding: "utf-8",
1283
+ timeout: 15e3,
1284
+ stdio: ["pipe", "pipe", "pipe"]
1285
+ }).trim();
1286
+ } catch {
1287
+ return null;
1288
+ }
1289
+ }
1290
+ function discoverAwsStages(region) {
1291
+ const stages = /* @__PURE__ */ new Map();
1292
+ const raw = runAws(
1293
+ `lambda list-functions --region ${region} --query "Functions[?starts_with(FunctionName, 'thinkwork-')].FunctionName" --output json`
1294
+ );
1295
+ if (!raw) return stages;
1296
+ const functions = JSON.parse(raw);
1297
+ for (const fn of functions) {
1298
+ const match = fn.match(/^thinkwork-(.+?)-api-graphql-http$/);
1299
+ if (match) {
1300
+ const stage = match[1];
1301
+ stages.set(stage, { stage, source: "aws", region });
1302
+ }
1303
+ }
1304
+ for (const [stage, info] of stages) {
1305
+ const count = functions.filter((f) => f.startsWith(`thinkwork-${stage}-`)).length;
1306
+ info.lambdaCount = count;
1307
+ const apiRaw = runAws(
1308
+ `apigatewayv2 get-apis --region ${region} --query "Items[?Name=='thinkwork-${stage}-api'].ApiEndpoint|[0]" --output text`
1309
+ );
1310
+ if (apiRaw && apiRaw !== "None") info.apiEndpoint = apiRaw;
1311
+ const acRaw = runAws(
1312
+ `lambda get-function --function-name thinkwork-${stage}-agentcore --region ${region} --query "Configuration.State" --output text 2>/dev/null`
1313
+ );
1314
+ info.agentcoreStatus = acRaw || "not deployed";
1315
+ const bucketRaw = runAws(
1316
+ `s3api head-bucket --bucket thinkwork-${stage}-storage --region ${region} 2>/dev/null && echo "exists"`
1317
+ );
1318
+ info.bucketName = bucketRaw ? `thinkwork-${stage}-storage` : void 0;
1319
+ const ecsRaw = runAws(
1320
+ `ecs describe-services --cluster thinkwork-${stage}-cluster --services thinkwork-${stage}-hindsight --region ${region} --query "services[0].runningCount" --output text 2>/dev/null`
1321
+ );
1322
+ if (ecsRaw && ecsRaw !== "None" && ecsRaw !== "0") {
1323
+ info.memoryEngine = "hindsight";
1324
+ const albRaw = runAws(
1325
+ `elbv2 describe-load-balancers --region ${region} --query "LoadBalancers[?contains(LoadBalancerName, 'tw-${stage}-hindsight')].DNSName|[0]" --output text`
1326
+ );
1327
+ if (albRaw && albRaw !== "None") {
1328
+ try {
1329
+ const health = execSync6(`curl -s --max-time 3 http://${albRaw}/health`, { encoding: "utf-8" }).trim();
1330
+ info.hindsightHealth = health.includes("healthy") ? "healthy" : "unhealthy";
1331
+ } catch {
1332
+ info.hindsightHealth = "unreachable";
1333
+ }
1334
+ }
1335
+ } else {
1336
+ info.memoryEngine = "managed";
1337
+ }
1338
+ }
1339
+ return stages;
1340
+ }
1341
+ function registerStatusCommand(program2) {
1342
+ program2.command("status").description("Show all Thinkwork environments (AWS + local)").option("-s, --stage <name>", "Show details for a specific stage").option("--region <region>", "AWS region to scan", "us-east-1").action(async (opts) => {
1343
+ const identity = getAwsIdentity();
1344
+ printHeader("status", opts.stage || "all", identity);
1345
+ if (!identity) {
1346
+ printError("AWS credentials not configured. Run `thinkwork login` first.");
1347
+ process.exit(1);
1348
+ }
1349
+ console.log(chalk6.dim(" Scanning AWS account for Thinkwork deployments...\n"));
1350
+ const awsStages = discoverAwsStages(opts.region);
1351
+ const localEnvs = listEnvironments();
1352
+ const merged = /* @__PURE__ */ new Map();
1353
+ for (const [stage, info] of awsStages) {
1354
+ const local = localEnvs.find((e) => e.stage === stage);
1355
+ merged.set(stage, {
1356
+ stage,
1357
+ source: local ? "both" : "aws",
1358
+ region: opts.region,
1359
+ accountId: identity.account,
1360
+ ...info
1361
+ });
1362
+ }
1363
+ for (const env of localEnvs) {
1364
+ if (!merged.has(env.stage)) {
1365
+ merged.set(env.stage, {
1366
+ stage: env.stage,
1367
+ source: "local",
1368
+ region: env.region,
1369
+ accountId: env.accountId
1370
+ });
1371
+ }
1372
+ }
1373
+ if (opts.stage) {
1374
+ const info = merged.get(opts.stage);
1375
+ if (!info) {
1376
+ printError(`No environment "${opts.stage}" found in AWS or local config.`);
1377
+ process.exit(1);
1378
+ }
1379
+ console.log(chalk6.bold.cyan(` \u2B21 ${info.stage}`));
1380
+ 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"));
1381
+ console.log(` ${chalk6.bold("Source:")} ${info.source === "both" ? "AWS + local config" : info.source === "aws" ? "AWS (no local config)" : "local only (not in AWS)"}`);
1382
+ console.log(` ${chalk6.bold("Region:")} ${info.region}`);
1383
+ console.log(` ${chalk6.bold("Account:")} ${info.accountId}`);
1384
+ if (info.apiEndpoint) console.log(` ${chalk6.bold("API:")} ${info.apiEndpoint}`);
1385
+ if (info.lambdaCount) console.log(` ${chalk6.bold("Lambda fns:")} ${info.lambdaCount}`);
1386
+ console.log(` ${chalk6.bold("AgentCore:")} ${info.agentcoreStatus || "unknown"}`);
1387
+ console.log(` ${chalk6.bold("Memory:")} ${info.memoryEngine || "unknown"}`);
1388
+ if (info.hindsightHealth) console.log(` ${chalk6.bold("Hindsight:")} ${info.hindsightHealth}`);
1389
+ if (info.bucketName) console.log(` ${chalk6.bold("S3 bucket:")} ${info.bucketName}`);
1390
+ 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"));
1391
+ const local = loadEnvironment(opts.stage);
1392
+ if (local) {
1393
+ console.log("");
1394
+ console.log(chalk6.dim(` Terraform dir: ${local.terraformDir}`));
1395
+ } else {
1396
+ console.log("");
1397
+ console.log(chalk6.dim(` No local config. Run: thinkwork init -s ${opts.stage}`));
1398
+ }
1399
+ console.log("");
1400
+ return;
1401
+ }
1402
+ if (merged.size === 0) {
1403
+ console.log(" No Thinkwork environments found.");
1404
+ console.log(` Run ${chalk6.cyan("thinkwork init -s <stage>")} to create one.`);
1405
+ console.log("");
1406
+ return;
1407
+ }
1408
+ console.log(chalk6.bold(" Environments"));
1409
+ 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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1410
+ console.log(
1411
+ chalk6.dim(" ") + "Stage".padEnd(16) + "Source".padEnd(10) + "Lambdas".padEnd(10) + "AgentCore".padEnd(14) + "Memory".padEnd(14) + "API"
1412
+ );
1413
+ 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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1414
+ for (const [, info] of [...merged].sort((a, b) => a[0].localeCompare(b[0]))) {
1415
+ const sourceBadge = info.source === "both" ? chalk6.green("\u25CF") : info.source === "aws" ? chalk6.yellow("\u25CF") : chalk6.dim("\u25CB");
1416
+ const acStatus = info.agentcoreStatus === "Active" ? chalk6.green("active") : info.agentcoreStatus === "not deployed" ? chalk6.dim("\u2014") : chalk6.yellow(info.agentcoreStatus || "\u2014");
1417
+ const memBadge = info.memoryEngine === "hindsight" ? info.hindsightHealth === "healthy" ? chalk6.magenta("hindsight \u2713") : chalk6.yellow("hindsight ?") : chalk6.dim(info.memoryEngine || "\u2014");
1418
+ console.log(
1419
+ ` ${sourceBadge} ` + chalk6.bold(info.stage.padEnd(14)) + (info.source === "both" ? "aws+cli" : info.source).padEnd(10) + String(info.lambdaCount || "\u2014").padEnd(10) + acStatus.padEnd(22) + memBadge.padEnd(22) + chalk6.dim(info.apiEndpoint || "\u2014")
1420
+ );
1421
+ }
1422
+ 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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1423
+ console.log(chalk6.dim(` ${merged.size} environment(s) `) + chalk6.green("\u25CF") + chalk6.dim(" aws+cli ") + chalk6.yellow("\u25CF") + chalk6.dim(" aws only ") + chalk6.dim("\u25CB local only"));
1424
+ console.log("");
1425
+ console.log(` Details: ${chalk6.cyan("thinkwork status -s <stage>")}`);
1426
+ console.log("");
1427
+ });
1428
+ }
1429
+
1276
1430
  // src/cli.ts
1277
1431
  var program = new Command();
1278
1432
  program.name("thinkwork").description(
@@ -1294,6 +1448,7 @@ registerPlanCommand(program);
1294
1448
  registerDeployCommand(program);
1295
1449
  registerBootstrapCommand(program);
1296
1450
  registerDestroyCommand(program);
1451
+ registerStatusCommand(program);
1297
1452
  registerOutputsCommand(program);
1298
1453
  registerConfigCommand(program);
1299
1454
  program.parse();
@@ -34,7 +34,7 @@ variable "database_url" {
34
34
  variable "image_tag" {
35
35
  description = "Hindsight Docker image tag (ghcr.io/vectorize-io/hindsight:<tag>)"
36
36
  type = string
37
- default = "0.4.22"
37
+ default = "0.5.0"
38
38
  }
39
39
 
40
40
  data "aws_region" "current" {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkwork-cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Thinkwork CLI — deploy, manage, and interact with your Thinkwork stack",
5
5
  "license": "MIT",
6
6
  "type": "module",