@sakupa/mcp 0.7.17 → 0.7.19
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 +16 -101
- package/dist/index.js +16 -101
- 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.19";
|
|
133
133
|
|
|
134
134
|
// ../core/dist/domain/errors.js
|
|
135
135
|
var HTTP_STATUS = {
|
|
@@ -603,20 +603,6 @@ var HttpApiClient = class {
|
|
|
603
603
|
}
|
|
604
604
|
);
|
|
605
605
|
}
|
|
606
|
-
async unbindDomain(siteId, credential, req) {
|
|
607
|
-
return this.call(
|
|
608
|
-
"POST",
|
|
609
|
-
`/v1/sites/${encodeURIComponent(siteId)}/domain/unbind`,
|
|
610
|
-
{ credential, body: req }
|
|
611
|
-
);
|
|
612
|
-
}
|
|
613
|
-
async previewUnbindDomain(siteId, credential, req) {
|
|
614
|
-
return this.call(
|
|
615
|
-
"POST",
|
|
616
|
-
`/v1/sites/${encodeURIComponent(siteId)}/domain/unbind/preview`,
|
|
617
|
-
{ credential, body: req }
|
|
618
|
-
);
|
|
619
|
-
}
|
|
620
606
|
async recoverDomain(req) {
|
|
621
607
|
return this.call("POST", "/v1/domains/recover", { body: req });
|
|
622
608
|
}
|
|
@@ -2097,40 +2083,37 @@ ${res2.message}
|
|
|
2097
2083
|
const res = await ctx.client.bindDomain(site.credential, req);
|
|
2098
2084
|
const apex = res.apexDomain;
|
|
2099
2085
|
const txtShort = shortHostFor(res.verificationRecord.name, apex);
|
|
2086
|
+
const switchNotice = res.supersedes ? `NOTE FOR THE USER: this site already serves ${res.supersedes}. ${res.supersedes} keeps working the whole time; once www.${apex} is confirmed live, it automatically REPLACES ${res.supersedes} (zero downtime). Tell the user this before they start.
|
|
2087
|
+
|
|
2088
|
+
` : "";
|
|
2100
2089
|
return text(
|
|
2101
2090
|
"domain_verification_started",
|
|
2102
|
-
`Domain binding started for ${apex} (
|
|
2103
|
-
|
|
2104
|
-
STEP 1 of 2 \u2014 add ALL THREE records NOW:
|
|
2091
|
+
switchNotice + `Domain binding started for ${apex} (will serve: ${res.includedHostnames.join(", ")}).
|
|
2105
2092
|
|
|
2106
|
-
|
|
2107
|
-
2) CNAME host: www value: ${res.servingTarget}
|
|
2108
|
-
3) APEX host: @ -> ${res.servingTarget}. Try a plain CNAME at host @ first (most panels accept it); if rejected use ALIAS / ANAME / CNAME-flattening; if the panel has neither, skip it \u2014 www alone works, and a URL redirect from @ to www covers bare-domain visitors.
|
|
2093
|
+
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.
|
|
2109
2094
|
|
|
2110
|
-
|
|
2095
|
+
STEP 1 of 2 \u2014 prove ownership. Add ONE record:
|
|
2111
2096
|
|
|
2112
|
-
|
|
2097
|
+
TXT host: ${txtShort} value: ${res.verificationRecord.value}
|
|
2113
2098
|
|
|
2114
|
-
|
|
2099
|
+
Host is the SHORT form: most panels append the domain automatically (the saved record must NOT show ${apex} twice in one name). Ownership comes ONLY from DNS control; paying never grants it. The first verified request wins and this challenge expires after 72 hours.
|
|
2115
2100
|
|
|
2116
|
-
|
|
2101
|
+
When the user says the TXT is set, run bind "status". It verifies ownership and then hands back STEP 2 \u2014 a SINGLE www CNAME (there are NO certificate TXT records; HTTPS validates automatically over that CNAME). Each "status" checks the previous step and, unless something is misconfigured, advances to the next \u2014 so run it whenever the user reports a step done, NOT on a timer. Any later session can resume with action "status" alone; the verificationId is optional.`,
|
|
2117
2102
|
{
|
|
2118
2103
|
verificationId: res.verificationId,
|
|
2119
2104
|
apexDomain: apex,
|
|
2120
2105
|
includedHostnames: res.includedHostnames,
|
|
2121
2106
|
verificationRecord: res.verificationRecord,
|
|
2122
2107
|
servingTarget: res.servingTarget,
|
|
2108
|
+
...res.supersedes ? { supersedes: res.supersedes } : {},
|
|
2123
2109
|
requiredRecords: [
|
|
2124
2110
|
{
|
|
2125
2111
|
type: "TXT",
|
|
2126
2112
|
shortHost: txtShort,
|
|
2127
2113
|
name: res.verificationRecord.name,
|
|
2128
2114
|
value: res.verificationRecord.value
|
|
2129
|
-
}
|
|
2130
|
-
{ type: "CNAME", shortHost: "www", name: `www.${apex}`, value: res.servingTarget },
|
|
2131
|
-
{ type: "ALIAS", shortHost: "@", name: apex, value: res.servingTarget }
|
|
2115
|
+
}
|
|
2132
2116
|
],
|
|
2133
|
-
servingInstructions: res.servingInstructions,
|
|
2134
2117
|
retryAfterSeconds: DNS_RETRY_AFTER_SECONDS,
|
|
2135
2118
|
maxAttempts: DNS_MAX_ATTEMPTS
|
|
2136
2119
|
},
|
|
@@ -2536,15 +2519,6 @@ var deleteConfirmation = z5.object({
|
|
|
2536
2519
|
expectedBoundHostnames: z5.array(z5.string()),
|
|
2537
2520
|
acknowledge: z5.literal("delete_and_cancel_renewal")
|
|
2538
2521
|
});
|
|
2539
|
-
var unbindConfirmation = z5.object({
|
|
2540
|
-
siteId: z5.string().min(1),
|
|
2541
|
-
bindingId: z5.string().min(1),
|
|
2542
|
-
expectedBindingUpdatedAt: z5.string().datetime(),
|
|
2543
|
-
expectedBindingStatus: z5.enum(["provisioning", "active"]),
|
|
2544
|
-
apexDomain: z5.string().min(1),
|
|
2545
|
-
expectedBoundHostnames: z5.array(z5.string()),
|
|
2546
|
-
acknowledge: z5.literal("unbind_and_remove_custom_hostnames")
|
|
2547
|
-
});
|
|
2548
2522
|
function registerLifecycleTools(server, baseCtx) {
|
|
2549
2523
|
server.registerTool(
|
|
2550
2524
|
"delete",
|
|
@@ -2606,66 +2580,6 @@ function registerLifecycleTools(server, baseCtx) {
|
|
|
2606
2580
|
}
|
|
2607
2581
|
}
|
|
2608
2582
|
);
|
|
2609
|
-
server.registerTool(
|
|
2610
|
-
"unbind",
|
|
2611
|
-
{
|
|
2612
|
-
description: "Preview or execute removal of the custom apex/www serving surface while preserving the subscription and permanent Sakupa URL.",
|
|
2613
|
-
inputSchema: {
|
|
2614
|
-
projectDir: projectDirInput,
|
|
2615
|
-
action: z5.enum(["preview", "confirm"]),
|
|
2616
|
-
operationId: z5.string().min(1).optional(),
|
|
2617
|
-
confirmation: unbindConfirmation.optional()
|
|
2618
|
-
},
|
|
2619
|
-
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
2620
|
-
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true }
|
|
2621
|
-
},
|
|
2622
|
-
async (args) => {
|
|
2623
|
-
try {
|
|
2624
|
-
const ctx = withProjectDir(baseCtx, args.projectDir);
|
|
2625
|
-
const site = requireSiteFile(ctx);
|
|
2626
|
-
const operationId = args.operationId ?? randomUUID2();
|
|
2627
|
-
if (args.action === "preview") {
|
|
2628
|
-
const preview = await ctx.client.previewUnbindDomain(site.siteId, site.credential, {
|
|
2629
|
-
operationId
|
|
2630
|
-
});
|
|
2631
|
-
return structuredToolResult({
|
|
2632
|
-
schemaVersion: 1,
|
|
2633
|
-
outcome: "waiting_user",
|
|
2634
|
-
resultCode: "unbind_confirmation_required",
|
|
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.`,
|
|
2637
|
-
data: { preview },
|
|
2638
|
-
nextActions: [
|
|
2639
|
-
{ tool: "unbind", allowed: true, reasonCode: "exact_confirmation_required" }
|
|
2640
|
-
]
|
|
2641
|
-
});
|
|
2642
|
-
}
|
|
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
|
-
}
|
|
2649
|
-
const result = await ctx.client.unbindDomain(site.siteId, site.credential, {
|
|
2650
|
-
operationId,
|
|
2651
|
-
confirmation: args.confirmation
|
|
2652
|
-
});
|
|
2653
|
-
const { boundDomain: _removed, ...remaining } = site;
|
|
2654
|
-
writeSiteFile(ctx.projectDir, remaining);
|
|
2655
|
-
return structuredToolResult({
|
|
2656
|
-
schemaVersion: 1,
|
|
2657
|
-
outcome: result.servingDeletionPending ? "pending_provider" : "completed",
|
|
2658
|
-
resultCode: "domain_unbound",
|
|
2659
|
-
operationId,
|
|
2660
|
-
summary: `Custom domain unbound; the subscription, deployed content, and permanent Sakupa URL are unchanged. (project: ${ctx.projectDir})`,
|
|
2661
|
-
data: { result },
|
|
2662
|
-
nextActions: [{ tool: "status", allowed: true }]
|
|
2663
|
-
});
|
|
2664
|
-
} catch (error) {
|
|
2665
|
-
return toolError(error);
|
|
2666
|
-
}
|
|
2667
|
-
}
|
|
2668
|
-
);
|
|
2669
2583
|
}
|
|
2670
2584
|
|
|
2671
2585
|
// src/transport.ts
|
|
@@ -2764,8 +2678,9 @@ Workflow:
|
|
|
2764
2678
|
serving surface alongside the permanent URL. Ownership is proven only by DNS control; the
|
|
2765
2679
|
first verified request wins; unverified requests expire after 72 hours. billing,
|
|
2766
2680
|
upgrade, portal and recover manage the paid
|
|
2767
|
-
lifecycle;
|
|
2768
|
-
|
|
2681
|
+
lifecycle; delete tears the whole site down after explicit confirmation. Binding a
|
|
2682
|
+
NEW domain while one is live is a zero-downtime SWITCH: the old domain keeps serving
|
|
2683
|
+
until the new domain's www is confirmed live, then it is replaced automatically.
|
|
2769
2684
|
Plan changes are confirmed only on Stripe and synchronized by Stripe webhook.
|
|
2770
2685
|
A cancellation keeps the site paid through the current period. Sakupa reverts it to a free
|
|
2771
2686
|
24h site and removes paid data after Stripe sends the signed final-cancellation webhook.
|
|
@@ -2779,7 +2694,7 @@ the user opened (for framework projects, where package.json lives \u2014 never t
|
|
|
2779
2694
|
build folder; output is auto-detected). The server NEVER guesses a directory and refuses
|
|
2780
2695
|
calls without one: only you can see which directory the user is actually in. After every deploy, TELL the user which environment it went to (deploy results carry an
|
|
2781
2696
|
Explicit Environment line: TEST vs PRODUCTION). analyze, deploy, status,
|
|
2782
|
-
refresh
|
|
2697
|
+
refresh and delete echo
|
|
2783
2698
|
the directory they acted on \u2014 verify it matches the user's active project.
|
|
2784
2699
|
|
|
2785
2700
|
When the same operation fails twice in a row, or the user is clearly stuck or
|
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.19";
|
|
128
128
|
|
|
129
129
|
// ../core/dist/domain/errors.js
|
|
130
130
|
var HTTP_STATUS = {
|
|
@@ -673,20 +673,6 @@ var HttpApiClient = class {
|
|
|
673
673
|
}
|
|
674
674
|
);
|
|
675
675
|
}
|
|
676
|
-
async unbindDomain(siteId, credential, req) {
|
|
677
|
-
return this.call(
|
|
678
|
-
"POST",
|
|
679
|
-
`/v1/sites/${encodeURIComponent(siteId)}/domain/unbind`,
|
|
680
|
-
{ credential, body: req }
|
|
681
|
-
);
|
|
682
|
-
}
|
|
683
|
-
async previewUnbindDomain(siteId, credential, req) {
|
|
684
|
-
return this.call(
|
|
685
|
-
"POST",
|
|
686
|
-
`/v1/sites/${encodeURIComponent(siteId)}/domain/unbind/preview`,
|
|
687
|
-
{ credential, body: req }
|
|
688
|
-
);
|
|
689
|
-
}
|
|
690
676
|
async recoverDomain(req) {
|
|
691
677
|
return this.call("POST", "/v1/domains/recover", { body: req });
|
|
692
678
|
}
|
|
@@ -2163,40 +2149,37 @@ ${res2.message}
|
|
|
2163
2149
|
const res = await ctx.client.bindDomain(site.credential, req);
|
|
2164
2150
|
const apex = res.apexDomain;
|
|
2165
2151
|
const txtShort = shortHostFor(res.verificationRecord.name, apex);
|
|
2152
|
+
const switchNotice = res.supersedes ? `NOTE FOR THE USER: this site already serves ${res.supersedes}. ${res.supersedes} keeps working the whole time; once www.${apex} is confirmed live, it automatically REPLACES ${res.supersedes} (zero downtime). Tell the user this before they start.
|
|
2153
|
+
|
|
2154
|
+
` : "";
|
|
2166
2155
|
return text(
|
|
2167
2156
|
"domain_verification_started",
|
|
2168
|
-
`Domain binding started for ${apex} (
|
|
2169
|
-
|
|
2170
|
-
STEP 1 of 2 \u2014 add ALL THREE records NOW:
|
|
2157
|
+
switchNotice + `Domain binding started for ${apex} (will serve: ${res.includedHostnames.join(", ")}).
|
|
2171
2158
|
|
|
2172
|
-
|
|
2173
|
-
2) CNAME host: www value: ${res.servingTarget}
|
|
2174
|
-
3) APEX host: @ -> ${res.servingTarget}. Try a plain CNAME at host @ first (most panels accept it); if rejected use ALIAS / ANAME / CNAME-flattening; if the panel has neither, skip it \u2014 www alone works, and a URL redirect from @ to www covers bare-domain visitors.
|
|
2159
|
+
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.
|
|
2175
2160
|
|
|
2176
|
-
|
|
2161
|
+
STEP 1 of 2 \u2014 prove ownership. Add ONE record:
|
|
2177
2162
|
|
|
2178
|
-
|
|
2163
|
+
TXT host: ${txtShort} value: ${res.verificationRecord.value}
|
|
2179
2164
|
|
|
2180
|
-
|
|
2165
|
+
Host is the SHORT form: most panels append the domain automatically (the saved record must NOT show ${apex} twice in one name). Ownership comes ONLY from DNS control; paying never grants it. The first verified request wins and this challenge expires after 72 hours.
|
|
2181
2166
|
|
|
2182
|
-
|
|
2167
|
+
When the user says the TXT is set, run bind "status". It verifies ownership and then hands back STEP 2 \u2014 a SINGLE www CNAME (there are NO certificate TXT records; HTTPS validates automatically over that CNAME). Each "status" checks the previous step and, unless something is misconfigured, advances to the next \u2014 so run it whenever the user reports a step done, NOT on a timer. Any later session can resume with action "status" alone; the verificationId is optional.`,
|
|
2183
2168
|
{
|
|
2184
2169
|
verificationId: res.verificationId,
|
|
2185
2170
|
apexDomain: apex,
|
|
2186
2171
|
includedHostnames: res.includedHostnames,
|
|
2187
2172
|
verificationRecord: res.verificationRecord,
|
|
2188
2173
|
servingTarget: res.servingTarget,
|
|
2174
|
+
...res.supersedes ? { supersedes: res.supersedes } : {},
|
|
2189
2175
|
requiredRecords: [
|
|
2190
2176
|
{
|
|
2191
2177
|
type: "TXT",
|
|
2192
2178
|
shortHost: txtShort,
|
|
2193
2179
|
name: res.verificationRecord.name,
|
|
2194
2180
|
value: res.verificationRecord.value
|
|
2195
|
-
}
|
|
2196
|
-
{ type: "CNAME", shortHost: "www", name: `www.${apex}`, value: res.servingTarget },
|
|
2197
|
-
{ type: "ALIAS", shortHost: "@", name: apex, value: res.servingTarget }
|
|
2181
|
+
}
|
|
2198
2182
|
],
|
|
2199
|
-
servingInstructions: res.servingInstructions,
|
|
2200
2183
|
retryAfterSeconds: DNS_RETRY_AFTER_SECONDS,
|
|
2201
2184
|
maxAttempts: DNS_MAX_ATTEMPTS
|
|
2202
2185
|
},
|
|
@@ -2530,15 +2513,6 @@ var deleteConfirmation = z4.object({
|
|
|
2530
2513
|
expectedBoundHostnames: z4.array(z4.string()),
|
|
2531
2514
|
acknowledge: z4.literal("delete_and_cancel_renewal")
|
|
2532
2515
|
});
|
|
2533
|
-
var unbindConfirmation = z4.object({
|
|
2534
|
-
siteId: z4.string().min(1),
|
|
2535
|
-
bindingId: z4.string().min(1),
|
|
2536
|
-
expectedBindingUpdatedAt: z4.string().datetime(),
|
|
2537
|
-
expectedBindingStatus: z4.enum(["provisioning", "active"]),
|
|
2538
|
-
apexDomain: z4.string().min(1),
|
|
2539
|
-
expectedBoundHostnames: z4.array(z4.string()),
|
|
2540
|
-
acknowledge: z4.literal("unbind_and_remove_custom_hostnames")
|
|
2541
|
-
});
|
|
2542
2516
|
function registerLifecycleTools(server, baseCtx) {
|
|
2543
2517
|
server.registerTool(
|
|
2544
2518
|
"delete",
|
|
@@ -2600,66 +2574,6 @@ function registerLifecycleTools(server, baseCtx) {
|
|
|
2600
2574
|
}
|
|
2601
2575
|
}
|
|
2602
2576
|
);
|
|
2603
|
-
server.registerTool(
|
|
2604
|
-
"unbind",
|
|
2605
|
-
{
|
|
2606
|
-
description: "Preview or execute removal of the custom apex/www serving surface while preserving the subscription and permanent Sakupa URL.",
|
|
2607
|
-
inputSchema: {
|
|
2608
|
-
projectDir: projectDirInput,
|
|
2609
|
-
action: z4.enum(["preview", "confirm"]),
|
|
2610
|
-
operationId: z4.string().min(1).optional(),
|
|
2611
|
-
confirmation: unbindConfirmation.optional()
|
|
2612
|
-
},
|
|
2613
|
-
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
2614
|
-
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true }
|
|
2615
|
-
},
|
|
2616
|
-
async (args) => {
|
|
2617
|
-
try {
|
|
2618
|
-
const ctx = withProjectDir(baseCtx, args.projectDir);
|
|
2619
|
-
const site = requireSiteFile(ctx);
|
|
2620
|
-
const operationId = args.operationId ?? randomUUID2();
|
|
2621
|
-
if (args.action === "preview") {
|
|
2622
|
-
const preview = await ctx.client.previewUnbindDomain(site.siteId, site.credential, {
|
|
2623
|
-
operationId
|
|
2624
|
-
});
|
|
2625
|
-
return structuredToolResult({
|
|
2626
|
-
schemaVersion: 1,
|
|
2627
|
-
outcome: "waiting_user",
|
|
2628
|
-
resultCode: "unbind_confirmation_required",
|
|
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.`,
|
|
2631
|
-
data: { preview },
|
|
2632
|
-
nextActions: [
|
|
2633
|
-
{ tool: "unbind", allowed: true, reasonCode: "exact_confirmation_required" }
|
|
2634
|
-
]
|
|
2635
|
-
});
|
|
2636
|
-
}
|
|
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
|
-
}
|
|
2643
|
-
const result = await ctx.client.unbindDomain(site.siteId, site.credential, {
|
|
2644
|
-
operationId,
|
|
2645
|
-
confirmation: args.confirmation
|
|
2646
|
-
});
|
|
2647
|
-
const { boundDomain: _removed, ...remaining } = site;
|
|
2648
|
-
writeSiteFile(ctx.projectDir, remaining);
|
|
2649
|
-
return structuredToolResult({
|
|
2650
|
-
schemaVersion: 1,
|
|
2651
|
-
outcome: result.servingDeletionPending ? "pending_provider" : "completed",
|
|
2652
|
-
resultCode: "domain_unbound",
|
|
2653
|
-
operationId,
|
|
2654
|
-
summary: `Custom domain unbound; the subscription, deployed content, and permanent Sakupa URL are unchanged. (project: ${ctx.projectDir})`,
|
|
2655
|
-
data: { result },
|
|
2656
|
-
nextActions: [{ tool: "status", allowed: true }]
|
|
2657
|
-
});
|
|
2658
|
-
} catch (error) {
|
|
2659
|
-
return toolError(error);
|
|
2660
|
-
}
|
|
2661
|
-
}
|
|
2662
|
-
);
|
|
2663
2577
|
}
|
|
2664
2578
|
|
|
2665
2579
|
// src/server.ts
|
|
@@ -2759,8 +2673,9 @@ Workflow:
|
|
|
2759
2673
|
serving surface alongside the permanent URL. Ownership is proven only by DNS control; the
|
|
2760
2674
|
first verified request wins; unverified requests expire after 72 hours. billing,
|
|
2761
2675
|
upgrade, portal and recover manage the paid
|
|
2762
|
-
lifecycle;
|
|
2763
|
-
|
|
2676
|
+
lifecycle; delete tears the whole site down after explicit confirmation. Binding a
|
|
2677
|
+
NEW domain while one is live is a zero-downtime SWITCH: the old domain keeps serving
|
|
2678
|
+
until the new domain's www is confirmed live, then it is replaced automatically.
|
|
2764
2679
|
Plan changes are confirmed only on Stripe and synchronized by Stripe webhook.
|
|
2765
2680
|
A cancellation keeps the site paid through the current period. Sakupa reverts it to a free
|
|
2766
2681
|
24h site and removes paid data after Stripe sends the signed final-cancellation webhook.
|
|
@@ -2774,7 +2689,7 @@ the user opened (for framework projects, where package.json lives \u2014 never t
|
|
|
2774
2689
|
build folder; output is auto-detected). The server NEVER guesses a directory and refuses
|
|
2775
2690
|
calls without one: only you can see which directory the user is actually in. After every deploy, TELL the user which environment it went to (deploy results carry an
|
|
2776
2691
|
Explicit Environment line: TEST vs PRODUCTION). analyze, deploy, status,
|
|
2777
|
-
refresh
|
|
2692
|
+
refresh and delete echo
|
|
2778
2693
|
the directory they acted on \u2014 verify it matches the user's active project.
|
|
2779
2694
|
|
|
2780
2695
|
When the same operation fails twice in a row, or the user is clearly stuck or
|