thinkwork-cli 0.3.2 → 0.4.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:
|
|
742
|
-
const secretJson =
|
|
741
|
+
const { execSync: execSync8 } = await import("child_process");
|
|
742
|
+
const secretJson = execSync8(
|
|
743
743
|
`aws secretsmanager get-secret-value --secret-id "${secretArn}" --query SecretString --output text`,
|
|
744
744
|
{ encoding: "utf-8" }
|
|
745
745
|
).trim();
|
|
@@ -1316,6 +1316,10 @@ function discoverAwsStages(region) {
|
|
|
1316
1316
|
`appsync list-graphql-apis --region ${region} --query "graphqlApis[?name=='thinkwork-${stage}-subscriptions'].uris.REALTIME|[0]" --output text`
|
|
1317
1317
|
);
|
|
1318
1318
|
if (appsyncRaw && appsyncRaw !== "None") info.appsyncUrl = appsyncRaw;
|
|
1319
|
+
const appsyncApiRaw = runAws(
|
|
1320
|
+
`appsync list-graphql-apis --region ${region} --query "graphqlApis[?name=='thinkwork-${stage}-subscriptions'].uris.GRAPHQL|[0]" --output text`
|
|
1321
|
+
);
|
|
1322
|
+
if (appsyncApiRaw && appsyncApiRaw !== "None") info.appsyncApiUrl = appsyncApiRaw;
|
|
1319
1323
|
const acRaw = runAws(
|
|
1320
1324
|
`lambda get-function --function-name thinkwork-${stage}-agentcore --region ${region} --query "Configuration.State" --output text 2>/dev/null`
|
|
1321
1325
|
);
|
|
@@ -1344,9 +1348,60 @@ function discoverAwsStages(region) {
|
|
|
1344
1348
|
} else {
|
|
1345
1349
|
info.memoryEngine = "managed";
|
|
1346
1350
|
}
|
|
1351
|
+
const dbRaw = runAws(
|
|
1352
|
+
`rds describe-db-clusters --region ${region} --query "DBClusters[?starts_with(DBClusterIdentifier, 'thinkwork-${stage}')].Endpoint|[0]" --output text`
|
|
1353
|
+
);
|
|
1354
|
+
if (dbRaw && dbRaw !== "None") info.dbEndpoint = dbRaw;
|
|
1355
|
+
const ecrRaw = runAws(
|
|
1356
|
+
`ecr describe-repositories --region ${region} --query "repositories[?repositoryName=='thinkwork-${stage}-agentcore'].repositoryUri|[0]" --output text`
|
|
1357
|
+
);
|
|
1358
|
+
if (ecrRaw && ecrRaw !== "None") info.ecrUrl = ecrRaw;
|
|
1359
|
+
const cfJson = runAws(
|
|
1360
|
+
`cloudfront list-distributions --query "DistributionList.Items[?contains(Origins.Items[0].DomainName, 'thinkwork-${stage}-')].{Origin:Origins.Items[0].DomainName,Domain:DomainName}" --output json`
|
|
1361
|
+
);
|
|
1362
|
+
if (cfJson) {
|
|
1363
|
+
try {
|
|
1364
|
+
const dists = JSON.parse(cfJson);
|
|
1365
|
+
for (const d of dists) {
|
|
1366
|
+
if (d.Origin.includes(`thinkwork-${stage}-admin`)) info.adminUrl = `https://${d.Domain}`;
|
|
1367
|
+
if (d.Origin.includes(`thinkwork-${stage}-docs`)) info.docsUrl = `https://${d.Domain}`;
|
|
1368
|
+
}
|
|
1369
|
+
} catch {
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1347
1372
|
}
|
|
1348
1373
|
return stages;
|
|
1349
1374
|
}
|
|
1375
|
+
function printStageDetail(info) {
|
|
1376
|
+
console.log(chalk6.bold.cyan(` \u2B21 ${info.stage}`));
|
|
1377
|
+
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"));
|
|
1378
|
+
console.log(` ${chalk6.bold("Source:")} ${info.source === "both" ? "AWS + local config" : info.source === "aws" ? "AWS (no local config)" : "local only (not in AWS)"}`);
|
|
1379
|
+
console.log(` ${chalk6.bold("Region:")} ${info.region}`);
|
|
1380
|
+
console.log(` ${chalk6.bold("Account:")} ${info.accountId}`);
|
|
1381
|
+
console.log(` ${chalk6.bold("Lambda fns:")} ${info.lambdaCount || "\u2014"}`);
|
|
1382
|
+
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}`);
|
|
1385
|
+
if (info.bucketName) console.log(` ${chalk6.bold("S3 bucket:")} ${info.bucketName}`);
|
|
1386
|
+
if (info.dbEndpoint) console.log(` ${chalk6.bold("Database:")} ${info.dbEndpoint}`);
|
|
1387
|
+
if (info.ecrUrl) console.log(` ${chalk6.bold("ECR:")} ${info.ecrUrl}`);
|
|
1388
|
+
console.log("");
|
|
1389
|
+
console.log(chalk6.bold(" URLs:"));
|
|
1390
|
+
if (info.adminUrl) console.log(` Admin: ${link(info.adminUrl)}`);
|
|
1391
|
+
if (info.docsUrl) console.log(` Docs: ${link(info.docsUrl)}`);
|
|
1392
|
+
if (info.apiEndpoint) console.log(` API: ${link(info.apiEndpoint)}`);
|
|
1393
|
+
if (info.appsyncApiUrl) console.log(` AppSync: ${link(info.appsyncApiUrl)}`);
|
|
1394
|
+
if (info.appsyncUrl) console.log(` WebSocket: ${link(info.appsyncUrl)}`);
|
|
1395
|
+
if (info.hindsightEndpoint) console.log(` Hindsight: ${link(info.hindsightEndpoint)}`);
|
|
1396
|
+
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"));
|
|
1397
|
+
const local = loadEnvironment(info.stage);
|
|
1398
|
+
if (local) {
|
|
1399
|
+
console.log(chalk6.dim(` Terraform dir: ${local.terraformDir}`));
|
|
1400
|
+
} else {
|
|
1401
|
+
console.log(chalk6.dim(` No local config. Run: thinkwork init -s ${info.stage}`));
|
|
1402
|
+
}
|
|
1403
|
+
console.log("");
|
|
1404
|
+
}
|
|
1350
1405
|
function registerStatusCommand(program2) {
|
|
1351
1406
|
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
1407
|
const identity = getAwsIdentity();
|
|
@@ -1385,29 +1440,7 @@ function registerStatusCommand(program2) {
|
|
|
1385
1440
|
printError(`No environment "${opts.stage}" found in AWS or local config.`);
|
|
1386
1441
|
process.exit(1);
|
|
1387
1442
|
}
|
|
1388
|
-
|
|
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("");
|
|
1443
|
+
printStageDetail(info);
|
|
1411
1444
|
return;
|
|
1412
1445
|
}
|
|
1413
1446
|
if (merged.size === 0) {
|
|
@@ -1416,43 +1449,189 @@ function registerStatusCommand(program2) {
|
|
|
1416
1449
|
console.log("");
|
|
1417
1450
|
return;
|
|
1418
1451
|
}
|
|
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
1452
|
for (const [, info] of [...merged].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1453
|
+
printStageDetail(info);
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// src/commands/mcp.ts
|
|
1459
|
+
import { readFileSync as readFileSync3, existsSync as existsSync6 } from "fs";
|
|
1460
|
+
import { execSync as execSync7 } from "child_process";
|
|
1461
|
+
import chalk7 from "chalk";
|
|
1462
|
+
function readTfVar2(tfvarsPath, key) {
|
|
1463
|
+
if (!existsSync6(tfvarsPath)) return null;
|
|
1464
|
+
const content = readFileSync3(tfvarsPath, "utf-8");
|
|
1465
|
+
const match = content.match(new RegExp(`^${key}\\s*=\\s*"([^"]*)"`, "m"));
|
|
1466
|
+
return match ? match[1] : null;
|
|
1467
|
+
}
|
|
1468
|
+
function resolveTfvarsPath2(stage) {
|
|
1469
|
+
const tfDir = resolveTerraformDir(stage);
|
|
1470
|
+
if (tfDir) {
|
|
1471
|
+
const direct = `${tfDir}/terraform.tfvars`;
|
|
1472
|
+
if (existsSync6(direct)) return direct;
|
|
1473
|
+
}
|
|
1474
|
+
const terraformDir = process.env.THINKWORK_TERRAFORM_DIR || process.cwd();
|
|
1475
|
+
const cwd = resolveTierDir(terraformDir, stage, "app");
|
|
1476
|
+
return `${cwd}/terraform.tfvars`;
|
|
1477
|
+
}
|
|
1478
|
+
function getApiEndpoint(stage, region) {
|
|
1479
|
+
try {
|
|
1480
|
+
const raw = execSync7(
|
|
1481
|
+
`aws apigatewayv2 get-apis --region ${region} --query "Items[?Name=='thinkwork-${stage}-api'].ApiEndpoint|[0]" --output text`,
|
|
1482
|
+
{ encoding: "utf-8", timeout: 15e3, stdio: ["pipe", "pipe", "pipe"] }
|
|
1483
|
+
).trim();
|
|
1484
|
+
return raw && raw !== "None" ? raw : null;
|
|
1485
|
+
} catch {
|
|
1486
|
+
return null;
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
async function apiFetch(apiUrl, authSecret, path2, options = {}) {
|
|
1490
|
+
const res = await fetch(`${apiUrl}${path2}`, {
|
|
1491
|
+
...options,
|
|
1492
|
+
headers: {
|
|
1493
|
+
"Content-Type": "application/json",
|
|
1494
|
+
Authorization: `Bearer ${authSecret}`,
|
|
1495
|
+
...options.headers
|
|
1496
|
+
}
|
|
1497
|
+
});
|
|
1498
|
+
if (!res.ok) {
|
|
1499
|
+
const body = await res.json().catch(() => ({}));
|
|
1500
|
+
throw new Error(body.error || `HTTP ${res.status}`);
|
|
1501
|
+
}
|
|
1502
|
+
return res.json();
|
|
1503
|
+
}
|
|
1504
|
+
function resolveApiConfig(stage) {
|
|
1505
|
+
const tfvarsPath = resolveTfvarsPath2(stage);
|
|
1506
|
+
const authSecret = readTfVar2(tfvarsPath, "api_auth_secret");
|
|
1507
|
+
if (!authSecret) {
|
|
1508
|
+
printError(`Cannot read api_auth_secret from ${tfvarsPath}`);
|
|
1509
|
+
return null;
|
|
1510
|
+
}
|
|
1511
|
+
const region = readTfVar2(tfvarsPath, "region") || "us-east-1";
|
|
1512
|
+
const apiUrl = getApiEndpoint(stage, region);
|
|
1513
|
+
if (!apiUrl) {
|
|
1514
|
+
printError(`Cannot discover API endpoint for stage "${stage}". Is the stack deployed?`);
|
|
1515
|
+
return null;
|
|
1516
|
+
}
|
|
1517
|
+
return { apiUrl, authSecret };
|
|
1518
|
+
}
|
|
1519
|
+
function registerMcpCommand(program2) {
|
|
1520
|
+
const mcp = program2.command("mcp").description("Manage MCP servers for agents");
|
|
1521
|
+
mcp.command("list").description("List MCP servers registered for an agent").requiredOption("-s, --stage <name>", "Deployment stage").requiredOption("--agent <id>", "Agent ID").action(async (opts) => {
|
|
1522
|
+
const check = validateStage(opts.stage);
|
|
1523
|
+
if (!check.valid) {
|
|
1524
|
+
printError(check.error);
|
|
1525
|
+
process.exit(1);
|
|
1526
|
+
}
|
|
1527
|
+
const api = resolveApiConfig(opts.stage);
|
|
1528
|
+
if (!api) process.exit(1);
|
|
1529
|
+
printHeader("mcp list", opts.stage);
|
|
1530
|
+
try {
|
|
1531
|
+
const { servers } = await apiFetch(api.apiUrl, api.authSecret, `/api/skills/agent/${opts.agent}/mcp-servers`);
|
|
1532
|
+
if (!servers || servers.length === 0) {
|
|
1533
|
+
console.log(chalk7.dim(" No MCP servers registered for this agent."));
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1536
|
+
console.log("");
|
|
1537
|
+
for (const s of servers) {
|
|
1538
|
+
const status = s.enabled ? chalk7.green("enabled") : chalk7.dim("disabled");
|
|
1539
|
+
console.log(` ${chalk7.bold(s.name)} ${status}`);
|
|
1540
|
+
console.log(` URL: ${s.url}`);
|
|
1541
|
+
console.log(` Transport: ${s.transport}`);
|
|
1542
|
+
console.log(` Auth: ${s.authType || "none"}`);
|
|
1543
|
+
if (s.tools?.length) {
|
|
1544
|
+
console.log(` Tools: ${s.tools.join(", ")}`);
|
|
1446
1545
|
}
|
|
1546
|
+
console.log("");
|
|
1447
1547
|
}
|
|
1548
|
+
} catch (err) {
|
|
1549
|
+
printError(err.message);
|
|
1550
|
+
process.exit(1);
|
|
1551
|
+
}
|
|
1552
|
+
});
|
|
1553
|
+
mcp.command("add <name>").description("Register an MCP server for an agent").requiredOption("-s, --stage <name>", "Deployment stage").requiredOption("--agent <id>", "Agent ID").requiredOption("--url <url>", "MCP server URL").option("--transport <type>", "Transport type (streamable-http|sse)", "streamable-http").option("--auth-type <type>", "Auth type (none|bearer|api-key)", "none").option("--auth-value <token>", "Auth token or API key").option("--connection-id <uuid>", "OAuth connection ID").option("--provider-id <uuid>", "OAuth provider ID (for connection-based auth)").option("--tools <list>", "Comma-separated tool allowlist").action(async (name, opts) => {
|
|
1554
|
+
const check = validateStage(opts.stage);
|
|
1555
|
+
if (!check.valid) {
|
|
1556
|
+
printError(check.error);
|
|
1557
|
+
process.exit(1);
|
|
1558
|
+
}
|
|
1559
|
+
const api = resolveApiConfig(opts.stage);
|
|
1560
|
+
if (!api) process.exit(1);
|
|
1561
|
+
printHeader("mcp add", opts.stage);
|
|
1562
|
+
const body = {
|
|
1563
|
+
name,
|
|
1564
|
+
url: opts.url,
|
|
1565
|
+
transport: opts.transport,
|
|
1566
|
+
authType: opts.authType !== "none" ? opts.authType : void 0
|
|
1567
|
+
};
|
|
1568
|
+
if (opts.authValue) body.apiKey = opts.authValue;
|
|
1569
|
+
if (opts.connectionId) body.connectionId = opts.connectionId;
|
|
1570
|
+
if (opts.providerId) body.providerId = opts.providerId;
|
|
1571
|
+
if (opts.tools) body.tools = opts.tools.split(",").map((t) => t.trim());
|
|
1572
|
+
try {
|
|
1573
|
+
const result = await apiFetch(api.apiUrl, api.authSecret, `/api/skills/agent/${opts.agent}/mcp-servers`, {
|
|
1574
|
+
method: "POST",
|
|
1575
|
+
body: JSON.stringify(body)
|
|
1576
|
+
});
|
|
1577
|
+
printSuccess(`MCP server "${name}" ${result.created ? "added" : "updated"} (skill: ${result.skillId})`);
|
|
1578
|
+
} catch (err) {
|
|
1579
|
+
printError(err.message);
|
|
1580
|
+
process.exit(1);
|
|
1581
|
+
}
|
|
1582
|
+
});
|
|
1583
|
+
mcp.command("remove <name>").description("Remove an MCP server from an agent").requiredOption("-s, --stage <name>", "Deployment stage").requiredOption("--agent <id>", "Agent ID").action(async (name, opts) => {
|
|
1584
|
+
const check = validateStage(opts.stage);
|
|
1585
|
+
if (!check.valid) {
|
|
1586
|
+
printError(check.error);
|
|
1587
|
+
process.exit(1);
|
|
1588
|
+
}
|
|
1589
|
+
const api = resolveApiConfig(opts.stage);
|
|
1590
|
+
if (!api) process.exit(1);
|
|
1591
|
+
try {
|
|
1592
|
+
await apiFetch(api.apiUrl, api.authSecret, `/api/skills/agent/${opts.agent}/mcp-servers/${name}`, {
|
|
1593
|
+
method: "DELETE"
|
|
1594
|
+
});
|
|
1595
|
+
printSuccess(`MCP server "${name}" removed.`);
|
|
1596
|
+
} catch (err) {
|
|
1597
|
+
printError(err.message);
|
|
1598
|
+
process.exit(1);
|
|
1599
|
+
}
|
|
1600
|
+
});
|
|
1601
|
+
mcp.command("test <name>").description("Test connection to an MCP server and list its tools").requiredOption("-s, --stage <name>", "Deployment stage").requiredOption("--agent <id>", "Agent ID").action(async (name, opts) => {
|
|
1602
|
+
const check = validateStage(opts.stage);
|
|
1603
|
+
if (!check.valid) {
|
|
1604
|
+
printError(check.error);
|
|
1605
|
+
process.exit(1);
|
|
1606
|
+
}
|
|
1607
|
+
const api = resolveApiConfig(opts.stage);
|
|
1608
|
+
if (!api) process.exit(1);
|
|
1609
|
+
printHeader("mcp test", opts.stage);
|
|
1610
|
+
try {
|
|
1611
|
+
const result = await apiFetch(api.apiUrl, api.authSecret, `/api/skills/agent/${opts.agent}/mcp-servers/${name}/test`, {
|
|
1612
|
+
method: "POST"
|
|
1613
|
+
});
|
|
1614
|
+
if (result.ok) {
|
|
1615
|
+
printSuccess(`Connection to "${name}" successful.`);
|
|
1616
|
+
if (result.tools?.length) {
|
|
1617
|
+
console.log(chalk7.bold(`
|
|
1618
|
+
Available tools (${result.tools.length}):
|
|
1619
|
+
`));
|
|
1620
|
+
for (const t of result.tools) {
|
|
1621
|
+
console.log(` ${chalk7.cyan(t.name)}${t.description ? chalk7.dim(` \u2014 ${t.description}`) : ""}`);
|
|
1622
|
+
}
|
|
1623
|
+
console.log("");
|
|
1624
|
+
} else {
|
|
1625
|
+
printWarning("Server connected but reported no tools.");
|
|
1626
|
+
}
|
|
1627
|
+
} else {
|
|
1628
|
+
printError(`Connection failed: ${result.error}`);
|
|
1629
|
+
process.exit(1);
|
|
1630
|
+
}
|
|
1631
|
+
} catch (err) {
|
|
1632
|
+
printError(err.message);
|
|
1633
|
+
process.exit(1);
|
|
1448
1634
|
}
|
|
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
1635
|
});
|
|
1457
1636
|
}
|
|
1458
1637
|
|
|
@@ -1480,4 +1659,5 @@ registerDestroyCommand(program);
|
|
|
1480
1659
|
registerStatusCommand(program);
|
|
1481
1660
|
registerOutputsCommand(program);
|
|
1482
1661
|
registerConfigCommand(program);
|
|
1662
|
+
registerMcpCommand(program);
|
|
1483
1663
|
program.parse();
|
|
@@ -31,9 +31,13 @@ terraform {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
backend "s3" {
|
|
35
|
+
bucket = "thinkwork-terraform-state"
|
|
36
|
+
key = "thinkwork/dev/terraform.tfstate"
|
|
37
|
+
region = "us-east-1"
|
|
38
|
+
dynamodb_table = "thinkwork-terraform-locks"
|
|
39
|
+
encrypt = true
|
|
40
|
+
}
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
provider "aws" {
|
|
@@ -134,11 +138,27 @@ output "api_endpoint" {
|
|
|
134
138
|
value = module.thinkwork.api_endpoint
|
|
135
139
|
}
|
|
136
140
|
|
|
141
|
+
output "appsync_api_url" {
|
|
142
|
+
description = "AppSync GraphQL URL"
|
|
143
|
+
value = module.thinkwork.appsync_api_url
|
|
144
|
+
}
|
|
145
|
+
|
|
137
146
|
output "appsync_realtime_url" {
|
|
138
147
|
description = "AppSync realtime WebSocket URL (for frontend subscription clients)"
|
|
139
148
|
value = module.thinkwork.appsync_realtime_url
|
|
140
149
|
}
|
|
141
150
|
|
|
151
|
+
output "appsync_api_key" {
|
|
152
|
+
description = "AppSync API key"
|
|
153
|
+
value = module.thinkwork.appsync_api_key
|
|
154
|
+
sensitive = true
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
output "auth_domain" {
|
|
158
|
+
description = "Cognito hosted UI domain"
|
|
159
|
+
value = module.thinkwork.auth_domain
|
|
160
|
+
}
|
|
161
|
+
|
|
142
162
|
output "user_pool_id" {
|
|
143
163
|
description = "Cognito user pool ID"
|
|
144
164
|
value = module.thinkwork.user_pool_id
|
|
@@ -188,3 +208,33 @@ output "hindsight_endpoint" {
|
|
|
188
208
|
description = "Hindsight API endpoint (null when memory_engine = managed)"
|
|
189
209
|
value = module.thinkwork.hindsight_endpoint
|
|
190
210
|
}
|
|
211
|
+
|
|
212
|
+
output "admin_url" {
|
|
213
|
+
description = "Admin app URL"
|
|
214
|
+
value = "https://${module.thinkwork.admin_distribution_domain}"
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
output "admin_distribution_id" {
|
|
218
|
+
description = "CloudFront distribution ID for admin (for cache invalidation)"
|
|
219
|
+
value = module.thinkwork.admin_distribution_id
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
output "admin_bucket_name" {
|
|
223
|
+
description = "S3 bucket for admin app assets"
|
|
224
|
+
value = module.thinkwork.admin_bucket_name
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
output "docs_url" {
|
|
228
|
+
description = "Docs site URL"
|
|
229
|
+
value = "https://${module.thinkwork.docs_distribution_domain}"
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
output "docs_distribution_id" {
|
|
233
|
+
description = "CloudFront distribution ID for docs (for cache invalidation)"
|
|
234
|
+
value = module.thinkwork.docs_distribution_id
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
output "docs_bucket_name" {
|
|
238
|
+
description = "S3 bucket for docs site assets"
|
|
239
|
+
value = module.thinkwork.docs_bucket_name
|
|
240
|
+
}
|
|
@@ -58,8 +58,14 @@ module "cognito" {
|
|
|
58
58
|
google_oauth_client_secret = var.google_oauth_client_secret
|
|
59
59
|
pre_signup_lambda_zip = var.pre_signup_lambda_zip
|
|
60
60
|
|
|
61
|
-
admin_callback_urls
|
|
62
|
-
|
|
61
|
+
admin_callback_urls = concat(
|
|
62
|
+
var.admin_callback_urls,
|
|
63
|
+
["https://${module.admin_site.distribution_domain}", "https://${module.admin_site.distribution_domain}/auth/callback"]
|
|
64
|
+
)
|
|
65
|
+
admin_logout_urls = concat(
|
|
66
|
+
var.admin_logout_urls,
|
|
67
|
+
["https://${module.admin_site.distribution_domain}"]
|
|
68
|
+
)
|
|
63
69
|
mobile_callback_urls = var.mobile_callback_urls
|
|
64
70
|
mobile_logout_urls = var.mobile_logout_urls
|
|
65
71
|
}
|
|
@@ -210,3 +216,27 @@ module "ses" {
|
|
|
210
216
|
stage = var.stage
|
|
211
217
|
account_id = var.account_id
|
|
212
218
|
}
|
|
219
|
+
|
|
220
|
+
################################################################################
|
|
221
|
+
# Admin Static Site
|
|
222
|
+
################################################################################
|
|
223
|
+
|
|
224
|
+
module "admin_site" {
|
|
225
|
+
source = "../app/static-site"
|
|
226
|
+
|
|
227
|
+
stage = var.stage
|
|
228
|
+
site_name = "admin"
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
################################################################################
|
|
232
|
+
# Docs Static Site
|
|
233
|
+
################################################################################
|
|
234
|
+
|
|
235
|
+
module "docs_site" {
|
|
236
|
+
source = "../app/static-site"
|
|
237
|
+
|
|
238
|
+
stage = var.stage
|
|
239
|
+
site_name = "docs"
|
|
240
|
+
custom_domain = var.docs_domain
|
|
241
|
+
certificate_arn = var.docs_certificate_arn
|
|
242
|
+
}
|
|
@@ -72,6 +72,11 @@ output "appsync_api_key" {
|
|
|
72
72
|
sensitive = true
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
output "auth_domain" {
|
|
76
|
+
description = "Cognito hosted UI domain"
|
|
77
|
+
value = module.cognito.auth_domain
|
|
78
|
+
}
|
|
79
|
+
|
|
75
80
|
output "ecr_repository_url" {
|
|
76
81
|
value = module.agentcore.ecr_repository_url
|
|
77
82
|
}
|
|
@@ -85,3 +90,35 @@ output "hindsight_endpoint" {
|
|
|
85
90
|
description = "Hindsight API endpoint (only when memory_engine = hindsight)"
|
|
86
91
|
value = var.memory_engine == "hindsight" ? module.hindsight[0].hindsight_endpoint : null
|
|
87
92
|
}
|
|
93
|
+
|
|
94
|
+
# Admin static site
|
|
95
|
+
output "admin_distribution_id" {
|
|
96
|
+
description = "CloudFront distribution ID for the admin app"
|
|
97
|
+
value = module.admin_site.distribution_id
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
output "admin_distribution_domain" {
|
|
101
|
+
description = "CloudFront domain for the admin app"
|
|
102
|
+
value = module.admin_site.distribution_domain
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
output "admin_bucket_name" {
|
|
106
|
+
description = "S3 bucket for admin app assets"
|
|
107
|
+
value = module.admin_site.bucket_name
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# Docs static site
|
|
111
|
+
output "docs_distribution_id" {
|
|
112
|
+
description = "CloudFront distribution ID for the docs site"
|
|
113
|
+
value = module.docs_site.distribution_id
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
output "docs_distribution_domain" {
|
|
117
|
+
description = "CloudFront domain for the docs site"
|
|
118
|
+
value = module.docs_site.distribution_domain
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
output "docs_bucket_name" {
|
|
122
|
+
description = "S3 bucket for docs site assets"
|
|
123
|
+
value = module.docs_site.bucket_name
|
|
124
|
+
}
|
|
@@ -239,3 +239,19 @@ variable "pre_signup_lambda_zip" {
|
|
|
239
239
|
type = string
|
|
240
240
|
default = ""
|
|
241
241
|
}
|
|
242
|
+
|
|
243
|
+
# ---------------------------------------------------------------------------
|
|
244
|
+
# Docs site (custom domain — optional)
|
|
245
|
+
# ---------------------------------------------------------------------------
|
|
246
|
+
|
|
247
|
+
variable "docs_domain" {
|
|
248
|
+
description = "Custom domain for the docs site (e.g. docs.thinkwork.ai). Leave empty for CloudFront default."
|
|
249
|
+
type = string
|
|
250
|
+
default = ""
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
variable "docs_certificate_arn" {
|
|
254
|
+
description = "ACM certificate ARN for the docs domain (us-east-1, required for CloudFront custom domains)"
|
|
255
|
+
type = string
|
|
256
|
+
default = ""
|
|
257
|
+
}
|