thinkwork-cli 0.12.3 → 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.
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((
|
|
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)
|
|
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((
|
|
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) =>
|
|
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((
|
|
472
|
+
return new Promise((resolve7) => {
|
|
473
473
|
rl.question(`${message} [y/N] `, (answer) => {
|
|
474
474
|
rl.close();
|
|
475
|
-
|
|
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((
|
|
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
|
-
|
|
567
|
+
resolve7();
|
|
568
568
|
});
|
|
569
569
|
proc.on("error", (err) => {
|
|
570
570
|
printWarning(`post-deploy probe spawn failed: ${err.message}`);
|
|
571
|
-
|
|
571
|
+
resolve7();
|
|
572
572
|
});
|
|
573
573
|
});
|
|
574
574
|
}
|
|
@@ -1025,7 +1025,7 @@ function registerConfigCommand(program2) {
|
|
|
1025
1025
|
import { spawn as spawn3 } from "child_process";
|
|
1026
1026
|
import { resolve } from "path";
|
|
1027
1027
|
function getTerraformOutput(cwd, key) {
|
|
1028
|
-
return new Promise((
|
|
1028
|
+
return new Promise((resolve7, reject) => {
|
|
1029
1029
|
const proc = spawn3("terraform", ["output", "-raw", key], {
|
|
1030
1030
|
cwd,
|
|
1031
1031
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -1033,17 +1033,17 @@ function getTerraformOutput(cwd, key) {
|
|
|
1033
1033
|
let stdout = "";
|
|
1034
1034
|
proc.stdout.on("data", (d) => stdout += d);
|
|
1035
1035
|
proc.on("close", (code) => {
|
|
1036
|
-
if (code === 0)
|
|
1036
|
+
if (code === 0) resolve7(stdout.trim());
|
|
1037
1037
|
else reject(new Error(`terraform output ${key} failed (exit ${code})`));
|
|
1038
1038
|
});
|
|
1039
1039
|
});
|
|
1040
1040
|
}
|
|
1041
1041
|
function runScript(scriptPath, args) {
|
|
1042
|
-
return new Promise((
|
|
1042
|
+
return new Promise((resolve7) => {
|
|
1043
1043
|
const proc = spawn3("bash", [scriptPath, ...args], {
|
|
1044
1044
|
stdio: "inherit"
|
|
1045
1045
|
});
|
|
1046
|
-
proc.on("close", (code) =>
|
|
1046
|
+
proc.on("close", (code) => resolve7(code ?? 1));
|
|
1047
1047
|
});
|
|
1048
1048
|
}
|
|
1049
1049
|
function registerBootstrapCommand(program2) {
|
|
@@ -1418,7 +1418,7 @@ function buildAuthorizeUrl(cognito, redirectUri, state) {
|
|
|
1418
1418
|
return `${cognito.domainUrl}/oauth2/authorize?${params.toString()}`;
|
|
1419
1419
|
}
|
|
1420
1420
|
function waitForCallbackCode(opts) {
|
|
1421
|
-
return new Promise((
|
|
1421
|
+
return new Promise((resolve7, reject) => {
|
|
1422
1422
|
const server = createServer((req, res) => handleRequest(req, res));
|
|
1423
1423
|
let finished = false;
|
|
1424
1424
|
const finish = (err, code) => {
|
|
@@ -1429,7 +1429,7 @@ function waitForCallbackCode(opts) {
|
|
|
1429
1429
|
closer.closeAllConnections?.();
|
|
1430
1430
|
server.close(() => {
|
|
1431
1431
|
if (err) reject(err);
|
|
1432
|
-
else
|
|
1432
|
+
else resolve7(code);
|
|
1433
1433
|
});
|
|
1434
1434
|
};
|
|
1435
1435
|
const timer = setTimeout(() => {
|
|
@@ -1620,10 +1620,10 @@ function escapeHtml(s) {
|
|
|
1620
1620
|
// src/commands/login.ts
|
|
1621
1621
|
function ask(prompt) {
|
|
1622
1622
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
1623
|
-
return new Promise((
|
|
1623
|
+
return new Promise((resolve7) => {
|
|
1624
1624
|
rl.question(prompt, (answer) => {
|
|
1625
1625
|
rl.close();
|
|
1626
|
-
|
|
1626
|
+
resolve7(answer.trim());
|
|
1627
1627
|
});
|
|
1628
1628
|
});
|
|
1629
1629
|
}
|
|
@@ -2184,10 +2184,10 @@ var __dirname = dirname3(fileURLToPath2(import.meta.url));
|
|
|
2184
2184
|
function ask2(prompt, defaultVal = "") {
|
|
2185
2185
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
2186
2186
|
const suffix = defaultVal ? chalk8.dim(` [${defaultVal}]`) : "";
|
|
2187
|
-
return new Promise((
|
|
2187
|
+
return new Promise((resolve7) => {
|
|
2188
2188
|
rl.question(` ${prompt}${suffix}: `, (answer) => {
|
|
2189
2189
|
rl.close();
|
|
2190
|
-
|
|
2190
|
+
resolve7(answer.trim() || defaultVal);
|
|
2191
2191
|
});
|
|
2192
2192
|
});
|
|
2193
2193
|
}
|
|
@@ -3923,7 +3923,7 @@ function registerUpdateCommand(program2) {
|
|
|
3923
3923
|
import { spawn as spawn5 } from "child_process";
|
|
3924
3924
|
import { input as input2, select as select7 } from "@inquirer/prompts";
|
|
3925
3925
|
function getTerraformOutput2(cwd, key) {
|
|
3926
|
-
return new Promise((
|
|
3926
|
+
return new Promise((resolve7, reject) => {
|
|
3927
3927
|
const proc = spawn5("terraform", ["output", "-raw", key], {
|
|
3928
3928
|
cwd,
|
|
3929
3929
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3933,7 +3933,7 @@ function getTerraformOutput2(cwd, key) {
|
|
|
3933
3933
|
proc.stdout.on("data", (d) => stdout += d);
|
|
3934
3934
|
proc.stderr.on("data", (d) => stderr += d);
|
|
3935
3935
|
proc.on("close", (code) => {
|
|
3936
|
-
if (code === 0)
|
|
3936
|
+
if (code === 0) resolve7(stdout.trim());
|
|
3937
3937
|
else
|
|
3938
3938
|
reject(
|
|
3939
3939
|
new Error(
|
|
@@ -3944,7 +3944,7 @@ function getTerraformOutput2(cwd, key) {
|
|
|
3944
3944
|
});
|
|
3945
3945
|
}
|
|
3946
3946
|
function runAwsCognitoReset(userPoolId, username, region) {
|
|
3947
|
-
return new Promise((
|
|
3947
|
+
return new Promise((resolve7) => {
|
|
3948
3948
|
const args = [
|
|
3949
3949
|
"cognito-idp",
|
|
3950
3950
|
"admin-reset-user-password",
|
|
@@ -3961,7 +3961,7 @@ function runAwsCognitoReset(userPoolId, username, region) {
|
|
|
3961
3961
|
let stderr = "";
|
|
3962
3962
|
proc.stdout.on("data", (d) => stdout += d);
|
|
3963
3963
|
proc.stderr.on("data", (d) => stderr += d);
|
|
3964
|
-
proc.on("close", (code) =>
|
|
3964
|
+
proc.on("close", (code) => resolve7({ code: code ?? 1, stdout, stderr }));
|
|
3965
3965
|
});
|
|
3966
3966
|
}
|
|
3967
3967
|
function requireTty2(label) {
|
|
@@ -4701,6 +4701,7 @@ var CliCreateWebhookDocument = { "kind": "Document", "definitions": [{ "kind": "
|
|
|
4701
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" } }] } }] } }] };
|
|
4702
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" } } }] }] } }] };
|
|
4703
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" } }] } }] } }] };
|
|
4704
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" } }] } }] } }] };
|
|
4705
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" } }] } }] } }] };
|
|
4706
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" } }] } }] } }] };
|
|
@@ -4877,6 +4878,7 @@ var documents = {
|
|
|
4877
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,
|
|
4878
4879
|
"\n mutation CliDeleteWebhook($id: ID!) {\n deleteWebhook(id: $id)\n }\n": CliDeleteWebhookDocument,
|
|
4879
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,
|
|
4880
4882
|
"\n query CliWebhookTenantBySlug($slug: String!) {\n tenantBySlug(slug: $slug) {\n id\n }\n }\n": CliWebhookTenantBySlugDocument,
|
|
4881
4883
|
"\n query CliWikiTenantBySlug($slug: String!) {\n tenantBySlug(slug: $slug) {\n id\n slug\n name\n }\n }\n": CliWikiTenantBySlugDocument,
|
|
4882
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,
|
|
@@ -10619,6 +10621,26 @@ var RegenerateWebhookTokenDoc = graphql(`
|
|
|
10619
10621
|
}
|
|
10620
10622
|
}
|
|
10621
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
|
+
`);
|
|
10622
10644
|
var WebhookTenantBySlugDoc = graphql(`
|
|
10623
10645
|
query CliWebhookTenantBySlug($slug: String!) {
|
|
10624
10646
|
tenantBySlug(slug: $slug) {
|
|
@@ -10852,6 +10874,50 @@ async function runWebhookRotate(id, opts) {
|
|
|
10852
10874
|
console.log(" New token (SAVE THIS):");
|
|
10853
10875
|
console.log(` ${wh.token}`);
|
|
10854
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
|
+
}
|
|
10855
10921
|
function notYetImplementedAtApi2(verb) {
|
|
10856
10922
|
printError(
|
|
10857
10923
|
`\`webhook ${verb}\` is not yet implemented at the GraphQL API.
|
|
@@ -10874,7 +10940,9 @@ Examples:
|
|
|
10874
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);
|
|
10875
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"));
|
|
10876
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);
|
|
10877
|
-
wh.command("deliveries <id>").description(
|
|
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);
|
|
10878
10946
|
}
|
|
10879
10947
|
|
|
10880
10948
|
// src/lib/plugin-zip.ts
|
|
@@ -13267,10 +13335,15 @@ async function runEvalSeed(opts) {
|
|
|
13267
13335
|
categories: opts.category && opts.category.length > 0 ? opts.category : null
|
|
13268
13336
|
});
|
|
13269
13337
|
if (isJsonMode()) {
|
|
13270
|
-
printJson({
|
|
13338
|
+
printJson({
|
|
13339
|
+
source: "built-in-yaml-seed",
|
|
13340
|
+
inserted: data.seedEvalTestCases
|
|
13341
|
+
});
|
|
13271
13342
|
return;
|
|
13272
13343
|
}
|
|
13273
|
-
printSuccess(
|
|
13344
|
+
printSuccess(
|
|
13345
|
+
`Seeded ${data.seedEvalTestCases} new test case(s). (Duplicates were skipped.)`
|
|
13346
|
+
);
|
|
13274
13347
|
}
|
|
13275
13348
|
|
|
13276
13349
|
// src/commands/eval/test-case/list.ts
|
|
@@ -15336,12 +15409,541 @@ function printBootstrapSummary(result) {
|
|
|
15336
15409
|
}
|
|
15337
15410
|
}
|
|
15338
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
|
+
|
|
15339
15940
|
// src/commands/enterprise.ts
|
|
15340
15941
|
function registerEnterpriseCommand(program2) {
|
|
15341
15942
|
const enterprise = program2.command("enterprise").description(
|
|
15342
15943
|
"Bootstrap and operate customer-owned ThinkWork enterprise deployment repositories."
|
|
15343
15944
|
);
|
|
15344
15945
|
registerEnterpriseBootstrapCommand(enterprise);
|
|
15946
|
+
registerEnterpriseOverlayCommand(enterprise);
|
|
15345
15947
|
}
|
|
15346
15948
|
|
|
15347
15949
|
// src/cli.ts
|
|
@@ -177,10 +177,14 @@ jobs:
|
|
|
177
177
|
shell: bash
|
|
178
178
|
run: |
|
|
179
179
|
set -euo pipefail
|
|
180
|
-
|
|
180
|
+
CLI_VERSION="${{ steps.lock.outputs.release }}"
|
|
181
|
+
CLI_VERSION="${CLI_VERSION#v}"
|
|
182
|
+
TENANT_SLUG="$(jq -r --arg stage "$STAGE" '.stages[$stage].tenantSlug' customer/deployment.json)"
|
|
183
|
+
npx -y "thinkwork-cli@${CLI_VERSION}" --json enterprise overlay apply . \
|
|
181
184
|
--stage "$STAGE" \
|
|
182
|
-
--
|
|
183
|
-
--
|
|
185
|
+
--region "${{ vars.AWS_REGION }}" \
|
|
186
|
+
--tenant "$TENANT_SLUG" \
|
|
187
|
+
> "$RELEASE_DIR/overlay-report.json"
|
|
184
188
|
|
|
185
189
|
- name: Run smoke checks
|
|
186
190
|
if: ${{ github.event.inputs.run_smokes == 'true' && (github.event.inputs.component == 'all' || github.event.inputs.component == 'smokes') }}
|
|
@@ -23,6 +23,8 @@ artifacts, and customer-specific work lives under `customer/`.
|
|
|
23
23
|
3. Add required environment secrets, including `TF_VAR_db_password` and
|
|
24
24
|
`TF_VAR_api_auth_secret`.
|
|
25
25
|
4. Dispatch `.github/workflows/deploy.yml` for the target stage.
|
|
26
|
+
5. Use `docs/runbook.md` for release upgrades, overlay-only changes,
|
|
27
|
+
rollback, and break-glass guidance.
|
|
26
28
|
|
|
27
29
|
## Customer Overlay
|
|
28
30
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<!-- thinkwork-managed: enterprise-deploy-template -->
|
|
2
|
+
|
|
3
|
+
# ThinkWork Enterprise Deployment Runbook
|
|
4
|
+
|
|
5
|
+
This repository deploys a pinned ThinkWork release into the customer AWS
|
|
6
|
+
account. It is a deployment repository, not a source fork.
|
|
7
|
+
|
|
8
|
+
## Normal Sequence
|
|
9
|
+
|
|
10
|
+
1. Bootstrap the repo once:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
thinkwork enterprise bootstrap . \
|
|
14
|
+
--customer {{CUSTOMER_SLUG}} \
|
|
15
|
+
--repo <github-owner>/<github-repo> \
|
|
16
|
+
--stage dev \
|
|
17
|
+
--stage prod
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
2. Review GitHub Environment settings for each stage.
|
|
21
|
+
3. Add required secrets such as `TF_VAR_db_password` and
|
|
22
|
+
`TF_VAR_api_auth_secret`.
|
|
23
|
+
4. Dispatch `.github/workflows/deploy.yml`.
|
|
24
|
+
5. CI verifies `thinkwork.lock`, prepares release artifacts, runs Terraform,
|
|
25
|
+
updates AgentCore runtimes, applies customer overlays, runs smoke checks,
|
|
26
|
+
and writes a summary artifact.
|
|
27
|
+
|
|
28
|
+
The exact operational path is:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
bootstrap -> workflow dispatch -> CI deploy -> overlay apply -> smoke summary
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Release Upgrades
|
|
35
|
+
|
|
36
|
+
1. Update `thinkwork.lock` to the approved release manifest URL and checksum.
|
|
37
|
+
2. Review ThinkWork release notes for schema or operator changes.
|
|
38
|
+
3. Dispatch the workflow with `component=all`.
|
|
39
|
+
4. Save the deploy summary artifact as evidence.
|
|
40
|
+
|
|
41
|
+
## Overlay-Only Changes
|
|
42
|
+
|
|
43
|
+
1. Edit files under `customer/`.
|
|
44
|
+
2. Run a local dry-run:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
thinkwork enterprise overlay apply . --stage dev --dry-run --json
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
3. Dispatch the workflow with `component=overlays`.
|
|
51
|
+
4. Confirm `overlay-report.json` in the workflow artifacts.
|
|
52
|
+
|
|
53
|
+
## Secrets
|
|
54
|
+
|
|
55
|
+
Never commit secrets. Supported secret homes are:
|
|
56
|
+
|
|
57
|
+
- GitHub Environment secrets for CI inputs.
|
|
58
|
+
- AWS Secrets Manager for deployed application/runtime secrets.
|
|
59
|
+
- SSM Parameter Store for stage configuration consumed by Terraform or runtime
|
|
60
|
+
code.
|
|
61
|
+
|
|
62
|
+
Do not store plaintext secrets in `terraform/stages/*.tfvars`, `.env`,
|
|
63
|
+
`customer/`, or runbook files.
|
|
64
|
+
|
|
65
|
+
## Rollback
|
|
66
|
+
|
|
67
|
+
To roll back application code, restore `thinkwork.lock` to the previously
|
|
68
|
+
working release and dispatch the workflow. To roll back customer overlays,
|
|
69
|
+
revert the customer repo commit and dispatch `component=overlays`.
|
|
70
|
+
|
|
71
|
+
If a deploy fails after Terraform apply but before smoke summary, inspect the
|
|
72
|
+
workflow artifacts first: `release-manifest.json`, `overlay-report.json`, and
|
|
73
|
+
the deploy summary identify which stage failed.
|
|
74
|
+
|
|
75
|
+
## Break Glass
|
|
76
|
+
|
|
77
|
+
A full ThinkWork source fork is emergency debt. Use it only when the customer
|
|
78
|
+
requires source changes that cannot be shipped upstream quickly enough. Record
|
|
79
|
+
the fork reason, owner, and expected upstream PR before deploying it.
|