thinkwork-cli 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -64,6 +64,8 @@ No repo clone required — `thinkwork init` scaffolds all Terraform modules from
|
|
|
64
64
|
|
|
65
65
|
| Command | Description |
|
|
66
66
|
|---------|-------------|
|
|
67
|
+
| `thinkwork status` | Discover all deployed environments in AWS (clickable URLs) |
|
|
68
|
+
| `thinkwork status -s <stage>` | Detailed view for one environment |
|
|
67
69
|
| `thinkwork outputs -s <stage>` | Show deployment outputs (API URL, Cognito IDs, etc.) |
|
|
68
70
|
| `thinkwork config list` | List all initialized environments |
|
|
69
71
|
| `thinkwork config list -s <stage>` | Show full config for an environment (secrets masked) |
|
|
@@ -110,6 +112,25 @@ All initialized environments are saved to `~/.thinkwork/environments/<stage>/con
|
|
|
110
112
|
|
|
111
113
|
## Examples
|
|
112
114
|
|
|
115
|
+
### Check all environments
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
thinkwork status
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
Environments
|
|
123
|
+
──────────────────────────────────────────────────────────────────────
|
|
124
|
+
Stage Source Lambdas AgentCore Memory URLs
|
|
125
|
+
──────────────────────────────────────────────────────────────────────
|
|
126
|
+
● dev aws+cli 42 active hindsight ✓ API: https://ho7oy...
|
|
127
|
+
WS: dcrs2r...
|
|
128
|
+
Mem: http://tw-dev...
|
|
129
|
+
──────────────────────────────────────────────────────────────────────
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
URLs are clickable in supported terminals (iTerm2, Windows Terminal, VS Code, etc.).
|
|
133
|
+
|
|
113
134
|
### Switch memory engine
|
|
114
135
|
|
|
115
136
|
```bash
|
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:
|
|
742
|
-
const secretJson =
|
|
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,189 @@ 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 link(url, label) {
|
|
1280
|
+
const text = label || url;
|
|
1281
|
+
return `\x1B]8;;${url}\x1B\\${text}\x1B]8;;\x1B\\`;
|
|
1282
|
+
}
|
|
1283
|
+
function runAws(cmd) {
|
|
1284
|
+
try {
|
|
1285
|
+
return execSync6(`aws ${cmd}`, {
|
|
1286
|
+
encoding: "utf-8",
|
|
1287
|
+
timeout: 15e3,
|
|
1288
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1289
|
+
}).trim();
|
|
1290
|
+
} catch {
|
|
1291
|
+
return null;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
function discoverAwsStages(region) {
|
|
1295
|
+
const stages = /* @__PURE__ */ new Map();
|
|
1296
|
+
const raw = runAws(
|
|
1297
|
+
`lambda list-functions --region ${region} --query "Functions[?starts_with(FunctionName, 'thinkwork-')].FunctionName" --output json`
|
|
1298
|
+
);
|
|
1299
|
+
if (!raw) return stages;
|
|
1300
|
+
const functions = JSON.parse(raw);
|
|
1301
|
+
for (const fn of functions) {
|
|
1302
|
+
const match = fn.match(/^thinkwork-(.+?)-api-graphql-http$/);
|
|
1303
|
+
if (match) {
|
|
1304
|
+
const stage = match[1];
|
|
1305
|
+
stages.set(stage, { stage, source: "aws", region });
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
for (const [stage, info] of stages) {
|
|
1309
|
+
const count = functions.filter((f) => f.startsWith(`thinkwork-${stage}-`)).length;
|
|
1310
|
+
info.lambdaCount = count;
|
|
1311
|
+
const apiRaw = runAws(
|
|
1312
|
+
`apigatewayv2 get-apis --region ${region} --query "Items[?Name=='thinkwork-${stage}-api'].ApiEndpoint|[0]" --output text`
|
|
1313
|
+
);
|
|
1314
|
+
if (apiRaw && apiRaw !== "None") info.apiEndpoint = apiRaw;
|
|
1315
|
+
const appsyncRaw = runAws(
|
|
1316
|
+
`appsync list-graphql-apis --region ${region} --query "graphqlApis[?name=='thinkwork-${stage}-subscriptions'].uris.REALTIME|[0]" --output text`
|
|
1317
|
+
);
|
|
1318
|
+
if (appsyncRaw && appsyncRaw !== "None") info.appsyncUrl = appsyncRaw;
|
|
1319
|
+
const acRaw = runAws(
|
|
1320
|
+
`lambda get-function --function-name thinkwork-${stage}-agentcore --region ${region} --query "Configuration.State" --output text 2>/dev/null`
|
|
1321
|
+
);
|
|
1322
|
+
info.agentcoreStatus = acRaw || "not deployed";
|
|
1323
|
+
const bucketRaw = runAws(
|
|
1324
|
+
`s3api head-bucket --bucket thinkwork-${stage}-storage --region ${region} 2>/dev/null && echo "exists"`
|
|
1325
|
+
);
|
|
1326
|
+
info.bucketName = bucketRaw ? `thinkwork-${stage}-storage` : void 0;
|
|
1327
|
+
const ecsRaw = runAws(
|
|
1328
|
+
`ecs describe-services --cluster thinkwork-${stage}-cluster --services thinkwork-${stage}-hindsight --region ${region} --query "services[0].runningCount" --output text 2>/dev/null`
|
|
1329
|
+
);
|
|
1330
|
+
if (ecsRaw && ecsRaw !== "None" && ecsRaw !== "0") {
|
|
1331
|
+
info.memoryEngine = "hindsight";
|
|
1332
|
+
const albRaw = runAws(
|
|
1333
|
+
`elbv2 describe-load-balancers --region ${region} --query "LoadBalancers[?contains(LoadBalancerName, 'tw-${stage}-hindsight')].DNSName|[0]" --output text`
|
|
1334
|
+
);
|
|
1335
|
+
if (albRaw && albRaw !== "None") {
|
|
1336
|
+
info.hindsightEndpoint = `http://${albRaw}`;
|
|
1337
|
+
try {
|
|
1338
|
+
const health = execSync6(`curl -s --max-time 3 http://${albRaw}/health`, { encoding: "utf-8" }).trim();
|
|
1339
|
+
info.hindsightHealth = health.includes("healthy") ? "healthy" : "unhealthy";
|
|
1340
|
+
} catch {
|
|
1341
|
+
info.hindsightHealth = "unreachable";
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
} else {
|
|
1345
|
+
info.memoryEngine = "managed";
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
return stages;
|
|
1349
|
+
}
|
|
1350
|
+
function registerStatusCommand(program2) {
|
|
1351
|
+
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) => {
|
|
1352
|
+
const identity = getAwsIdentity();
|
|
1353
|
+
printHeader("status", opts.stage || "all", identity);
|
|
1354
|
+
if (!identity) {
|
|
1355
|
+
printError("AWS credentials not configured. Run `thinkwork login` first.");
|
|
1356
|
+
process.exit(1);
|
|
1357
|
+
}
|
|
1358
|
+
console.log(chalk6.dim(" Scanning AWS account for Thinkwork deployments...\n"));
|
|
1359
|
+
const awsStages = discoverAwsStages(opts.region);
|
|
1360
|
+
const localEnvs = listEnvironments();
|
|
1361
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1362
|
+
for (const [stage, info] of awsStages) {
|
|
1363
|
+
const local = localEnvs.find((e) => e.stage === stage);
|
|
1364
|
+
merged.set(stage, {
|
|
1365
|
+
stage,
|
|
1366
|
+
source: local ? "both" : "aws",
|
|
1367
|
+
region: opts.region,
|
|
1368
|
+
accountId: identity.account,
|
|
1369
|
+
...info
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
for (const env of localEnvs) {
|
|
1373
|
+
if (!merged.has(env.stage)) {
|
|
1374
|
+
merged.set(env.stage, {
|
|
1375
|
+
stage: env.stage,
|
|
1376
|
+
source: "local",
|
|
1377
|
+
region: env.region,
|
|
1378
|
+
accountId: env.accountId
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
if (opts.stage) {
|
|
1383
|
+
const info = merged.get(opts.stage);
|
|
1384
|
+
if (!info) {
|
|
1385
|
+
printError(`No environment "${opts.stage}" found in AWS or local config.`);
|
|
1386
|
+
process.exit(1);
|
|
1387
|
+
}
|
|
1388
|
+
console.log(chalk6.bold.cyan(` \u2B21 ${info.stage}`));
|
|
1389
|
+
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"));
|
|
1390
|
+
console.log(` ${chalk6.bold("Source:")} ${info.source === "both" ? "AWS + local config" : info.source === "aws" ? "AWS (no local config)" : "local only (not in AWS)"}`);
|
|
1391
|
+
console.log(` ${chalk6.bold("Region:")} ${info.region}`);
|
|
1392
|
+
console.log(` ${chalk6.bold("Account:")} ${info.accountId}`);
|
|
1393
|
+
console.log(` ${chalk6.bold("Lambda fns:")} ${info.lambdaCount || "\u2014"}`);
|
|
1394
|
+
console.log(` ${chalk6.bold("AgentCore:")} ${info.agentcoreStatus || "unknown"}`);
|
|
1395
|
+
console.log(` ${chalk6.bold("Memory:")} ${info.memoryEngine || "unknown"}`);
|
|
1396
|
+
if (info.hindsightHealth) console.log(` ${chalk6.bold("Hindsight:")} ${info.hindsightHealth}`);
|
|
1397
|
+
if (info.bucketName) console.log(` ${chalk6.bold("S3 bucket:")} ${info.bucketName}`);
|
|
1398
|
+
console.log("");
|
|
1399
|
+
console.log(chalk6.bold(" URLs:"));
|
|
1400
|
+
if (info.apiEndpoint) console.log(` API: ${link(info.apiEndpoint)}`);
|
|
1401
|
+
if (info.appsyncUrl) console.log(` WebSocket: ${link(info.appsyncUrl)}`);
|
|
1402
|
+
if (info.hindsightEndpoint) console.log(` Hindsight: ${link(info.hindsightEndpoint)}`);
|
|
1403
|
+
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"));
|
|
1404
|
+
const local = loadEnvironment(opts.stage);
|
|
1405
|
+
if (local) {
|
|
1406
|
+
console.log(chalk6.dim(` Terraform dir: ${local.terraformDir}`));
|
|
1407
|
+
} else {
|
|
1408
|
+
console.log(chalk6.dim(` No local config. Run: thinkwork init -s ${opts.stage}`));
|
|
1409
|
+
}
|
|
1410
|
+
console.log("");
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
if (merged.size === 0) {
|
|
1414
|
+
console.log(" No Thinkwork environments found.");
|
|
1415
|
+
console.log(` Run ${chalk6.cyan("thinkwork init -s <stage>")} to create one.`);
|
|
1416
|
+
console.log("");
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
const COL1 = 16;
|
|
1420
|
+
const COL2 = 10;
|
|
1421
|
+
const COL3 = 10;
|
|
1422
|
+
const COL4 = 14;
|
|
1423
|
+
const COL5 = 14;
|
|
1424
|
+
const pad = " ".repeat(2 + 2 + COL1 + COL2 + COL3 + COL4 + COL5);
|
|
1425
|
+
console.log(chalk6.bold(" Environments"));
|
|
1426
|
+
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\u2500\u2500\u2500\u2500"));
|
|
1427
|
+
console.log(
|
|
1428
|
+
chalk6.dim(" ") + "Stage".padEnd(COL1) + "Source".padEnd(COL2) + "Lambdas".padEnd(COL3) + "AgentCore".padEnd(COL4) + "Memory".padEnd(COL5) + "URLs"
|
|
1429
|
+
);
|
|
1430
|
+
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\u2500\u2500\u2500\u2500"));
|
|
1431
|
+
for (const [, info] of [...merged].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
1432
|
+
const sourceBadge = info.source === "both" ? chalk6.green("\u25CF") : info.source === "aws" ? chalk6.yellow("\u25CF") : chalk6.dim("\u25CB");
|
|
1433
|
+
const acStatus = info.agentcoreStatus === "Active" ? chalk6.green("active") : info.agentcoreStatus === "not deployed" ? chalk6.dim("\u2014") : chalk6.yellow(info.agentcoreStatus || "\u2014");
|
|
1434
|
+
const memBadge = info.memoryEngine === "hindsight" ? info.hindsightHealth === "healthy" ? chalk6.magenta("hindsight \u2713") : chalk6.yellow("hindsight ?") : chalk6.dim(info.memoryEngine || "\u2014");
|
|
1435
|
+
const prefix = ` ${sourceBadge} ` + chalk6.bold(info.stage.padEnd(COL1 - 1)) + " " + (info.source === "both" ? "aws+cli" : info.source).padEnd(COL2) + String(info.lambdaCount || "\u2014").padEnd(COL3);
|
|
1436
|
+
const urls = [];
|
|
1437
|
+
if (info.apiEndpoint) urls.push(`API: ${link(info.apiEndpoint, info.apiEndpoint)}`);
|
|
1438
|
+
if (info.appsyncUrl) urls.push(`WS: ${link(info.appsyncUrl, info.appsyncUrl.replace("wss://", "").split(".")[0] + "...")}`);
|
|
1439
|
+
if (info.hindsightEndpoint) urls.push(`Mem: ${link(info.hindsightEndpoint, info.hindsightEndpoint)}`);
|
|
1440
|
+
if (urls.length === 0) {
|
|
1441
|
+
console.log(prefix + acStatus.padEnd(22) + memBadge.padEnd(22) + chalk6.dim("\u2014"));
|
|
1442
|
+
} else {
|
|
1443
|
+
console.log(prefix + acStatus.padEnd(22) + memBadge.padEnd(22) + chalk6.dim(urls[0]));
|
|
1444
|
+
for (let i = 1; i < urls.length; i++) {
|
|
1445
|
+
console.log(pad + chalk6.dim(urls[i]));
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
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\u2500\u2500\u2500\u2500"));
|
|
1450
|
+
console.log(
|
|
1451
|
+
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")
|
|
1452
|
+
);
|
|
1453
|
+
console.log("");
|
|
1454
|
+
console.log(` Details: ${chalk6.cyan("thinkwork status -s <stage>")}`);
|
|
1455
|
+
console.log("");
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1276
1459
|
// src/cli.ts
|
|
1277
1460
|
var program = new Command();
|
|
1278
1461
|
program.name("thinkwork").description(
|
|
@@ -1294,6 +1477,7 @@ registerPlanCommand(program);
|
|
|
1294
1477
|
registerDeployCommand(program);
|
|
1295
1478
|
registerBootstrapCommand(program);
|
|
1296
1479
|
registerDestroyCommand(program);
|
|
1480
|
+
registerStatusCommand(program);
|
|
1297
1481
|
registerOutputsCommand(program);
|
|
1298
1482
|
registerConfigCommand(program);
|
|
1299
1483
|
program.parse();
|
|
@@ -152,7 +152,7 @@ variable "memory_engine" {
|
|
|
152
152
|
variable "hindsight_image_tag" {
|
|
153
153
|
description = "Hindsight Docker image tag (only used when memory_engine = 'hindsight')"
|
|
154
154
|
type = string
|
|
155
|
-
default = "0.
|
|
155
|
+
default = "0.5.0"
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
variable "agentcore_memory_id" {
|