@sakupa/mcp 0.7.6 → 0.7.7
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 +30 -63
- package/dist/index.js +30 -63
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -128,7 +128,7 @@ var FORBIDDEN_PATH_SEGMENTS = [
|
|
|
128
128
|
var ALLOWED_HIDDEN_PATHS = [".well-known/"];
|
|
129
129
|
|
|
130
130
|
// ../core/dist/domain/version.js
|
|
131
|
-
var SAKUPA_MCP_VERSION = "0.7.
|
|
131
|
+
var SAKUPA_MCP_VERSION = "0.7.7";
|
|
132
132
|
|
|
133
133
|
// ../core/dist/domain/errors.js
|
|
134
134
|
var HTTP_STATUS = {
|
|
@@ -407,22 +407,13 @@ function validateDeployableFiles(files, opts) {
|
|
|
407
407
|
severity: "warning",
|
|
408
408
|
code: "missing_html_lang",
|
|
409
409
|
path: entryHtmlPath,
|
|
410
|
-
message: 'The entry HTML has no lang attribute.
|
|
410
|
+
message: 'The entry HTML has no lang attribute. Before deploying, add one matching the content language (html lang="en" | "ja" | "zh-CN") \u2014 it drives screen-reader pronunciation and search-engine language detection, and Sakupa surfaces follow it.'
|
|
411
411
|
});
|
|
412
412
|
}
|
|
413
413
|
}
|
|
414
414
|
}
|
|
415
415
|
const jsCount = files.filter((f) => ["js", "mjs"].includes(fileExtension(f.path))).length;
|
|
416
416
|
const looksLikeSpa = htmlPaths.length === 1 && entryHtmlPath === "index.html" && jsCount > 0;
|
|
417
|
-
const wantsSpa = opts.spaFallbackRequested === true || looksLikeSpa;
|
|
418
|
-
const spaFallbackConfirmationRequired = wantsSpa && opts.spaFallbackConfirmed !== true;
|
|
419
|
-
if (opts.spaFallbackRequested === true && opts.spaFallbackConfirmed !== true) {
|
|
420
|
-
issues.push({
|
|
421
|
-
severity: "error",
|
|
422
|
-
code: "spa_fallback_confirmation_required",
|
|
423
|
-
message: "SPA fallback rewrites unknown paths to index.html and changes normal 404 behavior. It must be explicitly confirmed."
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
417
|
const ok = issues.every((i) => i.severity !== "error");
|
|
427
418
|
return {
|
|
428
419
|
ok,
|
|
@@ -432,8 +423,7 @@ function validateDeployableFiles(files, opts) {
|
|
|
432
423
|
entryHtmlPath,
|
|
433
424
|
htmlLang,
|
|
434
425
|
supportedLang,
|
|
435
|
-
looksLikeSpa
|
|
436
|
-
spaFallbackConfirmationRequired
|
|
426
|
+
looksLikeSpa
|
|
437
427
|
};
|
|
438
428
|
}
|
|
439
429
|
function safeDecode(bytes) {
|
|
@@ -1052,7 +1042,7 @@ async function analyzeProject(projectDir, opts = {}) {
|
|
|
1052
1042
|
fileCount: 0,
|
|
1053
1043
|
issues: [],
|
|
1054
1044
|
ssrRisks,
|
|
1055
|
-
spa: { looksLikeSpa: false,
|
|
1045
|
+
spa: { looksLikeSpa: false, autoFallback: false },
|
|
1056
1046
|
deployable: false,
|
|
1057
1047
|
suggestedNextAction: suggestedNextAction2
|
|
1058
1048
|
};
|
|
@@ -1072,30 +1062,23 @@ async function analyzeProject(projectDir, opts = {}) {
|
|
|
1072
1062
|
}
|
|
1073
1063
|
candidates.push({ path: file.path, size: file.size, ...content ? { content } : {} });
|
|
1074
1064
|
}
|
|
1075
|
-
const validation = validateDeployableFiles(candidates, {
|
|
1076
|
-
mode: "free",
|
|
1077
|
-
...opts.spaFallbackRequested !== void 0 ? { spaFallbackRequested: opts.spaFallbackRequested } : {},
|
|
1078
|
-
...opts.spaFallbackConfirmed !== void 0 ? { spaFallbackConfirmed: opts.spaFallbackConfirmed } : {}
|
|
1079
|
-
});
|
|
1065
|
+
const validation = validateDeployableFiles(candidates, { mode: "free" });
|
|
1080
1066
|
ssrRisks.push(...serverAndDbDepRisks(pkg, true));
|
|
1081
1067
|
const deployable = validation.ok && walked.length > 0;
|
|
1082
1068
|
const spa = {
|
|
1083
1069
|
looksLikeSpa: validation.looksLikeSpa,
|
|
1084
|
-
|
|
1085
|
-
confirmationRequired: validation.spaFallbackConfirmationRequired
|
|
1070
|
+
autoFallback: validation.looksLikeSpa
|
|
1086
1071
|
};
|
|
1087
1072
|
let suggestedNextAction;
|
|
1088
1073
|
if (!deployable) {
|
|
1089
1074
|
const firstError = validation.issues.find((i) => i.severity === "error");
|
|
1090
1075
|
if (firstError?.code === "missing_index_html") {
|
|
1091
1076
|
suggestedNextAction = `No index.html at the root of "${outputDirRel}". Deploy the built static output (the directory whose root contains index.html), not the source project. Build locally first if needed (${buildCommandHint ?? "npm run build"}), then re-run analyze_site.`;
|
|
1092
|
-
} else if (firstError?.code === "spa_fallback_confirmation_required") {
|
|
1093
|
-
suggestedNextAction = "SPA fallback rewrites unknown paths to index.html and changes normal 404 behavior. Confirm it explicitly: re-run with spaFallbackRequested: true and spaFallbackConfirmed: true.";
|
|
1094
1077
|
} else {
|
|
1095
1078
|
suggestedNextAction = "Fix the listed issues (remove forbidden/secret files, reduce size, add missing entry HTML), then re-run analyze_site.";
|
|
1096
1079
|
}
|
|
1097
|
-
} else if (spa.
|
|
1098
|
-
suggestedNextAction = `
|
|
1080
|
+
} else if (spa.looksLikeSpa) {
|
|
1081
|
+
suggestedNextAction = `Run deploy_site to publish the static output in "${outputDirRel}". It looks like a single-page app, so SPA fallback (unknown paths rewrite to index.html) will be enabled automatically; pass spaFallback: false to opt out.`;
|
|
1099
1082
|
} else {
|
|
1100
1083
|
suggestedNextAction = `Run deploy_site to publish the static output in "${outputDirRel}".`;
|
|
1101
1084
|
}
|
|
@@ -1342,7 +1325,7 @@ ${JSON.stringify(obj, null, 2)}`;
|
|
|
1342
1325
|
var planEnum = z2.enum(["water", "personal", "share", "business"]);
|
|
1343
1326
|
var severityEnum = z2.enum(["low", "medium", "high", "critical"]);
|
|
1344
1327
|
function planCatalog() {
|
|
1345
|
-
return TIER_ORDER.map((p) => `${p}
|
|
1328
|
+
return TIER_ORDER.map((p) => `${p} JPY ${tierPriceJpy(p)}/month`).join(", ");
|
|
1346
1329
|
}
|
|
1347
1330
|
var ticketCategoryEnum = z2.enum([
|
|
1348
1331
|
"billing",
|
|
@@ -1370,22 +1353,6 @@ Analysis:`,
|
|
|
1370
1353
|
"blocked"
|
|
1371
1354
|
);
|
|
1372
1355
|
}
|
|
1373
|
-
function spaConfirmationResult(analysis) {
|
|
1374
|
-
return text(
|
|
1375
|
-
"spa_fallback_confirmation_required",
|
|
1376
|
-
`SPA fallback confirmation required \u2014 nothing was deployed yet.
|
|
1377
|
-
|
|
1378
|
-
This site looks like a single-page application (one index.html plus JavaScript). SPA fallback rewrites every unknown path to index.html so client-side routes work, but it CHANGES normal 404 behavior: visitors never see a not-found page.
|
|
1379
|
-
|
|
1380
|
-
Please ask the user to choose, then re-run deploy_site with:
|
|
1381
|
-
- spaFallback: true, spaFallbackConfirmed: true -> enable SPA fallback
|
|
1382
|
-
- spaFallbackConfirmed: true (spaFallback omitted or false) -> deploy WITHOUT fallback (unknown paths return 404)
|
|
1383
|
-
|
|
1384
|
-
Output directory: "${analysis.recommendedOutputDir ?? "."}", ${analysis.fileCount} files.`,
|
|
1385
|
-
{ analysis: analysisSummary(analysis), requestedConfirmation: "spa_fallback" },
|
|
1386
|
-
"waiting_user"
|
|
1387
|
-
);
|
|
1388
|
-
}
|
|
1389
1356
|
var MB2 = 1024 * 1024;
|
|
1390
1357
|
function ensureUploadSizeWithinLimits(manifest, isFirstFreeDeploy) {
|
|
1391
1358
|
const oversized = manifest.find((f) => f.size > MAX_SINGLE_FILE_BYTES);
|
|
@@ -1447,17 +1414,13 @@ function registerTools(server, ctx) {
|
|
|
1447
1414
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
1448
1415
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1449
1416
|
inputSchema: {
|
|
1450
|
-
outputDir: z2.string().optional().describe("Output directory relative to the project root (overrides detection).")
|
|
1451
|
-
spaFallbackRequested: z2.boolean().optional().describe("User asked for SPA fallback (unknown paths rewritten to index.html)."),
|
|
1452
|
-
spaFallbackConfirmed: z2.boolean().optional().describe("User explicitly confirmed the SPA fallback 404-behavior change.")
|
|
1417
|
+
outputDir: z2.string().optional().describe("Output directory relative to the project root (overrides detection).")
|
|
1453
1418
|
}
|
|
1454
1419
|
},
|
|
1455
1420
|
async (args) => {
|
|
1456
1421
|
try {
|
|
1457
1422
|
const analysis = await analyzeProject(ctx.projectDir, {
|
|
1458
|
-
...args.outputDir !== void 0 ? { outputDir: args.outputDir } : {}
|
|
1459
|
-
...args.spaFallbackRequested !== void 0 ? { spaFallbackRequested: args.spaFallbackRequested } : {},
|
|
1460
|
-
...args.spaFallbackConfirmed !== void 0 ? { spaFallbackConfirmed: args.spaFallbackConfirmed } : {}
|
|
1423
|
+
...args.outputDir !== void 0 ? { outputDir: args.outputDir } : {}
|
|
1461
1424
|
});
|
|
1462
1425
|
return textJson(
|
|
1463
1426
|
"site_analysis_completed",
|
|
@@ -1478,8 +1441,9 @@ Next action: ${analysis.suggestedNextAction}`,
|
|
|
1478
1441
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
1479
1442
|
inputSchema: {
|
|
1480
1443
|
outputDir: z2.string().optional().describe("Output directory relative to the project root (overrides detection)."),
|
|
1481
|
-
spaFallback: z2.boolean().optional().describe(
|
|
1482
|
-
|
|
1444
|
+
spaFallback: z2.boolean().optional().describe(
|
|
1445
|
+
"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."
|
|
1446
|
+
),
|
|
1483
1447
|
publicConfirmed: z2.boolean().optional().describe(
|
|
1484
1448
|
"Required only for the first deployment: user explicitly confirmed creation of a public 24-hour URL."
|
|
1485
1449
|
),
|
|
@@ -1489,13 +1453,8 @@ Next action: ${analysis.suggestedNextAction}`,
|
|
|
1489
1453
|
async (args) => {
|
|
1490
1454
|
try {
|
|
1491
1455
|
const analysis = await analyzeProject(ctx.projectDir, {
|
|
1492
|
-
...args.outputDir !== void 0 ? { outputDir: args.outputDir } : {}
|
|
1493
|
-
...args.spaFallback !== void 0 ? { spaFallbackRequested: args.spaFallback } : {},
|
|
1494
|
-
...args.spaFallbackConfirmed !== void 0 ? { spaFallbackConfirmed: args.spaFallbackConfirmed } : {}
|
|
1456
|
+
...args.outputDir !== void 0 ? { outputDir: args.outputDir } : {}
|
|
1495
1457
|
});
|
|
1496
|
-
if (analysis.spa.confirmationRequired && args.spaFallbackConfirmed !== true) {
|
|
1497
|
-
return spaConfirmationResult(analysis);
|
|
1498
|
-
}
|
|
1499
1458
|
if (!analysis.deployable || !analysis.files) {
|
|
1500
1459
|
return notDeployableResult(analysis);
|
|
1501
1460
|
}
|
|
@@ -1527,8 +1486,7 @@ Next action: ${analysis.suggestedNextAction}`,
|
|
|
1527
1486
|
const created = await ctx.client.createSite({
|
|
1528
1487
|
manifest,
|
|
1529
1488
|
...args.lang !== void 0 ? { lang: args.lang } : {},
|
|
1530
|
-
spaFallback: args.spaFallback
|
|
1531
|
-
...args.spaFallbackConfirmed !== void 0 ? { spaFallbackConfirmed: args.spaFallbackConfirmed } : {}
|
|
1489
|
+
...args.spaFallback !== void 0 ? { spaFallback: args.spaFallback } : {}
|
|
1532
1490
|
});
|
|
1533
1491
|
const uploaded2 = await uploadAll(ctx, created.uploadTargets, files, outputAbs);
|
|
1534
1492
|
const finalized2 = await ctx.client.finalizeDeployment(
|
|
@@ -1572,8 +1530,7 @@ ${JSON.stringify(finalized2.warnings, null, 2)}` : ""),
|
|
|
1572
1530
|
const req = {
|
|
1573
1531
|
manifest,
|
|
1574
1532
|
...args.lang !== void 0 ? { lang: args.lang } : {},
|
|
1575
|
-
spaFallback: args.spaFallback
|
|
1576
|
-
...args.spaFallbackConfirmed !== void 0 ? { spaFallbackConfirmed: args.spaFallbackConfirmed } : {},
|
|
1533
|
+
...args.spaFallback !== void 0 ? { spaFallback: args.spaFallback } : {},
|
|
1577
1534
|
...forceFullUpload ? { forceFullUpload: true } : {}
|
|
1578
1535
|
};
|
|
1579
1536
|
const deployment = await ctx.client.createDeployment(
|
|
@@ -1648,7 +1605,7 @@ ${JSON.stringify(finalized.warnings, null, 2)}` : ""),
|
|
|
1648
1605
|
return text(
|
|
1649
1606
|
"site_refreshed",
|
|
1650
1607
|
`Site validity refreshed. New expiry: ${res.expiresAt}
|
|
1651
|
-
Free sites stay live for ${FREE_SITE_TTL_HOURS} hours after each deploy or refresh.`,
|
|
1608
|
+
NO content was uploaded or changed by this call \u2014 to publish new or edited files, run deploy_site. Free sites stay live for ${FREE_SITE_TTL_HOURS} hours after each deploy or refresh.`,
|
|
1652
1609
|
{ siteId: site.siteId, expiresAt: res.expiresAt }
|
|
1653
1610
|
);
|
|
1654
1611
|
} catch (e) {
|
|
@@ -1699,7 +1656,7 @@ Free sites stay live for ${FREE_SITE_TTL_HOURS} hours after each deploy or refre
|
|
|
1699
1656
|
);
|
|
1700
1657
|
return text(
|
|
1701
1658
|
"subscription_checkout_ready",
|
|
1702
|
-
`Stripe Checkout link \u2014 Sakupa Hosting for this site: ${res.plan} plan,
|
|
1659
|
+
`Stripe Checkout link \u2014 Sakupa Hosting for this site: ${res.plan} plan, JPY ${res.monthlyPriceJpy}/month (Japanese yen)
|
|
1703
1660
|
${res.checkoutUrl}
|
|
1704
1661
|
|
|
1705
1662
|
Open this link in a browser to subscribe. Card data is entered only on the Stripe-hosted page \u2014 never give card numbers, passwords or security codes to the AI tool.
|
|
@@ -1811,7 +1768,7 @@ Then run bind_domain again with verificationId: "${res.verificationId}" to check
|
|
|
1811
1768
|
const lines = [
|
|
1812
1769
|
`Billing status for site ${res.siteId} (mode: ${res.mode})`,
|
|
1813
1770
|
res.permanentUrl ? `Permanent URL: ${res.permanentUrl}` : void 0,
|
|
1814
|
-
res.plan ? `Plan: ${res.plan} (
|
|
1771
|
+
res.plan ? `Plan: ${res.plan} (JPY ${res.monthlyPriceJpy ?? tierPriceJpy(res.plan)}/month)` : "Plan: (no subscription yet)",
|
|
1815
1772
|
res.subscriptionStatus ? `Subscription payment state: ${res.subscriptionStatus}` : void 0,
|
|
1816
1773
|
res.cancelAtPeriodEnd ? "Renewal: CANCELED \u2014 the site reverts to free at the end of the already-paid month" : void 0,
|
|
1817
1774
|
res.currentPeriodStart ? `Current paid period: ${res.currentPeriodStart} -> ${res.currentPeriodEnd ?? "?"}` : void 0,
|
|
@@ -2394,6 +2351,16 @@ Safety boundaries:
|
|
|
2394
2351
|
- Never upload source projects, secrets, .env files, private keys, archives, videos or audio.
|
|
2395
2352
|
- Payment card data is entered only on Stripe-hosted pages \u2014 never through the AI tool.
|
|
2396
2353
|
- A subscription never grants domain ownership; only DNS verification does.
|
|
2354
|
+
- Never repeat, echo, or memorize the credential value from .sakupa/site.json \u2014 quoting it
|
|
2355
|
+
into the conversation copies the site's only key outside the protected local file. Read it
|
|
2356
|
+
only through the tools.
|
|
2357
|
+
- Before deploying, if the entry HTML lacks a lang attribute, add one matching the content
|
|
2358
|
+
language (infer it from the content) and then deploy; only skip when the user explicitly
|
|
2359
|
+
wants no lang attribute.
|
|
2360
|
+
- Prices are authoritative in JPY (Japanese yen). When talking with a user in a language
|
|
2361
|
+
other than Japanese, look up the approximate exchange rate and show an estimated local
|
|
2362
|
+
price next to the JPY amount, clearly marked as an estimate \u2014 Stripe always settles the
|
|
2363
|
+
real charge in JPY. Never show a bare Yen sign.
|
|
2397
2364
|
- The management credential lives only in .sakupa/site.json; never share or upload it. Without
|
|
2398
2365
|
a bound custom domain, a lost credential is unrecoverable by design. manage_billing then opens
|
|
2399
2366
|
Stripe's public no-code portal login, where the customer verifies the checkout email with a
|
package/dist/index.js
CHANGED
|
@@ -123,7 +123,7 @@ var FORBIDDEN_PATH_SEGMENTS = [
|
|
|
123
123
|
var ALLOWED_HIDDEN_PATHS = [".well-known/"];
|
|
124
124
|
|
|
125
125
|
// ../core/dist/domain/version.js
|
|
126
|
-
var SAKUPA_MCP_VERSION = "0.7.
|
|
126
|
+
var SAKUPA_MCP_VERSION = "0.7.7";
|
|
127
127
|
|
|
128
128
|
// ../core/dist/domain/errors.js
|
|
129
129
|
var HTTP_STATUS = {
|
|
@@ -402,22 +402,13 @@ function validateDeployableFiles(files, opts) {
|
|
|
402
402
|
severity: "warning",
|
|
403
403
|
code: "missing_html_lang",
|
|
404
404
|
path: entryHtmlPath,
|
|
405
|
-
message: 'The entry HTML has no lang attribute.
|
|
405
|
+
message: 'The entry HTML has no lang attribute. Before deploying, add one matching the content language (html lang="en" | "ja" | "zh-CN") \u2014 it drives screen-reader pronunciation and search-engine language detection, and Sakupa surfaces follow it.'
|
|
406
406
|
});
|
|
407
407
|
}
|
|
408
408
|
}
|
|
409
409
|
}
|
|
410
410
|
const jsCount = files.filter((f) => ["js", "mjs"].includes(fileExtension(f.path))).length;
|
|
411
411
|
const looksLikeSpa = htmlPaths.length === 1 && entryHtmlPath === "index.html" && jsCount > 0;
|
|
412
|
-
const wantsSpa = opts.spaFallbackRequested === true || looksLikeSpa;
|
|
413
|
-
const spaFallbackConfirmationRequired = wantsSpa && opts.spaFallbackConfirmed !== true;
|
|
414
|
-
if (opts.spaFallbackRequested === true && opts.spaFallbackConfirmed !== true) {
|
|
415
|
-
issues.push({
|
|
416
|
-
severity: "error",
|
|
417
|
-
code: "spa_fallback_confirmation_required",
|
|
418
|
-
message: "SPA fallback rewrites unknown paths to index.html and changes normal 404 behavior. It must be explicitly confirmed."
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
412
|
const ok = issues.every((i) => i.severity !== "error");
|
|
422
413
|
return {
|
|
423
414
|
ok,
|
|
@@ -427,8 +418,7 @@ function validateDeployableFiles(files, opts) {
|
|
|
427
418
|
entryHtmlPath,
|
|
428
419
|
htmlLang,
|
|
429
420
|
supportedLang,
|
|
430
|
-
looksLikeSpa
|
|
431
|
-
spaFallbackConfirmationRequired
|
|
421
|
+
looksLikeSpa
|
|
432
422
|
};
|
|
433
423
|
}
|
|
434
424
|
function safeDecode(bytes) {
|
|
@@ -1215,7 +1205,7 @@ async function analyzeProject(projectDir, opts = {}) {
|
|
|
1215
1205
|
fileCount: 0,
|
|
1216
1206
|
issues: [],
|
|
1217
1207
|
ssrRisks,
|
|
1218
|
-
spa: { looksLikeSpa: false,
|
|
1208
|
+
spa: { looksLikeSpa: false, autoFallback: false },
|
|
1219
1209
|
deployable: false,
|
|
1220
1210
|
suggestedNextAction: suggestedNextAction2
|
|
1221
1211
|
};
|
|
@@ -1235,30 +1225,23 @@ async function analyzeProject(projectDir, opts = {}) {
|
|
|
1235
1225
|
}
|
|
1236
1226
|
candidates.push({ path: file.path, size: file.size, ...content ? { content } : {} });
|
|
1237
1227
|
}
|
|
1238
|
-
const validation = validateDeployableFiles(candidates, {
|
|
1239
|
-
mode: "free",
|
|
1240
|
-
...opts.spaFallbackRequested !== void 0 ? { spaFallbackRequested: opts.spaFallbackRequested } : {},
|
|
1241
|
-
...opts.spaFallbackConfirmed !== void 0 ? { spaFallbackConfirmed: opts.spaFallbackConfirmed } : {}
|
|
1242
|
-
});
|
|
1228
|
+
const validation = validateDeployableFiles(candidates, { mode: "free" });
|
|
1243
1229
|
ssrRisks.push(...serverAndDbDepRisks(pkg, true));
|
|
1244
1230
|
const deployable = validation.ok && walked.length > 0;
|
|
1245
1231
|
const spa = {
|
|
1246
1232
|
looksLikeSpa: validation.looksLikeSpa,
|
|
1247
|
-
|
|
1248
|
-
confirmationRequired: validation.spaFallbackConfirmationRequired
|
|
1233
|
+
autoFallback: validation.looksLikeSpa
|
|
1249
1234
|
};
|
|
1250
1235
|
let suggestedNextAction;
|
|
1251
1236
|
if (!deployable) {
|
|
1252
1237
|
const firstError = validation.issues.find((i) => i.severity === "error");
|
|
1253
1238
|
if (firstError?.code === "missing_index_html") {
|
|
1254
1239
|
suggestedNextAction = `No index.html at the root of "${outputDirRel}". Deploy the built static output (the directory whose root contains index.html), not the source project. Build locally first if needed (${buildCommandHint ?? "npm run build"}), then re-run analyze_site.`;
|
|
1255
|
-
} else if (firstError?.code === "spa_fallback_confirmation_required") {
|
|
1256
|
-
suggestedNextAction = "SPA fallback rewrites unknown paths to index.html and changes normal 404 behavior. Confirm it explicitly: re-run with spaFallbackRequested: true and spaFallbackConfirmed: true.";
|
|
1257
1240
|
} else {
|
|
1258
1241
|
suggestedNextAction = "Fix the listed issues (remove forbidden/secret files, reduce size, add missing entry HTML), then re-run analyze_site.";
|
|
1259
1242
|
}
|
|
1260
|
-
} else if (spa.
|
|
1261
|
-
suggestedNextAction = `
|
|
1243
|
+
} else if (spa.looksLikeSpa) {
|
|
1244
|
+
suggestedNextAction = `Run deploy_site to publish the static output in "${outputDirRel}". It looks like a single-page app, so SPA fallback (unknown paths rewrite to index.html) will be enabled automatically; pass spaFallback: false to opt out.`;
|
|
1262
1245
|
} else {
|
|
1263
1246
|
suggestedNextAction = `Run deploy_site to publish the static output in "${outputDirRel}".`;
|
|
1264
1247
|
}
|
|
@@ -1404,7 +1387,7 @@ ${JSON.stringify(obj, null, 2)}`;
|
|
|
1404
1387
|
var planEnum = z2.enum(["water", "personal", "share", "business"]);
|
|
1405
1388
|
var severityEnum = z2.enum(["low", "medium", "high", "critical"]);
|
|
1406
1389
|
function planCatalog() {
|
|
1407
|
-
return TIER_ORDER.map((p) => `${p}
|
|
1390
|
+
return TIER_ORDER.map((p) => `${p} JPY ${tierPriceJpy(p)}/month`).join(", ");
|
|
1408
1391
|
}
|
|
1409
1392
|
var ticketCategoryEnum = z2.enum([
|
|
1410
1393
|
"billing",
|
|
@@ -1432,22 +1415,6 @@ Analysis:`,
|
|
|
1432
1415
|
"blocked"
|
|
1433
1416
|
);
|
|
1434
1417
|
}
|
|
1435
|
-
function spaConfirmationResult(analysis) {
|
|
1436
|
-
return text(
|
|
1437
|
-
"spa_fallback_confirmation_required",
|
|
1438
|
-
`SPA fallback confirmation required \u2014 nothing was deployed yet.
|
|
1439
|
-
|
|
1440
|
-
This site looks like a single-page application (one index.html plus JavaScript). SPA fallback rewrites every unknown path to index.html so client-side routes work, but it CHANGES normal 404 behavior: visitors never see a not-found page.
|
|
1441
|
-
|
|
1442
|
-
Please ask the user to choose, then re-run deploy_site with:
|
|
1443
|
-
- spaFallback: true, spaFallbackConfirmed: true -> enable SPA fallback
|
|
1444
|
-
- spaFallbackConfirmed: true (spaFallback omitted or false) -> deploy WITHOUT fallback (unknown paths return 404)
|
|
1445
|
-
|
|
1446
|
-
Output directory: "${analysis.recommendedOutputDir ?? "."}", ${analysis.fileCount} files.`,
|
|
1447
|
-
{ analysis: analysisSummary(analysis), requestedConfirmation: "spa_fallback" },
|
|
1448
|
-
"waiting_user"
|
|
1449
|
-
);
|
|
1450
|
-
}
|
|
1451
1418
|
var MB2 = 1024 * 1024;
|
|
1452
1419
|
function ensureUploadSizeWithinLimits(manifest, isFirstFreeDeploy) {
|
|
1453
1420
|
const oversized = manifest.find((f) => f.size > MAX_SINGLE_FILE_BYTES);
|
|
@@ -1509,17 +1476,13 @@ function registerTools(server, ctx) {
|
|
|
1509
1476
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
1510
1477
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1511
1478
|
inputSchema: {
|
|
1512
|
-
outputDir: z2.string().optional().describe("Output directory relative to the project root (overrides detection).")
|
|
1513
|
-
spaFallbackRequested: z2.boolean().optional().describe("User asked for SPA fallback (unknown paths rewritten to index.html)."),
|
|
1514
|
-
spaFallbackConfirmed: z2.boolean().optional().describe("User explicitly confirmed the SPA fallback 404-behavior change.")
|
|
1479
|
+
outputDir: z2.string().optional().describe("Output directory relative to the project root (overrides detection).")
|
|
1515
1480
|
}
|
|
1516
1481
|
},
|
|
1517
1482
|
async (args) => {
|
|
1518
1483
|
try {
|
|
1519
1484
|
const analysis = await analyzeProject(ctx.projectDir, {
|
|
1520
|
-
...args.outputDir !== void 0 ? { outputDir: args.outputDir } : {}
|
|
1521
|
-
...args.spaFallbackRequested !== void 0 ? { spaFallbackRequested: args.spaFallbackRequested } : {},
|
|
1522
|
-
...args.spaFallbackConfirmed !== void 0 ? { spaFallbackConfirmed: args.spaFallbackConfirmed } : {}
|
|
1485
|
+
...args.outputDir !== void 0 ? { outputDir: args.outputDir } : {}
|
|
1523
1486
|
});
|
|
1524
1487
|
return textJson(
|
|
1525
1488
|
"site_analysis_completed",
|
|
@@ -1540,8 +1503,9 @@ Next action: ${analysis.suggestedNextAction}`,
|
|
|
1540
1503
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
1541
1504
|
inputSchema: {
|
|
1542
1505
|
outputDir: z2.string().optional().describe("Output directory relative to the project root (overrides detection)."),
|
|
1543
|
-
spaFallback: z2.boolean().optional().describe(
|
|
1544
|
-
|
|
1506
|
+
spaFallback: z2.boolean().optional().describe(
|
|
1507
|
+
"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."
|
|
1508
|
+
),
|
|
1545
1509
|
publicConfirmed: z2.boolean().optional().describe(
|
|
1546
1510
|
"Required only for the first deployment: user explicitly confirmed creation of a public 24-hour URL."
|
|
1547
1511
|
),
|
|
@@ -1551,13 +1515,8 @@ Next action: ${analysis.suggestedNextAction}`,
|
|
|
1551
1515
|
async (args) => {
|
|
1552
1516
|
try {
|
|
1553
1517
|
const analysis = await analyzeProject(ctx.projectDir, {
|
|
1554
|
-
...args.outputDir !== void 0 ? { outputDir: args.outputDir } : {}
|
|
1555
|
-
...args.spaFallback !== void 0 ? { spaFallbackRequested: args.spaFallback } : {},
|
|
1556
|
-
...args.spaFallbackConfirmed !== void 0 ? { spaFallbackConfirmed: args.spaFallbackConfirmed } : {}
|
|
1518
|
+
...args.outputDir !== void 0 ? { outputDir: args.outputDir } : {}
|
|
1557
1519
|
});
|
|
1558
|
-
if (analysis.spa.confirmationRequired && args.spaFallbackConfirmed !== true) {
|
|
1559
|
-
return spaConfirmationResult(analysis);
|
|
1560
|
-
}
|
|
1561
1520
|
if (!analysis.deployable || !analysis.files) {
|
|
1562
1521
|
return notDeployableResult(analysis);
|
|
1563
1522
|
}
|
|
@@ -1589,8 +1548,7 @@ Next action: ${analysis.suggestedNextAction}`,
|
|
|
1589
1548
|
const created = await ctx.client.createSite({
|
|
1590
1549
|
manifest,
|
|
1591
1550
|
...args.lang !== void 0 ? { lang: args.lang } : {},
|
|
1592
|
-
spaFallback: args.spaFallback
|
|
1593
|
-
...args.spaFallbackConfirmed !== void 0 ? { spaFallbackConfirmed: args.spaFallbackConfirmed } : {}
|
|
1551
|
+
...args.spaFallback !== void 0 ? { spaFallback: args.spaFallback } : {}
|
|
1594
1552
|
});
|
|
1595
1553
|
const uploaded2 = await uploadAll(ctx, created.uploadTargets, files, outputAbs);
|
|
1596
1554
|
const finalized2 = await ctx.client.finalizeDeployment(
|
|
@@ -1634,8 +1592,7 @@ ${JSON.stringify(finalized2.warnings, null, 2)}` : ""),
|
|
|
1634
1592
|
const req = {
|
|
1635
1593
|
manifest,
|
|
1636
1594
|
...args.lang !== void 0 ? { lang: args.lang } : {},
|
|
1637
|
-
spaFallback: args.spaFallback
|
|
1638
|
-
...args.spaFallbackConfirmed !== void 0 ? { spaFallbackConfirmed: args.spaFallbackConfirmed } : {},
|
|
1595
|
+
...args.spaFallback !== void 0 ? { spaFallback: args.spaFallback } : {},
|
|
1639
1596
|
...forceFullUpload ? { forceFullUpload: true } : {}
|
|
1640
1597
|
};
|
|
1641
1598
|
const deployment = await ctx.client.createDeployment(
|
|
@@ -1710,7 +1667,7 @@ ${JSON.stringify(finalized.warnings, null, 2)}` : ""),
|
|
|
1710
1667
|
return text(
|
|
1711
1668
|
"site_refreshed",
|
|
1712
1669
|
`Site validity refreshed. New expiry: ${res.expiresAt}
|
|
1713
|
-
Free sites stay live for ${FREE_SITE_TTL_HOURS} hours after each deploy or refresh.`,
|
|
1670
|
+
NO content was uploaded or changed by this call \u2014 to publish new or edited files, run deploy_site. Free sites stay live for ${FREE_SITE_TTL_HOURS} hours after each deploy or refresh.`,
|
|
1714
1671
|
{ siteId: site.siteId, expiresAt: res.expiresAt }
|
|
1715
1672
|
);
|
|
1716
1673
|
} catch (e) {
|
|
@@ -1761,7 +1718,7 @@ Free sites stay live for ${FREE_SITE_TTL_HOURS} hours after each deploy or refre
|
|
|
1761
1718
|
);
|
|
1762
1719
|
return text(
|
|
1763
1720
|
"subscription_checkout_ready",
|
|
1764
|
-
`Stripe Checkout link \u2014 Sakupa Hosting for this site: ${res.plan} plan,
|
|
1721
|
+
`Stripe Checkout link \u2014 Sakupa Hosting for this site: ${res.plan} plan, JPY ${res.monthlyPriceJpy}/month (Japanese yen)
|
|
1765
1722
|
${res.checkoutUrl}
|
|
1766
1723
|
|
|
1767
1724
|
Open this link in a browser to subscribe. Card data is entered only on the Stripe-hosted page \u2014 never give card numbers, passwords or security codes to the AI tool.
|
|
@@ -1873,7 +1830,7 @@ Then run bind_domain again with verificationId: "${res.verificationId}" to check
|
|
|
1873
1830
|
const lines = [
|
|
1874
1831
|
`Billing status for site ${res.siteId} (mode: ${res.mode})`,
|
|
1875
1832
|
res.permanentUrl ? `Permanent URL: ${res.permanentUrl}` : void 0,
|
|
1876
|
-
res.plan ? `Plan: ${res.plan} (
|
|
1833
|
+
res.plan ? `Plan: ${res.plan} (JPY ${res.monthlyPriceJpy ?? tierPriceJpy(res.plan)}/month)` : "Plan: (no subscription yet)",
|
|
1877
1834
|
res.subscriptionStatus ? `Subscription payment state: ${res.subscriptionStatus}` : void 0,
|
|
1878
1835
|
res.cancelAtPeriodEnd ? "Renewal: CANCELED \u2014 the site reverts to free at the end of the already-paid month" : void 0,
|
|
1879
1836
|
res.currentPeriodStart ? `Current paid period: ${res.currentPeriodStart} -> ${res.currentPeriodEnd ?? "?"}` : void 0,
|
|
@@ -2387,6 +2344,16 @@ Safety boundaries:
|
|
|
2387
2344
|
- Never upload source projects, secrets, .env files, private keys, archives, videos or audio.
|
|
2388
2345
|
- Payment card data is entered only on Stripe-hosted pages \u2014 never through the AI tool.
|
|
2389
2346
|
- A subscription never grants domain ownership; only DNS verification does.
|
|
2347
|
+
- Never repeat, echo, or memorize the credential value from .sakupa/site.json \u2014 quoting it
|
|
2348
|
+
into the conversation copies the site's only key outside the protected local file. Read it
|
|
2349
|
+
only through the tools.
|
|
2350
|
+
- Before deploying, if the entry HTML lacks a lang attribute, add one matching the content
|
|
2351
|
+
language (infer it from the content) and then deploy; only skip when the user explicitly
|
|
2352
|
+
wants no lang attribute.
|
|
2353
|
+
- Prices are authoritative in JPY (Japanese yen). When talking with a user in a language
|
|
2354
|
+
other than Japanese, look up the approximate exchange rate and show an estimated local
|
|
2355
|
+
price next to the JPY amount, clearly marked as an estimate \u2014 Stripe always settles the
|
|
2356
|
+
real charge in JPY. Never show a bare Yen sign.
|
|
2390
2357
|
- The management credential lives only in .sakupa/site.json; never share or upload it. Without
|
|
2391
2358
|
a bound custom domain, a lost credential is unrecoverable by design. manage_billing then opens
|
|
2392
2359
|
Stripe's public no-code portal login, where the customer verifies the checkout email with a
|