@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 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(process.env.XDG_CACHE_HOME ?? path2.join(process.env.HOME ?? ".", ".cache"), "suda", "agent");
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(agentCacheDir(), "themes", safeObjectKeyPart(themeKey), safeObjectKeyPart(version), "agent-manifest.json");
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(`rebuild failed (${errorCount} error${errorCount === 1 ? "" : "s"}): ${label}`);
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 stylesheetTag = resolved.runtime?.stylesheetUrl ? `<link rel="stylesheet" href="${escapeHtml(resolved.runtime.stylesheetUrl)}" />` : "";
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(path2.join(root, "dist", "agent-manifest.json"));
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(path2.join(agentCacheDir(), "catalog.json"), `${JSON.stringify(catalog, null, 2)}
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(`/api/cli/agent/themes/${encodeURIComponent(resolvedKey)}/${encodeURIComponent(version)}/schema`, auth.baseUrl);
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, { token: auth.token });
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
- await writeFile(
1274
- path2.join(dist, "manifest.json"),
1275
- `${JSON.stringify(theme.module.manifest, null, 2)}
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
- "--skip-check",
2258
- "Skip the AI metadata check that normally runs before upload."
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(createAgentPageSchemaOutput(manifest, { includeExample: options.example === true }));
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(createAgentPageSchemaOutput(manifest, { includeExample: options.example === true }));
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("--host <host>", "The SudaCloud workspace host to authenticate against.", "app.sudayun.cn").action(async (options) => {
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 () => {