@sakupa/mcp 1.4.0 → 1.5.0
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 +238 -24
- package/dist/index.js +238 -24
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -259,7 +259,8 @@ var DEFAULT_API_BASE_URL = "https://api.sakupa.com";
|
|
|
259
259
|
var FREE_SITE_URL_SUFFIX = `.${SERVICE_DOMAIN}`;
|
|
260
260
|
var TEST_ACCESS_HEADER = "x-sakupa-test-token";
|
|
261
261
|
var CREDENTIAL_ROTATION_RECOMMEND_AFTER_SECONDS = 7 * 24 * 60 * 60;
|
|
262
|
-
var
|
|
262
|
+
var FREE_SITE_TTL_DAYS = 30;
|
|
263
|
+
var FREE_SITE_TTL_HOURS = FREE_SITE_TTL_DAYS * 24;
|
|
263
264
|
var FREE_SITE_MAX_TOTAL_BYTES = 10 * 1024 * 1024;
|
|
264
265
|
var FREE_ACTIVE_SITES_PER_IP = 3;
|
|
265
266
|
var PAID_SITE_MAX_TOTAL_BYTES = 2 * 1024 * 1024 * 1024;
|
|
@@ -402,7 +403,7 @@ function isFreeSiteAllowanceNetworkReference(value) {
|
|
|
402
403
|
}
|
|
403
404
|
|
|
404
405
|
// ../core/dist/domain/version.js
|
|
405
|
-
var SAKUPA_MCP_VERSION = "1.
|
|
406
|
+
var SAKUPA_MCP_VERSION = "1.5.0";
|
|
406
407
|
|
|
407
408
|
// ../core/dist/domain/errors.js
|
|
408
409
|
var HTTP_STATUS = {
|
|
@@ -2207,15 +2208,15 @@ function strFromU8(dat, latin1) {
|
|
|
2207
2208
|
var slzh = function(d, b) {
|
|
2208
2209
|
return b + 30 + b2(d, b + 26) + b2(d, b + 28);
|
|
2209
2210
|
};
|
|
2210
|
-
var zh = function(d, b,
|
|
2211
|
+
var zh = function(d, b, z8) {
|
|
2211
2212
|
var fnl = b2(d, b + 28), efl = b2(d, b + 30), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl;
|
|
2212
|
-
var _a2 = z64hs(d, es, efl,
|
|
2213
|
+
var _a2 = z64hs(d, es, efl, z8, b4(d, b + 20), b4(d, b + 24), b4(d, b + 42)), sc = _a2[0], su = _a2[1], off = _a2[2];
|
|
2213
2214
|
return [b2(d, b + 10), sc, su, fn, es + efl + b2(d, b + 32), off];
|
|
2214
2215
|
};
|
|
2215
|
-
var z64hs = function(d, b, l,
|
|
2216
|
+
var z64hs = function(d, b, l, z8, sc, su, off) {
|
|
2216
2217
|
var nsc = sc == 4294967295, nsu = su == 4294967295, noff = off == 4294967295, e = b + l;
|
|
2217
2218
|
var nf = nsc + nsu + noff;
|
|
2218
|
-
if (
|
|
2219
|
+
if (z8 && nf) {
|
|
2219
2220
|
for (; b + 4 < e; b += 4 + b2(d, b + 2)) {
|
|
2220
2221
|
if (b2(d, b) == 1) {
|
|
2221
2222
|
return [
|
|
@@ -2226,7 +2227,7 @@ var z64hs = function(d, b, l, z7, sc, su, off) {
|
|
|
2226
2227
|
];
|
|
2227
2228
|
}
|
|
2228
2229
|
}
|
|
2229
|
-
if (
|
|
2230
|
+
if (z8 < 2)
|
|
2230
2231
|
err(13);
|
|
2231
2232
|
}
|
|
2232
2233
|
return [sc, su, off, 0];
|
|
@@ -2243,18 +2244,18 @@ function unzipSync(data, opts) {
|
|
|
2243
2244
|
if (!c)
|
|
2244
2245
|
return {};
|
|
2245
2246
|
var o = b4(data, e + 16);
|
|
2246
|
-
var
|
|
2247
|
-
if (
|
|
2247
|
+
var z8 = b4(data, e - 20) == 117853008;
|
|
2248
|
+
if (z8) {
|
|
2248
2249
|
var ze = b4(data, e - 12);
|
|
2249
|
-
|
|
2250
|
-
if (
|
|
2250
|
+
z8 = b4(data, ze) == 101075792;
|
|
2251
|
+
if (z8) {
|
|
2251
2252
|
c = b4(data, ze + 32);
|
|
2252
2253
|
o = b4(data, ze + 48);
|
|
2253
2254
|
}
|
|
2254
2255
|
}
|
|
2255
2256
|
var fltr = opts && opts.filter;
|
|
2256
2257
|
for (var i = 0; i < c; ++i) {
|
|
2257
|
-
var _a2 = zh(data, o,
|
|
2258
|
+
var _a2 = zh(data, o, z8), c_2 = _a2[0], sc = _a2[1], su = _a2[2], fn = _a2[3], no = _a2[4], off = _a2[5], b = slzh(data, off);
|
|
2258
2259
|
o = no;
|
|
2259
2260
|
if (!fltr || fltr({
|
|
2260
2261
|
name: fn,
|
|
@@ -3435,7 +3436,8 @@ var TARGET_MCP_TOOL_NAMES = [
|
|
|
3435
3436
|
"change",
|
|
3436
3437
|
"support",
|
|
3437
3438
|
"report",
|
|
3438
|
-
"apps"
|
|
3439
|
+
"apps",
|
|
3440
|
+
"delete"
|
|
3439
3441
|
];
|
|
3440
3442
|
var STRUCTURED_TOOL_OUTPUT_SCHEMA = z.object({
|
|
3441
3443
|
schemaVersion: z.literal(1),
|
|
@@ -4419,7 +4421,7 @@ function registerTools(server, baseCtx) {
|
|
|
4419
4421
|
"deploy",
|
|
4420
4422
|
{
|
|
4421
4423
|
title: "Deploy site",
|
|
4422
|
-
description: `Deploy the local static output to Sakupa. First deploy creates a free temporary site (valid ${
|
|
4424
|
+
description: `Deploy the local static output to Sakupa. First deploy creates a free temporary site (valid ${FREE_SITE_TTL_DAYS} days, public URL like https://${previewHostPattern}) and stores the management credential in .sakupa/site.json. Later runs update the existing site (free sites also refresh their validity; subscription-backed sites have no free-site expiry while the subscription remains active). Runs analyze first and refuses to upload source projects, secrets, .env files, archives, media or server code. The MCP process is locked to the current directory initialized by the no-argument init MCP tool; no tool argument can change that root. outputDir is a separate REQUIRED relative path supplied from the current project inspection. Never uploads anything when analysis says the project is not deployable.`,
|
|
4423
4425
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
4424
4426
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
4425
4427
|
inputSchema: z2.object({
|
|
@@ -4436,7 +4438,7 @@ function registerTools(server, baseCtx) {
|
|
|
4436
4438
|
"Override automatic SPA-fallback detection (single index.html + JS auto-enables rewriting unknown paths to index.html; multiple HTML pages auto-disable it). Pass only to force the behavior against the detected structure."
|
|
4437
4439
|
),
|
|
4438
4440
|
publicConfirmed: z2.boolean().optional().describe(
|
|
4439
|
-
"Required only for the first deployment: user explicitly confirmed creation of a public
|
|
4441
|
+
"Required only for the first deployment: user explicitly confirmed creation of a public URL valid for the free-site window."
|
|
4440
4442
|
),
|
|
4441
4443
|
reuseSiteUrl: z2.string().url().optional().describe(
|
|
4442
4444
|
"Exact existing free-site URL selected by the user when the three-site free-site allowance is full. Never invent this value; copy it from deploy nextActions."
|
|
@@ -4708,9 +4710,10 @@ function registerTools(server, baseCtx) {
|
|
|
4708
4710
|
const confirmArguments = { ...args, ...confirmation };
|
|
4709
4711
|
return presentDecision(baseCtx.decisions, call, "deploy", {
|
|
4710
4712
|
resultCode: "public_deployment_confirmation_required",
|
|
4711
|
-
summary: `First deployment creates a public URL that anyone with the link can open. The free preview stays live for ${
|
|
4713
|
+
summary: `First deployment creates a public URL that anyone with the link can open. The free preview stays live for ${FREE_SITE_TTL_DAYS} days. Nothing has been uploaded or made public yet. The exact confirmation field is publicConfirmed: true.`,
|
|
4712
4714
|
data: {
|
|
4713
4715
|
publicUrlLifetimeHours: FREE_SITE_TTL_HOURS,
|
|
4716
|
+
publicUrlLifetimeDays: FREE_SITE_TTL_DAYS,
|
|
4714
4717
|
confirmationField: "publicConfirmed",
|
|
4715
4718
|
confirmation,
|
|
4716
4719
|
confirmArguments
|
|
@@ -4720,7 +4723,7 @@ function registerTools(server, baseCtx) {
|
|
|
4720
4723
|
callToolDecisionOption({
|
|
4721
4724
|
id: "create_public_preview",
|
|
4722
4725
|
label: "Create the public preview",
|
|
4723
|
-
description: `Publish the selected files at a public URL for ${
|
|
4726
|
+
description: `Publish the selected files at a public URL for ${FREE_SITE_TTL_DAYS} days.`,
|
|
4724
4727
|
consequences: ["Anyone with the generated URL can open the site."],
|
|
4725
4728
|
tool: "deploy",
|
|
4726
4729
|
arguments: confirmArguments,
|
|
@@ -4932,7 +4935,7 @@ function registerTools(server, baseCtx) {
|
|
|
4932
4935
|
["Credential path", ".sakupa/site.json"]
|
|
4933
4936
|
],
|
|
4934
4937
|
notes: [
|
|
4935
|
-
`This is a FREE temporary preview: it stays live for ${
|
|
4938
|
+
`This is a FREE temporary preview: it stays live for ${FREE_SITE_TTL_DAYS} days. Deploying again or calling refresh extends the validity; while a subscription remains active, this URL stays live without the free-site expiry. This is conditional on the subscription remaining active: do NOT describe the site as permanent or long-term, and do NOT say the subscription is bound to the site.`,
|
|
4936
4939
|
"The management credential was saved to the exact relative path .sakupa/site.json \u2014 preserve this complete path verbatim and never shorten it to site.json. Keep that file: it is the only way to manage this site.",
|
|
4937
4940
|
...[credentialGitReminder(ctx.projectDir)].filter((line) => line.trim().length > 0)
|
|
4938
4941
|
],
|
|
@@ -5052,7 +5055,7 @@ ${JSON.stringify(finalized2.warnings, null, 2)}
|
|
|
5052
5055
|
...credentialRotationResumed ? [
|
|
5053
5056
|
"A previously confirmed credential rotation was resumed safely before this deploy; every older credential is revoked."
|
|
5054
5057
|
] : [],
|
|
5055
|
-
finalized.mode === "free" ? `Reminder: free sites stay live for ${
|
|
5058
|
+
finalized.mode === "free" ? `Reminder: free sites stay live for ${FREE_SITE_TTL_DAYS} days after the last deploy or refresh call. While a subscription remains active, the site stays live without this free-site expiry.` : "This site is subscription-backed and has no free-site expiry while the subscription remains active.",
|
|
5056
5059
|
...credentialSecurity?.rotationRecommended ? [
|
|
5057
5060
|
`Optional security recommendation: this management credential was created at ${timestampForAgent(credentialSecurity.credentialCreatedAt)} and is older than 7 days. The deploy SUCCEEDED and rotation is not required. Ask the user whether they want to rotate; call rotate without confirmed:true to show the exact revocation preview. Never rotate automatically.`
|
|
5058
5061
|
] : []
|
|
@@ -5154,7 +5157,7 @@ ${JSON.stringify(finalized.warnings, null, 2)}
|
|
|
5154
5157
|
],
|
|
5155
5158
|
notes: [
|
|
5156
5159
|
"NO content was uploaded or changed by this call \u2014 to publish new or edited files, run deploy.",
|
|
5157
|
-
`Free sites stay live for ${
|
|
5160
|
+
`Free sites stay live for ${FREE_SITE_TTL_DAYS} days after each deploy or refresh.`
|
|
5158
5161
|
],
|
|
5159
5162
|
next: ["`status`", "`deploy` to publish changed files"]
|
|
5160
5163
|
}),
|
|
@@ -5206,7 +5209,7 @@ ${JSON.stringify(finalized.warnings, null, 2)}
|
|
|
5206
5209
|
"subscribe",
|
|
5207
5210
|
{
|
|
5208
5211
|
title: "Subscribe (Stripe Checkout)",
|
|
5209
|
-
description: `Create a Stripe Checkout link that subscribes THIS site to a Sakupa Hosting monthly plan (${planCatalog()}). While the subscription remains active, its ${previewHostPattern} URL stays live without the free
|
|
5212
|
+
description: `Create a Stripe Checkout link that subscribes THIS site to a Sakupa Hosting monthly plan (${planCatalog()}). While the subscription remains active, its ${previewHostPattern} URL stays live without the free 30-day expiry. Binding a custom domain afterwards (bind) is an optional included extra and requires DNS control of that domain. Owner-only: requires this project's site credential (.sakupa/site.json) \u2014 deploy first. If the site outgrows its plan, Sakupa shows an over-limit notice and never changes billing automatically. The owner can explicitly choose another plan through Stripe Customer Portal. Card details are entered only on the Stripe-hosted page \u2014 never through the AI tool. Opening and completing Stripe Checkout is the final subscription confirmation.`,
|
|
5210
5213
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
5211
5214
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
5212
5215
|
inputSchema: z2.object({
|
|
@@ -6168,6 +6171,7 @@ var TOOL_TOPICS = [
|
|
|
6168
6171
|
"support",
|
|
6169
6172
|
"report",
|
|
6170
6173
|
"apps",
|
|
6174
|
+
"delete",
|
|
6171
6175
|
"help"
|
|
6172
6176
|
];
|
|
6173
6177
|
var HELP_TOPICS = ["diagnose", "overview", "terminology", ...TOOL_TOPICS];
|
|
@@ -6278,6 +6282,18 @@ var TOOL_MANUALS = {
|
|
|
6278
6282
|
nextStep: "Use the preview resumeWith arguments only after the user confirms.",
|
|
6279
6283
|
terminology: ["credentialRotation"]
|
|
6280
6284
|
},
|
|
6285
|
+
delete: {
|
|
6286
|
+
purpose: "Delete the bound FREE site after an explicit preview and confirmation.",
|
|
6287
|
+
sideEffects: "Preview is read-only. Confirmed deletion removes content, the public URL, installed apps, stored submissions and the local .sakupa/site.json.",
|
|
6288
|
+
preconditions: "A valid local site credential and a free site. A subscribed site is refused until the subscription ends (change \u2192 period-end cancellation) and the site reverts to free.",
|
|
6289
|
+
parameterNames: ["confirmed", "operationId", "confirmation"],
|
|
6290
|
+
parameters: "No arguments for the preview; confirmed=true with the exact operationId and confirmation copied from the preview decision.",
|
|
6291
|
+
warnings: [
|
|
6292
|
+
"Irreversible; the next deploy creates a brand-new site with a new URL.",
|
|
6293
|
+
"Never propose delete to free up the free-site allowance; use the deploy handoff decision instead."
|
|
6294
|
+
],
|
|
6295
|
+
nextStep: "Present every decision option; after confirmation copy the option arguments verbatim."
|
|
6296
|
+
},
|
|
6281
6297
|
plans: {
|
|
6282
6298
|
purpose: "Read the authoritative hosting plan catalog and rules.",
|
|
6283
6299
|
sideEffects: "Read-only public API request.",
|
|
@@ -7256,6 +7272,196 @@ function registerAppsTools(server, baseCtx) {
|
|
|
7256
7272
|
);
|
|
7257
7273
|
}
|
|
7258
7274
|
|
|
7275
|
+
// src/tools/delete.ts
|
|
7276
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
7277
|
+
import { z as z7 } from "zod";
|
|
7278
|
+
var confirmationSchema = z7.object({
|
|
7279
|
+
siteId: z7.string(),
|
|
7280
|
+
expectedSiteUpdatedAt: z7.string(),
|
|
7281
|
+
expectedStatus: z7.string(),
|
|
7282
|
+
expectedMode: z7.enum(["free", "paid"]),
|
|
7283
|
+
expectedServingMode: z7.string(),
|
|
7284
|
+
expectedShortId: z7.string().optional(),
|
|
7285
|
+
expectedSubscriptionStatus: z7.string().optional(),
|
|
7286
|
+
expectedPlan: z7.string().optional(),
|
|
7287
|
+
expectedCancelAtPeriodEnd: z7.boolean().optional(),
|
|
7288
|
+
expectedCurrentPeriodEnd: z7.string().optional(),
|
|
7289
|
+
expectedLastDeploymentId: z7.string().optional(),
|
|
7290
|
+
expectedBoundHostnames: z7.array(z7.string()),
|
|
7291
|
+
acknowledge: z7.literal("delete_and_cancel_renewal")
|
|
7292
|
+
});
|
|
7293
|
+
function registerDeleteTools(server, baseCtx) {
|
|
7294
|
+
server.registerTool(
|
|
7295
|
+
"delete",
|
|
7296
|
+
{
|
|
7297
|
+
title: "Delete site",
|
|
7298
|
+
description: "Delete the site bound to this project. Without arguments it only previews (zero writes) and returns a decision; after the user confirms, call again with the exact preview arguments (confirmed: true, operationId, confirmation). A FREE site is deleted immediately: content, public URL, installed apps and stored submissions are gone and the local .sakupa/site.json is removed, so the next deploy creates a brand-new site. A subscribed site cannot be deleted: end the subscription first (change \u2192 period-end cancellation on Stripe); when it ends the site reverts to a free site and delete becomes available. Owner-only.",
|
|
7299
|
+
inputSchema: z7.object({
|
|
7300
|
+
confirmed: z7.boolean().optional().describe("true only from the exact decision arguments returned by the preview."),
|
|
7301
|
+
operationId: z7.string().optional().describe("Copied verbatim from the preview."),
|
|
7302
|
+
confirmation: confirmationSchema.optional().describe("Copied verbatim from the preview; the cloud rejects any drift.")
|
|
7303
|
+
}),
|
|
7304
|
+
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
7305
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true }
|
|
7306
|
+
},
|
|
7307
|
+
withDecisionReentry("delete", async (args, call) => {
|
|
7308
|
+
try {
|
|
7309
|
+
const ctx = await withProjectDir(baseCtx, call);
|
|
7310
|
+
const site = requireSiteFile(ctx);
|
|
7311
|
+
if (args.confirmed === true) {
|
|
7312
|
+
if (!args.operationId || !args.confirmation) {
|
|
7313
|
+
return structuredToolResult({
|
|
7314
|
+
schemaVersion: 1,
|
|
7315
|
+
outcome: "blocked",
|
|
7316
|
+
resultCode: "delete_preview_required",
|
|
7317
|
+
summary: summaryMarkdown({
|
|
7318
|
+
title: "Nothing was deleted \u2014 run the preview first",
|
|
7319
|
+
lead: "A confirmed delete must carry the exact operationId and confirmation from the preview. Call delete with no arguments, show the preview to the user, then reuse its decision arguments.",
|
|
7320
|
+
next: ["`delete` with no arguments"]
|
|
7321
|
+
}),
|
|
7322
|
+
data: { siteId: site.siteId },
|
|
7323
|
+
nextActions: [{ tool: "delete", arguments: {}, allowed: true }]
|
|
7324
|
+
});
|
|
7325
|
+
}
|
|
7326
|
+
const result = await ctx.client.deleteSite(site.siteId, site.credential, {
|
|
7327
|
+
operationId: args.operationId,
|
|
7328
|
+
confirmation: args.confirmation
|
|
7329
|
+
});
|
|
7330
|
+
deleteSiteFile(ctx.projectDir);
|
|
7331
|
+
removeCreation(site.siteId);
|
|
7332
|
+
return structuredToolResult({
|
|
7333
|
+
schemaVersion: 1,
|
|
7334
|
+
outcome: "completed",
|
|
7335
|
+
resultCode: result.alreadyDeleted ? "site_already_deleted" : "site_deleted",
|
|
7336
|
+
summary: summaryMarkdown({
|
|
7337
|
+
title: result.alreadyDeleted ? "Site was already deleted" : "Site deleted",
|
|
7338
|
+
lead: `${site.url ?? site.siteId} no longer serves anything (visitors get HTTP 410). The local credential file .sakupa/site.json was removed; this project is no longer bound to any site.`,
|
|
7339
|
+
facts: [
|
|
7340
|
+
["Site ID", result.siteId],
|
|
7341
|
+
["Public URL released", site.url],
|
|
7342
|
+
[
|
|
7343
|
+
"Custom hostnames removed",
|
|
7344
|
+
result.removedHostnames.length ? result.removedHostnames.join(", ") : "none"
|
|
7345
|
+
],
|
|
7346
|
+
["Serving cleanup still pending", result.servingDeletionPending]
|
|
7347
|
+
],
|
|
7348
|
+
notes: [
|
|
7349
|
+
"Installed apps and stored form submissions were deleted with the site.",
|
|
7350
|
+
"The free-site allowance this site used on its deployment network is released."
|
|
7351
|
+
],
|
|
7352
|
+
next: ["`deploy` to publish a brand-new site (new URL, new credential)"]
|
|
7353
|
+
}),
|
|
7354
|
+
data: {
|
|
7355
|
+
...result,
|
|
7356
|
+
projectDir: ctx.projectDir,
|
|
7357
|
+
localSiteFileRemoved: true,
|
|
7358
|
+
releasedUrl: site.url
|
|
7359
|
+
},
|
|
7360
|
+
nextActions: []
|
|
7361
|
+
});
|
|
7362
|
+
}
|
|
7363
|
+
const operationId = randomUUID7();
|
|
7364
|
+
const preview = await ctx.client.previewDeleteSite(site.siteId, site.credential, {
|
|
7365
|
+
operationId
|
|
7366
|
+
});
|
|
7367
|
+
const c = preview.consequences;
|
|
7368
|
+
if (c.requiresFreeModeBeforeDelete) {
|
|
7369
|
+
const cancellationScheduled = preview.confirmation.expectedCancelAtPeriodEnd === true;
|
|
7370
|
+
return structuredToolResult({
|
|
7371
|
+
schemaVersion: 1,
|
|
7372
|
+
outcome: "blocked",
|
|
7373
|
+
resultCode: "delete_requires_free_mode",
|
|
7374
|
+
summary: summaryMarkdown({
|
|
7375
|
+
title: "This site is subscription-backed \u2014 it cannot be deleted yet",
|
|
7376
|
+
lead: "Nothing was changed. Sakupa deletes only FREE sites. End the subscription first; when the paid period ends the site automatically becomes a free 30-day site, and delete becomes available.",
|
|
7377
|
+
facts: [
|
|
7378
|
+
["Site", site.url ?? site.siteId],
|
|
7379
|
+
["Plan", preview.confirmation.expectedPlan],
|
|
7380
|
+
["Period-end cancellation already scheduled", cancellationScheduled ? "yes" : "no"],
|
|
7381
|
+
[
|
|
7382
|
+
"Paid access ends",
|
|
7383
|
+
c.currentPaidAccessEndsAt ? timestampForAgent(c.currentPaidAccessEndsAt) : void 0
|
|
7384
|
+
],
|
|
7385
|
+
[
|
|
7386
|
+
"Custom hostnames removed when it reverts",
|
|
7387
|
+
c.removesCustomHostnames.join(", ") || "none"
|
|
7388
|
+
]
|
|
7389
|
+
],
|
|
7390
|
+
steps: cancellationScheduled ? [
|
|
7391
|
+
"Wait for the paid period to end; Sakupa then reverts the site to a free 30-day site (content kept, custom domains removed).",
|
|
7392
|
+
"Call delete again at that point \u2014 or simply let the free site expire."
|
|
7393
|
+
] : [
|
|
7394
|
+
"Call change and open the Stripe-hosted link; the user chooses period-end cancellation there (Sakupa never cancels on its own).",
|
|
7395
|
+
"When the paid period ends, Sakupa reverts the site to a free 30-day site (content kept, custom domains removed).",
|
|
7396
|
+
"Call delete again at that point \u2014 or simply let the free site expire."
|
|
7397
|
+
],
|
|
7398
|
+
next: cancellationScheduled ? ["`billing`"] : ["`change`", "`billing`"]
|
|
7399
|
+
}),
|
|
7400
|
+
data: {
|
|
7401
|
+
siteId: site.siteId,
|
|
7402
|
+
operationId: preview.operationId,
|
|
7403
|
+
consequences: c,
|
|
7404
|
+
cancellationScheduled
|
|
7405
|
+
},
|
|
7406
|
+
nextActions: [
|
|
7407
|
+
{ tool: cancellationScheduled ? "billing" : "change", arguments: {}, allowed: true }
|
|
7408
|
+
]
|
|
7409
|
+
});
|
|
7410
|
+
}
|
|
7411
|
+
const confirmArguments = {
|
|
7412
|
+
confirmed: true,
|
|
7413
|
+
operationId: preview.operationId,
|
|
7414
|
+
confirmation: preview.confirmation
|
|
7415
|
+
};
|
|
7416
|
+
return presentDecision(baseCtx.decisions, call, "delete", {
|
|
7417
|
+
resultCode: "delete_confirmation_required",
|
|
7418
|
+
summary: summaryMarkdown({
|
|
7419
|
+
title: `Delete ${site.url ?? site.siteId}? Nothing was changed.`,
|
|
7420
|
+
lead: "This is a preview. Deleting cannot be undone: the content, the public URL, installed apps and stored form submissions disappear at once, and the local credential file is removed.",
|
|
7421
|
+
facts: [
|
|
7422
|
+
["Site ID", site.siteId],
|
|
7423
|
+
["Public URL", site.url],
|
|
7424
|
+
["Permanent URL released", c.releasesPermanentUrl],
|
|
7425
|
+
["Custom hostnames removed", c.removesCustomHostnames.join(", ") || "none"],
|
|
7426
|
+
["Exact confirm arguments", JSON.stringify(confirmArguments)]
|
|
7427
|
+
],
|
|
7428
|
+
notes: [
|
|
7429
|
+
"After deletion this project is unbound; the next deploy creates a brand-new site with a new URL and credential.",
|
|
7430
|
+
"To keep the URL but replace the content, deploy again instead of deleting."
|
|
7431
|
+
]
|
|
7432
|
+
}),
|
|
7433
|
+
data: {
|
|
7434
|
+
siteId: site.siteId,
|
|
7435
|
+
operationId: preview.operationId,
|
|
7436
|
+
consequences: c,
|
|
7437
|
+
confirmation: preview.confirmation,
|
|
7438
|
+
confirmArguments
|
|
7439
|
+
},
|
|
7440
|
+
prompt: `Delete the site ${site.url ?? site.siteId} now? This cannot be undone.`,
|
|
7441
|
+
options: [
|
|
7442
|
+
callToolDecisionOption({
|
|
7443
|
+
id: "delete_site",
|
|
7444
|
+
label: `Delete ${site.url ?? site.siteId}`,
|
|
7445
|
+
description: "Remove the site, its content, apps and submissions, and release the URL.",
|
|
7446
|
+
consequences: [
|
|
7447
|
+
"Visitors get HTTP 410 immediately.",
|
|
7448
|
+
"The local credential file is removed; this project is unbound."
|
|
7449
|
+
],
|
|
7450
|
+
tool: "delete",
|
|
7451
|
+
arguments: confirmArguments,
|
|
7452
|
+
reasonCode: "explicit_site_delete_confirmation"
|
|
7453
|
+
}),
|
|
7454
|
+
noActionDecisionOption({ description: "Keep the site exactly as it is." })
|
|
7455
|
+
],
|
|
7456
|
+
legacyUserAction: { type: "confirm_in_mcp", provider: "sakupa" }
|
|
7457
|
+
});
|
|
7458
|
+
} catch (error) {
|
|
7459
|
+
return toolError(error);
|
|
7460
|
+
}
|
|
7461
|
+
})
|
|
7462
|
+
);
|
|
7463
|
+
}
|
|
7464
|
+
|
|
7259
7465
|
// src/transport.ts
|
|
7260
7466
|
var DEFAULT_REQUEST_TIMEOUT_MS = 15e3;
|
|
7261
7467
|
var DEFAULT_UPLOAD_TIMEOUT_MS = 3e4;
|
|
@@ -7416,7 +7622,7 @@ Workflow:
|
|
|
7416
7622
|
(Vite/Vue/React/Svelte/Astro/Next static export/Nuxt generate), run the build LOCALLY first,
|
|
7417
7623
|
then re-run analyze.
|
|
7418
7624
|
2. deploy \u2014 uploads ONLY the built static output. The first deploy creates a free temporary
|
|
7419
|
-
site (public URL ${hostPattern}, valid
|
|
7625
|
+
site (public URL ${hostPattern}, valid 30 days, free banner shown) and stores the
|
|
7420
7626
|
management credential in .sakupa/site.json. Deploying again updates the site and refreshes
|
|
7421
7627
|
its validity; refresh extends validity without uploading; status shows the
|
|
7422
7628
|
current deployment and serving state at any time. Every update checks the credential's
|
|
@@ -7425,7 +7631,7 @@ Workflow:
|
|
|
7425
7631
|
3. To keep the site live beyond the free period, subscribe it to a monthly hosting plan (plans
|
|
7426
7632
|
shows the catalog; subscribe -> Stripe-hosted checkout;
|
|
7427
7633
|
water/personal/share/business). While the subscription remains active, the
|
|
7428
|
-
${hostPattern} URL stays live without the free
|
|
7634
|
+
${hostPattern} URL stays live without the free 30-day expiry. Usage over the chosen plan
|
|
7429
7635
|
shows an over-limit notice by default. An external AI may periodically query usage and
|
|
7430
7636
|
recommend a plan, but Sakupa never changes a subscription automatically.
|
|
7431
7637
|
4. Optionally bind a custom domain to the subscribed site (bind): an included extra
|
|
@@ -7436,13 +7642,20 @@ Workflow:
|
|
|
7436
7642
|
until the new domain's www is confirmed live, then it is replaced automatically.
|
|
7437
7643
|
Plan changes are confirmed only on Stripe and synchronized by Stripe webhook.
|
|
7438
7644
|
A cancellation keeps the site paid through the current period. Sakupa reverts it to a free
|
|
7439
|
-
|
|
7645
|
+
30-day site and removes paid data after Stripe sends the signed final-cancellation webhook.
|
|
7440
7646
|
Recovery writes the new local credential before downloading content. If a session stops after
|
|
7441
7647
|
.sakupa/site.json exists, NEVER repeat DNS recovery: resume with recover action "download".
|
|
7442
7648
|
5. If any Sakupa operation is difficult or fails, call help FIRST. support handles billing,
|
|
7443
7649
|
payment, refund and other customer-service requests. report is the LAST resort only when
|
|
7444
7650
|
help explicitly recommends a product bug report, and submission still requires user review.
|
|
7445
7651
|
|
|
7652
|
+
Deleting a site: delete with no arguments only previews and returns a decision; after the user
|
|
7653
|
+
confirms, call delete again with the exact preview arguments. A FREE site is deleted immediately
|
|
7654
|
+
(content, URL, apps and submissions gone; .sakupa/site.json removed; the next deploy creates a new
|
|
7655
|
+
site). A subscribed site cannot be deleted: end the subscription first (change \u2192 period-end
|
|
7656
|
+
cancellation on Stripe); when it ends the site reverts to a free 30-day site and delete becomes
|
|
7657
|
+
available. Never suggest delete as a way to free up the free-site allowance during deploy.
|
|
7658
|
+
|
|
7446
7659
|
Decision-options contract: when a result contains decision, present EVERY numbered option from the
|
|
7447
7660
|
tool to the user and wait for their selection. No option is selected by default. Never choose from
|
|
7448
7661
|
context, paraphrase a selection into different arguments, or invent another option. After the user
|
|
@@ -7599,6 +7812,7 @@ function createSakupaMcpServer(opts) {
|
|
|
7599
7812
|
registerBillingTools(server, ctx);
|
|
7600
7813
|
registerCredentialTools(server, ctx);
|
|
7601
7814
|
registerAppsTools(server, ctx);
|
|
7815
|
+
registerDeleteTools(server, ctx);
|
|
7602
7816
|
registerHelpTools(server, ctx);
|
|
7603
7817
|
return server;
|
|
7604
7818
|
}
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,8 @@ var DEFAULT_API_BASE_URL = "https://api.sakupa.com";
|
|
|
4
4
|
var FREE_SITE_URL_SUFFIX = `.${SERVICE_DOMAIN}`;
|
|
5
5
|
var TEST_ACCESS_HEADER = "x-sakupa-test-token";
|
|
6
6
|
var CREDENTIAL_ROTATION_RECOMMEND_AFTER_SECONDS = 7 * 24 * 60 * 60;
|
|
7
|
-
var
|
|
7
|
+
var FREE_SITE_TTL_DAYS = 30;
|
|
8
|
+
var FREE_SITE_TTL_HOURS = FREE_SITE_TTL_DAYS * 24;
|
|
8
9
|
var FREE_SITE_MAX_TOTAL_BYTES = 10 * 1024 * 1024;
|
|
9
10
|
var FREE_ACTIVE_SITES_PER_IP = 3;
|
|
10
11
|
var PAID_SITE_MAX_TOTAL_BYTES = 2 * 1024 * 1024 * 1024;
|
|
@@ -147,7 +148,7 @@ function isFreeSiteAllowanceNetworkReference(value) {
|
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
// ../core/dist/domain/version.js
|
|
150
|
-
var SAKUPA_MCP_VERSION = "1.
|
|
151
|
+
var SAKUPA_MCP_VERSION = "1.5.0";
|
|
151
152
|
|
|
152
153
|
// ../core/dist/domain/errors.js
|
|
153
154
|
var HTTP_STATUS = {
|
|
@@ -2242,7 +2243,8 @@ var TARGET_MCP_TOOL_NAMES = [
|
|
|
2242
2243
|
"change",
|
|
2243
2244
|
"support",
|
|
2244
2245
|
"report",
|
|
2245
|
-
"apps"
|
|
2246
|
+
"apps",
|
|
2247
|
+
"delete"
|
|
2246
2248
|
];
|
|
2247
2249
|
var STRUCTURED_TOOL_OUTPUT_SCHEMA = z.object({
|
|
2248
2250
|
schemaVersion: z.literal(1),
|
|
@@ -3083,15 +3085,15 @@ function strFromU8(dat, latin1) {
|
|
|
3083
3085
|
var slzh = function(d, b) {
|
|
3084
3086
|
return b + 30 + b2(d, b + 26) + b2(d, b + 28);
|
|
3085
3087
|
};
|
|
3086
|
-
var zh = function(d, b,
|
|
3088
|
+
var zh = function(d, b, z8) {
|
|
3087
3089
|
var fnl = b2(d, b + 28), efl = b2(d, b + 30), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl;
|
|
3088
|
-
var _a2 = z64hs(d, es, efl,
|
|
3090
|
+
var _a2 = z64hs(d, es, efl, z8, b4(d, b + 20), b4(d, b + 24), b4(d, b + 42)), sc = _a2[0], su = _a2[1], off = _a2[2];
|
|
3089
3091
|
return [b2(d, b + 10), sc, su, fn, es + efl + b2(d, b + 32), off];
|
|
3090
3092
|
};
|
|
3091
|
-
var z64hs = function(d, b, l,
|
|
3093
|
+
var z64hs = function(d, b, l, z8, sc, su, off) {
|
|
3092
3094
|
var nsc = sc == 4294967295, nsu = su == 4294967295, noff = off == 4294967295, e = b + l;
|
|
3093
3095
|
var nf = nsc + nsu + noff;
|
|
3094
|
-
if (
|
|
3096
|
+
if (z8 && nf) {
|
|
3095
3097
|
for (; b + 4 < e; b += 4 + b2(d, b + 2)) {
|
|
3096
3098
|
if (b2(d, b) == 1) {
|
|
3097
3099
|
return [
|
|
@@ -3102,7 +3104,7 @@ var z64hs = function(d, b, l, z7, sc, su, off) {
|
|
|
3102
3104
|
];
|
|
3103
3105
|
}
|
|
3104
3106
|
}
|
|
3105
|
-
if (
|
|
3107
|
+
if (z8 < 2)
|
|
3106
3108
|
err(13);
|
|
3107
3109
|
}
|
|
3108
3110
|
return [sc, su, off, 0];
|
|
@@ -3119,18 +3121,18 @@ function unzipSync(data, opts) {
|
|
|
3119
3121
|
if (!c)
|
|
3120
3122
|
return {};
|
|
3121
3123
|
var o = b4(data, e + 16);
|
|
3122
|
-
var
|
|
3123
|
-
if (
|
|
3124
|
+
var z8 = b4(data, e - 20) == 117853008;
|
|
3125
|
+
if (z8) {
|
|
3124
3126
|
var ze = b4(data, e - 12);
|
|
3125
|
-
|
|
3126
|
-
if (
|
|
3127
|
+
z8 = b4(data, ze) == 101075792;
|
|
3128
|
+
if (z8) {
|
|
3127
3129
|
c = b4(data, ze + 32);
|
|
3128
3130
|
o = b4(data, ze + 48);
|
|
3129
3131
|
}
|
|
3130
3132
|
}
|
|
3131
3133
|
var fltr = opts && opts.filter;
|
|
3132
3134
|
for (var i = 0; i < c; ++i) {
|
|
3133
|
-
var _a2 = zh(data, o,
|
|
3135
|
+
var _a2 = zh(data, o, z8), c_2 = _a2[0], sc = _a2[1], su = _a2[2], fn = _a2[3], no = _a2[4], off = _a2[5], b = slzh(data, off);
|
|
3134
3136
|
o = no;
|
|
3135
3137
|
if (!fltr || fltr({
|
|
3136
3138
|
name: fn,
|
|
@@ -4529,7 +4531,7 @@ function registerTools(server, baseCtx) {
|
|
|
4529
4531
|
"deploy",
|
|
4530
4532
|
{
|
|
4531
4533
|
title: "Deploy site",
|
|
4532
|
-
description: `Deploy the local static output to Sakupa. First deploy creates a free temporary site (valid ${
|
|
4534
|
+
description: `Deploy the local static output to Sakupa. First deploy creates a free temporary site (valid ${FREE_SITE_TTL_DAYS} days, public URL like https://${previewHostPattern}) and stores the management credential in .sakupa/site.json. Later runs update the existing site (free sites also refresh their validity; subscription-backed sites have no free-site expiry while the subscription remains active). Runs analyze first and refuses to upload source projects, secrets, .env files, archives, media or server code. The MCP process is locked to the current directory initialized by the no-argument init MCP tool; no tool argument can change that root. outputDir is a separate REQUIRED relative path supplied from the current project inspection. Never uploads anything when analysis says the project is not deployable.`,
|
|
4533
4535
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
4534
4536
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
4535
4537
|
inputSchema: z2.object({
|
|
@@ -4546,7 +4548,7 @@ function registerTools(server, baseCtx) {
|
|
|
4546
4548
|
"Override automatic SPA-fallback detection (single index.html + JS auto-enables rewriting unknown paths to index.html; multiple HTML pages auto-disable it). Pass only to force the behavior against the detected structure."
|
|
4547
4549
|
),
|
|
4548
4550
|
publicConfirmed: z2.boolean().optional().describe(
|
|
4549
|
-
"Required only for the first deployment: user explicitly confirmed creation of a public
|
|
4551
|
+
"Required only for the first deployment: user explicitly confirmed creation of a public URL valid for the free-site window."
|
|
4550
4552
|
),
|
|
4551
4553
|
reuseSiteUrl: z2.string().url().optional().describe(
|
|
4552
4554
|
"Exact existing free-site URL selected by the user when the three-site free-site allowance is full. Never invent this value; copy it from deploy nextActions."
|
|
@@ -4818,9 +4820,10 @@ function registerTools(server, baseCtx) {
|
|
|
4818
4820
|
const confirmArguments = { ...args, ...confirmation };
|
|
4819
4821
|
return presentDecision(baseCtx.decisions, call, "deploy", {
|
|
4820
4822
|
resultCode: "public_deployment_confirmation_required",
|
|
4821
|
-
summary: `First deployment creates a public URL that anyone with the link can open. The free preview stays live for ${
|
|
4823
|
+
summary: `First deployment creates a public URL that anyone with the link can open. The free preview stays live for ${FREE_SITE_TTL_DAYS} days. Nothing has been uploaded or made public yet. The exact confirmation field is publicConfirmed: true.`,
|
|
4822
4824
|
data: {
|
|
4823
4825
|
publicUrlLifetimeHours: FREE_SITE_TTL_HOURS,
|
|
4826
|
+
publicUrlLifetimeDays: FREE_SITE_TTL_DAYS,
|
|
4824
4827
|
confirmationField: "publicConfirmed",
|
|
4825
4828
|
confirmation,
|
|
4826
4829
|
confirmArguments
|
|
@@ -4830,7 +4833,7 @@ function registerTools(server, baseCtx) {
|
|
|
4830
4833
|
callToolDecisionOption({
|
|
4831
4834
|
id: "create_public_preview",
|
|
4832
4835
|
label: "Create the public preview",
|
|
4833
|
-
description: `Publish the selected files at a public URL for ${
|
|
4836
|
+
description: `Publish the selected files at a public URL for ${FREE_SITE_TTL_DAYS} days.`,
|
|
4834
4837
|
consequences: ["Anyone with the generated URL can open the site."],
|
|
4835
4838
|
tool: "deploy",
|
|
4836
4839
|
arguments: confirmArguments,
|
|
@@ -5042,7 +5045,7 @@ function registerTools(server, baseCtx) {
|
|
|
5042
5045
|
["Credential path", ".sakupa/site.json"]
|
|
5043
5046
|
],
|
|
5044
5047
|
notes: [
|
|
5045
|
-
`This is a FREE temporary preview: it stays live for ${
|
|
5048
|
+
`This is a FREE temporary preview: it stays live for ${FREE_SITE_TTL_DAYS} days. Deploying again or calling refresh extends the validity; while a subscription remains active, this URL stays live without the free-site expiry. This is conditional on the subscription remaining active: do NOT describe the site as permanent or long-term, and do NOT say the subscription is bound to the site.`,
|
|
5046
5049
|
"The management credential was saved to the exact relative path .sakupa/site.json \u2014 preserve this complete path verbatim and never shorten it to site.json. Keep that file: it is the only way to manage this site.",
|
|
5047
5050
|
...[credentialGitReminder(ctx.projectDir)].filter((line) => line.trim().length > 0)
|
|
5048
5051
|
],
|
|
@@ -5162,7 +5165,7 @@ ${JSON.stringify(finalized2.warnings, null, 2)}
|
|
|
5162
5165
|
...credentialRotationResumed ? [
|
|
5163
5166
|
"A previously confirmed credential rotation was resumed safely before this deploy; every older credential is revoked."
|
|
5164
5167
|
] : [],
|
|
5165
|
-
finalized.mode === "free" ? `Reminder: free sites stay live for ${
|
|
5168
|
+
finalized.mode === "free" ? `Reminder: free sites stay live for ${FREE_SITE_TTL_DAYS} days after the last deploy or refresh call. While a subscription remains active, the site stays live without this free-site expiry.` : "This site is subscription-backed and has no free-site expiry while the subscription remains active.",
|
|
5166
5169
|
...credentialSecurity?.rotationRecommended ? [
|
|
5167
5170
|
`Optional security recommendation: this management credential was created at ${timestampForAgent(credentialSecurity.credentialCreatedAt)} and is older than 7 days. The deploy SUCCEEDED and rotation is not required. Ask the user whether they want to rotate; call rotate without confirmed:true to show the exact revocation preview. Never rotate automatically.`
|
|
5168
5171
|
] : []
|
|
@@ -5264,7 +5267,7 @@ ${JSON.stringify(finalized.warnings, null, 2)}
|
|
|
5264
5267
|
],
|
|
5265
5268
|
notes: [
|
|
5266
5269
|
"NO content was uploaded or changed by this call \u2014 to publish new or edited files, run deploy.",
|
|
5267
|
-
`Free sites stay live for ${
|
|
5270
|
+
`Free sites stay live for ${FREE_SITE_TTL_DAYS} days after each deploy or refresh.`
|
|
5268
5271
|
],
|
|
5269
5272
|
next: ["`status`", "`deploy` to publish changed files"]
|
|
5270
5273
|
}),
|
|
@@ -5316,7 +5319,7 @@ ${JSON.stringify(finalized.warnings, null, 2)}
|
|
|
5316
5319
|
"subscribe",
|
|
5317
5320
|
{
|
|
5318
5321
|
title: "Subscribe (Stripe Checkout)",
|
|
5319
|
-
description: `Create a Stripe Checkout link that subscribes THIS site to a Sakupa Hosting monthly plan (${planCatalog()}). While the subscription remains active, its ${previewHostPattern} URL stays live without the free
|
|
5322
|
+
description: `Create a Stripe Checkout link that subscribes THIS site to a Sakupa Hosting monthly plan (${planCatalog()}). While the subscription remains active, its ${previewHostPattern} URL stays live without the free 30-day expiry. Binding a custom domain afterwards (bind) is an optional included extra and requires DNS control of that domain. Owner-only: requires this project's site credential (.sakupa/site.json) \u2014 deploy first. If the site outgrows its plan, Sakupa shows an over-limit notice and never changes billing automatically. The owner can explicitly choose another plan through Stripe Customer Portal. Card details are entered only on the Stripe-hosted page \u2014 never through the AI tool. Opening and completing Stripe Checkout is the final subscription confirmation.`,
|
|
5320
5323
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
5321
5324
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
5322
5325
|
inputSchema: z2.object({
|
|
@@ -6287,6 +6290,7 @@ var TOOL_TOPICS = [
|
|
|
6287
6290
|
"support",
|
|
6288
6291
|
"report",
|
|
6289
6292
|
"apps",
|
|
6293
|
+
"delete",
|
|
6290
6294
|
"help"
|
|
6291
6295
|
];
|
|
6292
6296
|
var HELP_TOPICS = ["diagnose", "overview", "terminology", ...TOOL_TOPICS];
|
|
@@ -6397,6 +6401,18 @@ var TOOL_MANUALS = {
|
|
|
6397
6401
|
nextStep: "Use the preview resumeWith arguments only after the user confirms.",
|
|
6398
6402
|
terminology: ["credentialRotation"]
|
|
6399
6403
|
},
|
|
6404
|
+
delete: {
|
|
6405
|
+
purpose: "Delete the bound FREE site after an explicit preview and confirmation.",
|
|
6406
|
+
sideEffects: "Preview is read-only. Confirmed deletion removes content, the public URL, installed apps, stored submissions and the local .sakupa/site.json.",
|
|
6407
|
+
preconditions: "A valid local site credential and a free site. A subscribed site is refused until the subscription ends (change \u2192 period-end cancellation) and the site reverts to free.",
|
|
6408
|
+
parameterNames: ["confirmed", "operationId", "confirmation"],
|
|
6409
|
+
parameters: "No arguments for the preview; confirmed=true with the exact operationId and confirmation copied from the preview decision.",
|
|
6410
|
+
warnings: [
|
|
6411
|
+
"Irreversible; the next deploy creates a brand-new site with a new URL.",
|
|
6412
|
+
"Never propose delete to free up the free-site allowance; use the deploy handoff decision instead."
|
|
6413
|
+
],
|
|
6414
|
+
nextStep: "Present every decision option; after confirmation copy the option arguments verbatim."
|
|
6415
|
+
},
|
|
6400
6416
|
plans: {
|
|
6401
6417
|
purpose: "Read the authoritative hosting plan catalog and rules.",
|
|
6402
6418
|
sideEffects: "Read-only public API request.",
|
|
@@ -7375,6 +7391,196 @@ function registerAppsTools(server, baseCtx) {
|
|
|
7375
7391
|
);
|
|
7376
7392
|
}
|
|
7377
7393
|
|
|
7394
|
+
// src/tools/delete.ts
|
|
7395
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
7396
|
+
import { z as z7 } from "zod";
|
|
7397
|
+
var confirmationSchema = z7.object({
|
|
7398
|
+
siteId: z7.string(),
|
|
7399
|
+
expectedSiteUpdatedAt: z7.string(),
|
|
7400
|
+
expectedStatus: z7.string(),
|
|
7401
|
+
expectedMode: z7.enum(["free", "paid"]),
|
|
7402
|
+
expectedServingMode: z7.string(),
|
|
7403
|
+
expectedShortId: z7.string().optional(),
|
|
7404
|
+
expectedSubscriptionStatus: z7.string().optional(),
|
|
7405
|
+
expectedPlan: z7.string().optional(),
|
|
7406
|
+
expectedCancelAtPeriodEnd: z7.boolean().optional(),
|
|
7407
|
+
expectedCurrentPeriodEnd: z7.string().optional(),
|
|
7408
|
+
expectedLastDeploymentId: z7.string().optional(),
|
|
7409
|
+
expectedBoundHostnames: z7.array(z7.string()),
|
|
7410
|
+
acknowledge: z7.literal("delete_and_cancel_renewal")
|
|
7411
|
+
});
|
|
7412
|
+
function registerDeleteTools(server, baseCtx) {
|
|
7413
|
+
server.registerTool(
|
|
7414
|
+
"delete",
|
|
7415
|
+
{
|
|
7416
|
+
title: "Delete site",
|
|
7417
|
+
description: "Delete the site bound to this project. Without arguments it only previews (zero writes) and returns a decision; after the user confirms, call again with the exact preview arguments (confirmed: true, operationId, confirmation). A FREE site is deleted immediately: content, public URL, installed apps and stored submissions are gone and the local .sakupa/site.json is removed, so the next deploy creates a brand-new site. A subscribed site cannot be deleted: end the subscription first (change \u2192 period-end cancellation on Stripe); when it ends the site reverts to a free site and delete becomes available. Owner-only.",
|
|
7418
|
+
inputSchema: z7.object({
|
|
7419
|
+
confirmed: z7.boolean().optional().describe("true only from the exact decision arguments returned by the preview."),
|
|
7420
|
+
operationId: z7.string().optional().describe("Copied verbatim from the preview."),
|
|
7421
|
+
confirmation: confirmationSchema.optional().describe("Copied verbatim from the preview; the cloud rejects any drift.")
|
|
7422
|
+
}),
|
|
7423
|
+
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
7424
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true }
|
|
7425
|
+
},
|
|
7426
|
+
withDecisionReentry("delete", async (args, call) => {
|
|
7427
|
+
try {
|
|
7428
|
+
const ctx = await withProjectDir(baseCtx, call);
|
|
7429
|
+
const site = requireSiteFile(ctx);
|
|
7430
|
+
if (args.confirmed === true) {
|
|
7431
|
+
if (!args.operationId || !args.confirmation) {
|
|
7432
|
+
return structuredToolResult({
|
|
7433
|
+
schemaVersion: 1,
|
|
7434
|
+
outcome: "blocked",
|
|
7435
|
+
resultCode: "delete_preview_required",
|
|
7436
|
+
summary: summaryMarkdown({
|
|
7437
|
+
title: "Nothing was deleted \u2014 run the preview first",
|
|
7438
|
+
lead: "A confirmed delete must carry the exact operationId and confirmation from the preview. Call delete with no arguments, show the preview to the user, then reuse its decision arguments.",
|
|
7439
|
+
next: ["`delete` with no arguments"]
|
|
7440
|
+
}),
|
|
7441
|
+
data: { siteId: site.siteId },
|
|
7442
|
+
nextActions: [{ tool: "delete", arguments: {}, allowed: true }]
|
|
7443
|
+
});
|
|
7444
|
+
}
|
|
7445
|
+
const result = await ctx.client.deleteSite(site.siteId, site.credential, {
|
|
7446
|
+
operationId: args.operationId,
|
|
7447
|
+
confirmation: args.confirmation
|
|
7448
|
+
});
|
|
7449
|
+
deleteSiteFile(ctx.projectDir);
|
|
7450
|
+
removeCreation(site.siteId);
|
|
7451
|
+
return structuredToolResult({
|
|
7452
|
+
schemaVersion: 1,
|
|
7453
|
+
outcome: "completed",
|
|
7454
|
+
resultCode: result.alreadyDeleted ? "site_already_deleted" : "site_deleted",
|
|
7455
|
+
summary: summaryMarkdown({
|
|
7456
|
+
title: result.alreadyDeleted ? "Site was already deleted" : "Site deleted",
|
|
7457
|
+
lead: `${site.url ?? site.siteId} no longer serves anything (visitors get HTTP 410). The local credential file .sakupa/site.json was removed; this project is no longer bound to any site.`,
|
|
7458
|
+
facts: [
|
|
7459
|
+
["Site ID", result.siteId],
|
|
7460
|
+
["Public URL released", site.url],
|
|
7461
|
+
[
|
|
7462
|
+
"Custom hostnames removed",
|
|
7463
|
+
result.removedHostnames.length ? result.removedHostnames.join(", ") : "none"
|
|
7464
|
+
],
|
|
7465
|
+
["Serving cleanup still pending", result.servingDeletionPending]
|
|
7466
|
+
],
|
|
7467
|
+
notes: [
|
|
7468
|
+
"Installed apps and stored form submissions were deleted with the site.",
|
|
7469
|
+
"The free-site allowance this site used on its deployment network is released."
|
|
7470
|
+
],
|
|
7471
|
+
next: ["`deploy` to publish a brand-new site (new URL, new credential)"]
|
|
7472
|
+
}),
|
|
7473
|
+
data: {
|
|
7474
|
+
...result,
|
|
7475
|
+
projectDir: ctx.projectDir,
|
|
7476
|
+
localSiteFileRemoved: true,
|
|
7477
|
+
releasedUrl: site.url
|
|
7478
|
+
},
|
|
7479
|
+
nextActions: []
|
|
7480
|
+
});
|
|
7481
|
+
}
|
|
7482
|
+
const operationId = randomUUID7();
|
|
7483
|
+
const preview = await ctx.client.previewDeleteSite(site.siteId, site.credential, {
|
|
7484
|
+
operationId
|
|
7485
|
+
});
|
|
7486
|
+
const c = preview.consequences;
|
|
7487
|
+
if (c.requiresFreeModeBeforeDelete) {
|
|
7488
|
+
const cancellationScheduled = preview.confirmation.expectedCancelAtPeriodEnd === true;
|
|
7489
|
+
return structuredToolResult({
|
|
7490
|
+
schemaVersion: 1,
|
|
7491
|
+
outcome: "blocked",
|
|
7492
|
+
resultCode: "delete_requires_free_mode",
|
|
7493
|
+
summary: summaryMarkdown({
|
|
7494
|
+
title: "This site is subscription-backed \u2014 it cannot be deleted yet",
|
|
7495
|
+
lead: "Nothing was changed. Sakupa deletes only FREE sites. End the subscription first; when the paid period ends the site automatically becomes a free 30-day site, and delete becomes available.",
|
|
7496
|
+
facts: [
|
|
7497
|
+
["Site", site.url ?? site.siteId],
|
|
7498
|
+
["Plan", preview.confirmation.expectedPlan],
|
|
7499
|
+
["Period-end cancellation already scheduled", cancellationScheduled ? "yes" : "no"],
|
|
7500
|
+
[
|
|
7501
|
+
"Paid access ends",
|
|
7502
|
+
c.currentPaidAccessEndsAt ? timestampForAgent(c.currentPaidAccessEndsAt) : void 0
|
|
7503
|
+
],
|
|
7504
|
+
[
|
|
7505
|
+
"Custom hostnames removed when it reverts",
|
|
7506
|
+
c.removesCustomHostnames.join(", ") || "none"
|
|
7507
|
+
]
|
|
7508
|
+
],
|
|
7509
|
+
steps: cancellationScheduled ? [
|
|
7510
|
+
"Wait for the paid period to end; Sakupa then reverts the site to a free 30-day site (content kept, custom domains removed).",
|
|
7511
|
+
"Call delete again at that point \u2014 or simply let the free site expire."
|
|
7512
|
+
] : [
|
|
7513
|
+
"Call change and open the Stripe-hosted link; the user chooses period-end cancellation there (Sakupa never cancels on its own).",
|
|
7514
|
+
"When the paid period ends, Sakupa reverts the site to a free 30-day site (content kept, custom domains removed).",
|
|
7515
|
+
"Call delete again at that point \u2014 or simply let the free site expire."
|
|
7516
|
+
],
|
|
7517
|
+
next: cancellationScheduled ? ["`billing`"] : ["`change`", "`billing`"]
|
|
7518
|
+
}),
|
|
7519
|
+
data: {
|
|
7520
|
+
siteId: site.siteId,
|
|
7521
|
+
operationId: preview.operationId,
|
|
7522
|
+
consequences: c,
|
|
7523
|
+
cancellationScheduled
|
|
7524
|
+
},
|
|
7525
|
+
nextActions: [
|
|
7526
|
+
{ tool: cancellationScheduled ? "billing" : "change", arguments: {}, allowed: true }
|
|
7527
|
+
]
|
|
7528
|
+
});
|
|
7529
|
+
}
|
|
7530
|
+
const confirmArguments = {
|
|
7531
|
+
confirmed: true,
|
|
7532
|
+
operationId: preview.operationId,
|
|
7533
|
+
confirmation: preview.confirmation
|
|
7534
|
+
};
|
|
7535
|
+
return presentDecision(baseCtx.decisions, call, "delete", {
|
|
7536
|
+
resultCode: "delete_confirmation_required",
|
|
7537
|
+
summary: summaryMarkdown({
|
|
7538
|
+
title: `Delete ${site.url ?? site.siteId}? Nothing was changed.`,
|
|
7539
|
+
lead: "This is a preview. Deleting cannot be undone: the content, the public URL, installed apps and stored form submissions disappear at once, and the local credential file is removed.",
|
|
7540
|
+
facts: [
|
|
7541
|
+
["Site ID", site.siteId],
|
|
7542
|
+
["Public URL", site.url],
|
|
7543
|
+
["Permanent URL released", c.releasesPermanentUrl],
|
|
7544
|
+
["Custom hostnames removed", c.removesCustomHostnames.join(", ") || "none"],
|
|
7545
|
+
["Exact confirm arguments", JSON.stringify(confirmArguments)]
|
|
7546
|
+
],
|
|
7547
|
+
notes: [
|
|
7548
|
+
"After deletion this project is unbound; the next deploy creates a brand-new site with a new URL and credential.",
|
|
7549
|
+
"To keep the URL but replace the content, deploy again instead of deleting."
|
|
7550
|
+
]
|
|
7551
|
+
}),
|
|
7552
|
+
data: {
|
|
7553
|
+
siteId: site.siteId,
|
|
7554
|
+
operationId: preview.operationId,
|
|
7555
|
+
consequences: c,
|
|
7556
|
+
confirmation: preview.confirmation,
|
|
7557
|
+
confirmArguments
|
|
7558
|
+
},
|
|
7559
|
+
prompt: `Delete the site ${site.url ?? site.siteId} now? This cannot be undone.`,
|
|
7560
|
+
options: [
|
|
7561
|
+
callToolDecisionOption({
|
|
7562
|
+
id: "delete_site",
|
|
7563
|
+
label: `Delete ${site.url ?? site.siteId}`,
|
|
7564
|
+
description: "Remove the site, its content, apps and submissions, and release the URL.",
|
|
7565
|
+
consequences: [
|
|
7566
|
+
"Visitors get HTTP 410 immediately.",
|
|
7567
|
+
"The local credential file is removed; this project is unbound."
|
|
7568
|
+
],
|
|
7569
|
+
tool: "delete",
|
|
7570
|
+
arguments: confirmArguments,
|
|
7571
|
+
reasonCode: "explicit_site_delete_confirmation"
|
|
7572
|
+
}),
|
|
7573
|
+
noActionDecisionOption({ description: "Keep the site exactly as it is." })
|
|
7574
|
+
],
|
|
7575
|
+
legacyUserAction: { type: "confirm_in_mcp", provider: "sakupa" }
|
|
7576
|
+
});
|
|
7577
|
+
} catch (error) {
|
|
7578
|
+
return toolError(error);
|
|
7579
|
+
}
|
|
7580
|
+
})
|
|
7581
|
+
);
|
|
7582
|
+
}
|
|
7583
|
+
|
|
7378
7584
|
// src/server.ts
|
|
7379
7585
|
import { resolve as resolve6 } from "node:path";
|
|
7380
7586
|
var instructionsFor = (hostPattern) => `Sakupa publishes AI-made static websites. AI-made pages, live in seconds.
|
|
@@ -7384,7 +7590,7 @@ Workflow:
|
|
|
7384
7590
|
(Vite/Vue/React/Svelte/Astro/Next static export/Nuxt generate), run the build LOCALLY first,
|
|
7385
7591
|
then re-run analyze.
|
|
7386
7592
|
2. deploy \u2014 uploads ONLY the built static output. The first deploy creates a free temporary
|
|
7387
|
-
site (public URL ${hostPattern}, valid
|
|
7593
|
+
site (public URL ${hostPattern}, valid 30 days, free banner shown) and stores the
|
|
7388
7594
|
management credential in .sakupa/site.json. Deploying again updates the site and refreshes
|
|
7389
7595
|
its validity; refresh extends validity without uploading; status shows the
|
|
7390
7596
|
current deployment and serving state at any time. Every update checks the credential's
|
|
@@ -7393,7 +7599,7 @@ Workflow:
|
|
|
7393
7599
|
3. To keep the site live beyond the free period, subscribe it to a monthly hosting plan (plans
|
|
7394
7600
|
shows the catalog; subscribe -> Stripe-hosted checkout;
|
|
7395
7601
|
water/personal/share/business). While the subscription remains active, the
|
|
7396
|
-
${hostPattern} URL stays live without the free
|
|
7602
|
+
${hostPattern} URL stays live without the free 30-day expiry. Usage over the chosen plan
|
|
7397
7603
|
shows an over-limit notice by default. An external AI may periodically query usage and
|
|
7398
7604
|
recommend a plan, but Sakupa never changes a subscription automatically.
|
|
7399
7605
|
4. Optionally bind a custom domain to the subscribed site (bind): an included extra
|
|
@@ -7404,13 +7610,20 @@ Workflow:
|
|
|
7404
7610
|
until the new domain's www is confirmed live, then it is replaced automatically.
|
|
7405
7611
|
Plan changes are confirmed only on Stripe and synchronized by Stripe webhook.
|
|
7406
7612
|
A cancellation keeps the site paid through the current period. Sakupa reverts it to a free
|
|
7407
|
-
|
|
7613
|
+
30-day site and removes paid data after Stripe sends the signed final-cancellation webhook.
|
|
7408
7614
|
Recovery writes the new local credential before downloading content. If a session stops after
|
|
7409
7615
|
.sakupa/site.json exists, NEVER repeat DNS recovery: resume with recover action "download".
|
|
7410
7616
|
5. If any Sakupa operation is difficult or fails, call help FIRST. support handles billing,
|
|
7411
7617
|
payment, refund and other customer-service requests. report is the LAST resort only when
|
|
7412
7618
|
help explicitly recommends a product bug report, and submission still requires user review.
|
|
7413
7619
|
|
|
7620
|
+
Deleting a site: delete with no arguments only previews and returns a decision; after the user
|
|
7621
|
+
confirms, call delete again with the exact preview arguments. A FREE site is deleted immediately
|
|
7622
|
+
(content, URL, apps and submissions gone; .sakupa/site.json removed; the next deploy creates a new
|
|
7623
|
+
site). A subscribed site cannot be deleted: end the subscription first (change \u2192 period-end
|
|
7624
|
+
cancellation on Stripe); when it ends the site reverts to a free 30-day site and delete becomes
|
|
7625
|
+
available. Never suggest delete as a way to free up the free-site allowance during deploy.
|
|
7626
|
+
|
|
7414
7627
|
Decision-options contract: when a result contains decision, present EVERY numbered option from the
|
|
7415
7628
|
tool to the user and wait for their selection. No option is selected by default. Never choose from
|
|
7416
7629
|
context, paraphrase a selection into different arguments, or invent another option. After the user
|
|
@@ -7567,6 +7780,7 @@ function createSakupaMcpServer(opts) {
|
|
|
7567
7780
|
registerBillingTools(server, ctx);
|
|
7568
7781
|
registerCredentialTools(server, ctx);
|
|
7569
7782
|
registerAppsTools(server, ctx);
|
|
7783
|
+
registerDeleteTools(server, ctx);
|
|
7570
7784
|
registerHelpTools(server, ctx);
|
|
7571
7785
|
return server;
|
|
7572
7786
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sakupa/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"mcpName": "io.github.myerwang/sakupa",
|
|
5
5
|
"description": "Sakupa MCP server: publish AI-made static sites from your AI tool. AI-made pages, live in seconds.",
|
|
6
6
|
"homepage": "https://sakupa.com/manual/",
|