thinkwork-cli 0.12.2 → 0.12.4

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.
Files changed (29) hide show
  1. package/dist/cli.js +1586 -32
  2. package/dist/commands/enterprise/templates/deploy-repo/.github/workflows/deploy.yml +236 -0
  3. package/dist/commands/enterprise/templates/deploy-repo/README.md +33 -0
  4. package/dist/commands/enterprise/templates/deploy-repo/customer/branding/README.md +7 -0
  5. package/dist/commands/enterprise/templates/deploy-repo/customer/deployment.json +6 -0
  6. package/dist/commands/enterprise/templates/deploy-repo/customer/evals/README.md +10 -0
  7. package/dist/commands/enterprise/templates/deploy-repo/customer/seeds/README.md +7 -0
  8. package/dist/commands/enterprise/templates/deploy-repo/customer/skills/README.md +7 -0
  9. package/dist/commands/enterprise/templates/deploy-repo/customer/workspace-defaults/README.md +7 -0
  10. package/dist/commands/enterprise/templates/deploy-repo/docs/runbook.md +79 -0
  11. package/dist/commands/enterprise/templates/deploy-repo/scripts/apply-release.mjs +606 -0
  12. package/dist/commands/enterprise/templates/deploy-repo/scripts/smoke.mjs +99 -0
  13. package/dist/commands/enterprise/templates/deploy-repo/terraform/backend-dev.hcl +6 -0
  14. package/dist/commands/enterprise/templates/deploy-repo/terraform/main.tf +101 -0
  15. package/dist/commands/enterprise/templates/deploy-repo/terraform/stages/dev.tfvars +9 -0
  16. package/dist/commands/enterprise/templates/deploy-repo/terraform/stages/prod.tfvars +9 -0
  17. package/dist/commands/enterprise/templates/deploy-repo/thinkwork.lock +17 -0
  18. package/dist/terraform/examples/greenfield/main.tf +26 -0
  19. package/dist/terraform/examples/greenfield/terraform.tfvars.example +12 -0
  20. package/dist/terraform/modules/app/lambda-api/eval-fanout.tf +7 -7
  21. package/dist/terraform/modules/app/lambda-api/handlers.tf +78 -68
  22. package/dist/terraform/modules/app/lambda-api/outputs.tf +9 -4
  23. package/dist/terraform/modules/app/lambda-api/remote-artifacts.tf +36 -0
  24. package/dist/terraform/modules/app/lambda-api/variables.tf +7 -0
  25. package/dist/terraform/modules/app/lambda-api/workspace-events.tf +1 -1
  26. package/dist/terraform/modules/thinkwork/main.tf +3 -2
  27. package/dist/terraform/modules/thinkwork/outputs.tf +5 -0
  28. package/dist/terraform/modules/thinkwork/variables.tf +6 -0
  29. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -201,7 +201,7 @@ async function ensureWorkspace(cwd, stage) {
201
201
  }
202
202
  }
203
203
  function runTerraformRaw(cwd, args) {
204
- return new Promise((resolve4, reject) => {
204
+ return new Promise((resolve7, reject) => {
205
205
  const proc = spawn("terraform", args, {
206
206
  cwd,
207
207
  stdio: ["pipe", "pipe", "pipe"]
@@ -211,13 +211,13 @@ function runTerraformRaw(cwd, args) {
211
211
  proc.stdout.on("data", (d) => stdout += d);
212
212
  proc.stderr.on("data", (d) => stderr += d);
213
213
  proc.on("close", (code) => {
214
- if (code === 0) resolve4(stdout);
214
+ if (code === 0) resolve7(stdout);
215
215
  else reject(new Error(`terraform ${args.join(" ")} failed (exit ${code}): ${stderr}`));
216
216
  });
217
217
  });
218
218
  }
219
219
  function runTerraform(cwd, args) {
220
- return new Promise((resolve4) => {
220
+ return new Promise((resolve7) => {
221
221
  console.log(`
222
222
  \u2192 terraform ${args.join(" ")}
223
223
  `);
@@ -225,7 +225,7 @@ function runTerraform(cwd, args) {
225
225
  cwd,
226
226
  stdio: "inherit"
227
227
  });
228
- proc.on("close", (code) => resolve4(code ?? 1));
228
+ proc.on("close", (code) => resolve7(code ?? 1));
229
229
  });
230
230
  }
231
231
  async function ensureInit(cwd) {
@@ -469,10 +469,10 @@ async function confirm(message) {
469
469
  input: process.stdin,
470
470
  output: process.stdout
471
471
  });
472
- return new Promise((resolve4) => {
472
+ return new Promise((resolve7) => {
473
473
  rl.question(`${message} [y/N] `, (answer) => {
474
474
  rl.close();
475
- resolve4(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
475
+ resolve7(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
476
476
  });
477
477
  });
478
478
  }
@@ -553,7 +553,7 @@ async function runPostDeployProbe(stage) {
553
553
  );
554
554
  return;
555
555
  }
556
- await new Promise((resolve4) => {
556
+ await new Promise((resolve7) => {
557
557
  const proc = spawn2("bash", [scriptPath, "--stage", stage], {
558
558
  stdio: "inherit",
559
559
  env: process.env
@@ -564,11 +564,11 @@ async function runPostDeployProbe(stage) {
564
564
  `post-deploy probe exited ${code} \u2014 deploy not rolled back`
565
565
  );
566
566
  }
567
- resolve4();
567
+ resolve7();
568
568
  });
569
569
  proc.on("error", (err) => {
570
570
  printWarning(`post-deploy probe spawn failed: ${err.message}`);
571
- resolve4();
571
+ resolve7();
572
572
  });
573
573
  });
574
574
  }
@@ -782,11 +782,21 @@ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsS
782
782
  import chalk4 from "chalk";
783
783
 
784
784
  // src/environments.ts
785
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync2, readdirSync } from "fs";
785
+ import {
786
+ existsSync as existsSync4,
787
+ mkdirSync as mkdirSync2,
788
+ writeFileSync as writeFileSync2,
789
+ readFileSync as readFileSync2,
790
+ readdirSync
791
+ } from "fs";
786
792
  import { join as join2 } from "path";
787
793
  import { homedir as homedir2 } from "os";
788
794
  var THINKWORK_HOME = join2(homedir2(), ".thinkwork");
789
795
  var ENVIRONMENTS_DIR = join2(THINKWORK_HOME, "environments");
796
+ var ENTERPRISE_DEPLOYMENTS_DIR = join2(
797
+ THINKWORK_HOME,
798
+ "enterprise-deployments"
799
+ );
790
800
  function ensureDir(dir) {
791
801
  if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
792
802
  }
@@ -799,6 +809,13 @@ function saveEnvironment(config) {
799
809
  JSON.stringify(config, null, 2) + "\n"
800
810
  );
801
811
  }
812
+ function saveEnterpriseDeployment(config) {
813
+ ensureDir(ENTERPRISE_DEPLOYMENTS_DIR);
814
+ writeFileSync2(
815
+ join2(ENTERPRISE_DEPLOYMENTS_DIR, `${config.customerSlug}.json`),
816
+ JSON.stringify(config, null, 2) + "\n"
817
+ );
818
+ }
802
819
  function loadEnvironment(stage) {
803
820
  const configPath = join2(ENVIRONMENTS_DIR, stage, "config.json");
804
821
  if (!existsSync4(configPath)) return null;
@@ -1008,7 +1025,7 @@ function registerConfigCommand(program2) {
1008
1025
  import { spawn as spawn3 } from "child_process";
1009
1026
  import { resolve } from "path";
1010
1027
  function getTerraformOutput(cwd, key) {
1011
- return new Promise((resolve4, reject) => {
1028
+ return new Promise((resolve7, reject) => {
1012
1029
  const proc = spawn3("terraform", ["output", "-raw", key], {
1013
1030
  cwd,
1014
1031
  stdio: ["pipe", "pipe", "pipe"]
@@ -1016,17 +1033,17 @@ function getTerraformOutput(cwd, key) {
1016
1033
  let stdout = "";
1017
1034
  proc.stdout.on("data", (d) => stdout += d);
1018
1035
  proc.on("close", (code) => {
1019
- if (code === 0) resolve4(stdout.trim());
1036
+ if (code === 0) resolve7(stdout.trim());
1020
1037
  else reject(new Error(`terraform output ${key} failed (exit ${code})`));
1021
1038
  });
1022
1039
  });
1023
1040
  }
1024
1041
  function runScript(scriptPath, args) {
1025
- return new Promise((resolve4) => {
1042
+ return new Promise((resolve7) => {
1026
1043
  const proc = spawn3("bash", [scriptPath, ...args], {
1027
1044
  stdio: "inherit"
1028
1045
  });
1029
- proc.on("close", (code) => resolve4(code ?? 1));
1046
+ proc.on("close", (code) => resolve7(code ?? 1));
1030
1047
  });
1031
1048
  }
1032
1049
  function registerBootstrapCommand(program2) {
@@ -1266,9 +1283,9 @@ async function ensureTerraform() {
1266
1283
  }
1267
1284
  async function ensurePrerequisites() {
1268
1285
  console.log(chalk5.dim(" Checking prerequisites...\n"));
1269
- const awsOk = await ensureAwsCli();
1286
+ const awsOk2 = await ensureAwsCli();
1270
1287
  const tfOk = await ensureTerraform();
1271
- if (awsOk && tfOk) {
1288
+ if (awsOk2 && tfOk) {
1272
1289
  console.log("");
1273
1290
  return true;
1274
1291
  }
@@ -1401,7 +1418,7 @@ function buildAuthorizeUrl(cognito, redirectUri, state) {
1401
1418
  return `${cognito.domainUrl}/oauth2/authorize?${params.toString()}`;
1402
1419
  }
1403
1420
  function waitForCallbackCode(opts) {
1404
- return new Promise((resolve4, reject) => {
1421
+ return new Promise((resolve7, reject) => {
1405
1422
  const server = createServer((req, res) => handleRequest(req, res));
1406
1423
  let finished = false;
1407
1424
  const finish = (err, code) => {
@@ -1412,7 +1429,7 @@ function waitForCallbackCode(opts) {
1412
1429
  closer.closeAllConnections?.();
1413
1430
  server.close(() => {
1414
1431
  if (err) reject(err);
1415
- else resolve4(code);
1432
+ else resolve7(code);
1416
1433
  });
1417
1434
  };
1418
1435
  const timer = setTimeout(() => {
@@ -1603,10 +1620,10 @@ function escapeHtml(s) {
1603
1620
  // src/commands/login.ts
1604
1621
  function ask(prompt) {
1605
1622
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
1606
- return new Promise((resolve4) => {
1623
+ return new Promise((resolve7) => {
1607
1624
  rl.question(prompt, (answer) => {
1608
1625
  rl.close();
1609
- resolve4(answer.trim());
1626
+ resolve7(answer.trim());
1610
1627
  });
1611
1628
  });
1612
1629
  }
@@ -2018,8 +2035,8 @@ Registered callback URL:
2018
2035
  }
2019
2036
  const targetProfile = opts.profile ?? "thinkwork";
2020
2037
  printHeader("login", targetProfile);
2021
- const awsOk = await ensureAwsCli();
2022
- if (!awsOk) process.exit(1);
2038
+ const awsOk2 = await ensureAwsCli();
2039
+ if (!awsOk2) process.exit(1);
2023
2040
  const port = Number.parseInt(opts.port, 10);
2024
2041
  if (!Number.isFinite(port) || port < 1 || port > 65535) {
2025
2042
  printError(`Invalid --port value: "${opts.port}".`);
@@ -2167,10 +2184,10 @@ var __dirname = dirname3(fileURLToPath2(import.meta.url));
2167
2184
  function ask2(prompt, defaultVal = "") {
2168
2185
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
2169
2186
  const suffix = defaultVal ? chalk8.dim(` [${defaultVal}]`) : "";
2170
- return new Promise((resolve4) => {
2187
+ return new Promise((resolve7) => {
2171
2188
  rl.question(` ${prompt}${suffix}: `, (answer) => {
2172
2189
  rl.close();
2173
- resolve4(answer.trim() || defaultVal);
2190
+ resolve7(answer.trim() || defaultVal);
2174
2191
  });
2175
2192
  });
2176
2193
  }
@@ -3906,7 +3923,7 @@ function registerUpdateCommand(program2) {
3906
3923
  import { spawn as spawn5 } from "child_process";
3907
3924
  import { input as input2, select as select7 } from "@inquirer/prompts";
3908
3925
  function getTerraformOutput2(cwd, key) {
3909
- return new Promise((resolve4, reject) => {
3926
+ return new Promise((resolve7, reject) => {
3910
3927
  const proc = spawn5("terraform", ["output", "-raw", key], {
3911
3928
  cwd,
3912
3929
  stdio: ["pipe", "pipe", "pipe"]
@@ -3916,7 +3933,7 @@ function getTerraformOutput2(cwd, key) {
3916
3933
  proc.stdout.on("data", (d) => stdout += d);
3917
3934
  proc.stderr.on("data", (d) => stderr += d);
3918
3935
  proc.on("close", (code) => {
3919
- if (code === 0) resolve4(stdout.trim());
3936
+ if (code === 0) resolve7(stdout.trim());
3920
3937
  else
3921
3938
  reject(
3922
3939
  new Error(
@@ -3927,7 +3944,7 @@ function getTerraformOutput2(cwd, key) {
3927
3944
  });
3928
3945
  }
3929
3946
  function runAwsCognitoReset(userPoolId, username, region) {
3930
- return new Promise((resolve4) => {
3947
+ return new Promise((resolve7) => {
3931
3948
  const args = [
3932
3949
  "cognito-idp",
3933
3950
  "admin-reset-user-password",
@@ -3944,7 +3961,7 @@ function runAwsCognitoReset(userPoolId, username, region) {
3944
3961
  let stderr = "";
3945
3962
  proc.stdout.on("data", (d) => stdout += d);
3946
3963
  proc.stderr.on("data", (d) => stderr += d);
3947
- proc.on("close", (code) => resolve4({ code: code ?? 1, stdout, stderr }));
3964
+ proc.on("close", (code) => resolve7({ code: code ?? 1, stdout, stderr }));
3948
3965
  });
3949
3966
  }
3950
3967
  function requireTty2(label) {
@@ -4625,6 +4642,7 @@ var CliRoutineTenantBySlugDocument = { "kind": "Document", "definitions": [{ "ki
4625
4642
  var CliScheduledJobsDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "CliScheduledJobs" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "tenantId" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "ID" } } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "agentId" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "ID" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "routineId" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "ID" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "triggerType" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "String" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "enabled" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "Boolean" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "limit" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "Int" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "scheduledJobs" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "tenantId" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "tenantId" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "agentId" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "agentId" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "routineId" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "routineId" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "triggerType" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "triggerType" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "enabled" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "enabled" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "limit" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "limit" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "name" } }, { "kind": "Field", "name": { "kind": "Name", "value": "description" } }, { "kind": "Field", "name": { "kind": "Name", "value": "triggerType" } }, { "kind": "Field", "name": { "kind": "Name", "value": "agentId" } }, { "kind": "Field", "name": { "kind": "Name", "value": "routineId" } }, { "kind": "Field", "name": { "kind": "Name", "value": "scheduleType" } }, { "kind": "Field", "name": { "kind": "Name", "value": "scheduleExpression" } }, { "kind": "Field", "name": { "kind": "Name", "value": "timezone" } }, { "kind": "Field", "name": { "kind": "Name", "value": "enabled" } }, { "kind": "Field", "name": { "kind": "Name", "value": "lastRunAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "nextRunAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "createdAt" } }] } }] } }] };
4626
4643
  var CliScheduledJobDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "CliScheduledJob" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "id" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "ID" } } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "scheduledJob" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "id" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "id" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "name" } }, { "kind": "Field", "name": { "kind": "Name", "value": "description" } }, { "kind": "Field", "name": { "kind": "Name", "value": "triggerType" } }, { "kind": "Field", "name": { "kind": "Name", "value": "agentId" } }, { "kind": "Field", "name": { "kind": "Name", "value": "routineId" } }, { "kind": "Field", "name": { "kind": "Name", "value": "prompt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "scheduleType" } }, { "kind": "Field", "name": { "kind": "Name", "value": "scheduleExpression" } }, { "kind": "Field", "name": { "kind": "Name", "value": "timezone" } }, { "kind": "Field", "name": { "kind": "Name", "value": "enabled" } }, { "kind": "Field", "name": { "kind": "Name", "value": "ebScheduleName" } }, { "kind": "Field", "name": { "kind": "Name", "value": "lastRunAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "nextRunAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "createdAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "updatedAt" } }] } }] } }] };
4627
4644
  var CliCreateScheduledJobDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "mutation", "name": { "kind": "Name", "value": "CliCreateScheduledJob" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "input" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "CreateScheduledJobInput" } } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "createScheduledJob" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "input" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "input" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "name" } }, { "kind": "Field", "name": { "kind": "Name", "value": "enabled" } }, { "kind": "Field", "name": { "kind": "Name", "value": "scheduleExpression" } }, { "kind": "Field", "name": { "kind": "Name", "value": "timezone" } }] } }] } }] };
4645
+ var CliDeleteScheduledJobDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "mutation", "name": { "kind": "Name", "value": "CliDeleteScheduledJob" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "id" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "ID" } } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "deleteScheduledJob" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "id" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "id" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "ok" } }] } }] } }] };
4628
4646
  var CliSchedJobTenantBySlugDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "CliSchedJobTenantBySlug" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "slug" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "String" } } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "tenantBySlug" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "slug" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "slug" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }] } }] } }] };
4629
4647
  var CliSkillCatalogDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "CliSkillCatalog" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "skillCatalog" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "skillId" } }, { "kind": "Field", "name": { "kind": "Name", "value": "displayName" } }, { "kind": "Field", "name": { "kind": "Name", "value": "description" } }, { "kind": "Field", "name": { "kind": "Name", "value": "category" } }, { "kind": "Field", "name": { "kind": "Name", "value": "icon" } }, { "kind": "Field", "name": { "kind": "Name", "value": "source" } }, { "kind": "Field", "name": { "kind": "Name", "value": "enabled" } }] } }] } }] };
4630
4648
  var CliSkillTenantBySlugDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "CliSkillTenantBySlug" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "slug" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "String" } } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "tenantBySlug" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "slug" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "slug" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }] } }] } }] };
@@ -4683,6 +4701,7 @@ var CliCreateWebhookDocument = { "kind": "Document", "definitions": [{ "kind": "
4683
4701
  var CliUpdateWebhookDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "mutation", "name": { "kind": "Name", "value": "CliUpdateWebhook" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "id" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "ID" } } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "input" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "UpdateWebhookInput" } } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "updateWebhook" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "id" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "id" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "input" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "input" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "name" } }, { "kind": "Field", "name": { "kind": "Name", "value": "targetType" } }, { "kind": "Field", "name": { "kind": "Name", "value": "enabled" } }, { "kind": "Field", "name": { "kind": "Name", "value": "rateLimit" } }] } }] } }] };
4684
4702
  var CliDeleteWebhookDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "mutation", "name": { "kind": "Name", "value": "CliDeleteWebhook" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "id" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "ID" } } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "deleteWebhook" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "id" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "id" } } }] }] } }] };
4685
4703
  var CliRegenerateWebhookTokenDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "mutation", "name": { "kind": "Name", "value": "CliRegenerateWebhookToken" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "id" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "ID" } } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "regenerateWebhookToken" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "id" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "id" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "token" } }] } }] } }] };
4704
+ var CliWebhookDeliveriesDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "CliWebhookDeliveries" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "webhookId" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "ID" } } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "limit" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "Int" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "webhookDeliveries" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "webhookId" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "webhookId" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "limit" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "limit" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "providerName" } }, { "kind": "Field", "name": { "kind": "Name", "value": "providerEventId" } }, { "kind": "Field", "name": { "kind": "Name", "value": "normalizedKind" } }, { "kind": "Field", "name": { "kind": "Name", "value": "receivedAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "signatureStatus" } }, { "kind": "Field", "name": { "kind": "Name", "value": "resolutionStatus" } }, { "kind": "Field", "name": { "kind": "Name", "value": "statusCode" } }, { "kind": "Field", "name": { "kind": "Name", "value": "durationMs" } }, { "kind": "Field", "name": { "kind": "Name", "value": "threadId" } }, { "kind": "Field", "name": { "kind": "Name", "value": "threadCreated" } }, { "kind": "Field", "name": { "kind": "Name", "value": "retryCount" } }, { "kind": "Field", "name": { "kind": "Name", "value": "isReplay" } }, { "kind": "Field", "name": { "kind": "Name", "value": "errorMessage" } }] } }] } }] };
4686
4705
  var CliWebhookTenantBySlugDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "CliWebhookTenantBySlug" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "slug" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "String" } } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "tenantBySlug" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "slug" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "slug" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }] } }] } }] };
4687
4706
  var CliWikiTenantBySlugDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "CliWikiTenantBySlug" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "slug" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "String" } } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "tenantBySlug" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "slug" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "slug" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "slug" } }, { "kind": "Field", "name": { "kind": "Name", "value": "name" } }] } }] } }] };
4688
4707
  var CliAllTenantAgentsForWikiDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "CliAllTenantAgentsForWiki" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "tenantId" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "ID" } } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "allTenantAgents" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "tenantId" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "tenantId" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "includeSystem" }, "value": { "kind": "BooleanValue", "value": false } }, { "kind": "Argument", "name": { "kind": "Name", "value": "includeSubAgents" }, "value": { "kind": "BooleanValue", "value": false } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "name" } }, { "kind": "Field", "name": { "kind": "Name", "value": "slug" } }, { "kind": "Field", "name": { "kind": "Name", "value": "type" } }, { "kind": "Field", "name": { "kind": "Name", "value": "status" } }] } }] } }] };
@@ -4800,6 +4819,7 @@ var documents = {
4800
4819
  "\n query CliScheduledJobs(\n $tenantId: ID!\n $agentId: ID\n $routineId: ID\n $triggerType: String\n $enabled: Boolean\n $limit: Int\n ) {\n scheduledJobs(\n tenantId: $tenantId\n agentId: $agentId\n routineId: $routineId\n triggerType: $triggerType\n enabled: $enabled\n limit: $limit\n ) {\n id\n name\n description\n triggerType\n agentId\n routineId\n scheduleType\n scheduleExpression\n timezone\n enabled\n lastRunAt\n nextRunAt\n createdAt\n }\n }\n": CliScheduledJobsDocument,
4801
4820
  "\n query CliScheduledJob($id: ID!) {\n scheduledJob(id: $id) {\n id\n name\n description\n triggerType\n agentId\n routineId\n prompt\n scheduleType\n scheduleExpression\n timezone\n enabled\n ebScheduleName\n lastRunAt\n nextRunAt\n createdAt\n updatedAt\n }\n }\n": CliScheduledJobDocument,
4802
4821
  "\n mutation CliCreateScheduledJob($input: CreateScheduledJobInput!) {\n createScheduledJob(input: $input) {\n id\n name\n enabled\n scheduleExpression\n timezone\n }\n }\n": CliCreateScheduledJobDocument,
4822
+ "\n mutation CliDeleteScheduledJob($id: ID!) {\n deleteScheduledJob(id: $id) {\n id\n ok\n }\n }\n": CliDeleteScheduledJobDocument,
4803
4823
  "\n query CliSchedJobTenantBySlug($slug: String!) {\n tenantBySlug(slug: $slug) {\n id\n }\n }\n": CliSchedJobTenantBySlugDocument,
4804
4824
  "\n query CliSkillCatalog {\n skillCatalog {\n id\n skillId\n displayName\n description\n category\n icon\n source\n enabled\n }\n }\n": CliSkillCatalogDocument,
4805
4825
  "\n query CliSkillTenantBySlug($slug: String!) {\n tenantBySlug(slug: $slug) {\n id\n }\n }\n": CliSkillTenantBySlugDocument,
@@ -4858,6 +4878,7 @@ var documents = {
4858
4878
  "\n mutation CliUpdateWebhook($id: ID!, $input: UpdateWebhookInput!) {\n updateWebhook(id: $id, input: $input) {\n id\n name\n targetType\n enabled\n rateLimit\n }\n }\n": CliUpdateWebhookDocument,
4859
4879
  "\n mutation CliDeleteWebhook($id: ID!) {\n deleteWebhook(id: $id)\n }\n": CliDeleteWebhookDocument,
4860
4880
  "\n mutation CliRegenerateWebhookToken($id: ID!) {\n regenerateWebhookToken(id: $id) {\n id\n token\n }\n }\n": CliRegenerateWebhookTokenDocument,
4881
+ "\n query CliWebhookDeliveries($webhookId: ID!, $limit: Int) {\n webhookDeliveries(webhookId: $webhookId, limit: $limit) {\n id\n providerName\n providerEventId\n normalizedKind\n receivedAt\n signatureStatus\n resolutionStatus\n statusCode\n durationMs\n threadId\n threadCreated\n retryCount\n isReplay\n errorMessage\n }\n }\n": CliWebhookDeliveriesDocument,
4861
4882
  "\n query CliWebhookTenantBySlug($slug: String!) {\n tenantBySlug(slug: $slug) {\n id\n }\n }\n": CliWebhookTenantBySlugDocument,
4862
4883
  "\n query CliWikiTenantBySlug($slug: String!) {\n tenantBySlug(slug: $slug) {\n id\n slug\n name\n }\n }\n": CliWikiTenantBySlugDocument,
4863
4884
  "\n query CliAllTenantAgentsForWiki($tenantId: ID!) {\n allTenantAgents(tenantId: $tenantId, includeSystem: false, includeSubAgents: false) {\n id\n name\n slug\n type\n status\n }\n }\n": CliAllTenantAgentsForWikiDocument,
@@ -9912,6 +9933,14 @@ var CreateScheduledJobDoc = graphql(`
9912
9933
  }
9913
9934
  }
9914
9935
  `);
9936
+ var DeleteScheduledJobDoc = graphql(`
9937
+ mutation CliDeleteScheduledJob($id: ID!) {
9938
+ deleteScheduledJob(id: $id) {
9939
+ id
9940
+ ok
9941
+ }
9942
+ }
9943
+ `);
9915
9944
  var SchedJobTenantBySlugDoc = graphql(`
9916
9945
  query CliSchedJobTenantBySlug($slug: String!) {
9917
9946
  tenantBySlug(slug: $slug) {
@@ -10072,6 +10101,39 @@ async function runSchedCreate(name, opts) {
10072
10101
  `Created scheduled job ${data.createScheduledJob.id} \u2014 ${data.createScheduledJob.name} (${data.createScheduledJob.scheduleExpression}, ${data.createScheduledJob.timezone}).`
10073
10102
  );
10074
10103
  }
10104
+ async function runSchedDelete(id, opts) {
10105
+ const ctx = await resolveSchedContext(opts);
10106
+ if (!opts.yes) {
10107
+ if (!isInteractive()) {
10108
+ printError(
10109
+ "Refusing to delete without --yes in non-interactive mode (CI / piped stdin)."
10110
+ );
10111
+ process.exit(1);
10112
+ }
10113
+ requireTty("confirmation");
10114
+ const answer = await promptOrExit(
10115
+ () => input16({
10116
+ message: `Delete scheduled job ${id}? Type "delete" to confirm:`
10117
+ })
10118
+ );
10119
+ if (answer.trim() !== "delete") {
10120
+ console.log(" Cancelled.");
10121
+ return;
10122
+ }
10123
+ }
10124
+ const data = await gqlMutate(ctx.client, DeleteScheduledJobDoc, { id });
10125
+ if (isJsonMode()) {
10126
+ printJson(data.deleteScheduledJob);
10127
+ return;
10128
+ }
10129
+ if (data.deleteScheduledJob.ok) {
10130
+ printSuccess(`Deleted scheduled job ${data.deleteScheduledJob.id}.`);
10131
+ } else {
10132
+ console.log(
10133
+ ` Scheduled job ${id} was already deleted (no row matched).`
10134
+ );
10135
+ }
10136
+ }
10075
10137
  function notYetImplementedAtApi(verb) {
10076
10138
  printError(
10077
10139
  `\`scheduled-job ${verb}\` is not yet implemented at the GraphQL API.
@@ -10096,7 +10158,9 @@ Examples:
10096
10158
  `
10097
10159
  ).action(runSchedCreate);
10098
10160
  job.command("update <id>").description("Update a scheduled job. (API surface pending \u2014 currently a no-op.)").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("--schedule <expr>").option("--timezone <tz>").option("--payload <json>").option("--enable").option("--disable").action(() => notYetImplementedAtApi("update"));
10099
- job.command("delete <id>").description("Delete a scheduled job. (API surface pending.)").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("-y, --yes", "Skip confirmation").action(() => notYetImplementedAtApi("delete"));
10161
+ job.command("delete <id>").description(
10162
+ "Delete a scheduled job. Deprovisions the EventBridge schedule first, then removes the row."
10163
+ ).option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("-y, --yes", "Skip confirmation").action(runSchedDelete);
10100
10164
  job.command("run <id>").description("Trigger a scheduled job immediately. (API surface pending.)").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("--wait", "Block until the run completes").action(() => notYetImplementedAtApi("run"));
10101
10165
  }
10102
10166
 
@@ -10557,6 +10621,26 @@ var RegenerateWebhookTokenDoc = graphql(`
10557
10621
  }
10558
10622
  }
10559
10623
  `);
10624
+ var WebhookDeliveriesDoc = graphql(`
10625
+ query CliWebhookDeliveries($webhookId: ID!, $limit: Int) {
10626
+ webhookDeliveries(webhookId: $webhookId, limit: $limit) {
10627
+ id
10628
+ providerName
10629
+ providerEventId
10630
+ normalizedKind
10631
+ receivedAt
10632
+ signatureStatus
10633
+ resolutionStatus
10634
+ statusCode
10635
+ durationMs
10636
+ threadId
10637
+ threadCreated
10638
+ retryCount
10639
+ isReplay
10640
+ errorMessage
10641
+ }
10642
+ }
10643
+ `);
10560
10644
  var WebhookTenantBySlugDoc = graphql(`
10561
10645
  query CliWebhookTenantBySlug($slug: String!) {
10562
10646
  tenantBySlug(slug: $slug) {
@@ -10790,6 +10874,50 @@ async function runWebhookRotate(id, opts) {
10790
10874
  console.log(" New token (SAVE THIS):");
10791
10875
  console.log(` ${wh.token}`);
10792
10876
  }
10877
+ async function runWebhookDeliveries(id, opts) {
10878
+ const ctx = await resolveWebhookContext(opts);
10879
+ const limit = Math.min(
10880
+ Math.max(Number.parseInt(opts.limit ?? "25", 10) || 25, 1),
10881
+ 500
10882
+ );
10883
+ const data = await gqlQuery(ctx.client, WebhookDeliveriesDoc, {
10884
+ webhookId: id,
10885
+ limit
10886
+ });
10887
+ const rows = data.webhookDeliveries;
10888
+ if (isJsonMode()) {
10889
+ printJson({ items: rows });
10890
+ return;
10891
+ }
10892
+ if (rows.length === 0) {
10893
+ logStderr(`No deliveries recorded for webhook ${id}.`);
10894
+ return;
10895
+ }
10896
+ printTable(
10897
+ rows.map((r) => ({
10898
+ received: r.receivedAt ?? "",
10899
+ provider: r.providerName ?? "\u2014",
10900
+ event: r.normalizedKind ?? r.providerEventId ?? "\u2014",
10901
+ sig: r.signatureStatus,
10902
+ resolution: r.resolutionStatus,
10903
+ status: r.statusCode != null ? String(r.statusCode) : "\u2014",
10904
+ durMs: r.durationMs != null ? String(r.durationMs) : "\u2014",
10905
+ retry: r.retryCount != null ? String(r.retryCount) : "\u2014",
10906
+ thread: r.threadId ?? "\u2014"
10907
+ })),
10908
+ [
10909
+ { key: "received", header: "Received" },
10910
+ { key: "provider", header: "Provider" },
10911
+ { key: "event", header: "Event" },
10912
+ { key: "sig", header: "Sig" },
10913
+ { key: "resolution", header: "Resolution" },
10914
+ { key: "status", header: "Status" },
10915
+ { key: "durMs", header: "Dur(ms)" },
10916
+ { key: "retry", header: "Retry" },
10917
+ { key: "thread", header: "Thread" }
10918
+ ]
10919
+ );
10920
+ }
10793
10921
  function notYetImplementedAtApi2(verb) {
10794
10922
  printError(
10795
10923
  `\`webhook ${verb}\` is not yet implemented at the GraphQL API.
@@ -10812,7 +10940,9 @@ Examples:
10812
10940
  wh.command("delete <id>").description("Delete a webhook (its URL stops working immediately).").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("-y, --yes", "Skip confirmation").action(runWebhookDelete);
10813
10941
  wh.command("test <id>").description("Send a synthetic payload to the webhook. (API surface pending.)").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("--payload <json>").action(() => notYetImplementedAtApi2("test"));
10814
10942
  wh.command("rotate <id>").description("Generate a new token for an existing webhook.").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("-y, --yes", "Skip confirmation").action(runWebhookRotate);
10815
- wh.command("deliveries <id>").description("Show recent delivery attempts. (API surface pending.)").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("--limit <n>", "Max rows", "25").action(() => notYetImplementedAtApi2("deliveries"));
10943
+ wh.command("deliveries <id>").description(
10944
+ "Show recent delivery attempts for a webhook (newest first). Default 25, max 500."
10945
+ ).option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("--limit <n>", "Max rows (1-500)", "25").action(runWebhookDeliveries);
10816
10946
  }
10817
10947
 
10818
10948
  // src/lib/plugin-zip.ts
@@ -13205,10 +13335,15 @@ async function runEvalSeed(opts) {
13205
13335
  categories: opts.category && opts.category.length > 0 ? opts.category : null
13206
13336
  });
13207
13337
  if (isJsonMode()) {
13208
- printJson({ inserted: data.seedEvalTestCases });
13338
+ printJson({
13339
+ source: "built-in-yaml-seed",
13340
+ inserted: data.seedEvalTestCases
13341
+ });
13209
13342
  return;
13210
13343
  }
13211
- printSuccess(`Seeded ${data.seedEvalTestCases} new test case(s). (Duplicates were skipped.)`);
13344
+ printSuccess(
13345
+ `Seeded ${data.seedEvalTestCases} new test case(s). (Duplicates were skipped.)`
13346
+ );
13212
13347
  }
13213
13348
 
13214
13349
  // src/commands/eval/test-case/list.ts
@@ -14393,6 +14528,1424 @@ Examples:
14393
14528
  });
14394
14529
  }
14395
14530
 
14531
+ // src/commands/enterprise/bootstrap.ts
14532
+ import { resolve as resolve5 } from "path";
14533
+
14534
+ // src/commands/enterprise/template.ts
14535
+ import {
14536
+ existsSync as existsSync10,
14537
+ mkdirSync as mkdirSync5,
14538
+ readFileSync as readFileSync8,
14539
+ readdirSync as readdirSync2,
14540
+ statSync as statSync2,
14541
+ writeFileSync as writeFileSync5
14542
+ } from "fs";
14543
+ import { dirname as dirname4, join as join7, relative as relative2, resolve as resolve4 } from "path";
14544
+ import { fileURLToPath as fileURLToPath3 } from "url";
14545
+ var __dirname2 = dirname4(fileURLToPath3(import.meta.url));
14546
+ var MANAGED_MARKER = "thinkwork-managed: enterprise-deploy-template";
14547
+ var CUSTOMER_SLUG_PATTERN = /^[a-z][a-z0-9-]{1,38}[a-z0-9]$/;
14548
+ function renderEnterpriseDeployRepoTemplate(options) {
14549
+ const customerSlug = validateCustomerSlug(options.customerSlug);
14550
+ const stages = validateStages(options.stages ?? ["dev", "prod"]);
14551
+ const targetDir = resolve4(options.targetDir);
14552
+ const templateRoot = findEnterpriseTemplateRoot();
14553
+ const replacements = buildTemplateReplacements({
14554
+ ...options,
14555
+ customerSlug,
14556
+ stages
14557
+ });
14558
+ const templateFiles = listTemplateFiles(templateRoot).filter(
14559
+ (path2) => !relative2(templateRoot, path2).startsWith("terraform/stages/") && !/^terraform\/backend-[^.]+\.hcl$/.test(
14560
+ relative2(templateRoot, path2).split("\\").join("/")
14561
+ )
14562
+ );
14563
+ const written = [];
14564
+ const preserved = [];
14565
+ for (const templatePath of templateFiles) {
14566
+ const relativePath = relative2(templateRoot, templatePath);
14567
+ const outputPath = join7(targetDir, relativePath);
14568
+ let content = applyTemplate(
14569
+ readFileSync8(templatePath, "utf8"),
14570
+ replacements
14571
+ );
14572
+ if (relativePath.split("\\").join("/") === "customer/deployment.json") {
14573
+ content = renderCustomerDeploymentJson(content, customerSlug, stages);
14574
+ }
14575
+ writeManagedFile(outputPath, content, written, preserved);
14576
+ }
14577
+ const stageTemplateRoot = join7(templateRoot, "terraform", "stages");
14578
+ const backendTemplatePath = join7(
14579
+ templateRoot,
14580
+ "terraform",
14581
+ "backend-dev.hcl"
14582
+ );
14583
+ for (const stage of stages) {
14584
+ const explicitTemplate = join7(stageTemplateRoot, `${stage}.tfvars`);
14585
+ const fallbackTemplate = join7(stageTemplateRoot, "dev.tfvars");
14586
+ const templatePath = existsSync10(explicitTemplate) ? explicitTemplate : fallbackTemplate;
14587
+ const outputPath = join7(
14588
+ targetDir,
14589
+ "terraform",
14590
+ "stages",
14591
+ `${stage}.tfvars`
14592
+ );
14593
+ const content = applyTemplate(readFileSync8(templatePath, "utf8"), {
14594
+ ...replacements,
14595
+ STAGE: stage
14596
+ });
14597
+ writeManagedFile(outputPath, content, written, preserved);
14598
+ const backendPath = join7(targetDir, "terraform", `backend-${stage}.hcl`);
14599
+ const backendContent = applyTemplate(
14600
+ readFileSync8(backendTemplatePath, "utf8"),
14601
+ {
14602
+ ...replacements,
14603
+ STAGE: stage
14604
+ }
14605
+ );
14606
+ writeManagedFile(backendPath, backendContent, written, preserved);
14607
+ }
14608
+ return { targetDir, written, preserved };
14609
+ }
14610
+ function validateCustomerSlug(slug) {
14611
+ const normalized = slug.trim();
14612
+ if (!CUSTOMER_SLUG_PATTERN.test(normalized)) {
14613
+ throw new Error(
14614
+ `Invalid customer slug "${slug}". Must be lowercase alphanumeric + hyphens, 3-40 characters, starting with a letter.`
14615
+ );
14616
+ }
14617
+ return normalized;
14618
+ }
14619
+ function validateStages(stages) {
14620
+ const normalized = [
14621
+ ...new Set(stages.map((stage) => stage.trim()).filter(Boolean))
14622
+ ];
14623
+ if (normalized.length === 0) {
14624
+ throw new Error("At least one deployment stage is required.");
14625
+ }
14626
+ for (const stage of normalized) {
14627
+ const check = validateStage(stage);
14628
+ if (!check.valid) {
14629
+ throw new Error(check.error ?? `Invalid stage name "${stage}".`);
14630
+ }
14631
+ }
14632
+ return normalized;
14633
+ }
14634
+ function findEnterpriseTemplateRoot() {
14635
+ const candidates = [
14636
+ resolve4(__dirname2, "templates/deploy-repo"),
14637
+ resolve4(__dirname2, "commands/enterprise/templates/deploy-repo")
14638
+ ];
14639
+ for (const candidate of candidates) {
14640
+ if (existsSync10(join7(candidate, "thinkwork.lock"))) return candidate;
14641
+ }
14642
+ throw new Error(
14643
+ "Enterprise deployment repo template not found. The CLI package may be incomplete."
14644
+ );
14645
+ }
14646
+ function buildTemplateReplacements(options) {
14647
+ const releaseVersion = options.releaseVersion ?? "v0.0.0";
14648
+ const artifactBucket = options.artifactBucket ?? `${options.customerSlug}-thinkwork-release-artifacts`;
14649
+ return {
14650
+ ACCOUNT_ID: options.accountId ?? "123456789012",
14651
+ ARTIFACT_BUCKET: artifactBucket,
14652
+ CUSTOMER_SLUG: options.customerSlug,
14653
+ LAMBDA_ARTIFACT_PREFIX: `releases/${releaseVersion}/lambdas`,
14654
+ REGION: options.region ?? "us-east-1",
14655
+ RELEASE_MANIFEST_SHA256: options.releaseManifestSha256 ?? "CHANGE_ME",
14656
+ RELEASE_MANIFEST_URL: options.releaseManifestUrl ?? `https://github.com/thinkwork-ai/thinkwork/releases/download/${releaseVersion}/thinkwork-release.json`,
14657
+ RELEASE_VERSION: releaseVersion,
14658
+ TERRAFORM_MODULE_VERSION: options.terraformModuleVersion ?? releaseVersion.replace(/^v/, "")
14659
+ };
14660
+ }
14661
+ function renderCustomerDeploymentJson(content, customerSlug, stages) {
14662
+ const deployment = JSON.parse(content);
14663
+ deployment.stages = Object.fromEntries(
14664
+ stages.map((stage) => [
14665
+ stage,
14666
+ {
14667
+ tenantSlug: stage === "prod" ? customerSlug : `${customerSlug}-${stage}`,
14668
+ evalPacks: [],
14669
+ seedPacks: [],
14670
+ skillPacks: [],
14671
+ workspaceDefaultPacks: [],
14672
+ branding: null
14673
+ }
14674
+ ])
14675
+ );
14676
+ return `${JSON.stringify(deployment, null, 2)}
14677
+ `;
14678
+ }
14679
+ function listTemplateFiles(root) {
14680
+ const out = [];
14681
+ for (const entry of readdirSync2(root)) {
14682
+ const path2 = join7(root, entry);
14683
+ const stat = statSync2(path2);
14684
+ if (stat.isDirectory()) {
14685
+ out.push(...listTemplateFiles(path2));
14686
+ } else if (stat.isFile()) {
14687
+ out.push(path2);
14688
+ }
14689
+ }
14690
+ return out.sort();
14691
+ }
14692
+ function applyTemplate(source, replacements) {
14693
+ return source.replaceAll(/\{\{([A-Z0-9_]+)\}\}/g, (_match, key) => {
14694
+ if (!(key in replacements)) {
14695
+ throw new Error(`Unknown enterprise deployment template token: ${key}`);
14696
+ }
14697
+ return replacements[key];
14698
+ });
14699
+ }
14700
+ function writeManagedFile(path2, content, written, preserved) {
14701
+ mkdirSync5(dirname4(path2), { recursive: true });
14702
+ if (existsSync10(path2)) {
14703
+ const current = readFileSync8(path2, "utf8");
14704
+ if (!current.includes(MANAGED_MARKER)) {
14705
+ preserved.push(path2);
14706
+ return;
14707
+ }
14708
+ }
14709
+ writeFileSync5(path2, content);
14710
+ written.push(path2);
14711
+ }
14712
+
14713
+ // src/commands/enterprise/aws-bootstrap.ts
14714
+ import { execFileSync } from "child_process";
14715
+ import { mkdtempSync, writeFileSync as writeFileSync6 } from "fs";
14716
+ import { tmpdir } from "os";
14717
+ import { join as join8 } from "path";
14718
+ function buildEnterpriseAwsBootstrapPlan(config) {
14719
+ const oidcProviderArn = `arn:aws:iam::${config.accountId}:oidc-provider/token.actions.githubusercontent.com`;
14720
+ return {
14721
+ stateBucket: config.stateBucket,
14722
+ lockTable: config.lockTable,
14723
+ artifactBucket: config.artifactBucket,
14724
+ oidcProviderArn,
14725
+ stageRoles: config.stages.map((stage) => {
14726
+ const roleName = `thinkwork-${config.customerSlug}-${stage}-deploy`;
14727
+ return {
14728
+ stage,
14729
+ roleName,
14730
+ roleArn: `arn:aws:iam::${config.accountId}:role/${roleName}`,
14731
+ trustPolicy: buildGitHubOidcTrustPolicy({
14732
+ oidcProviderArn,
14733
+ repository: config.repository,
14734
+ stage
14735
+ }),
14736
+ deployPolicyName: `thinkwork-${config.customerSlug}-${stage}-deploy`,
14737
+ deployPolicy: buildEnterpriseDeployRolePolicy({
14738
+ accountId: config.accountId,
14739
+ region: config.region,
14740
+ stage,
14741
+ stateBucket: config.stateBucket,
14742
+ lockTable: config.lockTable,
14743
+ artifactBucket: config.artifactBucket
14744
+ })
14745
+ };
14746
+ })
14747
+ };
14748
+ }
14749
+ function buildGitHubOidcTrustPolicy(options) {
14750
+ return {
14751
+ Version: "2012-10-17",
14752
+ Statement: [
14753
+ {
14754
+ Effect: "Allow",
14755
+ Principal: {
14756
+ Federated: options.oidcProviderArn
14757
+ },
14758
+ Action: "sts:AssumeRoleWithWebIdentity",
14759
+ Condition: {
14760
+ StringEquals: {
14761
+ "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
14762
+ "token.actions.githubusercontent.com:sub": `repo:${options.repository}:environment:${options.stage}`
14763
+ }
14764
+ }
14765
+ }
14766
+ ]
14767
+ };
14768
+ }
14769
+ function buildEnterpriseDeployRolePolicy(options) {
14770
+ const thinkworkPrefix = `thinkwork-${options.stage}`;
14771
+ return {
14772
+ Version: "2012-10-17",
14773
+ Statement: [
14774
+ {
14775
+ Sid: "TerraformStateAndReleaseBuckets",
14776
+ Effect: "Allow",
14777
+ Action: [
14778
+ "s3:GetBucketLocation",
14779
+ "s3:GetBucketVersioning",
14780
+ "s3:GetEncryptionConfiguration",
14781
+ "s3:ListBucket",
14782
+ "s3:PutBucketVersioning",
14783
+ "s3:PutEncryptionConfiguration",
14784
+ "s3:PutLifecycleConfiguration",
14785
+ "s3:PutPublicAccessBlock"
14786
+ ],
14787
+ Resource: [
14788
+ `arn:aws:s3:::${options.stateBucket}`,
14789
+ `arn:aws:s3:::${options.artifactBucket}`,
14790
+ `arn:aws:s3:::${thinkworkPrefix}-*`
14791
+ ]
14792
+ },
14793
+ {
14794
+ Sid: "TerraformStateAndReleaseObjects",
14795
+ Effect: "Allow",
14796
+ Action: [
14797
+ "s3:AbortMultipartUpload",
14798
+ "s3:DeleteObject",
14799
+ "s3:GetObject",
14800
+ "s3:PutObject"
14801
+ ],
14802
+ Resource: [
14803
+ `arn:aws:s3:::${options.stateBucket}/*`,
14804
+ `arn:aws:s3:::${options.artifactBucket}/*`,
14805
+ `arn:aws:s3:::${thinkworkPrefix}-*/*`
14806
+ ]
14807
+ },
14808
+ {
14809
+ Sid: "TerraformStateLocks",
14810
+ Effect: "Allow",
14811
+ Action: [
14812
+ "dynamodb:CreateTable",
14813
+ "dynamodb:DescribeTable",
14814
+ "dynamodb:GetItem",
14815
+ "dynamodb:PutItem",
14816
+ "dynamodb:DeleteItem",
14817
+ "dynamodb:UpdateItem"
14818
+ ],
14819
+ Resource: `arn:aws:dynamodb:${options.region}:${options.accountId}:table/${options.lockTable}`
14820
+ },
14821
+ {
14822
+ Sid: "ThinkWorkNamedResources",
14823
+ Effect: "Allow",
14824
+ Action: [
14825
+ "apigateway:*",
14826
+ "appsync:*",
14827
+ "bedrock:*",
14828
+ "bedrock-agentcore:*",
14829
+ "cloudfront:*",
14830
+ "cognito-idp:*",
14831
+ "dynamodb:*",
14832
+ "ec2:*",
14833
+ "ecr:*",
14834
+ "ecs:*",
14835
+ "elasticfilesystem:*",
14836
+ "elasticloadbalancing:*",
14837
+ "events:*",
14838
+ "iam:*",
14839
+ "lambda:*",
14840
+ "logs:*",
14841
+ "rds:*",
14842
+ "scheduler:*",
14843
+ "secretsmanager:*",
14844
+ "ses:*",
14845
+ "sqs:*",
14846
+ "ssm:*",
14847
+ "states:*",
14848
+ "xray:*"
14849
+ ],
14850
+ Resource: "*"
14851
+ }
14852
+ ]
14853
+ };
14854
+ }
14855
+ var AwsCliEnterpriseBootstrapClient = class {
14856
+ async ensureStateBucket(bucket, region) {
14857
+ return ensureBucket(bucket, region, "terraform state bucket");
14858
+ }
14859
+ async ensureLockTable(table, region) {
14860
+ if (awsOk([
14861
+ "dynamodb",
14862
+ "describe-table",
14863
+ "--table-name",
14864
+ table,
14865
+ "--region",
14866
+ region
14867
+ ])) {
14868
+ return {
14869
+ target: table,
14870
+ status: "reused",
14871
+ message: `DynamoDB lock table ${table} already exists.`
14872
+ };
14873
+ }
14874
+ execFileSync("aws", [
14875
+ "dynamodb",
14876
+ "create-table",
14877
+ "--table-name",
14878
+ table,
14879
+ "--attribute-definitions",
14880
+ "AttributeName=LockID,AttributeType=S",
14881
+ "--key-schema",
14882
+ "AttributeName=LockID,KeyType=HASH",
14883
+ "--billing-mode",
14884
+ "PAY_PER_REQUEST",
14885
+ "--region",
14886
+ region
14887
+ ]);
14888
+ return {
14889
+ target: table,
14890
+ status: "created",
14891
+ message: `Created DynamoDB lock table ${table}.`
14892
+ };
14893
+ }
14894
+ async ensureArtifactBucket(bucket, region) {
14895
+ return ensureBucket(bucket, region, "release artifact bucket");
14896
+ }
14897
+ async ensureOidcProvider(accountId) {
14898
+ const arn = `arn:aws:iam::${accountId}:oidc-provider/token.actions.githubusercontent.com`;
14899
+ if (awsOk([
14900
+ "iam",
14901
+ "get-open-id-connect-provider",
14902
+ "--open-id-connect-provider-arn",
14903
+ arn
14904
+ ])) {
14905
+ return {
14906
+ target: arn,
14907
+ status: "reused",
14908
+ message: "GitHub Actions OIDC provider already exists."
14909
+ };
14910
+ }
14911
+ execFileSync("aws", [
14912
+ "iam",
14913
+ "create-open-id-connect-provider",
14914
+ "--url",
14915
+ "https://token.actions.githubusercontent.com",
14916
+ "--client-id-list",
14917
+ "sts.amazonaws.com",
14918
+ "--thumbprint-list",
14919
+ "6938fd4d98bab03faadb97b34396831e3780aea1"
14920
+ ]);
14921
+ return {
14922
+ target: arn,
14923
+ status: "created",
14924
+ message: "Created GitHub Actions OIDC provider."
14925
+ };
14926
+ }
14927
+ async ensureDeployRole(role) {
14928
+ if (awsOk(["iam", "get-role", "--role-name", role.roleName])) {
14929
+ putRolePolicy(role);
14930
+ return {
14931
+ target: role.roleArn,
14932
+ status: "updated",
14933
+ message: `Deploy role ${role.roleName} already exists; updated inline deploy policy ${role.deployPolicyName}.`
14934
+ };
14935
+ }
14936
+ const dir = mkdtempSync(join8(tmpdir(), "thinkwork-enterprise-role-"));
14937
+ const trustPath = join8(dir, "trust.json");
14938
+ writeFileSync6(trustPath, JSON.stringify(role.trustPolicy));
14939
+ execFileSync("aws", [
14940
+ "iam",
14941
+ "create-role",
14942
+ "--role-name",
14943
+ role.roleName,
14944
+ "--assume-role-policy-document",
14945
+ `file://${trustPath}`
14946
+ ]);
14947
+ putRolePolicy(role);
14948
+ return {
14949
+ target: role.roleArn,
14950
+ status: "created",
14951
+ message: `Created deploy role ${role.roleName} and attached inline deploy policy ${role.deployPolicyName}.`
14952
+ };
14953
+ }
14954
+ };
14955
+ function putRolePolicy(role) {
14956
+ const dir = mkdtempSync(join8(tmpdir(), "thinkwork-enterprise-policy-"));
14957
+ const policyPath = join8(dir, "policy.json");
14958
+ writeFileSync6(policyPath, JSON.stringify(role.deployPolicy));
14959
+ execFileSync("aws", [
14960
+ "iam",
14961
+ "put-role-policy",
14962
+ "--role-name",
14963
+ role.roleName,
14964
+ "--policy-name",
14965
+ role.deployPolicyName,
14966
+ "--policy-document",
14967
+ `file://${policyPath}`
14968
+ ]);
14969
+ }
14970
+ function ensureBucket(bucket, region, label) {
14971
+ if (awsOk(["s3api", "head-bucket", "--bucket", bucket])) {
14972
+ return {
14973
+ target: bucket,
14974
+ status: "reused",
14975
+ message: `${label} ${bucket} already exists.`
14976
+ };
14977
+ }
14978
+ const args = region === "us-east-1" ? ["s3api", "create-bucket", "--bucket", bucket, "--region", region] : [
14979
+ "s3api",
14980
+ "create-bucket",
14981
+ "--bucket",
14982
+ bucket,
14983
+ "--region",
14984
+ region,
14985
+ "--create-bucket-configuration",
14986
+ `LocationConstraint=${region}`
14987
+ ];
14988
+ execFileSync("aws", args);
14989
+ execFileSync("aws", [
14990
+ "s3api",
14991
+ "put-public-access-block",
14992
+ "--bucket",
14993
+ bucket,
14994
+ "--public-access-block-configuration",
14995
+ "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
14996
+ ]);
14997
+ return {
14998
+ target: bucket,
14999
+ status: "created",
15000
+ message: `Created ${label} ${bucket}.`
15001
+ };
15002
+ }
15003
+ function awsOk(args) {
15004
+ try {
15005
+ execFileSync("aws", args, { stdio: "ignore" });
15006
+ return true;
15007
+ } catch {
15008
+ return false;
15009
+ }
15010
+ }
15011
+
15012
+ // src/commands/enterprise/github.ts
15013
+ import { execFileSync as execFileSync2 } from "child_process";
15014
+ function parseGitHubRepository(input20) {
15015
+ const trimmed = input20.trim();
15016
+ const match = /^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/.exec(trimmed);
15017
+ if (!match) {
15018
+ throw new Error(
15019
+ `Invalid GitHub repository "${input20}". Use owner/name, for example acme/thinkwork-deploy.`
15020
+ );
15021
+ }
15022
+ return {
15023
+ owner: match[1],
15024
+ name: match[2],
15025
+ fullName: `${match[1]}/${match[2]}`
15026
+ };
15027
+ }
15028
+ function buildEnterpriseGitHubBootstrapPlan(options) {
15029
+ const repository = parseGitHubRepository(options.repository);
15030
+ return {
15031
+ repository,
15032
+ dispatchWorkflow: options.dispatchWorkflow ?? false,
15033
+ environments: options.stages.map((stage) => {
15034
+ const role = options.stageRoles.find((item) => item.stage === stage);
15035
+ if (!role) {
15036
+ throw new Error(`Missing deploy role for stage "${stage}".`);
15037
+ }
15038
+ return {
15039
+ stage,
15040
+ roleArn: role.roleArn,
15041
+ vars: {
15042
+ AWS_REGION: options.region,
15043
+ AWS_ROLE_ARN: role.roleArn,
15044
+ THINKWORK_ARTIFACT_BUCKET: options.artifactBucket
15045
+ },
15046
+ secretPlaceholders: ["TF_VAR_DB_PASSWORD", "TF_VAR_API_AUTH_SECRET"]
15047
+ };
15048
+ })
15049
+ };
15050
+ }
15051
+ var GhCliEnterpriseBootstrapClient = class {
15052
+ constructor(repository) {
15053
+ this.repository = repository;
15054
+ }
15055
+ repository;
15056
+ async ensureEnvironment(environment) {
15057
+ gh([
15058
+ "api",
15059
+ "--method",
15060
+ "PUT",
15061
+ `repos/${this.repository.fullName}/environments/${environment.stage}`,
15062
+ "--field",
15063
+ "wait_timer=0"
15064
+ ]);
15065
+ return {
15066
+ target: `${this.repository.fullName}:${environment.stage}`,
15067
+ status: "updated",
15068
+ message: `Ensured GitHub Environment ${environment.stage}.`
15069
+ };
15070
+ }
15071
+ async upsertEnvironmentVariables(environment) {
15072
+ for (const [name, value] of Object.entries(environment.vars)) {
15073
+ gh([
15074
+ "variable",
15075
+ "set",
15076
+ name,
15077
+ "--repo",
15078
+ this.repository.fullName,
15079
+ "--env",
15080
+ environment.stage,
15081
+ "--body",
15082
+ value
15083
+ ]);
15084
+ }
15085
+ return {
15086
+ target: `${this.repository.fullName}:${environment.stage}:vars`,
15087
+ status: "updated",
15088
+ message: `Updated non-secret GitHub Environment variables for ${environment.stage}.`
15089
+ };
15090
+ }
15091
+ async writeRepositoryFiles(targetDir) {
15092
+ return {
15093
+ target: targetDir,
15094
+ status: "updated",
15095
+ message: "Repository files were written locally. Commit/push this directory or run from a checked-out deployment repo."
15096
+ };
15097
+ }
15098
+ async dispatchWorkflow(stage) {
15099
+ gh([
15100
+ "workflow",
15101
+ "run",
15102
+ "deploy.yml",
15103
+ "--repo",
15104
+ this.repository.fullName,
15105
+ "--field",
15106
+ `stage=${stage}`
15107
+ ]);
15108
+ return {
15109
+ target: `${this.repository.fullName}:deploy.yml:${stage}`,
15110
+ status: "created",
15111
+ message: `Dispatched deploy workflow for ${stage}.`
15112
+ };
15113
+ }
15114
+ };
15115
+ function gh(args) {
15116
+ return execFileSync2("gh", args, { encoding: "utf8" });
15117
+ }
15118
+
15119
+ // src/commands/enterprise/release.ts
15120
+ function resolveEnterpriseReleasePin(options) {
15121
+ const version = options.releaseVersion ?? `v${VERSION}`;
15122
+ return {
15123
+ version,
15124
+ manifestUrl: options.manifestUrl ?? `https://github.com/thinkwork-ai/thinkwork/releases/download/${version}/thinkwork-release.json`,
15125
+ manifestSha256: options.manifestSha256 ?? "CHANGE_ME",
15126
+ terraformModuleVersion: options.terraformModuleVersion ?? version.replace(/^v/, "")
15127
+ };
15128
+ }
15129
+
15130
+ // src/commands/enterprise/bootstrap.ts
15131
+ function buildEnterpriseBootstrapPlan(options, identity) {
15132
+ const customerSlug = validateCustomerSlug(options.customerSlug);
15133
+ const repository = parseGitHubRepository(options.repository).fullName;
15134
+ const stages = validateStages(options.stages ?? ["dev", "prod"]);
15135
+ const accountId = options.accountId ?? identity?.account;
15136
+ const region = options.region ?? identity?.region;
15137
+ if (!accountId) {
15138
+ throw new Error(
15139
+ "AWS account ID is required. Configure AWS credentials or pass --account-id."
15140
+ );
15141
+ }
15142
+ if (!region || region === "unknown") {
15143
+ throw new Error(
15144
+ "AWS region is required. Configure AWS_REGION or pass --region."
15145
+ );
15146
+ }
15147
+ const release = resolveEnterpriseReleasePin({
15148
+ releaseVersion: options.releaseVersion,
15149
+ manifestUrl: options.manifestUrl,
15150
+ manifestSha256: options.manifestSha256,
15151
+ terraformModuleVersion: options.terraformModuleVersion
15152
+ });
15153
+ const artifactBucket = options.artifactBucket ?? `${customerSlug}-thinkwork-release-artifacts`;
15154
+ const stateBucket = options.stateBucket ?? `${customerSlug}-thinkwork-terraform-state`;
15155
+ const lockTable = options.lockTable ?? `${customerSlug}-thinkwork-terraform-locks`;
15156
+ const aws = buildEnterpriseAwsBootstrapPlan({
15157
+ accountId,
15158
+ region,
15159
+ repository,
15160
+ stages,
15161
+ customerSlug,
15162
+ stateBucket,
15163
+ lockTable,
15164
+ artifactBucket
15165
+ });
15166
+ const github = buildEnterpriseGitHubBootstrapPlan({
15167
+ repository,
15168
+ stages,
15169
+ region,
15170
+ artifactBucket,
15171
+ stageRoles: aws.stageRoles,
15172
+ dispatchWorkflow: options.dispatchWorkflow
15173
+ });
15174
+ return {
15175
+ customerSlug,
15176
+ targetDir: resolve5(options.targetDir),
15177
+ repository,
15178
+ stages,
15179
+ accountId,
15180
+ region,
15181
+ release,
15182
+ aws,
15183
+ github
15184
+ };
15185
+ }
15186
+ async function runEnterpriseBootstrap(options, deps = {}) {
15187
+ const identity = deps.identity === void 0 ? getAwsIdentity() : deps.identity;
15188
+ if (!options.dryRun && !identity && (!options.accountId || !options.region)) {
15189
+ throw new Error(
15190
+ "AWS identity is required before mutating AWS or GitHub resources."
15191
+ );
15192
+ }
15193
+ const plan = buildEnterpriseBootstrapPlan(options, identity);
15194
+ const template = renderEnterpriseDeployRepoTemplate({
15195
+ targetDir: plan.targetDir,
15196
+ customerSlug: plan.customerSlug,
15197
+ stages: plan.stages,
15198
+ region: plan.region,
15199
+ accountId: plan.accountId,
15200
+ releaseVersion: plan.release.version,
15201
+ releaseManifestUrl: plan.release.manifestUrl,
15202
+ releaseManifestSha256: plan.release.manifestSha256,
15203
+ terraformModuleVersion: plan.release.terraformModuleVersion,
15204
+ artifactBucket: plan.aws.artifactBucket
15205
+ });
15206
+ const awsClient = deps.awsClient ?? new AwsCliEnterpriseBootstrapClient();
15207
+ const githubClient = deps.githubClient ?? new GhCliEnterpriseBootstrapClient(parseGitHubRepository(plan.repository));
15208
+ const awsResults = [];
15209
+ const githubResults = [];
15210
+ if (options.dryRun) {
15211
+ awsResults.push(
15212
+ planned(plan.aws.stateBucket, "Would ensure Terraform state bucket."),
15213
+ planned(plan.aws.lockTable, "Would ensure Terraform lock table."),
15214
+ planned(plan.aws.artifactBucket, "Would ensure release artifact bucket."),
15215
+ planned(
15216
+ plan.aws.oidcProviderArn,
15217
+ "Would ensure GitHub Actions OIDC provider."
15218
+ ),
15219
+ ...plan.aws.stageRoles.map(
15220
+ (role) => planned(role.roleArn, `Would ensure deploy role for ${role.stage}.`)
15221
+ )
15222
+ );
15223
+ githubResults.push(
15224
+ planned(plan.targetDir, "Would write deployment repository files."),
15225
+ ...plan.github.environments.flatMap((environment) => [
15226
+ planned(
15227
+ `${plan.repository}:${environment.stage}`,
15228
+ `Would ensure GitHub Environment ${environment.stage}.`
15229
+ ),
15230
+ planned(
15231
+ `${plan.repository}:${environment.stage}:vars`,
15232
+ `Would upsert non-secret GitHub variables for ${environment.stage}.`
15233
+ ),
15234
+ planned(
15235
+ `${plan.repository}:${environment.stage}:secrets`,
15236
+ `Would require GitHub Environment secrets for ${environment.stage}: ${environment.secretPlaceholders.join(", ")}.`
15237
+ ),
15238
+ ...plan.github.dispatchWorkflow ? [
15239
+ planned(
15240
+ `${plan.repository}:deploy.yml:${environment.stage}`,
15241
+ `Would dispatch deploy workflow for ${environment.stage}.`
15242
+ )
15243
+ ] : []
15244
+ ])
15245
+ );
15246
+ } else {
15247
+ awsResults.push(
15248
+ await awsClient.ensureStateBucket(plan.aws.stateBucket, plan.region),
15249
+ await awsClient.ensureLockTable(plan.aws.lockTable, plan.region),
15250
+ await awsClient.ensureArtifactBucket(
15251
+ plan.aws.artifactBucket,
15252
+ plan.region
15253
+ ),
15254
+ await awsClient.ensureOidcProvider(plan.accountId)
15255
+ );
15256
+ for (const role of plan.aws.stageRoles) {
15257
+ awsResults.push(await awsClient.ensureDeployRole(role));
15258
+ }
15259
+ githubResults.push(await githubClient.writeRepositoryFiles(plan.targetDir));
15260
+ for (const environment of plan.github.environments) {
15261
+ githubResults.push(
15262
+ await githubClient.ensureEnvironment(environment),
15263
+ await githubClient.upsertEnvironmentVariables(environment),
15264
+ secretPlaceholderResult(plan.repository, environment)
15265
+ );
15266
+ if (plan.github.dispatchWorkflow) {
15267
+ githubResults.push(
15268
+ await githubClient.dispatchWorkflow(environment.stage)
15269
+ );
15270
+ }
15271
+ }
15272
+ }
15273
+ const metadata = options.dryRun ? planned(
15274
+ plan.customerSlug,
15275
+ "Would record local enterprise deployment metadata without secrets."
15276
+ ) : recordDeploymentMetadata(plan, deps.saveDeployment);
15277
+ return {
15278
+ plan,
15279
+ template,
15280
+ aws: awsResults,
15281
+ github: githubResults,
15282
+ metadata
15283
+ };
15284
+ }
15285
+ function registerEnterpriseBootstrapCommand(program2) {
15286
+ program2.command("bootstrap [targetDir]").description(
15287
+ "Bootstrap a customer-owned ThinkWork deployment repository and CI trust bridge."
15288
+ ).option("--customer <slug>", "Customer slug, e.g. acme").option("--repo <owner/name>", "Customer GitHub deployment repository").option("--stage <stage...>", "Deployment stage(s)", ["dev", "prod"]).option("--region <region>", "AWS region").option("--account-id <id>", "AWS account ID").option("--release-version <version>", "Pinned ThinkWork release version").option("--manifest-url <url>", "Pinned ThinkWork release manifest URL").option(
15289
+ "--manifest-sha256 <sha256>",
15290
+ "Pinned ThinkWork release manifest SHA-256"
15291
+ ).option(
15292
+ "--terraform-module-version <version>",
15293
+ "Pinned Terraform Registry module version"
15294
+ ).option(
15295
+ "--artifact-bucket <bucket>",
15296
+ "Customer-owned release artifact bucket"
15297
+ ).option("--state-bucket <bucket>", "Terraform state bucket").option("--lock-table <table>", "Terraform state lock table").option("--dispatch", "Dispatch deploy workflow after bootstrap").option(
15298
+ "--dry-run",
15299
+ "Render files and print the plan without mutating AWS/GitHub"
15300
+ ).option("-y, --yes", "Skip confirmation for mutating bootstrap").action(
15301
+ async (targetDir, opts) => {
15302
+ try {
15303
+ const customerSlug = await resolveCustomerSlug(opts.customer);
15304
+ const repository = await resolveRepository(opts.repo);
15305
+ const identity = getAwsIdentity();
15306
+ printHeader("enterprise bootstrap", customerSlug, identity);
15307
+ if (!opts.dryRun && !opts.yes) {
15308
+ const prodStages = (opts.stage ?? ["dev", "prod"]).filter(
15309
+ isProdLike
15310
+ );
15311
+ const message = prodStages.length > 0 ? ` Bootstrap deployment authority for ${repository} including production-like stage(s): ${prodStages.join(", ")}?` : ` Bootstrap deployment authority for ${repository}?`;
15312
+ if (!await confirm(message)) {
15313
+ console.log(" Aborted.");
15314
+ return;
15315
+ }
15316
+ }
15317
+ const result = await runEnterpriseBootstrap(
15318
+ {
15319
+ targetDir: targetDir ?? ".",
15320
+ customerSlug,
15321
+ repository,
15322
+ stages: opts.stage,
15323
+ region: opts.region,
15324
+ accountId: opts.accountId,
15325
+ releaseVersion: opts.releaseVersion,
15326
+ manifestUrl: opts.manifestUrl,
15327
+ manifestSha256: opts.manifestSha256,
15328
+ terraformModuleVersion: opts.terraformModuleVersion,
15329
+ artifactBucket: opts.artifactBucket,
15330
+ stateBucket: opts.stateBucket,
15331
+ lockTable: opts.lockTable,
15332
+ dispatchWorkflow: opts.dispatch,
15333
+ dryRun: opts.dryRun
15334
+ },
15335
+ { identity }
15336
+ );
15337
+ printBootstrapSummary(result);
15338
+ printSuccess(
15339
+ opts.dryRun ? "Enterprise bootstrap dry-run complete" : "Enterprise bootstrap complete"
15340
+ );
15341
+ } catch (err) {
15342
+ printError(err.message);
15343
+ process.exit(1);
15344
+ }
15345
+ }
15346
+ );
15347
+ }
15348
+ function planned(target, message) {
15349
+ return { target, status: "planned", message };
15350
+ }
15351
+ function secretPlaceholderResult(repository, environment) {
15352
+ return {
15353
+ target: `${repository}:${environment.stage}:secrets`,
15354
+ status: "planned",
15355
+ message: `Set GitHub Environment secrets for ${environment.stage}: ${environment.secretPlaceholders.join(", ")}.`
15356
+ };
15357
+ }
15358
+ function recordDeploymentMetadata(plan, saveDeployment = saveEnterpriseDeployment) {
15359
+ saveDeployment({
15360
+ customerSlug: plan.customerSlug,
15361
+ repository: plan.repository,
15362
+ targetDir: plan.targetDir,
15363
+ accountId: plan.accountId,
15364
+ region: plan.region,
15365
+ stages: plan.stages,
15366
+ artifactBucket: plan.aws.artifactBucket,
15367
+ stateBucket: plan.aws.stateBucket,
15368
+ lockTable: plan.aws.lockTable,
15369
+ releaseVersion: plan.release.version,
15370
+ releaseManifestUrl: plan.release.manifestUrl,
15371
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
15372
+ });
15373
+ return {
15374
+ target: plan.customerSlug,
15375
+ status: "updated",
15376
+ message: "Recorded local enterprise deployment metadata without secrets."
15377
+ };
15378
+ }
15379
+ async function resolveCustomerSlug(flag) {
15380
+ if (flag) return flag;
15381
+ if (!process.stdin.isTTY) {
15382
+ throw new Error("Customer slug is required. Pass --customer <slug>.");
15383
+ }
15384
+ const { input: input20 } = await import("@inquirer/prompts");
15385
+ return input20({ message: "Customer slug:" });
15386
+ }
15387
+ async function resolveRepository(flag) {
15388
+ if (flag) return flag;
15389
+ if (!process.stdin.isTTY) {
15390
+ throw new Error("GitHub repository is required. Pass --repo <owner/name>.");
15391
+ }
15392
+ const { input: input20 } = await import("@inquirer/prompts");
15393
+ return input20({ message: "GitHub deployment repo (owner/name):" });
15394
+ }
15395
+ function printBootstrapSummary(result) {
15396
+ console.log("");
15397
+ console.log(" AWS");
15398
+ for (const step of result.aws) {
15399
+ console.log(` - ${step.status}: ${step.message}`);
15400
+ }
15401
+ console.log(" GitHub");
15402
+ for (const step of result.github) {
15403
+ console.log(` - ${step.status}: ${step.message}`);
15404
+ }
15405
+ if (result.template.preserved.length > 0) {
15406
+ printWarning(
15407
+ `Preserved ${result.template.preserved.length} customer-owned file(s) without the ThinkWork managed marker.`
15408
+ );
15409
+ }
15410
+ }
15411
+
15412
+ // src/commands/enterprise/overlay.ts
15413
+ import { resolve as resolve6 } from "path";
15414
+
15415
+ // src/commands/enterprise/overlay-schema.ts
15416
+ import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync3 } from "fs";
15417
+ import { join as join9, relative as relative3, sep as sep2 } from "path";
15418
+ function loadEnterpriseOverlayDefinition(repoRoot) {
15419
+ const path2 = join9(repoRoot, "customer", "deployment.json");
15420
+ if (!existsSync11(path2)) {
15421
+ throw new Error(`Missing customer overlay definition: ${path2}`);
15422
+ }
15423
+ const parsed = JSON.parse(readFileSync9(path2, "utf8"));
15424
+ if (parsed.schemaVersion !== 1) {
15425
+ throw new Error(
15426
+ `Unsupported customer/deployment.json schemaVersion ${parsed.schemaVersion}`
15427
+ );
15428
+ }
15429
+ if (!isNonEmptyString(parsed.customerSlug)) {
15430
+ throw new Error("customer/deployment.json requires customerSlug");
15431
+ }
15432
+ if (!isRecord(parsed.stages)) {
15433
+ throw new Error("customer/deployment.json requires stages");
15434
+ }
15435
+ return {
15436
+ schemaVersion: 1,
15437
+ customerSlug: parsed.customerSlug,
15438
+ stages: Object.fromEntries(
15439
+ Object.entries(parsed.stages).map(([stage, value]) => [
15440
+ stage,
15441
+ normalizeStage(stage, value)
15442
+ ])
15443
+ )
15444
+ };
15445
+ }
15446
+ function stageOverlay(definition, stage) {
15447
+ const config = definition.stages[stage];
15448
+ if (!config) {
15449
+ throw new Error(`customer/deployment.json does not define stage ${stage}`);
15450
+ }
15451
+ return config;
15452
+ }
15453
+ function readCustomerEvalPack(repoRoot, packName) {
15454
+ assertPackName(packName, "eval");
15455
+ const path2 = join9(repoRoot, "customer", "evals", `${packName}.json`);
15456
+ if (!existsSync11(path2)) {
15457
+ throw new Error(`Eval pack "${packName}" is missing: ${path2}`);
15458
+ }
15459
+ const parsed = JSON.parse(readFileSync9(path2, "utf8"));
15460
+ if (!Array.isArray(parsed)) {
15461
+ throw new Error(`Eval pack "${packName}" must be a JSON array`);
15462
+ }
15463
+ return parsed.map(
15464
+ (value, index) => normalizeEvalSeed(value, `customer/evals/${packName}.json[${index}]`)
15465
+ );
15466
+ }
15467
+ function readJsonSeedPack(repoRoot, packName) {
15468
+ assertPackName(packName, "seed");
15469
+ const path2 = join9(repoRoot, "customer", "seeds", `${packName}.json`);
15470
+ if (!existsSync11(path2)) {
15471
+ throw new Error(`Seed pack "${packName}" is missing: ${path2}`);
15472
+ }
15473
+ return JSON.parse(readFileSync9(path2, "utf8"));
15474
+ }
15475
+ function collectOverlayFiles(repoRoot, family, packName) {
15476
+ assertPackName(packName, family);
15477
+ const root = join9(repoRoot, "customer", family, packName);
15478
+ if (!existsSync11(root) || !statSync3(root).isDirectory()) {
15479
+ throw new Error(`Overlay pack "${family}/${packName}" is missing: ${root}`);
15480
+ }
15481
+ const files = [];
15482
+ walkFiles(root, (path2) => {
15483
+ const relativePath = relative3(root, path2).split(sep2).join("/");
15484
+ if (relativePath === ".DS_Store" || relativePath.endsWith("/.DS_Store")) {
15485
+ return;
15486
+ }
15487
+ files.push({
15488
+ relativePath,
15489
+ content: readFileSync9(path2, "utf8")
15490
+ });
15491
+ });
15492
+ if (family === "skills" && !files.some((file) => file.relativePath === "SKILL.md")) {
15493
+ throw new Error(`Skill pack "${packName}" must include SKILL.md`);
15494
+ }
15495
+ if (files.length === 0) {
15496
+ throw new Error(`Overlay pack "${family}/${packName}" contains no files`);
15497
+ }
15498
+ return files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
15499
+ }
15500
+ function normalizeStage(stage, value) {
15501
+ if (!isRecord(value)) {
15502
+ throw new Error(`Stage ${stage} must be an object`);
15503
+ }
15504
+ if (!isNonEmptyString(value.tenantSlug)) {
15505
+ throw new Error(`Stage ${stage} requires tenantSlug`);
15506
+ }
15507
+ const branding = value.branding;
15508
+ if (branding !== null && branding !== void 0 && !isRecord(branding)) {
15509
+ throw new Error(`Stage ${stage} branding must be an object or null`);
15510
+ }
15511
+ return {
15512
+ tenantSlug: value.tenantSlug,
15513
+ evalPacks: normalizePackArray(value.evalPacks, `${stage}.evalPacks`),
15514
+ seedPacks: normalizePackArray(value.seedPacks, `${stage}.seedPacks`),
15515
+ skillPacks: normalizePackArray(value.skillPacks, `${stage}.skillPacks`),
15516
+ workspaceDefaultPacks: normalizePackArray(
15517
+ value.workspaceDefaultPacks,
15518
+ `${stage}.workspaceDefaultPacks`
15519
+ ),
15520
+ branding: branding ?? null,
15521
+ defaultAgentTemplateSlug: isNonEmptyString(value.defaultAgentTemplateSlug) ? value.defaultAgentTemplateSlug : "default"
15522
+ };
15523
+ }
15524
+ function normalizeEvalSeed(value, label) {
15525
+ if (!isRecord(value)) throw new Error(`${label} must be an object`);
15526
+ if (!isNonEmptyString(value.name))
15527
+ throw new Error(`${label}.name is required`);
15528
+ if (!isNonEmptyString(value.category))
15529
+ throw new Error(`${label}.category is required`);
15530
+ if (!isNonEmptyString(value.query))
15531
+ throw new Error(`${label}.query is required`);
15532
+ const assertions = value.assertions;
15533
+ if (!Array.isArray(assertions)) {
15534
+ throw new Error(`${label}.assertions must be an array`);
15535
+ }
15536
+ return {
15537
+ name: value.name,
15538
+ category: value.category,
15539
+ query: value.query,
15540
+ systemPrompt: typeof value.systemPrompt === "string" ? value.systemPrompt : null,
15541
+ agentTemplateId: typeof value.agentTemplateId === "string" ? value.agentTemplateId : null,
15542
+ assertions: assertions.map(
15543
+ (assertion, index) => normalizeAssertion(assertion, `${label}.assertions[${index}]`)
15544
+ ),
15545
+ agentcoreEvaluatorIds: normalizeOptionalStringArray(
15546
+ value.agentcoreEvaluatorIds,
15547
+ `${label}.agentcoreEvaluatorIds`
15548
+ ),
15549
+ tags: normalizeOptionalStringArray(value.tags, `${label}.tags`),
15550
+ enabled: typeof value.enabled === "boolean" ? value.enabled : true
15551
+ };
15552
+ }
15553
+ function normalizeAssertion(value, label) {
15554
+ if (!isRecord(value)) throw new Error(`${label} must be an object`);
15555
+ if (!isNonEmptyString(value.type))
15556
+ throw new Error(`${label}.type is required`);
15557
+ return {
15558
+ type: value.type,
15559
+ value: typeof value.value === "string" || value.value === null ? value.value : void 0,
15560
+ path: typeof value.path === "string" || value.path === null ? value.path : void 0
15561
+ };
15562
+ }
15563
+ function normalizePackArray(value, label) {
15564
+ const items = normalizeStringArray(value, label);
15565
+ for (const item of items) assertPackName(item, label);
15566
+ return items;
15567
+ }
15568
+ function normalizeStringArray(value, label) {
15569
+ if (value === void 0) return [];
15570
+ if (!Array.isArray(value)) throw new Error(`${label} must be an array`);
15571
+ return value.map((item, index) => {
15572
+ if (!isNonEmptyString(item)) {
15573
+ throw new Error(`${label}[${index}] must be a non-empty string`);
15574
+ }
15575
+ return item;
15576
+ });
15577
+ }
15578
+ function normalizeOptionalStringArray(value, label) {
15579
+ if (value === void 0 || value === null) return [];
15580
+ return normalizeStringArray(value, label);
15581
+ }
15582
+ function assertPackName(value, label) {
15583
+ if (!/^[a-z0-9][a-z0-9_.-]*$/.test(value)) {
15584
+ throw new Error(
15585
+ `${label} pack "${value}" must use lowercase letters, numbers, dot, underscore, or hyphen`
15586
+ );
15587
+ }
15588
+ }
15589
+ function walkFiles(root, visit) {
15590
+ for (const entry of readdirSync3(root, { withFileTypes: true })) {
15591
+ const path2 = join9(root, entry.name);
15592
+ if (entry.isDirectory()) {
15593
+ walkFiles(path2, visit);
15594
+ } else if (entry.isFile()) {
15595
+ visit(path2);
15596
+ }
15597
+ }
15598
+ }
15599
+ function isRecord(value) {
15600
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15601
+ }
15602
+ function isNonEmptyString(value) {
15603
+ return typeof value === "string" && value.trim().length > 0;
15604
+ }
15605
+
15606
+ // src/commands/enterprise/overlay-apply.ts
15607
+ function buildEnterpriseOverlayPlan(options) {
15608
+ const definition = loadEnterpriseOverlayDefinition(options.repoRoot);
15609
+ const stage = stageOverlay(definition, options.stage);
15610
+ return {
15611
+ repoRoot: options.repoRoot,
15612
+ stage: options.stage,
15613
+ tenantSlug: stage.tenantSlug,
15614
+ targetTemplateSlug: stage.defaultAgentTemplateSlug,
15615
+ operations: [
15616
+ ...evalOperations(options.repoRoot, stage),
15617
+ ...workspaceFileOperations(options.repoRoot, stage),
15618
+ ...seedOperations(options.repoRoot, stage),
15619
+ ...stage.branding ? [{ kind: "branding", branding: stage.branding }] : []
15620
+ ]
15621
+ };
15622
+ }
15623
+ async function applyEnterpriseOverlay(plan, client) {
15624
+ const result = {
15625
+ stage: plan.stage,
15626
+ tenantSlug: plan.tenantSlug,
15627
+ evals: { inserted: 0, updated: 0, skipped: 0, packs: [] },
15628
+ workspaceFiles: { written: 0, packs: [] },
15629
+ seeds: { validated: 0, packs: [] },
15630
+ branding: "not-configured"
15631
+ };
15632
+ const existing = await client.listEvalTestCases();
15633
+ const existingByOverlayKey = new Map(
15634
+ existing.flatMap(
15635
+ (testCase) => (testCase.tags ?? []).filter((tag) => tag.startsWith("customer-overlay:key:")).map((tag) => [tag, testCase])
15636
+ )
15637
+ );
15638
+ for (const operation of plan.operations) {
15639
+ if (operation.kind === "eval-pack") {
15640
+ const packResult = {
15641
+ pack: operation.pack,
15642
+ inserted: 0,
15643
+ updated: 0,
15644
+ skipped: 0
15645
+ };
15646
+ for (const testCase of operation.testCases) {
15647
+ const input20 = withOverlayTags(
15648
+ operation.pack,
15649
+ testCase,
15650
+ client.targetAgentTemplateId
15651
+ );
15652
+ const existingCase = existingByOverlayKey.get(
15653
+ overlayKeyTag(operation.pack, testCase)
15654
+ );
15655
+ if (existingCase) {
15656
+ if (sameEval(existingCase, input20)) {
15657
+ result.evals.skipped++;
15658
+ packResult.skipped++;
15659
+ } else {
15660
+ await client.updateEvalTestCase(existingCase.id, input20);
15661
+ result.evals.updated++;
15662
+ packResult.updated++;
15663
+ }
15664
+ } else {
15665
+ await client.createEvalTestCase(input20);
15666
+ result.evals.inserted++;
15667
+ packResult.inserted++;
15668
+ }
15669
+ }
15670
+ result.evals.packs.push(packResult);
15671
+ continue;
15672
+ }
15673
+ if (operation.kind === "workspace-file-pack") {
15674
+ for (const file of operation.files) {
15675
+ await client.putWorkspaceFile({
15676
+ templateSlug: operation.targetTemplateSlug,
15677
+ path: workspaceTargetPath(
15678
+ operation.family,
15679
+ operation.pack,
15680
+ file.relativePath
15681
+ ),
15682
+ content: file.content
15683
+ });
15684
+ result.workspaceFiles.written++;
15685
+ }
15686
+ result.workspaceFiles.packs.push({
15687
+ family: operation.family,
15688
+ pack: operation.pack,
15689
+ written: operation.files.length
15690
+ });
15691
+ continue;
15692
+ }
15693
+ if (operation.kind === "seed-pack") {
15694
+ result.seeds.validated++;
15695
+ result.seeds.packs.push(operation.pack);
15696
+ continue;
15697
+ }
15698
+ if (operation.kind === "branding") {
15699
+ result.branding = "recorded";
15700
+ }
15701
+ }
15702
+ return result;
15703
+ }
15704
+ function evalOperations(repoRoot, stage) {
15705
+ return stage.evalPacks.map((pack) => ({
15706
+ kind: "eval-pack",
15707
+ pack,
15708
+ testCases: readCustomerEvalPack(repoRoot, pack)
15709
+ }));
15710
+ }
15711
+ function workspaceFileOperations(repoRoot, stage) {
15712
+ return [
15713
+ ...stage.skillPacks.map((pack) => ({
15714
+ kind: "workspace-file-pack",
15715
+ family: "skills",
15716
+ pack,
15717
+ targetTemplateSlug: stage.defaultAgentTemplateSlug,
15718
+ files: collectOverlayFiles(repoRoot, "skills", pack)
15719
+ })),
15720
+ ...stage.workspaceDefaultPacks.map((pack) => ({
15721
+ kind: "workspace-file-pack",
15722
+ family: "workspace-defaults",
15723
+ pack,
15724
+ targetTemplateSlug: stage.defaultAgentTemplateSlug,
15725
+ files: collectOverlayFiles(repoRoot, "workspace-defaults", pack)
15726
+ }))
15727
+ ];
15728
+ }
15729
+ function seedOperations(repoRoot, stage) {
15730
+ return stage.seedPacks.map((pack) => ({
15731
+ kind: "seed-pack",
15732
+ pack,
15733
+ payload: readJsonSeedPack(repoRoot, pack)
15734
+ }));
15735
+ }
15736
+ function workspaceTargetPath(family, pack, relativePath) {
15737
+ if (family === "skills") return `skills/${pack}/${relativePath}`;
15738
+ return relativePath;
15739
+ }
15740
+ function withOverlayTags(pack, testCase, defaultAgentTemplateId) {
15741
+ return {
15742
+ ...testCase,
15743
+ agentTemplateId: testCase.agentTemplateId ?? defaultAgentTemplateId,
15744
+ tags: Array.from(
15745
+ /* @__PURE__ */ new Set([
15746
+ ...testCase.tags ?? [],
15747
+ "source:customer-overlay",
15748
+ `customer-overlay:pack:${pack}`,
15749
+ overlayKeyTag(pack, testCase)
15750
+ ])
15751
+ )
15752
+ };
15753
+ }
15754
+ function overlayKeyTag(pack, testCase) {
15755
+ return `customer-overlay:key:${pack}/${slugify(testCase.name)}`;
15756
+ }
15757
+ function sameEval(existing, next) {
15758
+ return existing.name === next.name && existing.category === next.category && existing.query === next.query && (existing.systemPrompt ?? null) === (next.systemPrompt ?? null) && JSON.stringify(existing.assertions ?? []) === JSON.stringify(next.assertions ?? []) && JSON.stringify(existing.agentcoreEvaluatorIds ?? []) === JSON.stringify(next.agentcoreEvaluatorIds ?? []) && JSON.stringify(existing.tags ?? []) === JSON.stringify(next.tags ?? []) && (existing.enabled ?? true) === (next.enabled ?? true);
15759
+ }
15760
+ function slugify(value) {
15761
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
15762
+ }
15763
+
15764
+ // src/commands/enterprise/overlay.ts
15765
+ function registerEnterpriseOverlayCommand(program2) {
15766
+ const overlay = program2.command("overlay").description(
15767
+ "Validate and apply customer overlay packs from a deployment repo."
15768
+ );
15769
+ overlay.command("plan").argument("[repoRoot]", "Deployment repository root", ".").description(
15770
+ "Validate customer/deployment.json and print the overlay plan."
15771
+ ).option("-s, --stage <name>", "Deployment stage").option("-r, --region <region>", "AWS region", "us-east-1").action(
15772
+ (repoRoot, opts) => runEnterpriseOverlayPlan(repoRoot, opts)
15773
+ );
15774
+ overlay.command("apply").argument("[repoRoot]", "Deployment repository root", ".").description("Apply customer overlay packs to the deployed stage.").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("-r, --region <region>", "AWS region", "us-east-1").option("--dry-run", "Validate and print the apply plan without mutation").action(
15775
+ (repoRoot, opts) => runEnterpriseOverlayApply(repoRoot, opts)
15776
+ );
15777
+ }
15778
+ async function runEnterpriseOverlayPlan(repoRoot, opts) {
15779
+ const plan = buildEnterpriseOverlayPlan({
15780
+ repoRoot: resolve6(repoRoot),
15781
+ stage: resolveOverlayStage(opts)
15782
+ });
15783
+ printJson(publicPlan(plan));
15784
+ }
15785
+ async function runEnterpriseOverlayApply(repoRoot, opts) {
15786
+ const stage = resolveOverlayStage(opts);
15787
+ const plan = buildEnterpriseOverlayPlan({
15788
+ repoRoot: resolve6(repoRoot),
15789
+ stage
15790
+ });
15791
+ if (opts.dryRun) {
15792
+ printJson({ dryRun: true, plan: publicPlan(plan) });
15793
+ return;
15794
+ }
15795
+ const client = await createOverlayApiClient(plan, opts);
15796
+ const result = await applyEnterpriseOverlay(plan, client);
15797
+ if (isJsonMode()) {
15798
+ printJson(result);
15799
+ return;
15800
+ }
15801
+ printSuccess(
15802
+ `Applied overlay for ${plan.tenantSlug}: ${result.evals.inserted} eval(s) inserted, ${result.evals.updated} updated, ${result.workspaceFiles.written} workspace file(s) written.`
15803
+ );
15804
+ }
15805
+ async function createOverlayApiClient(plan, opts) {
15806
+ const region = opts.region ?? "us-east-1";
15807
+ const ctx = await resolveEvalContext({
15808
+ stage: plan.stage,
15809
+ region,
15810
+ tenant: opts.tenant ?? plan.tenantSlug
15811
+ });
15812
+ const templateId = await resolveTemplateId(
15813
+ ctx.client,
15814
+ ctx.tenantId,
15815
+ plan.targetTemplateSlug
15816
+ );
15817
+ const auth = await resolveAuth({ stage: plan.stage, region });
15818
+ const apiUrl = getApiEndpoint(plan.stage, region);
15819
+ if (!apiUrl) {
15820
+ printError(
15821
+ `Cannot discover API endpoint for stage "${plan.stage}" in ${region}.`
15822
+ );
15823
+ process.exit(1);
15824
+ }
15825
+ return {
15826
+ targetAgentTemplateId: templateId,
15827
+ async listEvalTestCases() {
15828
+ const data = await gqlQuery(ctx.client, EvalTestCasesDoc, {
15829
+ tenantId: ctx.tenantId,
15830
+ category: null,
15831
+ search: null
15832
+ });
15833
+ return data.evalTestCases;
15834
+ },
15835
+ async createEvalTestCase(input20) {
15836
+ await gqlMutate(ctx.client, CreateEvalTestCaseDoc, {
15837
+ tenantId: ctx.tenantId,
15838
+ input: evalInput(input20)
15839
+ });
15840
+ },
15841
+ async updateEvalTestCase(id, input20) {
15842
+ await gqlMutate(ctx.client, UpdateEvalTestCaseDoc, {
15843
+ id,
15844
+ input: evalInput(input20)
15845
+ });
15846
+ },
15847
+ async putWorkspaceFile(input20) {
15848
+ const response = await fetch(
15849
+ `${apiUrl.replace(/\/+$/, "")}/api/workspaces/files`,
15850
+ {
15851
+ method: "POST",
15852
+ headers: {
15853
+ "content-type": "application/json",
15854
+ ...auth.headers,
15855
+ "x-tenant-id": ctx.tenantId
15856
+ },
15857
+ body: JSON.stringify({
15858
+ action: "put",
15859
+ templateId,
15860
+ path: input20.path,
15861
+ content: input20.content
15862
+ })
15863
+ }
15864
+ );
15865
+ if (!response.ok) {
15866
+ const text = await response.text();
15867
+ throw new Error(
15868
+ `PUT ${input20.path} failed: ${response.status} ${text || response.statusText}`
15869
+ );
15870
+ }
15871
+ }
15872
+ };
15873
+ }
15874
+ async function resolveTemplateId(client, tenantId, templateSlug) {
15875
+ const data = await gqlQuery(client, AgentTemplatesForEvalDoc, { tenantId });
15876
+ const template = data.agentTemplates.find(
15877
+ (item) => item.slug === templateSlug
15878
+ );
15879
+ if (!template) {
15880
+ throw new Error(
15881
+ `Agent template "${templateSlug}" not found for tenant ${tenantId}`
15882
+ );
15883
+ }
15884
+ return template.id;
15885
+ }
15886
+ function evalInput(input20) {
15887
+ return {
15888
+ name: input20.name,
15889
+ category: input20.category,
15890
+ query: input20.query,
15891
+ systemPrompt: input20.systemPrompt ?? null,
15892
+ agentTemplateId: input20.agentTemplateId ?? null,
15893
+ assertions: input20.assertions,
15894
+ agentcoreEvaluatorIds: input20.agentcoreEvaluatorIds ?? [],
15895
+ tags: input20.tags ?? [],
15896
+ enabled: input20.enabled ?? true
15897
+ };
15898
+ }
15899
+ function publicPlan(plan) {
15900
+ return {
15901
+ stage: plan.stage,
15902
+ tenantSlug: plan.tenantSlug,
15903
+ targetTemplateSlug: plan.targetTemplateSlug,
15904
+ operations: plan.operations.map((operation) => {
15905
+ if (operation.kind === "eval-pack") {
15906
+ return {
15907
+ kind: operation.kind,
15908
+ pack: operation.pack,
15909
+ testCases: operation.testCases.length
15910
+ };
15911
+ }
15912
+ if (operation.kind === "workspace-file-pack") {
15913
+ return {
15914
+ kind: operation.kind,
15915
+ family: operation.family,
15916
+ pack: operation.pack,
15917
+ targetTemplateSlug: operation.targetTemplateSlug,
15918
+ files: operation.files.length
15919
+ };
15920
+ }
15921
+ if (operation.kind === "seed-pack") {
15922
+ return {
15923
+ kind: operation.kind,
15924
+ pack: operation.pack,
15925
+ status: "validated"
15926
+ };
15927
+ }
15928
+ return { kind: operation.kind, status: "recorded" };
15929
+ })
15930
+ };
15931
+ }
15932
+ function resolveOverlayStage(opts) {
15933
+ const stage = opts.stage ?? process.env.STAGE;
15934
+ if (!stage) {
15935
+ throw new Error("Deployment stage is required. Pass --stage or set STAGE.");
15936
+ }
15937
+ return stage;
15938
+ }
15939
+
15940
+ // src/commands/enterprise.ts
15941
+ function registerEnterpriseCommand(program2) {
15942
+ const enterprise = program2.command("enterprise").description(
15943
+ "Bootstrap and operate customer-owned ThinkWork enterprise deployment repositories."
15944
+ );
15945
+ registerEnterpriseBootstrapCommand(enterprise);
15946
+ registerEnterpriseOverlayCommand(enterprise);
15947
+ }
15948
+
14396
15949
  // src/cli.ts
14397
15950
  var program = new Command();
14398
15951
  program.name("thinkwork").description(
@@ -14458,6 +16011,7 @@ registerPerformanceCommand(program);
14458
16011
  registerTraceCommand(program);
14459
16012
  registerDashboardCommand(program);
14460
16013
  registerEvalCommand(program);
16014
+ registerEnterpriseCommand(program);
14461
16015
  registerWikiCommand(program);
14462
16016
  for (const cmd of program.commands) {
14463
16017
  if (!cmd.options.some((o) => o.long === "--json")) {