@sudajs/cli 0.4.3 → 0.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/index.js +174 -46
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { fileURLToPath, pathToFileURL } from 'url';
|
|
|
7
7
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
8
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
9
9
|
import { createAgentPageSchemaOutput, checkThemeModule, formatThemeCheckResult, createThemeAgentManifest, validateAgentPageContentWithManifest, agentValidationResultSchema, createPageDataFromAgentContent, findAgentSection, createAgentComponentSchemaOutput, agentPageSchemaOutputSchema, agentComponentOutputSchema } from '@sudajs/theme-engine';
|
|
10
|
-
import { FileSystemThemeRegistry } from '@sudajs/theme-engine/server';
|
|
10
|
+
import { themeManifestSchema, FileSystemThemeRegistry } from '@sudajs/theme-engine/server';
|
|
11
11
|
import { Command } from 'commander';
|
|
12
12
|
import { build, context } from 'esbuild';
|
|
13
13
|
import { z } from 'zod';
|
|
@@ -391,10 +391,20 @@ async function fetchJson(url, options = {}) {
|
|
|
391
391
|
return await response.json();
|
|
392
392
|
}
|
|
393
393
|
function agentCacheDir() {
|
|
394
|
-
return path2.join(
|
|
394
|
+
return path2.join(
|
|
395
|
+
process.env.XDG_CACHE_HOME ?? path2.join(process.env.HOME ?? ".", ".cache"),
|
|
396
|
+
"suda",
|
|
397
|
+
"agent"
|
|
398
|
+
);
|
|
395
399
|
}
|
|
396
400
|
function cachedThemeManifestPath(themeKey, version) {
|
|
397
|
-
return path2.join(
|
|
401
|
+
return path2.join(
|
|
402
|
+
agentCacheDir(),
|
|
403
|
+
"themes",
|
|
404
|
+
safeObjectKeyPart(themeKey),
|
|
405
|
+
safeObjectKeyPart(version),
|
|
406
|
+
"agent-manifest.json"
|
|
407
|
+
);
|
|
398
408
|
}
|
|
399
409
|
async function loadThemeModule(serverEntryPath) {
|
|
400
410
|
const imported = await import(`${pathToFileURL(serverEntryPath).href}?t=${Date.now()}`);
|
|
@@ -410,6 +420,12 @@ function assertRecord(value, label) {
|
|
|
410
420
|
}
|
|
411
421
|
function validateThemeModule(module) {
|
|
412
422
|
assertRecord(module.manifest, "manifest");
|
|
423
|
+
const parsedManifest = themeManifestSchema.safeParse(module.manifest);
|
|
424
|
+
if (!parsedManifest.success) {
|
|
425
|
+
const issues = parsedManifest.error.issues.map((issue) => ` - ${issue.path.join(".") || "<root>"}: ${issue.message}`).join("\n");
|
|
426
|
+
throw new Error(`manifest does not satisfy the Suda theme contract:
|
|
427
|
+
${issues}`);
|
|
428
|
+
}
|
|
413
429
|
if (!module.manifest.key || !module.manifest.version || !module.manifest.name) {
|
|
414
430
|
throw new Error("manifest.key, manifest.version and manifest.name are required.");
|
|
415
431
|
}
|
|
@@ -604,7 +620,9 @@ function createWatchLogPlugin(label) {
|
|
|
604
620
|
}
|
|
605
621
|
const errorCount = result.errors.length;
|
|
606
622
|
if (errorCount > 0) {
|
|
607
|
-
console.log(
|
|
623
|
+
console.log(
|
|
624
|
+
`rebuild failed (${errorCount} error${errorCount === 1 ? "" : "s"}): ${label}`
|
|
625
|
+
);
|
|
608
626
|
return;
|
|
609
627
|
}
|
|
610
628
|
const stamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
@@ -639,6 +657,10 @@ function createPreviewAssetResolver(resolveAssetPath) {
|
|
|
639
657
|
});
|
|
640
658
|
};
|
|
641
659
|
}
|
|
660
|
+
function createPreviewThemeAssetUrl(themeKey, themeVersion, relativePath) {
|
|
661
|
+
const normalizedRelativePath = relativePath.replace(/^\/+/, "");
|
|
662
|
+
return `${PREVIEW_PUBLIC_BASE_PATH}/${themeKey}/${themeVersion}/assets/${normalizedRelativePath}?v=${encodeURIComponent(themeVersion)}`;
|
|
663
|
+
}
|
|
642
664
|
async function loadThemeScopedRenderer(themeRoot) {
|
|
643
665
|
const themeRequire = createRequire(path2.join(themeRoot, "package.json"));
|
|
644
666
|
const reactPath = themeRequire.resolve("react");
|
|
@@ -671,7 +693,12 @@ function renderStarterPageHtml(theme, resolved, page, renderer) {
|
|
|
671
693
|
})
|
|
672
694
|
);
|
|
673
695
|
const cssVarStyle = Object.entries(chrome.cssVariables).map(([key, value]) => `${key}: ${value};`).join(" ");
|
|
674
|
-
const
|
|
696
|
+
const stylesheetUrl = resolved.runtime?.stylesheet ? createPreviewThemeAssetUrl(
|
|
697
|
+
theme.module.manifest.key,
|
|
698
|
+
theme.module.manifest.version,
|
|
699
|
+
resolved.runtime.stylesheet
|
|
700
|
+
) : null;
|
|
701
|
+
const stylesheetTag = stylesheetUrl ? `<link rel="stylesheet" href="${escapeHtml(stylesheetUrl)}" />` : "";
|
|
675
702
|
const customHead = chrome.customHeadCode ?? "";
|
|
676
703
|
const customBody = chrome.customBodyCode ?? "";
|
|
677
704
|
return [
|
|
@@ -728,7 +755,6 @@ async function startPreviewServer(theme, port) {
|
|
|
728
755
|
const { createServer } = await import('http');
|
|
729
756
|
const renderer = await loadThemeScopedRenderer(theme.root);
|
|
730
757
|
const registry = new FileSystemThemeRegistry({
|
|
731
|
-
publicBasePath: PREVIEW_PUBLIC_BASE_PATH,
|
|
732
758
|
artifacts: [
|
|
733
759
|
{
|
|
734
760
|
rootPath: theme.root,
|
|
@@ -901,7 +927,9 @@ async function screenshotTheme(root, options) {
|
|
|
901
927
|
await captureScreenshot(theme, resolved);
|
|
902
928
|
}
|
|
903
929
|
async function loadLocalAgentManifest(root) {
|
|
904
|
-
const built = await readJsonIfExists(
|
|
930
|
+
const built = await readJsonIfExists(
|
|
931
|
+
path2.join(root, "dist", "agent-manifest.json")
|
|
932
|
+
);
|
|
905
933
|
if (built) {
|
|
906
934
|
return built;
|
|
907
935
|
}
|
|
@@ -930,8 +958,11 @@ async function listAgentThemes(options) {
|
|
|
930
958
|
}
|
|
931
959
|
const catalog = await fetchJson(url.href, { token: auth.token });
|
|
932
960
|
await mkdir(agentCacheDir(), { recursive: true });
|
|
933
|
-
await writeFile(
|
|
934
|
-
|
|
961
|
+
await writeFile(
|
|
962
|
+
path2.join(agentCacheDir(), "catalog.json"),
|
|
963
|
+
`${JSON.stringify(catalog, null, 2)}
|
|
964
|
+
`
|
|
965
|
+
);
|
|
935
966
|
return catalog.themes;
|
|
936
967
|
}
|
|
937
968
|
async function fetchAgentManifest(themeKey, options) {
|
|
@@ -953,11 +984,16 @@ async function fetchAgentManifest(themeKey, options) {
|
|
|
953
984
|
if (cached) {
|
|
954
985
|
return cached;
|
|
955
986
|
}
|
|
956
|
-
const url = new URL(
|
|
987
|
+
const url = new URL(
|
|
988
|
+
`/api/cli/agent/themes/${encodeURIComponent(resolvedKey)}/${encodeURIComponent(version)}/schema`,
|
|
989
|
+
auth.baseUrl
|
|
990
|
+
);
|
|
957
991
|
if (resolvedOptions.projectId) {
|
|
958
992
|
url.searchParams.set("projectId", resolvedOptions.projectId);
|
|
959
993
|
}
|
|
960
|
-
const result = await fetchJson(url.href, {
|
|
994
|
+
const result = await fetchJson(url.href, {
|
|
995
|
+
token: auth.token
|
|
996
|
+
});
|
|
961
997
|
if (!result.agentManifest || !Array.isArray(result.agentManifest.sections)) {
|
|
962
998
|
throw new Error(
|
|
963
999
|
`Theme schema is not available for "${resolvedKey}@${version}". The theme may not be published, not active for the project, or its agent manifest is missing. Republish the theme or activate it before retrying.`
|
|
@@ -1270,11 +1306,10 @@ async function startMcpServer() {
|
|
|
1270
1306
|
async function writeThemeArtifacts(theme) {
|
|
1271
1307
|
const dist = path2.join(theme.root, "dist");
|
|
1272
1308
|
const agentManifest = createThemeAgentManifest(theme.module);
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
);
|
|
1309
|
+
const manifestJson = `${JSON.stringify(theme.module.manifest, null, 2)}
|
|
1310
|
+
`;
|
|
1311
|
+
await writeFile(path2.join(dist, "manifest.json"), manifestJson);
|
|
1312
|
+
await writeFile(path2.join(theme.root, "manifest.json"), manifestJson);
|
|
1278
1313
|
await writeFile(
|
|
1279
1314
|
path2.join(dist, "starter-pages.json"),
|
|
1280
1315
|
`${JSON.stringify(theme.module.starterPages, null, 2)}
|
|
@@ -1295,9 +1330,12 @@ function contentTypeFor(relativePath) {
|
|
|
1295
1330
|
if (relativePath.endsWith(".css")) {
|
|
1296
1331
|
return "text/css; charset=utf-8";
|
|
1297
1332
|
}
|
|
1298
|
-
if (relativePath.endsWith(".js")) {
|
|
1333
|
+
if (relativePath.endsWith(".js") || relativePath.endsWith(".mjs")) {
|
|
1299
1334
|
return "application/javascript; charset=utf-8";
|
|
1300
1335
|
}
|
|
1336
|
+
if (relativePath.endsWith(".map")) {
|
|
1337
|
+
return "application/json; charset=utf-8";
|
|
1338
|
+
}
|
|
1301
1339
|
if (relativePath.endsWith(".json")) {
|
|
1302
1340
|
return "application/json; charset=utf-8";
|
|
1303
1341
|
}
|
|
@@ -1313,6 +1351,39 @@ function contentTypeFor(relativePath) {
|
|
|
1313
1351
|
if (relativePath.endsWith(".webp")) {
|
|
1314
1352
|
return "image/webp";
|
|
1315
1353
|
}
|
|
1354
|
+
if (relativePath.endsWith(".avif")) {
|
|
1355
|
+
return "image/avif";
|
|
1356
|
+
}
|
|
1357
|
+
if (relativePath.endsWith(".gif")) {
|
|
1358
|
+
return "image/gif";
|
|
1359
|
+
}
|
|
1360
|
+
if (relativePath.endsWith(".ico")) {
|
|
1361
|
+
return "image/x-icon";
|
|
1362
|
+
}
|
|
1363
|
+
if (relativePath.endsWith(".woff2")) {
|
|
1364
|
+
return "font/woff2";
|
|
1365
|
+
}
|
|
1366
|
+
if (relativePath.endsWith(".woff")) {
|
|
1367
|
+
return "font/woff";
|
|
1368
|
+
}
|
|
1369
|
+
if (relativePath.endsWith(".ttf")) {
|
|
1370
|
+
return "font/ttf";
|
|
1371
|
+
}
|
|
1372
|
+
if (relativePath.endsWith(".otf")) {
|
|
1373
|
+
return "font/otf";
|
|
1374
|
+
}
|
|
1375
|
+
if (relativePath.endsWith(".mp4")) {
|
|
1376
|
+
return "video/mp4";
|
|
1377
|
+
}
|
|
1378
|
+
if (relativePath.endsWith(".webm")) {
|
|
1379
|
+
return "video/webm";
|
|
1380
|
+
}
|
|
1381
|
+
if (relativePath.endsWith(".txt")) {
|
|
1382
|
+
return "text/plain; charset=utf-8";
|
|
1383
|
+
}
|
|
1384
|
+
if (relativePath.endsWith(".xml")) {
|
|
1385
|
+
return "application/xml; charset=utf-8";
|
|
1386
|
+
}
|
|
1316
1387
|
return "application/octet-stream";
|
|
1317
1388
|
}
|
|
1318
1389
|
async function collectFiles(root, directory) {
|
|
@@ -1347,8 +1418,22 @@ async function collectThemeArtifactFiles(theme) {
|
|
|
1347
1418
|
contentType: "text/css; charset=utf-8"
|
|
1348
1419
|
});
|
|
1349
1420
|
}
|
|
1421
|
+
const rootManifestPath = path2.join(theme.root, "manifest.json");
|
|
1422
|
+
if (await pathExists(rootManifestPath)) {
|
|
1423
|
+
files.push({
|
|
1424
|
+
absolutePath: rootManifestPath,
|
|
1425
|
+
relativePath: "manifest.json",
|
|
1426
|
+
contentType: "application/json; charset=utf-8"
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1350
1429
|
return files;
|
|
1351
1430
|
}
|
|
1431
|
+
var SSR_REQUIRED_ARTIFACTS = [
|
|
1432
|
+
"dist/index.js",
|
|
1433
|
+
"dist/runtime.client.js",
|
|
1434
|
+
"manifest.json",
|
|
1435
|
+
"styles.css"
|
|
1436
|
+
];
|
|
1352
1437
|
async function checksum(files) {
|
|
1353
1438
|
const hash = createHash("sha256");
|
|
1354
1439
|
for (const file of files.sort((a, b) => a.relativePath.localeCompare(b.relativePath))) {
|
|
@@ -1375,26 +1460,53 @@ ${lines.join("\n")}`;
|
|
|
1375
1460
|
}
|
|
1376
1461
|
return message;
|
|
1377
1462
|
}
|
|
1378
|
-
async function publishTheme(root, skipBuild, skipCheck) {
|
|
1463
|
+
async function publishTheme(root, skipBuild, skipCheck, force) {
|
|
1379
1464
|
const theme = skipBuild ? await validateTheme(root) : await buildTheme(root, false);
|
|
1465
|
+
await writeThemeArtifacts(theme);
|
|
1380
1466
|
if (!skipCheck) {
|
|
1381
1467
|
const ok = runThemeCheck(theme);
|
|
1382
1468
|
if (!ok) {
|
|
1383
|
-
throw new Error(
|
|
1384
|
-
"AI metadata check failed. Fix the errors above or rerun with --skip-check."
|
|
1385
|
-
);
|
|
1469
|
+
throw new Error("AI metadata check failed. Fix the errors above or rerun with --skip-check.");
|
|
1386
1470
|
}
|
|
1387
1471
|
}
|
|
1388
1472
|
const files = await collectThemeArtifactFiles(theme);
|
|
1389
1473
|
if (!theme.clientEntryPath) {
|
|
1390
1474
|
throw new Error("Missing dist/runtime.client.js. Run `suda theme build` first.");
|
|
1391
1475
|
}
|
|
1476
|
+
const collectedPaths = new Set(files.map((f) => f.relativePath));
|
|
1477
|
+
for (const required of SSR_REQUIRED_ARTIFACTS) {
|
|
1478
|
+
if (!collectedPaths.has(required)) {
|
|
1479
|
+
throw new Error(
|
|
1480
|
+
`Missing required SSR artifact: ${required}. Run \`suda theme build\` and ensure ${required === "styles.css" ? "a `styles.css` file exists at the theme root" : `\`${required}\` is generated`}.`
|
|
1481
|
+
);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1392
1484
|
const config = await readAuthConfig();
|
|
1393
1485
|
if (!config) {
|
|
1394
1486
|
throw new Error("Not logged in. Run `suda auth login` to authenticate first.");
|
|
1395
1487
|
}
|
|
1396
1488
|
const baseUrl = `${protocolForHost2(config.host)}://${config.host}`;
|
|
1397
1489
|
const { key, version } = theme.module.manifest;
|
|
1490
|
+
const digest = await checksum(files);
|
|
1491
|
+
if (force) {
|
|
1492
|
+
const forceRes = await fetch(`${baseUrl}/api/cli/themes/publish-force`, {
|
|
1493
|
+
method: "POST",
|
|
1494
|
+
headers: {
|
|
1495
|
+
"Content-Type": "application/json",
|
|
1496
|
+
Authorization: `Bearer ${config.sessionToken}`
|
|
1497
|
+
},
|
|
1498
|
+
body: JSON.stringify({ key, version })
|
|
1499
|
+
});
|
|
1500
|
+
if (!forceRes.ok && forceRes.status !== 404) {
|
|
1501
|
+
throw new Error(`Failed to force-clear ${key}@${version}: ${await formatHttpErrorBody(forceRes)}`);
|
|
1502
|
+
}
|
|
1503
|
+
if (forceRes.ok) {
|
|
1504
|
+
const { deletedObjects } = await forceRes.json();
|
|
1505
|
+
console.log(
|
|
1506
|
+
`force-cleared ${key}@${version} (${deletedObjects ?? 0} objects removed). Republishing...`
|
|
1507
|
+
);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1398
1510
|
const intentRes = await fetch(`${baseUrl}/api/cli/themes/publish-intent`, {
|
|
1399
1511
|
method: "POST",
|
|
1400
1512
|
headers: {
|
|
@@ -1404,13 +1516,12 @@ async function publishTheme(root, skipBuild, skipCheck) {
|
|
|
1404
1516
|
body: JSON.stringify({
|
|
1405
1517
|
key,
|
|
1406
1518
|
version,
|
|
1519
|
+
checksum: digest,
|
|
1407
1520
|
files: files.map((f) => ({ relativePath: f.relativePath, contentType: f.contentType }))
|
|
1408
1521
|
})
|
|
1409
1522
|
});
|
|
1410
1523
|
if (!intentRes.ok) {
|
|
1411
|
-
throw new Error(
|
|
1412
|
-
`Failed to get publish intent: ${await formatHttpErrorBody(intentRes)}`
|
|
1413
|
-
);
|
|
1524
|
+
throw new Error(`Failed to get publish intent: ${await formatHttpErrorBody(intentRes)}`);
|
|
1414
1525
|
}
|
|
1415
1526
|
const { urls } = await intentRes.json();
|
|
1416
1527
|
for (const file of files) {
|
|
@@ -1431,12 +1542,7 @@ async function publishTheme(root, skipBuild, skipCheck) {
|
|
|
1431
1542
|
}
|
|
1432
1543
|
console.log(`uploaded ${createThemeObjectKey(key, version, file.relativePath)}`);
|
|
1433
1544
|
}
|
|
1434
|
-
const digest = await checksum(files);
|
|
1435
1545
|
const bundleArtifactRelative = "dist/index.js";
|
|
1436
|
-
const bundleArtifactExists = files.some((f) => f.relativePath === bundleArtifactRelative);
|
|
1437
|
-
if (!bundleArtifactExists) {
|
|
1438
|
-
throw new Error(`Missing ${bundleArtifactRelative} in theme artifacts.`);
|
|
1439
|
-
}
|
|
1440
1546
|
const previewArtifactRelative = "dist/preview/desktop.png";
|
|
1441
1547
|
const previewArtifactExists = await pathExists(path2.join(theme.root, previewArtifactRelative));
|
|
1442
1548
|
const agentManifest = createThemeAgentManifest(theme.module);
|
|
@@ -1457,9 +1563,7 @@ async function publishTheme(root, skipBuild, skipCheck) {
|
|
|
1457
1563
|
})
|
|
1458
1564
|
});
|
|
1459
1565
|
if (!completeRes.ok) {
|
|
1460
|
-
throw new Error(
|
|
1461
|
-
`Failed to complete publish: ${await formatHttpErrorBody(completeRes)}`
|
|
1462
|
-
);
|
|
1566
|
+
throw new Error(`Failed to complete publish: ${await formatHttpErrorBody(completeRes)}`);
|
|
1463
1567
|
}
|
|
1464
1568
|
console.log(`published ${key}@${version}`);
|
|
1465
1569
|
}
|
|
@@ -1617,6 +1721,10 @@ export const sourceManifest: ThemeSourceManifest = {
|
|
|
1617
1721
|
minEngineVersion: "0.0.0",
|
|
1618
1722
|
entry: "dist/index.js",
|
|
1619
1723
|
clientEntry: "dist/runtime.client.js",
|
|
1724
|
+
// Required. SudaCloud currently hosts SSR themes only; CSR/Hybrid postures
|
|
1725
|
+
// are on the roadmap. Authoring \`renderMode\` is the contract a theme uses
|
|
1726
|
+
// to declare which posture it expects from the host.
|
|
1727
|
+
renderMode: "ssr",
|
|
1620
1728
|
};
|
|
1621
1729
|
`
|
|
1622
1730
|
);
|
|
@@ -2026,6 +2134,14 @@ export { pageConfig, layoutConfig, defaultLayout, starterPages };
|
|
|
2026
2134
|
This directory is a Suda theme source project generated by \`suda theme init\`.
|
|
2027
2135
|
It is a **standalone** npm package \u2014 it does not depend on the SudaCloud monorepo.
|
|
2028
2136
|
|
|
2137
|
+
## Render mode
|
|
2138
|
+
|
|
2139
|
+
This theme contract requires \`renderMode: "ssr"\` in the source manifest
|
|
2140
|
+
(\`src/manifest.ts\`). SudaCloud's site runtime currently only supports SSR
|
|
2141
|
+
themes; CSR / Hybrid postures are on the roadmap. **Do not** introduce
|
|
2142
|
+
client-only entry points or assume browser-only globals at module scope \u2014
|
|
2143
|
+
the theme bundle must execute in a Node SSR context.
|
|
2144
|
+
|
|
2029
2145
|
## What to edit
|
|
2030
2146
|
|
|
2031
2147
|
- Add page sections in \`src/sections.tsx\` and register them in \`SECTION_COMPONENTS\`.
|
|
@@ -2166,6 +2282,10 @@ Read \`AGENTS.md\` first. It is the canonical guide for this Suda theme project.
|
|
|
2166
2282
|
A Suda theme scaffolded with \`suda theme init\`. This is a standalone
|
|
2167
2283
|
package; it does not need to live inside the SudaCloud monorepo.
|
|
2168
2284
|
|
|
2285
|
+
This theme targets SudaCloud's SSR runtime (\`renderMode: "ssr"\` in
|
|
2286
|
+
\`src/manifest.ts\`); CSR / Hybrid postures are not yet supported by the
|
|
2287
|
+
host.
|
|
2288
|
+
|
|
2169
2289
|
## Setup
|
|
2170
2290
|
|
|
2171
2291
|
\`\`\`bash
|
|
@@ -2181,6 +2301,10 @@ pnpm dev # watch the browser runtime and start preview server
|
|
|
2181
2301
|
pnpm validate # validate the dist artifact
|
|
2182
2302
|
\`\`\`
|
|
2183
2303
|
|
|
2304
|
+
> \`renderMode\` currently only accepts \`"ssr"\`. \`suda theme validate\` and
|
|
2305
|
+
> \`suda theme check\` will reject manifests that omit it or set a different
|
|
2306
|
+
> value.
|
|
2307
|
+
|
|
2184
2308
|
## Agent tooling
|
|
2185
2309
|
|
|
2186
2310
|
\`\`\`bash
|
|
@@ -2216,10 +2340,7 @@ function buildProgram() {
|
|
|
2216
2340
|
);
|
|
2217
2341
|
}
|
|
2218
2342
|
});
|
|
2219
|
-
theme.command("build").description("Build server dist (via package script) and browser runtime.").option("--theme-root <path>", "Theme source/artifact root.").option("--skip-theme-build", "Skip the theme package build script.").option(
|
|
2220
|
-
"--skip-check",
|
|
2221
|
-
"Skip the AI metadata check that normally runs after build."
|
|
2222
|
-
).action(async (options) => {
|
|
2343
|
+
theme.command("build").description("Build server dist (via package script) and browser runtime.").option("--theme-root <path>", "Theme source/artifact root.").option("--skip-theme-build", "Skip the theme package build script.").option("--skip-check", "Skip the AI metadata check that normally runs after build.").action(async (options) => {
|
|
2223
2344
|
const result = await buildTheme(resolveThemeRoot(options), options.skipThemeBuild === true);
|
|
2224
2345
|
console.log(`built ${result.module.manifest.key}@${result.module.manifest.version}`);
|
|
2225
2346
|
if (options.skipCheck !== true) {
|
|
@@ -2248,19 +2369,18 @@ function buildProgram() {
|
|
|
2248
2369
|
Number.isFinite(port) ? port : 4177
|
|
2249
2370
|
);
|
|
2250
2371
|
});
|
|
2251
|
-
theme.command("screenshot").description(
|
|
2252
|
-
"Capture a desktop preview screenshot of the home starter page using Playwright."
|
|
2253
|
-
).option("--theme-root <path>", "Theme source/artifact root.").option("--output <path>", "Output PNG path relative to theme root.").option("--width <px>", "Viewport width in pixels.", "1280").option("--height <px>", "Viewport height in pixels.", "800").option("--port <port>", "Preview server port used during capture.", "4178").option("--skip-build", "Skip rebuilding the theme before capturing.").action(async (options) => {
|
|
2372
|
+
theme.command("screenshot").description("Capture a desktop preview screenshot of the home starter page using Playwright.").option("--theme-root <path>", "Theme source/artifact root.").option("--output <path>", "Output PNG path relative to theme root.").option("--width <px>", "Viewport width in pixels.", "1280").option("--height <px>", "Viewport height in pixels.", "800").option("--port <port>", "Preview server port used during capture.", "4178").option("--skip-build", "Skip rebuilding the theme before capturing.").action(async (options) => {
|
|
2254
2373
|
await screenshotTheme(resolveThemeRoot(options), options);
|
|
2255
2374
|
});
|
|
2256
|
-
theme.command("publish").description("Upload artifact to S3 and upsert ThemePackage/ThemeVersion.").option("--theme-root <path>", "Theme source/artifact root.").option("--skip-build", "Publish existing dist files without rebuilding.").option(
|
|
2257
|
-
"--
|
|
2258
|
-
"
|
|
2375
|
+
theme.command("publish").description("Upload artifact to S3 and upsert ThemePackage/ThemeVersion.").option("--theme-root <path>", "Theme source/artifact root.").option("--skip-build", "Publish existing dist files without rebuilding.").option("--skip-check", "Skip the AI metadata check that normally runs before upload.").option(
|
|
2376
|
+
"--force",
|
|
2377
|
+
"Development recovery only: clear the existing themes/<key>/<version>/ prefix and ThemeVersion row before republishing. Re-published clients pinned to this version are unavailable until the new publish completes."
|
|
2259
2378
|
).action(async (options) => {
|
|
2260
2379
|
await publishTheme(
|
|
2261
2380
|
resolveThemeRoot(options),
|
|
2262
2381
|
options.skipBuild === true,
|
|
2263
|
-
options.skipCheck === true
|
|
2382
|
+
options.skipCheck === true,
|
|
2383
|
+
options.force === true
|
|
2264
2384
|
);
|
|
2265
2385
|
});
|
|
2266
2386
|
const agent = program.command("agent").description("Agent-friendly theme and page tooling.");
|
|
@@ -2295,12 +2415,16 @@ function buildProgram() {
|
|
|
2295
2415
|
const agentTheme = agent.command("theme").description("Inspect a single theme.");
|
|
2296
2416
|
agentTheme.command("describe").description("Describe a theme's AI-first page schema.").argument("<theme>", "Theme key.").option("--version <version>", "Theme version.").option("--project-id <projectId>", "Project scope.").option("--theme-root <path>", "Read a local theme.").option("--example", "Include example page content.").action(async (themeKey, options) => {
|
|
2297
2417
|
const manifest = await fetchAgentManifest(themeKey, options);
|
|
2298
|
-
printJson(
|
|
2418
|
+
printJson(
|
|
2419
|
+
createAgentPageSchemaOutput(manifest, { includeExample: options.example === true })
|
|
2420
|
+
);
|
|
2299
2421
|
});
|
|
2300
2422
|
const agentPage = agent.command("page").description("AI-first page content tooling.");
|
|
2301
2423
|
agentPage.command("schema").description("Print the AI-first page content output schema for a theme.").argument("<theme>", "Theme key.").option("--version <version>", "Theme version.").option("--project-id <projectId>", "Project scope.").option("--theme-root <path>", "Read a local theme.").option("--example", "Include example page content.").action(async (themeKey, options) => {
|
|
2302
2424
|
const manifest = await fetchAgentManifest(themeKey, options);
|
|
2303
|
-
printJson(
|
|
2425
|
+
printJson(
|
|
2426
|
+
createAgentPageSchemaOutput(manifest, { includeExample: options.example === true })
|
|
2427
|
+
);
|
|
2304
2428
|
});
|
|
2305
2429
|
agentPage.command("validate").description("Validate an AI-generated Suda page content JSON document.").requiredOption("--theme <theme>", "Theme key.").requiredOption("--input <file>", "JSON file containing Suda page content.").option("--version <version>", "Theme version.").option("--project-id <projectId>", "Project scope.").option("--theme-root <path>", "Read a local theme.").action(async (options) => {
|
|
2306
2430
|
await validateAgentPage(options.theme, options.input, options);
|
|
@@ -2317,7 +2441,11 @@ function buildProgram() {
|
|
|
2317
2441
|
await startMcpServer();
|
|
2318
2442
|
});
|
|
2319
2443
|
const authCmd = program.command("auth").description("Manage Suda authentication.");
|
|
2320
|
-
authCmd.command("login").description("Authenticate Suda CLI with a SudaCloud workspace.").option(
|
|
2444
|
+
authCmd.command("login").description("Authenticate Suda CLI with a SudaCloud workspace.").option(
|
|
2445
|
+
"--host <host>",
|
|
2446
|
+
"The SudaCloud workspace host to authenticate against.",
|
|
2447
|
+
"app.sudayun.cn"
|
|
2448
|
+
).action(async (options) => {
|
|
2321
2449
|
await login(options.host);
|
|
2322
2450
|
});
|
|
2323
2451
|
authCmd.command("status").description("Check current authentication status.").action(async () => {
|