@sakupa/mcp 0.7.19 → 0.7.20
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/bin.js +41 -11
- package/dist/index.js +41 -11
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -129,7 +129,7 @@ var FORBIDDEN_PATH_SEGMENTS = [
|
|
|
129
129
|
var ALLOWED_HIDDEN_PATHS = [".well-known/"];
|
|
130
130
|
|
|
131
131
|
// ../core/dist/domain/version.js
|
|
132
|
-
var SAKUPA_MCP_VERSION = "0.7.
|
|
132
|
+
var SAKUPA_MCP_VERSION = "0.7.20";
|
|
133
133
|
|
|
134
134
|
// ../core/dist/domain/errors.js
|
|
135
135
|
var HTTP_STATUS = {
|
|
@@ -455,6 +455,9 @@ var MCP_VERSION_HEADER = "x-sakupa-mcp-version";
|
|
|
455
455
|
// ../core/dist/services/subscriptions.js
|
|
456
456
|
var WEBHOOK_PROCESSING_LEASE_MS = 5 * 60 * 1e3;
|
|
457
457
|
|
|
458
|
+
// ../core/dist/services/lifecycle.js
|
|
459
|
+
var EPHEMERAL_RETENTION_HOURS = 90 * 24;
|
|
460
|
+
|
|
458
461
|
// src/config.ts
|
|
459
462
|
var TEST_API_BASE_URL = "https://api-test.sakupa.com";
|
|
460
463
|
function previewHostPatternFor(apiBaseUrl) {
|
|
@@ -1351,8 +1354,8 @@ async function diagnoseBinding(input) {
|
|
|
1351
1354
|
const apexResolves = apexAnswers.length > 0;
|
|
1352
1355
|
const allOk = checks.every((c) => c.state === "ok");
|
|
1353
1356
|
const checklist = checks.map(renderCheck).join("\n") + `
|
|
1354
|
-
[${apexResolves ? "OK" : "
|
|
1355
|
-
const layers = `Pipeline: [1] public DNS (checked LIVE above) -> [2] Sakupa ownership verification: ${input.verificationStatus} -> [3] HTTPS certificate & serving: ` + (input.provisioning ? "provisioning (Sakupa's certificate service validates and issues within minutes once the
|
|
1357
|
+
[${apexResolves ? "OK" : "OPTIONAL"}] APEX ${apex} \u2014 ` + (apexResolves ? "resolves; this optional extra entrance is available." : `not configured, which is valid and does NOT block binding. If the user explicitly wants the naked domain too, use the first option their DNS provider supports: (1) try adding a plain record \u2014 type CNAME, host @, value ${input.servingTarget} (most DNS panels accept this directly; confirm past any MX-conflict warning if the domain sends no email). (2) If rejected, look for ALIAS / ANAME / CNAME-flattening in the record-type list \u2014 same host and value. (3) Otherwise leave the apex unconfigured or add a URL redirect from @ to www; never treat this as an error.`);
|
|
1358
|
+
const layers = `Pipeline: [1] public DNS (checked LIVE above) -> [2] Sakupa ownership verification: ${input.verificationStatus} -> [3] HTTPS certificate & serving: ` + (input.provisioning ? "provisioning (Sakupa's certificate service validates and issues within minutes once the required www record above is OK; run bind status again in about 5 minutes to re-check the provider and advance activation)." : "starts after verification.");
|
|
1356
1359
|
return { checks, apexResolves, allOk, checklist, layers };
|
|
1357
1360
|
}
|
|
1358
1361
|
var DNS_RETRY_AFTER_SECONDS = 300;
|
|
@@ -1373,7 +1376,7 @@ ${diag.checklist}
|
|
|
1373
1376
|
|
|
1374
1377
|
${diag.layers}
|
|
1375
1378
|
|
|
1376
|
-
` + (diag.allOk ? "All records are live; certificate issuance completes automatically \u2014 re-check in a few minutes until the binding is active." : `${fixTail} ${cadence}`);
|
|
1379
|
+
` + (diag.allOk ? "All required records are live; certificate issuance completes automatically \u2014 re-check in a few minutes until the binding is active." : `${fixTail} ${cadence}`);
|
|
1377
1380
|
}
|
|
1378
1381
|
|
|
1379
1382
|
// src/version.ts
|
|
@@ -2018,7 +2021,7 @@ Once payment confirms, the site becomes permanent on its current URL. Binding a
|
|
|
2018
2021
|
server.registerTool(
|
|
2019
2022
|
"bind",
|
|
2020
2023
|
{
|
|
2021
|
-
description: `Bind a custom domain to this subscribed site \u2014 an OPTIONAL extra serving surface; the permanent ${previewHostPattern} URL keeps working alongside it. The binding unit is the APEX domain: binding example.com
|
|
2024
|
+
description: `Bind a custom domain to this subscribed site \u2014 an OPTIONAL extra serving surface; the permanent ${previewHostPattern} URL keeps working alongside it. The binding unit is the APEX domain: binding example.com reserves routes for example.com and www.example.com, but ONLY www is required and judged for activation; the naked apex is optional because many DNS providers cannot point it. One apex TXT verification covers both, and one site binds at most ONE apex domain \u2014 a second domain needs a second subscribed site. Requires an ACTIVE subscription (subscribe). Ownership is proven ONLY by DNS control of the apex \u2014 payment never grants ownership, and bindings are ALWAYS challengeable: whoever proves CURRENT DNS control takes the domain, even from an existing binding (the displaced site keeps its subscription, content and permanent URL). Unverified requests expire after 72 hours. Call again with action "status" to check progress.`,
|
|
2022
2025
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
2023
2026
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
2024
2027
|
inputSchema: {
|
|
@@ -2036,7 +2039,7 @@ Once payment confirms, the site becomes permanent on its current URL. Binding a
|
|
|
2036
2039
|
const site = requireSiteFile(ctx);
|
|
2037
2040
|
if (args.action === "status") {
|
|
2038
2041
|
const res2 = args.verificationId ? await ctx.client.checkVerification(args.verificationId, site.credential) : await ctx.client.checkVerification("latest", site.credential, site.siteId);
|
|
2039
|
-
if (res2.
|
|
2042
|
+
if (res2.bindingStatus === "active") {
|
|
2040
2043
|
writeSiteFile(ctx.projectDir, { ...site, boundDomain: res2.apexDomain });
|
|
2041
2044
|
}
|
|
2042
2045
|
const apex2 = res2.apexDomain;
|
|
@@ -2046,11 +2049,11 @@ Once payment confirms, the site becomes permanent on its current URL. Binding a
|
|
|
2046
2049
|
...res2.verificationRecord ? { verificationRecord: res2.verificationRecord } : {},
|
|
2047
2050
|
pendingDnsRecords: res2.pendingDnsRecords,
|
|
2048
2051
|
verificationStatus: res2.status,
|
|
2049
|
-
provisioning: res2.
|
|
2052
|
+
provisioning: res2.bindingStatus === "provisioning"
|
|
2050
2053
|
});
|
|
2051
2054
|
const { apexResolves, allOk } = diag;
|
|
2052
2055
|
return text(
|
|
2053
|
-
res2.status === "verified" ? "domain_verification_succeeded" : "domain_verification_pending",
|
|
2056
|
+
res2.bindingStatus === "active" ? "domain_binding_active" : res2.status === "verified" ? "domain_verification_succeeded" : "domain_verification_pending",
|
|
2054
2057
|
`Domain binding status for ${apex2}: ${res2.status}
|
|
2055
2058
|
${res2.message}
|
|
2056
2059
|
|
|
@@ -2061,16 +2064,20 @@ ${res2.message}
|
|
|
2061
2064
|
{
|
|
2062
2065
|
verificationId: res2.verificationId,
|
|
2063
2066
|
status: res2.status,
|
|
2067
|
+
bindingStatus: res2.bindingStatus,
|
|
2064
2068
|
apexDomain: apex2,
|
|
2065
2069
|
provisioningJobId: res2.provisioningJobId,
|
|
2066
2070
|
servingTarget: res2.servingTarget,
|
|
2067
2071
|
dnsChecklist: toDnsChecklist(diag.checks),
|
|
2068
2072
|
apexResolves,
|
|
2073
|
+
apexRequired: false,
|
|
2074
|
+
requiredServingHostname: `www.${apex2}`,
|
|
2075
|
+
optionalApexHostname: apex2,
|
|
2069
2076
|
retryAfterSeconds: DNS_RETRY_AFTER_SECONDS,
|
|
2070
2077
|
maxAttempts: DNS_MAX_ATTEMPTS,
|
|
2071
2078
|
message: res2.message
|
|
2072
2079
|
},
|
|
2073
|
-
res2.status === "verified" ? "pending_provider" : "waiting_user"
|
|
2080
|
+
res2.bindingStatus === "active" ? "completed" : res2.status === "verified" ? "pending_provider" : "waiting_user"
|
|
2074
2081
|
);
|
|
2075
2082
|
}
|
|
2076
2083
|
if (!args.hostname) {
|
|
@@ -2088,7 +2095,7 @@ ${res2.message}
|
|
|
2088
2095
|
` : "";
|
|
2089
2096
|
return text(
|
|
2090
2097
|
"domain_verification_started",
|
|
2091
|
-
switchNotice + `Domain binding started for ${apex} (
|
|
2098
|
+
switchNotice + `Domain binding started for ${apex} (routes reserved: ${res.includedHostnames.join(", ")}). Only www.${apex} is required to go live; the naked domain is optional.
|
|
2092
2099
|
|
|
2093
2100
|
This is a STEP-BY-STEP setup \u2014 give the user ONE record at a time so they do not get overwhelmed and give up.
|
|
2094
2101
|
|
|
@@ -2103,6 +2110,9 @@ When the user says the TXT is set, run bind "status". It verifies ownership and
|
|
|
2103
2110
|
verificationId: res.verificationId,
|
|
2104
2111
|
apexDomain: apex,
|
|
2105
2112
|
includedHostnames: res.includedHostnames,
|
|
2113
|
+
apexRequired: false,
|
|
2114
|
+
requiredServingHostname: `www.${apex}`,
|
|
2115
|
+
optionalApexHostname: apex,
|
|
2106
2116
|
verificationRecord: res.verificationRecord,
|
|
2107
2117
|
servingTarget: res.servingTarget,
|
|
2108
2118
|
...res.supersedes ? { supersedes: res.supersedes } : {},
|
|
@@ -2523,7 +2533,7 @@ function registerLifecycleTools(server, baseCtx) {
|
|
|
2523
2533
|
server.registerTool(
|
|
2524
2534
|
"delete",
|
|
2525
2535
|
{
|
|
2526
|
-
description: "Preview or execute deletion of this Sakupa site. Execution requires an exact server-validated confirmation bound to the current site state.",
|
|
2536
|
+
description: "Preview or execute deletion of this Sakupa site. Execution requires an exact server-validated confirmation bound to the current site state. Paid sites must first cancel renewal through portal and return to free mode. For a temporary pause, publish a pause notice as index.html with deploy instead of deleting the site.",
|
|
2527
2537
|
inputSchema: {
|
|
2528
2538
|
projectDir: projectDirInput,
|
|
2529
2539
|
action: z5.enum(["preview", "confirm"]),
|
|
@@ -2542,6 +2552,20 @@ function registerLifecycleTools(server, baseCtx) {
|
|
|
2542
2552
|
const preview = await ctx.client.previewDeleteSite(site.siteId, site.credential, {
|
|
2543
2553
|
operationId
|
|
2544
2554
|
});
|
|
2555
|
+
if (preview.consequences.requiresFreeModeBeforeDelete) {
|
|
2556
|
+
return structuredToolResult({
|
|
2557
|
+
schemaVersion: 1,
|
|
2558
|
+
outcome: "waiting_user",
|
|
2559
|
+
resultCode: "paid_site_must_return_to_free",
|
|
2560
|
+
operationId,
|
|
2561
|
+
summary: "This paid site cannot be deleted yet. Open portal to cancel renewal, then wait until Stripe ends the subscription and Sakupa reports free mode before calling delete again. If the goal is only a temporary pause, edit the site index.html to show a pause notice and call deploy; this keeps the subscription and URL.",
|
|
2562
|
+
data: { preview },
|
|
2563
|
+
nextActions: [
|
|
2564
|
+
{ tool: "portal", allowed: true, reasonCode: "cancel_renewal_first" },
|
|
2565
|
+
{ tool: "deploy", allowed: true, reasonCode: "temporary_pause_alternative" }
|
|
2566
|
+
]
|
|
2567
|
+
});
|
|
2568
|
+
}
|
|
2545
2569
|
return structuredToolResult({
|
|
2546
2570
|
schemaVersion: 1,
|
|
2547
2571
|
outcome: "waiting_user",
|
|
@@ -2560,6 +2584,12 @@ function registerLifecycleTools(server, baseCtx) {
|
|
|
2560
2584
|
'To CONFIRM deletion, first call delete with action:"preview" to get the exact confirmation object bound to the current site state, then call again with action:"confirm", the same operationId, and that confirmation object.'
|
|
2561
2585
|
);
|
|
2562
2586
|
}
|
|
2587
|
+
if (args.confirmation.expectedMode === "paid") {
|
|
2588
|
+
throw new LocalGuidanceError(
|
|
2589
|
+
"state_conflict",
|
|
2590
|
+
"A paid site cannot be deleted. Cancel renewal through portal and wait until it returns to free mode. For a temporary pause, publish a pause notice as index.html."
|
|
2591
|
+
);
|
|
2592
|
+
}
|
|
2563
2593
|
const result = await ctx.client.deleteSite(site.siteId, site.credential, {
|
|
2564
2594
|
operationId,
|
|
2565
2595
|
confirmation: args.confirmation
|
package/dist/index.js
CHANGED
|
@@ -124,7 +124,7 @@ var FORBIDDEN_PATH_SEGMENTS = [
|
|
|
124
124
|
var ALLOWED_HIDDEN_PATHS = [".well-known/"];
|
|
125
125
|
|
|
126
126
|
// ../core/dist/domain/version.js
|
|
127
|
-
var SAKUPA_MCP_VERSION = "0.7.
|
|
127
|
+
var SAKUPA_MCP_VERSION = "0.7.20";
|
|
128
128
|
|
|
129
129
|
// ../core/dist/domain/errors.js
|
|
130
130
|
var HTTP_STATUS = {
|
|
@@ -450,6 +450,9 @@ var MCP_VERSION_HEADER = "x-sakupa-mcp-version";
|
|
|
450
450
|
// ../core/dist/services/subscriptions.js
|
|
451
451
|
var WEBHOOK_PROCESSING_LEASE_MS = 5 * 60 * 1e3;
|
|
452
452
|
|
|
453
|
+
// ../core/dist/services/lifecycle.js
|
|
454
|
+
var EPHEMERAL_RETENTION_HOURS = 90 * 24;
|
|
455
|
+
|
|
453
456
|
// src/version.ts
|
|
454
457
|
var MCP_VERSION = SAKUPA_MCP_VERSION;
|
|
455
458
|
var CLIENT_TYPE = "sakupa-mcp";
|
|
@@ -1560,8 +1563,8 @@ async function diagnoseBinding(input) {
|
|
|
1560
1563
|
const apexResolves = apexAnswers.length > 0;
|
|
1561
1564
|
const allOk = checks.every((c) => c.state === "ok");
|
|
1562
1565
|
const checklist = checks.map(renderCheck).join("\n") + `
|
|
1563
|
-
[${apexResolves ? "OK" : "
|
|
1564
|
-
const layers = `Pipeline: [1] public DNS (checked LIVE above) -> [2] Sakupa ownership verification: ${input.verificationStatus} -> [3] HTTPS certificate & serving: ` + (input.provisioning ? "provisioning (Sakupa's certificate service validates and issues within minutes once the
|
|
1566
|
+
[${apexResolves ? "OK" : "OPTIONAL"}] APEX ${apex} \u2014 ` + (apexResolves ? "resolves; this optional extra entrance is available." : `not configured, which is valid and does NOT block binding. If the user explicitly wants the naked domain too, use the first option their DNS provider supports: (1) try adding a plain record \u2014 type CNAME, host @, value ${input.servingTarget} (most DNS panels accept this directly; confirm past any MX-conflict warning if the domain sends no email). (2) If rejected, look for ALIAS / ANAME / CNAME-flattening in the record-type list \u2014 same host and value. (3) Otherwise leave the apex unconfigured or add a URL redirect from @ to www; never treat this as an error.`);
|
|
1567
|
+
const layers = `Pipeline: [1] public DNS (checked LIVE above) -> [2] Sakupa ownership verification: ${input.verificationStatus} -> [3] HTTPS certificate & serving: ` + (input.provisioning ? "provisioning (Sakupa's certificate service validates and issues within minutes once the required www record above is OK; run bind status again in about 5 minutes to re-check the provider and advance activation)." : "starts after verification.");
|
|
1565
1568
|
return { checks, apexResolves, allOk, checklist, layers };
|
|
1566
1569
|
}
|
|
1567
1570
|
var DNS_RETRY_AFTER_SECONDS = 300;
|
|
@@ -1582,7 +1585,7 @@ ${diag.checklist}
|
|
|
1582
1585
|
|
|
1583
1586
|
${diag.layers}
|
|
1584
1587
|
|
|
1585
|
-
` + (diag.allOk ? "All records are live; certificate issuance completes automatically \u2014 re-check in a few minutes until the binding is active." : `${fixTail} ${cadence}`);
|
|
1588
|
+
` + (diag.allOk ? "All required records are live; certificate issuance completes automatically \u2014 re-check in a few minutes until the binding is active." : `${fixTail} ${cadence}`);
|
|
1586
1589
|
}
|
|
1587
1590
|
|
|
1588
1591
|
// src/tools/definitions.ts
|
|
@@ -2084,7 +2087,7 @@ Once payment confirms, the site becomes permanent on its current URL. Binding a
|
|
|
2084
2087
|
server.registerTool(
|
|
2085
2088
|
"bind",
|
|
2086
2089
|
{
|
|
2087
|
-
description: `Bind a custom domain to this subscribed site \u2014 an OPTIONAL extra serving surface; the permanent ${previewHostPattern} URL keeps working alongside it. The binding unit is the APEX domain: binding example.com
|
|
2090
|
+
description: `Bind a custom domain to this subscribed site \u2014 an OPTIONAL extra serving surface; the permanent ${previewHostPattern} URL keeps working alongside it. The binding unit is the APEX domain: binding example.com reserves routes for example.com and www.example.com, but ONLY www is required and judged for activation; the naked apex is optional because many DNS providers cannot point it. One apex TXT verification covers both, and one site binds at most ONE apex domain \u2014 a second domain needs a second subscribed site. Requires an ACTIVE subscription (subscribe). Ownership is proven ONLY by DNS control of the apex \u2014 payment never grants ownership, and bindings are ALWAYS challengeable: whoever proves CURRENT DNS control takes the domain, even from an existing binding (the displaced site keeps its subscription, content and permanent URL). Unverified requests expire after 72 hours. Call again with action "status" to check progress.`,
|
|
2088
2091
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
2089
2092
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
2090
2093
|
inputSchema: {
|
|
@@ -2102,7 +2105,7 @@ Once payment confirms, the site becomes permanent on its current URL. Binding a
|
|
|
2102
2105
|
const site = requireSiteFile(ctx);
|
|
2103
2106
|
if (args.action === "status") {
|
|
2104
2107
|
const res2 = args.verificationId ? await ctx.client.checkVerification(args.verificationId, site.credential) : await ctx.client.checkVerification("latest", site.credential, site.siteId);
|
|
2105
|
-
if (res2.
|
|
2108
|
+
if (res2.bindingStatus === "active") {
|
|
2106
2109
|
writeSiteFile(ctx.projectDir, { ...site, boundDomain: res2.apexDomain });
|
|
2107
2110
|
}
|
|
2108
2111
|
const apex2 = res2.apexDomain;
|
|
@@ -2112,11 +2115,11 @@ Once payment confirms, the site becomes permanent on its current URL. Binding a
|
|
|
2112
2115
|
...res2.verificationRecord ? { verificationRecord: res2.verificationRecord } : {},
|
|
2113
2116
|
pendingDnsRecords: res2.pendingDnsRecords,
|
|
2114
2117
|
verificationStatus: res2.status,
|
|
2115
|
-
provisioning: res2.
|
|
2118
|
+
provisioning: res2.bindingStatus === "provisioning"
|
|
2116
2119
|
});
|
|
2117
2120
|
const { apexResolves, allOk } = diag;
|
|
2118
2121
|
return text(
|
|
2119
|
-
res2.status === "verified" ? "domain_verification_succeeded" : "domain_verification_pending",
|
|
2122
|
+
res2.bindingStatus === "active" ? "domain_binding_active" : res2.status === "verified" ? "domain_verification_succeeded" : "domain_verification_pending",
|
|
2120
2123
|
`Domain binding status for ${apex2}: ${res2.status}
|
|
2121
2124
|
${res2.message}
|
|
2122
2125
|
|
|
@@ -2127,16 +2130,20 @@ ${res2.message}
|
|
|
2127
2130
|
{
|
|
2128
2131
|
verificationId: res2.verificationId,
|
|
2129
2132
|
status: res2.status,
|
|
2133
|
+
bindingStatus: res2.bindingStatus,
|
|
2130
2134
|
apexDomain: apex2,
|
|
2131
2135
|
provisioningJobId: res2.provisioningJobId,
|
|
2132
2136
|
servingTarget: res2.servingTarget,
|
|
2133
2137
|
dnsChecklist: toDnsChecklist(diag.checks),
|
|
2134
2138
|
apexResolves,
|
|
2139
|
+
apexRequired: false,
|
|
2140
|
+
requiredServingHostname: `www.${apex2}`,
|
|
2141
|
+
optionalApexHostname: apex2,
|
|
2135
2142
|
retryAfterSeconds: DNS_RETRY_AFTER_SECONDS,
|
|
2136
2143
|
maxAttempts: DNS_MAX_ATTEMPTS,
|
|
2137
2144
|
message: res2.message
|
|
2138
2145
|
},
|
|
2139
|
-
res2.status === "verified" ? "pending_provider" : "waiting_user"
|
|
2146
|
+
res2.bindingStatus === "active" ? "completed" : res2.status === "verified" ? "pending_provider" : "waiting_user"
|
|
2140
2147
|
);
|
|
2141
2148
|
}
|
|
2142
2149
|
if (!args.hostname) {
|
|
@@ -2154,7 +2161,7 @@ ${res2.message}
|
|
|
2154
2161
|
` : "";
|
|
2155
2162
|
return text(
|
|
2156
2163
|
"domain_verification_started",
|
|
2157
|
-
switchNotice + `Domain binding started for ${apex} (
|
|
2164
|
+
switchNotice + `Domain binding started for ${apex} (routes reserved: ${res.includedHostnames.join(", ")}). Only www.${apex} is required to go live; the naked domain is optional.
|
|
2158
2165
|
|
|
2159
2166
|
This is a STEP-BY-STEP setup \u2014 give the user ONE record at a time so they do not get overwhelmed and give up.
|
|
2160
2167
|
|
|
@@ -2169,6 +2176,9 @@ When the user says the TXT is set, run bind "status". It verifies ownership and
|
|
|
2169
2176
|
verificationId: res.verificationId,
|
|
2170
2177
|
apexDomain: apex,
|
|
2171
2178
|
includedHostnames: res.includedHostnames,
|
|
2179
|
+
apexRequired: false,
|
|
2180
|
+
requiredServingHostname: `www.${apex}`,
|
|
2181
|
+
optionalApexHostname: apex,
|
|
2172
2182
|
verificationRecord: res.verificationRecord,
|
|
2173
2183
|
servingTarget: res.servingTarget,
|
|
2174
2184
|
...res.supersedes ? { supersedes: res.supersedes } : {},
|
|
@@ -2517,7 +2527,7 @@ function registerLifecycleTools(server, baseCtx) {
|
|
|
2517
2527
|
server.registerTool(
|
|
2518
2528
|
"delete",
|
|
2519
2529
|
{
|
|
2520
|
-
description: "Preview or execute deletion of this Sakupa site. Execution requires an exact server-validated confirmation bound to the current site state.",
|
|
2530
|
+
description: "Preview or execute deletion of this Sakupa site. Execution requires an exact server-validated confirmation bound to the current site state. Paid sites must first cancel renewal through portal and return to free mode. For a temporary pause, publish a pause notice as index.html with deploy instead of deleting the site.",
|
|
2521
2531
|
inputSchema: {
|
|
2522
2532
|
projectDir: projectDirInput,
|
|
2523
2533
|
action: z4.enum(["preview", "confirm"]),
|
|
@@ -2536,6 +2546,20 @@ function registerLifecycleTools(server, baseCtx) {
|
|
|
2536
2546
|
const preview = await ctx.client.previewDeleteSite(site.siteId, site.credential, {
|
|
2537
2547
|
operationId
|
|
2538
2548
|
});
|
|
2549
|
+
if (preview.consequences.requiresFreeModeBeforeDelete) {
|
|
2550
|
+
return structuredToolResult({
|
|
2551
|
+
schemaVersion: 1,
|
|
2552
|
+
outcome: "waiting_user",
|
|
2553
|
+
resultCode: "paid_site_must_return_to_free",
|
|
2554
|
+
operationId,
|
|
2555
|
+
summary: "This paid site cannot be deleted yet. Open portal to cancel renewal, then wait until Stripe ends the subscription and Sakupa reports free mode before calling delete again. If the goal is only a temporary pause, edit the site index.html to show a pause notice and call deploy; this keeps the subscription and URL.",
|
|
2556
|
+
data: { preview },
|
|
2557
|
+
nextActions: [
|
|
2558
|
+
{ tool: "portal", allowed: true, reasonCode: "cancel_renewal_first" },
|
|
2559
|
+
{ tool: "deploy", allowed: true, reasonCode: "temporary_pause_alternative" }
|
|
2560
|
+
]
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2539
2563
|
return structuredToolResult({
|
|
2540
2564
|
schemaVersion: 1,
|
|
2541
2565
|
outcome: "waiting_user",
|
|
@@ -2554,6 +2578,12 @@ function registerLifecycleTools(server, baseCtx) {
|
|
|
2554
2578
|
'To CONFIRM deletion, first call delete with action:"preview" to get the exact confirmation object bound to the current site state, then call again with action:"confirm", the same operationId, and that confirmation object.'
|
|
2555
2579
|
);
|
|
2556
2580
|
}
|
|
2581
|
+
if (args.confirmation.expectedMode === "paid") {
|
|
2582
|
+
throw new LocalGuidanceError(
|
|
2583
|
+
"state_conflict",
|
|
2584
|
+
"A paid site cannot be deleted. Cancel renewal through portal and wait until it returns to free mode. For a temporary pause, publish a pause notice as index.html."
|
|
2585
|
+
);
|
|
2586
|
+
}
|
|
2557
2587
|
const result = await ctx.client.deleteSite(site.siteId, site.credential, {
|
|
2558
2588
|
operationId,
|
|
2559
2589
|
confirmation: args.confirmation
|