thinkwork-cli 0.3.2 → 0.4.0
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,6 +1348,27 @@ 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
|
}
|
|
@@ -1395,9 +1420,14 @@ function registerStatusCommand(program2) {
|
|
|
1395
1420
|
console.log(` ${chalk6.bold("Memory:")} ${info.memoryEngine || "unknown"}`);
|
|
1396
1421
|
if (info.hindsightHealth) console.log(` ${chalk6.bold("Hindsight:")} ${info.hindsightHealth}`);
|
|
1397
1422
|
if (info.bucketName) console.log(` ${chalk6.bold("S3 bucket:")} ${info.bucketName}`);
|
|
1423
|
+
if (info.dbEndpoint) console.log(` ${chalk6.bold("Database:")} ${info.dbEndpoint}`);
|
|
1424
|
+
if (info.ecrUrl) console.log(` ${chalk6.bold("ECR:")} ${info.ecrUrl}`);
|
|
1398
1425
|
console.log("");
|
|
1399
1426
|
console.log(chalk6.bold(" URLs:"));
|
|
1427
|
+
if (info.adminUrl) console.log(` Admin: ${link(info.adminUrl)}`);
|
|
1428
|
+
if (info.docsUrl) console.log(` Docs: ${link(info.docsUrl)}`);
|
|
1400
1429
|
if (info.apiEndpoint) console.log(` API: ${link(info.apiEndpoint)}`);
|
|
1430
|
+
if (info.appsyncApiUrl) console.log(` AppSync: ${link(info.appsyncApiUrl)}`);
|
|
1401
1431
|
if (info.appsyncUrl) console.log(` WebSocket: ${link(info.appsyncUrl)}`);
|
|
1402
1432
|
if (info.hindsightEndpoint) console.log(` Hindsight: ${link(info.hindsightEndpoint)}`);
|
|
1403
1433
|
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"));
|
|
@@ -1434,9 +1464,11 @@ function registerStatusCommand(program2) {
|
|
|
1434
1464
|
const memBadge = info.memoryEngine === "hindsight" ? info.hindsightHealth === "healthy" ? chalk6.magenta("hindsight \u2713") : chalk6.yellow("hindsight ?") : chalk6.dim(info.memoryEngine || "\u2014");
|
|
1435
1465
|
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
1466
|
const urls = [];
|
|
1437
|
-
if (info.
|
|
1438
|
-
if (info.
|
|
1439
|
-
if (info.
|
|
1467
|
+
if (info.adminUrl) urls.push(`Admin: ${link(info.adminUrl, info.adminUrl)}`);
|
|
1468
|
+
if (info.docsUrl) urls.push(`Docs: ${link(info.docsUrl, info.docsUrl)}`);
|
|
1469
|
+
if (info.apiEndpoint) urls.push(`API: ${link(info.apiEndpoint, info.apiEndpoint)}`);
|
|
1470
|
+
if (info.appsyncUrl) urls.push(`WS: ${link(info.appsyncUrl, info.appsyncUrl.replace("wss://", "").split(".")[0] + "...")}`);
|
|
1471
|
+
if (info.hindsightEndpoint) urls.push(`Mem: ${link(info.hindsightEndpoint, info.hindsightEndpoint)}`);
|
|
1440
1472
|
if (urls.length === 0) {
|
|
1441
1473
|
console.log(prefix + acStatus.padEnd(22) + memBadge.padEnd(22) + chalk6.dim("\u2014"));
|
|
1442
1474
|
} else {
|
|
@@ -1456,6 +1488,186 @@ function registerStatusCommand(program2) {
|
|
|
1456
1488
|
});
|
|
1457
1489
|
}
|
|
1458
1490
|
|
|
1491
|
+
// src/commands/mcp.ts
|
|
1492
|
+
import { readFileSync as readFileSync3, existsSync as existsSync6 } from "fs";
|
|
1493
|
+
import { execSync as execSync7 } from "child_process";
|
|
1494
|
+
import chalk7 from "chalk";
|
|
1495
|
+
function readTfVar2(tfvarsPath, key) {
|
|
1496
|
+
if (!existsSync6(tfvarsPath)) return null;
|
|
1497
|
+
const content = readFileSync3(tfvarsPath, "utf-8");
|
|
1498
|
+
const match = content.match(new RegExp(`^${key}\\s*=\\s*"([^"]*)"`, "m"));
|
|
1499
|
+
return match ? match[1] : null;
|
|
1500
|
+
}
|
|
1501
|
+
function resolveTfvarsPath2(stage) {
|
|
1502
|
+
const tfDir = resolveTerraformDir(stage);
|
|
1503
|
+
if (tfDir) {
|
|
1504
|
+
const direct = `${tfDir}/terraform.tfvars`;
|
|
1505
|
+
if (existsSync6(direct)) return direct;
|
|
1506
|
+
}
|
|
1507
|
+
const terraformDir = process.env.THINKWORK_TERRAFORM_DIR || process.cwd();
|
|
1508
|
+
const cwd = resolveTierDir(terraformDir, stage, "app");
|
|
1509
|
+
return `${cwd}/terraform.tfvars`;
|
|
1510
|
+
}
|
|
1511
|
+
function getApiEndpoint(stage, region) {
|
|
1512
|
+
try {
|
|
1513
|
+
const raw = execSync7(
|
|
1514
|
+
`aws apigatewayv2 get-apis --region ${region} --query "Items[?Name=='thinkwork-${stage}-api'].ApiEndpoint|[0]" --output text`,
|
|
1515
|
+
{ encoding: "utf-8", timeout: 15e3, stdio: ["pipe", "pipe", "pipe"] }
|
|
1516
|
+
).trim();
|
|
1517
|
+
return raw && raw !== "None" ? raw : null;
|
|
1518
|
+
} catch {
|
|
1519
|
+
return null;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
async function apiFetch(apiUrl, authSecret, path2, options = {}) {
|
|
1523
|
+
const res = await fetch(`${apiUrl}${path2}`, {
|
|
1524
|
+
...options,
|
|
1525
|
+
headers: {
|
|
1526
|
+
"Content-Type": "application/json",
|
|
1527
|
+
Authorization: `Bearer ${authSecret}`,
|
|
1528
|
+
...options.headers
|
|
1529
|
+
}
|
|
1530
|
+
});
|
|
1531
|
+
if (!res.ok) {
|
|
1532
|
+
const body = await res.json().catch(() => ({}));
|
|
1533
|
+
throw new Error(body.error || `HTTP ${res.status}`);
|
|
1534
|
+
}
|
|
1535
|
+
return res.json();
|
|
1536
|
+
}
|
|
1537
|
+
function resolveApiConfig(stage) {
|
|
1538
|
+
const tfvarsPath = resolveTfvarsPath2(stage);
|
|
1539
|
+
const authSecret = readTfVar2(tfvarsPath, "api_auth_secret");
|
|
1540
|
+
if (!authSecret) {
|
|
1541
|
+
printError(`Cannot read api_auth_secret from ${tfvarsPath}`);
|
|
1542
|
+
return null;
|
|
1543
|
+
}
|
|
1544
|
+
const region = readTfVar2(tfvarsPath, "region") || "us-east-1";
|
|
1545
|
+
const apiUrl = getApiEndpoint(stage, region);
|
|
1546
|
+
if (!apiUrl) {
|
|
1547
|
+
printError(`Cannot discover API endpoint for stage "${stage}". Is the stack deployed?`);
|
|
1548
|
+
return null;
|
|
1549
|
+
}
|
|
1550
|
+
return { apiUrl, authSecret };
|
|
1551
|
+
}
|
|
1552
|
+
function registerMcpCommand(program2) {
|
|
1553
|
+
const mcp = program2.command("mcp").description("Manage MCP servers for agents");
|
|
1554
|
+
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) => {
|
|
1555
|
+
const check = validateStage(opts.stage);
|
|
1556
|
+
if (!check.valid) {
|
|
1557
|
+
printError(check.error);
|
|
1558
|
+
process.exit(1);
|
|
1559
|
+
}
|
|
1560
|
+
const api = resolveApiConfig(opts.stage);
|
|
1561
|
+
if (!api) process.exit(1);
|
|
1562
|
+
printHeader("mcp list", opts.stage);
|
|
1563
|
+
try {
|
|
1564
|
+
const { servers } = await apiFetch(api.apiUrl, api.authSecret, `/api/skills/agent/${opts.agent}/mcp-servers`);
|
|
1565
|
+
if (!servers || servers.length === 0) {
|
|
1566
|
+
console.log(chalk7.dim(" No MCP servers registered for this agent."));
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
console.log("");
|
|
1570
|
+
for (const s of servers) {
|
|
1571
|
+
const status = s.enabled ? chalk7.green("enabled") : chalk7.dim("disabled");
|
|
1572
|
+
console.log(` ${chalk7.bold(s.name)} ${status}`);
|
|
1573
|
+
console.log(` URL: ${s.url}`);
|
|
1574
|
+
console.log(` Transport: ${s.transport}`);
|
|
1575
|
+
console.log(` Auth: ${s.authType || "none"}`);
|
|
1576
|
+
if (s.tools?.length) {
|
|
1577
|
+
console.log(` Tools: ${s.tools.join(", ")}`);
|
|
1578
|
+
}
|
|
1579
|
+
console.log("");
|
|
1580
|
+
}
|
|
1581
|
+
} catch (err) {
|
|
1582
|
+
printError(err.message);
|
|
1583
|
+
process.exit(1);
|
|
1584
|
+
}
|
|
1585
|
+
});
|
|
1586
|
+
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) => {
|
|
1587
|
+
const check = validateStage(opts.stage);
|
|
1588
|
+
if (!check.valid) {
|
|
1589
|
+
printError(check.error);
|
|
1590
|
+
process.exit(1);
|
|
1591
|
+
}
|
|
1592
|
+
const api = resolveApiConfig(opts.stage);
|
|
1593
|
+
if (!api) process.exit(1);
|
|
1594
|
+
printHeader("mcp add", opts.stage);
|
|
1595
|
+
const body = {
|
|
1596
|
+
name,
|
|
1597
|
+
url: opts.url,
|
|
1598
|
+
transport: opts.transport,
|
|
1599
|
+
authType: opts.authType !== "none" ? opts.authType : void 0
|
|
1600
|
+
};
|
|
1601
|
+
if (opts.authValue) body.apiKey = opts.authValue;
|
|
1602
|
+
if (opts.connectionId) body.connectionId = opts.connectionId;
|
|
1603
|
+
if (opts.providerId) body.providerId = opts.providerId;
|
|
1604
|
+
if (opts.tools) body.tools = opts.tools.split(",").map((t) => t.trim());
|
|
1605
|
+
try {
|
|
1606
|
+
const result = await apiFetch(api.apiUrl, api.authSecret, `/api/skills/agent/${opts.agent}/mcp-servers`, {
|
|
1607
|
+
method: "POST",
|
|
1608
|
+
body: JSON.stringify(body)
|
|
1609
|
+
});
|
|
1610
|
+
printSuccess(`MCP server "${name}" ${result.created ? "added" : "updated"} (skill: ${result.skillId})`);
|
|
1611
|
+
} catch (err) {
|
|
1612
|
+
printError(err.message);
|
|
1613
|
+
process.exit(1);
|
|
1614
|
+
}
|
|
1615
|
+
});
|
|
1616
|
+
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) => {
|
|
1617
|
+
const check = validateStage(opts.stage);
|
|
1618
|
+
if (!check.valid) {
|
|
1619
|
+
printError(check.error);
|
|
1620
|
+
process.exit(1);
|
|
1621
|
+
}
|
|
1622
|
+
const api = resolveApiConfig(opts.stage);
|
|
1623
|
+
if (!api) process.exit(1);
|
|
1624
|
+
try {
|
|
1625
|
+
await apiFetch(api.apiUrl, api.authSecret, `/api/skills/agent/${opts.agent}/mcp-servers/${name}`, {
|
|
1626
|
+
method: "DELETE"
|
|
1627
|
+
});
|
|
1628
|
+
printSuccess(`MCP server "${name}" removed.`);
|
|
1629
|
+
} catch (err) {
|
|
1630
|
+
printError(err.message);
|
|
1631
|
+
process.exit(1);
|
|
1632
|
+
}
|
|
1633
|
+
});
|
|
1634
|
+
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) => {
|
|
1635
|
+
const check = validateStage(opts.stage);
|
|
1636
|
+
if (!check.valid) {
|
|
1637
|
+
printError(check.error);
|
|
1638
|
+
process.exit(1);
|
|
1639
|
+
}
|
|
1640
|
+
const api = resolveApiConfig(opts.stage);
|
|
1641
|
+
if (!api) process.exit(1);
|
|
1642
|
+
printHeader("mcp test", opts.stage);
|
|
1643
|
+
try {
|
|
1644
|
+
const result = await apiFetch(api.apiUrl, api.authSecret, `/api/skills/agent/${opts.agent}/mcp-servers/${name}/test`, {
|
|
1645
|
+
method: "POST"
|
|
1646
|
+
});
|
|
1647
|
+
if (result.ok) {
|
|
1648
|
+
printSuccess(`Connection to "${name}" successful.`);
|
|
1649
|
+
if (result.tools?.length) {
|
|
1650
|
+
console.log(chalk7.bold(`
|
|
1651
|
+
Available tools (${result.tools.length}):
|
|
1652
|
+
`));
|
|
1653
|
+
for (const t of result.tools) {
|
|
1654
|
+
console.log(` ${chalk7.cyan(t.name)}${t.description ? chalk7.dim(` \u2014 ${t.description}`) : ""}`);
|
|
1655
|
+
}
|
|
1656
|
+
console.log("");
|
|
1657
|
+
} else {
|
|
1658
|
+
printWarning("Server connected but reported no tools.");
|
|
1659
|
+
}
|
|
1660
|
+
} else {
|
|
1661
|
+
printError(`Connection failed: ${result.error}`);
|
|
1662
|
+
process.exit(1);
|
|
1663
|
+
}
|
|
1664
|
+
} catch (err) {
|
|
1665
|
+
printError(err.message);
|
|
1666
|
+
process.exit(1);
|
|
1667
|
+
}
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1459
1671
|
// src/cli.ts
|
|
1460
1672
|
var program = new Command();
|
|
1461
1673
|
program.name("thinkwork").description(
|
|
@@ -1480,4 +1692,5 @@ registerDestroyCommand(program);
|
|
|
1480
1692
|
registerStatusCommand(program);
|
|
1481
1693
|
registerOutputsCommand(program);
|
|
1482
1694
|
registerConfigCommand(program);
|
|
1695
|
+
registerMcpCommand(program);
|
|
1483
1696
|
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
|
+
}
|