@sakupa/mcp 0.7.15 → 0.7.17
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 +42 -25
- package/dist/index.js +42 -25
- 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.17";
|
|
133
133
|
|
|
134
134
|
// ../core/dist/domain/errors.js
|
|
135
135
|
var HTTP_STATUS = {
|
|
@@ -1365,8 +1365,8 @@ async function diagnoseBinding(input) {
|
|
|
1365
1365
|
const apexResolves = apexAnswers.length > 0;
|
|
1366
1366
|
const allOk = checks.every((c) => c.state === "ok");
|
|
1367
1367
|
const checklist = checks.map(renderCheck).join("\n") + `
|
|
1368
|
-
[${apexResolves ? "OK" : "MISSING"}] APEX ${apex} \u2014 ` + (apexResolves ? "resolves." : `does not resolve yet. Three-step fix, stop at the first that works: (1) try adding a plain record \u2014 type CNAME, host @, value ${input.servingTarget} (most panels
|
|
1369
|
-
const layers = `Pipeline: [1] public DNS (checked LIVE above) -> [2] Sakupa ownership verification: ${input.verificationStatus} -> [3] HTTPS certificate & serving: ` + (input.provisioning ? "provisioning (
|
|
1368
|
+
[${apexResolves ? "OK" : "MISSING"}] APEX ${apex} \u2014 ` + (apexResolves ? "resolves." : `does not resolve yet. Three-step fix, stop at the first that works: (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) If the panel has neither, skip the apex: www alone works fine (certificates do not depend on the apex record); optionally add a URL redirect from @ to www.`);
|
|
1369
|
+
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 records above are all OK; Sakupa retries automatically every ~5 minutes)." : "starts after verification.");
|
|
1370
1370
|
return { checks, apexResolves, allOk, checklist, layers };
|
|
1371
1371
|
}
|
|
1372
1372
|
var DNS_RETRY_AFTER_SECONDS = 300;
|
|
@@ -1497,8 +1497,10 @@ function requireSiteFile(ctx) {
|
|
|
1497
1497
|
}
|
|
1498
1498
|
var UNAUTHORIZED_SUMMARY = "The server rejected the site credential: the one in .sakupa/site.json no longer matches the server-side verifier. The site itself is intact on the server \u2014 only the local binding file is the problem. Repair the file (restore a backup or undo the local edit). Do NOT delete the .sakupa directory to work around this: the credential is unrecoverable by design, so abandoning it permanently orphans the existing site.";
|
|
1499
1499
|
function toolError(e) {
|
|
1500
|
-
const
|
|
1501
|
-
const
|
|
1500
|
+
const isSakupa = isSakupaError(e);
|
|
1501
|
+
const errorCode = isSakupa ? e.code : "internal";
|
|
1502
|
+
const retryable = errorCode === "rate_limited" || isSakupa && errorCode === "internal";
|
|
1503
|
+
const opaqueUnclassified = errorCode === "internal" && !retryable;
|
|
1502
1504
|
const safeDetailKeys = /* @__PURE__ */ new Set([
|
|
1503
1505
|
"retryAfterSeconds",
|
|
1504
1506
|
"reasonCode",
|
|
@@ -1515,7 +1517,7 @@ function toolError(e) {
|
|
|
1515
1517
|
) : void 0;
|
|
1516
1518
|
const minimumVersion = rawDetails && typeof rawDetails["minimumVersion"] === "string" ? rawDetails["minimumVersion"] : void 0;
|
|
1517
1519
|
const serverGuidance = isSakupaError(e) && errorCode !== "internal" && errorCode !== "unauthorized" && errorCode !== "upgrade_required" && e.message.trim().length > 0 ? e.message : void 0;
|
|
1518
|
-
const safeSummary = e instanceof LocalGuidanceError ? e.message : errorCode === "upgrade_required" ? `This Sakupa MCP client is v${MCP_VERSION}, older than the server's minimum supported version${minimumVersion !== void 0 ? ` (v${minimumVersion})` : ""}, so the server refused the call. To fix it: ask the user to fully restart their MCP client session \u2014 "npx -y @sakupa/mcp@latest" setups fetch the current version on restart (run "npx clear-npx-cache" first if the old version persists); global installs need "npm install -g @sakupa/mcp@latest". After the restart, retry this exact tool call.` : errorCode === "unauthorized" ? UNAUTHORIZED_SUMMARY : serverGuidance ?? (retryable ? "An upstream service is temporarily unavailable or busy; retry shortly." : "The operation failed; no server-internal details are exposed.");
|
|
1520
|
+
const safeSummary = e instanceof LocalGuidanceError ? e.message : errorCode === "upgrade_required" ? `This Sakupa MCP client is v${MCP_VERSION}, older than the server's minimum supported version${minimumVersion !== void 0 ? ` (v${minimumVersion})` : ""}, so the server refused the call. To fix it: ask the user to fully restart their MCP client session \u2014 "npx -y @sakupa/mcp@latest" setups fetch the current version on restart (run "npx clear-npx-cache" first if the old version persists); global installs need "npm install -g @sakupa/mcp@latest". After the restart, retry this exact tool call.` : errorCode === "unauthorized" ? UNAUTHORIZED_SUMMARY : serverGuidance ?? (retryable ? "An upstream service is temporarily unavailable or busy; retry shortly." : opaqueUnclassified ? "This failed with an error Sakupa could not classify, and retrying will not help. Tell the user briefly, then offer to file it: run the report tool to send Sakupa a diagnostic report of what was being attempted so we can investigate." : "The operation failed; no server-internal details are exposed.");
|
|
1519
1521
|
const result = structuredToolResult({
|
|
1520
1522
|
schemaVersion: 1,
|
|
1521
1523
|
outcome: "failed",
|
|
@@ -2109,7 +2111,7 @@ Host fields above are the SHORT form: most DNS panels append the domain automati
|
|
|
2109
2111
|
|
|
2110
2112
|
Ownership comes ONLY from DNS control; paying never grants it. The first verified request wins and this challenge expires after 72 hours.
|
|
2111
2113
|
|
|
2112
|
-
STEP 2 (after ownership verifies):
|
|
2114
|
+
STEP 2 (after ownership verifies): Sakupa issues certificate-validation TXT records \u2014 the verification result and bind "status" list them the moment they exist; relay each to the user and add them too (copy names and values EXACTLY as given). Everything then completes automatically.
|
|
2113
2115
|
|
|
2114
2116
|
Drive the whole flow with bind "status": it live-checks every record and names the exact fix for anything wrong. Re-check every 5 minutes (up to 10 times). Any later session can resume with action "status" alone; the verificationId is optional.`,
|
|
2115
2117
|
{
|
|
@@ -2517,6 +2519,7 @@ function registerBillingTools(server, baseCtx) {
|
|
|
2517
2519
|
}
|
|
2518
2520
|
|
|
2519
2521
|
// src/tools/lifecycle.ts
|
|
2522
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
2520
2523
|
import { z as z5 } from "zod";
|
|
2521
2524
|
var deleteConfirmation = z5.object({
|
|
2522
2525
|
siteId: z5.string().min(1),
|
|
@@ -2560,28 +2563,31 @@ function registerLifecycleTools(server, baseCtx) {
|
|
|
2560
2563
|
try {
|
|
2561
2564
|
const ctx = withProjectDir(baseCtx, args.projectDir);
|
|
2562
2565
|
const site = requireSiteFile(ctx);
|
|
2563
|
-
|
|
2564
|
-
throw new Error("operationId is required for delete");
|
|
2565
|
-
}
|
|
2566
|
+
const operationId = args.operationId ?? randomUUID2();
|
|
2566
2567
|
if (args.action === "preview") {
|
|
2567
2568
|
const preview = await ctx.client.previewDeleteSite(site.siteId, site.credential, {
|
|
2568
|
-
operationId
|
|
2569
|
+
operationId
|
|
2569
2570
|
});
|
|
2570
2571
|
return structuredToolResult({
|
|
2571
2572
|
schemaVersion: 1,
|
|
2572
2573
|
outcome: "waiting_user",
|
|
2573
2574
|
resultCode: "delete_confirmation_required",
|
|
2574
|
-
operationId
|
|
2575
|
-
summary:
|
|
2575
|
+
operationId,
|
|
2576
|
+
summary: `Deletion consequences returned, bound to the current site and billing state; after confirmation the content and the permanent URL are unrecoverable. To proceed, call delete again with action:"confirm", operationId:"${operationId}", and the confirmation object from data.preview.confirmation.`,
|
|
2576
2577
|
data: { preview },
|
|
2577
2578
|
nextActions: [
|
|
2578
2579
|
{ tool: "delete", allowed: true, reasonCode: "exact_confirmation_required" }
|
|
2579
2580
|
]
|
|
2580
2581
|
});
|
|
2581
2582
|
}
|
|
2582
|
-
if (!args.confirmation)
|
|
2583
|
+
if (!args.confirmation) {
|
|
2584
|
+
throw new LocalGuidanceError(
|
|
2585
|
+
"invalid_request",
|
|
2586
|
+
'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.'
|
|
2587
|
+
);
|
|
2588
|
+
}
|
|
2583
2589
|
const result = await ctx.client.deleteSite(site.siteId, site.credential, {
|
|
2584
|
-
operationId
|
|
2590
|
+
operationId,
|
|
2585
2591
|
confirmation: args.confirmation
|
|
2586
2592
|
});
|
|
2587
2593
|
deleteSiteFile(ctx.projectDir);
|
|
@@ -2590,7 +2596,7 @@ function registerLifecycleTools(server, baseCtx) {
|
|
|
2590
2596
|
schemaVersion: 1,
|
|
2591
2597
|
outcome: result.servingDeletionPending ? "pending_provider" : "completed",
|
|
2592
2598
|
resultCode: "site_deleted",
|
|
2593
|
-
operationId
|
|
2599
|
+
operationId,
|
|
2594
2600
|
summary: `Site deleted; the local management credential file was removed from ${ctx.projectDir}.`,
|
|
2595
2601
|
data: { result, projectDir: ctx.projectDir },
|
|
2596
2602
|
nextActions: []
|
|
@@ -2617,26 +2623,31 @@ function registerLifecycleTools(server, baseCtx) {
|
|
|
2617
2623
|
try {
|
|
2618
2624
|
const ctx = withProjectDir(baseCtx, args.projectDir);
|
|
2619
2625
|
const site = requireSiteFile(ctx);
|
|
2620
|
-
|
|
2626
|
+
const operationId = args.operationId ?? randomUUID2();
|
|
2621
2627
|
if (args.action === "preview") {
|
|
2622
2628
|
const preview = await ctx.client.previewUnbindDomain(site.siteId, site.credential, {
|
|
2623
|
-
operationId
|
|
2629
|
+
operationId
|
|
2624
2630
|
});
|
|
2625
2631
|
return structuredToolResult({
|
|
2626
2632
|
schemaVersion: 1,
|
|
2627
2633
|
outcome: "waiting_user",
|
|
2628
2634
|
resultCode: "unbind_confirmation_required",
|
|
2629
|
-
operationId
|
|
2630
|
-
summary:
|
|
2635
|
+
operationId,
|
|
2636
|
+
summary: `Exact binding snapshot returned; unbinding removes ONLY the custom domain \u2014 the subscription, content, and permanent URL stay unchanged. To proceed, call unbind again with action:"confirm", operationId:"${operationId}", and the confirmation object from data.preview.confirmation.`,
|
|
2631
2637
|
data: { preview },
|
|
2632
2638
|
nextActions: [
|
|
2633
2639
|
{ tool: "unbind", allowed: true, reasonCode: "exact_confirmation_required" }
|
|
2634
2640
|
]
|
|
2635
2641
|
});
|
|
2636
2642
|
}
|
|
2637
|
-
if (!args.confirmation)
|
|
2643
|
+
if (!args.confirmation) {
|
|
2644
|
+
throw new LocalGuidanceError(
|
|
2645
|
+
"invalid_request",
|
|
2646
|
+
'To CONFIRM unbind, first call unbind with action:"preview" to get the exact confirmation object bound to the current binding, then call again with action:"confirm", the same operationId, and that confirmation object.'
|
|
2647
|
+
);
|
|
2648
|
+
}
|
|
2638
2649
|
const result = await ctx.client.unbindDomain(site.siteId, site.credential, {
|
|
2639
|
-
operationId
|
|
2650
|
+
operationId,
|
|
2640
2651
|
confirmation: args.confirmation
|
|
2641
2652
|
});
|
|
2642
2653
|
const { boundDomain: _removed, ...remaining } = site;
|
|
@@ -2645,7 +2656,7 @@ function registerLifecycleTools(server, baseCtx) {
|
|
|
2645
2656
|
schemaVersion: 1,
|
|
2646
2657
|
outcome: result.servingDeletionPending ? "pending_provider" : "completed",
|
|
2647
2658
|
resultCode: "domain_unbound",
|
|
2648
|
-
operationId
|
|
2659
|
+
operationId,
|
|
2649
2660
|
summary: `Custom domain unbound; the subscription, deployed content, and permanent Sakupa URL are unchanged. (project: ${ctx.projectDir})`,
|
|
2650
2661
|
data: { result },
|
|
2651
2662
|
nextActions: [{ tool: "status", allowed: true }]
|
|
@@ -2722,8 +2733,10 @@ var FetchTransport = class {
|
|
|
2722
2733
|
});
|
|
2723
2734
|
if (!res.ok) {
|
|
2724
2735
|
const text2 = await res.text().catch(() => "");
|
|
2725
|
-
|
|
2726
|
-
|
|
2736
|
+
const detail = `Upload of "${target.path}" failed with HTTP ${res.status}${text2 ? `: ${text2.slice(0, 200)}` : ""}`;
|
|
2737
|
+
throw new SakupaError(
|
|
2738
|
+
res.status === 429 || res.status >= 500 ? "internal" : "validation_failed",
|
|
2739
|
+
detail
|
|
2727
2740
|
);
|
|
2728
2741
|
}
|
|
2729
2742
|
}
|
|
@@ -2773,6 +2786,10 @@ When the same operation fails twice in a row, or the user is clearly stuck or
|
|
|
2773
2786
|
frustrated, proactively offer report: it files the problem into Sakupa's ticket and
|
|
2774
2787
|
alert stream, and you should attach your own factual account via agentContext.
|
|
2775
2788
|
|
|
2789
|
+
Present every step as Sakupa's own: never attribute DNS, certificates or hosting to
|
|
2790
|
+
underlying infrastructure vendors in front of the user. Relay DNS record names and values
|
|
2791
|
+
VERBATIM (some point into Sakupa's managed certificate network) without naming vendors.
|
|
2792
|
+
|
|
2776
2793
|
Safety boundaries:
|
|
2777
2794
|
- Static output only: no SSR, API routes, middleware, server actions, databases or online builds.
|
|
2778
2795
|
- Never upload source projects, secrets, .env files, private keys, archives, videos or audio.
|
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.17";
|
|
128
128
|
|
|
129
129
|
// ../core/dist/domain/errors.js
|
|
130
130
|
var HTTP_STATUS = {
|
|
@@ -546,8 +546,10 @@ var FetchTransport = class {
|
|
|
546
546
|
});
|
|
547
547
|
if (!res.ok) {
|
|
548
548
|
const text2 = await res.text().catch(() => "");
|
|
549
|
-
|
|
550
|
-
|
|
549
|
+
const detail = `Upload of "${target.path}" failed with HTTP ${res.status}${text2 ? `: ${text2.slice(0, 200)}` : ""}`;
|
|
550
|
+
throw new SakupaError(
|
|
551
|
+
res.status === 429 || res.status >= 500 ? "internal" : "validation_failed",
|
|
552
|
+
detail
|
|
551
553
|
);
|
|
552
554
|
}
|
|
553
555
|
}
|
|
@@ -1378,8 +1380,10 @@ function requireSiteFile(ctx) {
|
|
|
1378
1380
|
}
|
|
1379
1381
|
var UNAUTHORIZED_SUMMARY = "The server rejected the site credential: the one in .sakupa/site.json no longer matches the server-side verifier. The site itself is intact on the server \u2014 only the local binding file is the problem. Repair the file (restore a backup or undo the local edit). Do NOT delete the .sakupa directory to work around this: the credential is unrecoverable by design, so abandoning it permanently orphans the existing site.";
|
|
1380
1382
|
function toolError(e) {
|
|
1381
|
-
const
|
|
1382
|
-
const
|
|
1383
|
+
const isSakupa = isSakupaError(e);
|
|
1384
|
+
const errorCode = isSakupa ? e.code : "internal";
|
|
1385
|
+
const retryable = errorCode === "rate_limited" || isSakupa && errorCode === "internal";
|
|
1386
|
+
const opaqueUnclassified = errorCode === "internal" && !retryable;
|
|
1383
1387
|
const safeDetailKeys = /* @__PURE__ */ new Set([
|
|
1384
1388
|
"retryAfterSeconds",
|
|
1385
1389
|
"reasonCode",
|
|
@@ -1396,7 +1400,7 @@ function toolError(e) {
|
|
|
1396
1400
|
) : void 0;
|
|
1397
1401
|
const minimumVersion = rawDetails && typeof rawDetails["minimumVersion"] === "string" ? rawDetails["minimumVersion"] : void 0;
|
|
1398
1402
|
const serverGuidance = isSakupaError(e) && errorCode !== "internal" && errorCode !== "unauthorized" && errorCode !== "upgrade_required" && e.message.trim().length > 0 ? e.message : void 0;
|
|
1399
|
-
const safeSummary = e instanceof LocalGuidanceError ? e.message : errorCode === "upgrade_required" ? `This Sakupa MCP client is v${MCP_VERSION}, older than the server's minimum supported version${minimumVersion !== void 0 ? ` (v${minimumVersion})` : ""}, so the server refused the call. To fix it: ask the user to fully restart their MCP client session \u2014 "npx -y @sakupa/mcp@latest" setups fetch the current version on restart (run "npx clear-npx-cache" first if the old version persists); global installs need "npm install -g @sakupa/mcp@latest". After the restart, retry this exact tool call.` : errorCode === "unauthorized" ? UNAUTHORIZED_SUMMARY : serverGuidance ?? (retryable ? "An upstream service is temporarily unavailable or busy; retry shortly." : "The operation failed; no server-internal details are exposed.");
|
|
1403
|
+
const safeSummary = e instanceof LocalGuidanceError ? e.message : errorCode === "upgrade_required" ? `This Sakupa MCP client is v${MCP_VERSION}, older than the server's minimum supported version${minimumVersion !== void 0 ? ` (v${minimumVersion})` : ""}, so the server refused the call. To fix it: ask the user to fully restart their MCP client session \u2014 "npx -y @sakupa/mcp@latest" setups fetch the current version on restart (run "npx clear-npx-cache" first if the old version persists); global installs need "npm install -g @sakupa/mcp@latest". After the restart, retry this exact tool call.` : errorCode === "unauthorized" ? UNAUTHORIZED_SUMMARY : serverGuidance ?? (retryable ? "An upstream service is temporarily unavailable or busy; retry shortly." : opaqueUnclassified ? "This failed with an error Sakupa could not classify, and retrying will not help. Tell the user briefly, then offer to file it: run the report tool to send Sakupa a diagnostic report of what was being attempted so we can investigate." : "The operation failed; no server-internal details are exposed.");
|
|
1400
1404
|
const result = structuredToolResult({
|
|
1401
1405
|
schemaVersion: 1,
|
|
1402
1406
|
outcome: "failed",
|
|
@@ -1570,8 +1574,8 @@ async function diagnoseBinding(input) {
|
|
|
1570
1574
|
const apexResolves = apexAnswers.length > 0;
|
|
1571
1575
|
const allOk = checks.every((c) => c.state === "ok");
|
|
1572
1576
|
const checklist = checks.map(renderCheck).join("\n") + `
|
|
1573
|
-
[${apexResolves ? "OK" : "MISSING"}] APEX ${apex} \u2014 ` + (apexResolves ? "resolves." : `does not resolve yet. Three-step fix, stop at the first that works: (1) try adding a plain record \u2014 type CNAME, host @, value ${input.servingTarget} (most panels
|
|
1574
|
-
const layers = `Pipeline: [1] public DNS (checked LIVE above) -> [2] Sakupa ownership verification: ${input.verificationStatus} -> [3] HTTPS certificate & serving: ` + (input.provisioning ? "provisioning (
|
|
1577
|
+
[${apexResolves ? "OK" : "MISSING"}] APEX ${apex} \u2014 ` + (apexResolves ? "resolves." : `does not resolve yet. Three-step fix, stop at the first that works: (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) If the panel has neither, skip the apex: www alone works fine (certificates do not depend on the apex record); optionally add a URL redirect from @ to www.`);
|
|
1578
|
+
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 records above are all OK; Sakupa retries automatically every ~5 minutes)." : "starts after verification.");
|
|
1575
1579
|
return { checks, apexResolves, allOk, checklist, layers };
|
|
1576
1580
|
}
|
|
1577
1581
|
var DNS_RETRY_AFTER_SECONDS = 300;
|
|
@@ -2173,7 +2177,7 @@ Host fields above are the SHORT form: most DNS panels append the domain automati
|
|
|
2173
2177
|
|
|
2174
2178
|
Ownership comes ONLY from DNS control; paying never grants it. The first verified request wins and this challenge expires after 72 hours.
|
|
2175
2179
|
|
|
2176
|
-
STEP 2 (after ownership verifies):
|
|
2180
|
+
STEP 2 (after ownership verifies): Sakupa issues certificate-validation TXT records \u2014 the verification result and bind "status" list them the moment they exist; relay each to the user and add them too (copy names and values EXACTLY as given). Everything then completes automatically.
|
|
2177
2181
|
|
|
2178
2182
|
Drive the whole flow with bind "status": it live-checks every record and names the exact fix for anything wrong. Re-check every 5 minutes (up to 10 times). Any later session can resume with action "status" alone; the verificationId is optional.`,
|
|
2179
2183
|
{
|
|
@@ -2509,6 +2513,7 @@ Summary: ${res.sanitizedSummary}`,
|
|
|
2509
2513
|
}
|
|
2510
2514
|
|
|
2511
2515
|
// src/tools/lifecycle.ts
|
|
2516
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
2512
2517
|
import { z as z4 } from "zod";
|
|
2513
2518
|
var deleteConfirmation = z4.object({
|
|
2514
2519
|
siteId: z4.string().min(1),
|
|
@@ -2552,28 +2557,31 @@ function registerLifecycleTools(server, baseCtx) {
|
|
|
2552
2557
|
try {
|
|
2553
2558
|
const ctx = withProjectDir(baseCtx, args.projectDir);
|
|
2554
2559
|
const site = requireSiteFile(ctx);
|
|
2555
|
-
|
|
2556
|
-
throw new Error("operationId is required for delete");
|
|
2557
|
-
}
|
|
2560
|
+
const operationId = args.operationId ?? randomUUID2();
|
|
2558
2561
|
if (args.action === "preview") {
|
|
2559
2562
|
const preview = await ctx.client.previewDeleteSite(site.siteId, site.credential, {
|
|
2560
|
-
operationId
|
|
2563
|
+
operationId
|
|
2561
2564
|
});
|
|
2562
2565
|
return structuredToolResult({
|
|
2563
2566
|
schemaVersion: 1,
|
|
2564
2567
|
outcome: "waiting_user",
|
|
2565
2568
|
resultCode: "delete_confirmation_required",
|
|
2566
|
-
operationId
|
|
2567
|
-
summary:
|
|
2569
|
+
operationId,
|
|
2570
|
+
summary: `Deletion consequences returned, bound to the current site and billing state; after confirmation the content and the permanent URL are unrecoverable. To proceed, call delete again with action:"confirm", operationId:"${operationId}", and the confirmation object from data.preview.confirmation.`,
|
|
2568
2571
|
data: { preview },
|
|
2569
2572
|
nextActions: [
|
|
2570
2573
|
{ tool: "delete", allowed: true, reasonCode: "exact_confirmation_required" }
|
|
2571
2574
|
]
|
|
2572
2575
|
});
|
|
2573
2576
|
}
|
|
2574
|
-
if (!args.confirmation)
|
|
2577
|
+
if (!args.confirmation) {
|
|
2578
|
+
throw new LocalGuidanceError(
|
|
2579
|
+
"invalid_request",
|
|
2580
|
+
'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.'
|
|
2581
|
+
);
|
|
2582
|
+
}
|
|
2575
2583
|
const result = await ctx.client.deleteSite(site.siteId, site.credential, {
|
|
2576
|
-
operationId
|
|
2584
|
+
operationId,
|
|
2577
2585
|
confirmation: args.confirmation
|
|
2578
2586
|
});
|
|
2579
2587
|
deleteSiteFile(ctx.projectDir);
|
|
@@ -2582,7 +2590,7 @@ function registerLifecycleTools(server, baseCtx) {
|
|
|
2582
2590
|
schemaVersion: 1,
|
|
2583
2591
|
outcome: result.servingDeletionPending ? "pending_provider" : "completed",
|
|
2584
2592
|
resultCode: "site_deleted",
|
|
2585
|
-
operationId
|
|
2593
|
+
operationId,
|
|
2586
2594
|
summary: `Site deleted; the local management credential file was removed from ${ctx.projectDir}.`,
|
|
2587
2595
|
data: { result, projectDir: ctx.projectDir },
|
|
2588
2596
|
nextActions: []
|
|
@@ -2609,26 +2617,31 @@ function registerLifecycleTools(server, baseCtx) {
|
|
|
2609
2617
|
try {
|
|
2610
2618
|
const ctx = withProjectDir(baseCtx, args.projectDir);
|
|
2611
2619
|
const site = requireSiteFile(ctx);
|
|
2612
|
-
|
|
2620
|
+
const operationId = args.operationId ?? randomUUID2();
|
|
2613
2621
|
if (args.action === "preview") {
|
|
2614
2622
|
const preview = await ctx.client.previewUnbindDomain(site.siteId, site.credential, {
|
|
2615
|
-
operationId
|
|
2623
|
+
operationId
|
|
2616
2624
|
});
|
|
2617
2625
|
return structuredToolResult({
|
|
2618
2626
|
schemaVersion: 1,
|
|
2619
2627
|
outcome: "waiting_user",
|
|
2620
2628
|
resultCode: "unbind_confirmation_required",
|
|
2621
|
-
operationId
|
|
2622
|
-
summary:
|
|
2629
|
+
operationId,
|
|
2630
|
+
summary: `Exact binding snapshot returned; unbinding removes ONLY the custom domain \u2014 the subscription, content, and permanent URL stay unchanged. To proceed, call unbind again with action:"confirm", operationId:"${operationId}", and the confirmation object from data.preview.confirmation.`,
|
|
2623
2631
|
data: { preview },
|
|
2624
2632
|
nextActions: [
|
|
2625
2633
|
{ tool: "unbind", allowed: true, reasonCode: "exact_confirmation_required" }
|
|
2626
2634
|
]
|
|
2627
2635
|
});
|
|
2628
2636
|
}
|
|
2629
|
-
if (!args.confirmation)
|
|
2637
|
+
if (!args.confirmation) {
|
|
2638
|
+
throw new LocalGuidanceError(
|
|
2639
|
+
"invalid_request",
|
|
2640
|
+
'To CONFIRM unbind, first call unbind with action:"preview" to get the exact confirmation object bound to the current binding, then call again with action:"confirm", the same operationId, and that confirmation object.'
|
|
2641
|
+
);
|
|
2642
|
+
}
|
|
2630
2643
|
const result = await ctx.client.unbindDomain(site.siteId, site.credential, {
|
|
2631
|
-
operationId
|
|
2644
|
+
operationId,
|
|
2632
2645
|
confirmation: args.confirmation
|
|
2633
2646
|
});
|
|
2634
2647
|
const { boundDomain: _removed, ...remaining } = site;
|
|
@@ -2637,7 +2650,7 @@ function registerLifecycleTools(server, baseCtx) {
|
|
|
2637
2650
|
schemaVersion: 1,
|
|
2638
2651
|
outcome: result.servingDeletionPending ? "pending_provider" : "completed",
|
|
2639
2652
|
resultCode: "domain_unbound",
|
|
2640
|
-
operationId
|
|
2653
|
+
operationId,
|
|
2641
2654
|
summary: `Custom domain unbound; the subscription, deployed content, and permanent Sakupa URL are unchanged. (project: ${ctx.projectDir})`,
|
|
2642
2655
|
data: { result },
|
|
2643
2656
|
nextActions: [{ tool: "status", allowed: true }]
|
|
@@ -2768,6 +2781,10 @@ When the same operation fails twice in a row, or the user is clearly stuck or
|
|
|
2768
2781
|
frustrated, proactively offer report: it files the problem into Sakupa's ticket and
|
|
2769
2782
|
alert stream, and you should attach your own factual account via agentContext.
|
|
2770
2783
|
|
|
2784
|
+
Present every step as Sakupa's own: never attribute DNS, certificates or hosting to
|
|
2785
|
+
underlying infrastructure vendors in front of the user. Relay DNS record names and values
|
|
2786
|
+
VERBATIM (some point into Sakupa's managed certificate network) without naming vendors.
|
|
2787
|
+
|
|
2771
2788
|
Safety boundaries:
|
|
2772
2789
|
- Static output only: no SSR, API routes, middleware, server actions, databases or online builds.
|
|
2773
2790
|
- Never upload source projects, secrets, .env files, private keys, archives, videos or audio.
|