@sudajs/cli 0.0.2 → 0.1.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
@@ -3,11 +3,15 @@ import { createHash } from 'crypto';
3
3
  import fs, { mkdir, writeFile, readFile, stat, readdir } from 'fs/promises';
4
4
  import path2 from 'path';
5
5
  import { pathToFileURL } from 'url';
6
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
8
+ import { createAgentPageSchemaOutput, createThemeAgentManifest, validateAgentPageContentWithManifest, agentValidationResultSchema, createPageDataFromAgentContent, findAgentSection, createAgentComponentSchemaOutput, agentPageSchemaOutputSchema, agentComponentOutputSchema } from '@sudajs/theme-engine';
6
9
  import { extractLayoutChrome, ThemeRender } from '@sudajs/theme-engine/server';
7
10
  import { Command } from 'commander';
8
11
  import { build, context } from 'esbuild';
9
12
  import { createElement } from 'react';
10
13
  import { renderToStaticMarkup } from 'react-dom/server';
14
+ import { z } from 'zod';
11
15
  import os from 'os';
12
16
 
13
17
  function getConfigPath() {
@@ -38,7 +42,7 @@ async function clearAuthConfig() {
38
42
  }
39
43
  }
40
44
  }
41
- async function login(host = "workspace.sudacloud.com") {
45
+ async function login(host = "app.sudayun.cn") {
42
46
  const protocol = host.includes("localhost") || host.includes("127.0.0.1") ? "http" : "https";
43
47
  const baseUrl = `${protocol}://${host}`;
44
48
  console.log(`Requesting device authorization from ${baseUrl}...`);
@@ -120,6 +124,22 @@ async function logout() {
120
124
  }
121
125
 
122
126
  // src/index.ts
127
+ var createPageDraftOutputSchema = z.discriminatedUnion("status", [
128
+ z.object({
129
+ status: z.literal("created").describe("The page draft was created."),
130
+ pageId: z.string().describe("New page id.")
131
+ }),
132
+ z.object({
133
+ status: z.literal("invalid").describe("The page content failed validation."),
134
+ valid: z.literal(false),
135
+ issues: z.array(
136
+ z.object({
137
+ path: z.string(),
138
+ message: z.string()
139
+ })
140
+ )
141
+ })
142
+ ]);
123
143
  function createHostReactShimPlugin() {
124
144
  return {
125
145
  name: "suda-host-react-shim",
@@ -266,6 +286,43 @@ async function readJson(filePath) {
266
286
  const raw = await readFile(filePath, "utf8");
267
287
  return JSON.parse(raw);
268
288
  }
289
+ async function readJsonIfExists(filePath) {
290
+ if (!await pathExists(filePath)) {
291
+ return null;
292
+ }
293
+ return readJson(filePath);
294
+ }
295
+ function protocolForHost(host) {
296
+ return host.includes("localhost") || host.includes("127.0.0.1") ? "http" : "https";
297
+ }
298
+ async function requireCliBaseUrl() {
299
+ const config = await readAuthConfig();
300
+ if (!config) {
301
+ throw new Error("Not logged in. Run `suda auth login` to authenticate first.");
302
+ }
303
+ return {
304
+ baseUrl: `${protocolForHost(config.host)}://${config.host}`,
305
+ token: config.sessionToken
306
+ };
307
+ }
308
+ async function fetchJson(url, options = {}) {
309
+ const headers = new Headers(options.headers);
310
+ if (options.token) {
311
+ headers.set("Authorization", `Bearer ${options.token}`);
312
+ }
313
+ const response = await fetch(url, { ...options, headers });
314
+ if (!response.ok) {
315
+ const errorData = await response.json().catch(() => ({}));
316
+ throw new Error(errorData.message || errorData.error || response.statusText);
317
+ }
318
+ return await response.json();
319
+ }
320
+ function agentCacheDir() {
321
+ return path2.join(process.env.XDG_CACHE_HOME ?? path2.join(process.env.HOME ?? ".", ".cache"), "suda", "agent");
322
+ }
323
+ function cachedThemeManifestPath(themeKey, version) {
324
+ return path2.join(agentCacheDir(), "themes", safeObjectKeyPart(themeKey), safeObjectKeyPart(version), "agent-manifest.json");
325
+ }
269
326
  async function loadThemeModule(serverEntryPath) {
270
327
  const imported = await import(`${pathToFileURL(serverEntryPath).href}?t=${Date.now()}`);
271
328
  if (!imported.default) {
@@ -617,8 +674,292 @@ async function screenshotTheme(root, options) {
617
674
  const theme = resolved.skipBuild ? await validateTheme(root) : await buildTheme(root, false);
618
675
  await captureScreenshot(theme, resolved);
619
676
  }
677
+ async function loadLocalAgentManifest(root) {
678
+ const built = await readJsonIfExists(path2.join(root, "dist", "agent-manifest.json"));
679
+ if (built) {
680
+ return built;
681
+ }
682
+ throw new Error(
683
+ `No agent manifest found at ${path2.join(root, "dist", "agent-manifest.json")}. Run "suda theme build" first.`
684
+ );
685
+ }
686
+ async function listAgentThemes(options) {
687
+ if (options.themeRoot) {
688
+ const manifest = await loadLocalAgentManifest(resolveThemeRoot(options));
689
+ return [
690
+ {
691
+ key: manifest.manifest.key,
692
+ name: manifest.manifest.name,
693
+ version: manifest.manifest.version,
694
+ description: manifest.manifest.description ?? null,
695
+ categories: manifest.manifest.categories,
696
+ preview: manifest.manifest.preview
697
+ }
698
+ ];
699
+ }
700
+ const auth = await requireCliBaseUrl();
701
+ const url = new URL("/api/cli/agent/themes", auth.baseUrl);
702
+ if (options.projectId) {
703
+ url.searchParams.set("projectId", options.projectId);
704
+ }
705
+ const catalog = await fetchJson(url.href, { token: auth.token });
706
+ await mkdir(agentCacheDir(), { recursive: true });
707
+ await writeFile(path2.join(agentCacheDir(), "catalog.json"), `${JSON.stringify(catalog, null, 2)}
708
+ `);
709
+ return catalog.themes;
710
+ }
711
+ async function fetchAgentManifest(themeKey, options) {
712
+ if (options.themeRoot) {
713
+ return loadLocalAgentManifest(resolveThemeRoot(options));
714
+ }
715
+ const auth = await requireCliBaseUrl();
716
+ const version = options.version ?? "latest";
717
+ const cachePath = cachedThemeManifestPath(themeKey, version);
718
+ const cached = version === "latest" ? null : await readJsonIfExists(cachePath);
719
+ if (cached) {
720
+ return cached;
721
+ }
722
+ const url = new URL(`/api/cli/agent/themes/${encodeURIComponent(themeKey)}/${encodeURIComponent(version)}/schema`, auth.baseUrl);
723
+ if (options.projectId) {
724
+ url.searchParams.set("projectId", options.projectId);
725
+ }
726
+ const result = await fetchJson(url.href, { token: auth.token });
727
+ const realVersion = result.agentManifest.manifest.version;
728
+ const realCachePath = cachedThemeManifestPath(themeKey, realVersion);
729
+ await mkdir(path2.dirname(realCachePath), { recursive: true });
730
+ await writeFile(realCachePath, `${JSON.stringify(result.agentManifest, null, 2)}
731
+ `);
732
+ return result.agentManifest;
733
+ }
734
+ function printJson(value) {
735
+ console.log(JSON.stringify(value, null, 2));
736
+ }
737
+ function createSectionSchema(manifest, sectionType) {
738
+ const section = findAgentSection(manifest, sectionType);
739
+ if (!section) {
740
+ throw new Error(`Unknown section type "${sectionType}".`);
741
+ }
742
+ return createAgentComponentSchemaOutput(section, "section");
743
+ }
744
+ async function validateAgentPage(themeKey, input, options) {
745
+ const manifest = await fetchAgentManifest(themeKey, options);
746
+ const pageContent = await readJson(path2.resolve(input));
747
+ const result = validateAgentPageContentWithManifest(pageContent, manifest);
748
+ printJson(agentValidationResultSchema.parse(result));
749
+ if (!result.valid) {
750
+ process.exitCode = 1;
751
+ }
752
+ }
753
+ async function createAgentPage(options) {
754
+ if (!options.projectId) {
755
+ throw new Error("--project-id is required.");
756
+ }
757
+ const pageContent = await readJson(path2.resolve(options.input));
758
+ const manifest = await fetchAgentManifest(options.theme, options);
759
+ const validation = validateAgentPageContentWithManifest(pageContent, manifest);
760
+ if (!validation.valid) {
761
+ printJson(agentValidationResultSchema.parse(validation));
762
+ process.exitCode = 1;
763
+ return;
764
+ }
765
+ const pageData = createPageDataFromAgentContent(pageContent);
766
+ const auth = await requireCliBaseUrl();
767
+ const response = await fetchJson(
768
+ `${auth.baseUrl}/api/cli/agent/projects/${encodeURIComponent(options.projectId)}/pages`,
769
+ {
770
+ method: "POST",
771
+ token: auth.token,
772
+ headers: { "Content-Type": "application/json" },
773
+ body: JSON.stringify({
774
+ title: options.title,
775
+ slug: options.slug,
776
+ themeKey: manifest.manifest.key,
777
+ themeVersion: manifest.manifest.version,
778
+ data: pageData
779
+ })
780
+ }
781
+ );
782
+ printJson({ success: true, ...response });
783
+ }
784
+ function mcpStructured(summary, value) {
785
+ return {
786
+ content: [
787
+ {
788
+ type: "text",
789
+ text: summary
790
+ }
791
+ ],
792
+ structuredContent: value
793
+ };
794
+ }
795
+ async function startMcpServer() {
796
+ const server = new McpServer({
797
+ name: "suda",
798
+ version: "0.1.0"
799
+ });
800
+ server.registerTool(
801
+ "list_themes",
802
+ {
803
+ description: "List themes visible to the current Suda CLI user.",
804
+ inputSchema: {
805
+ projectId: z.string().optional(),
806
+ themeRoot: z.string().optional()
807
+ },
808
+ outputSchema: {
809
+ themes: z.array(
810
+ z.object({
811
+ key: z.string(),
812
+ name: z.string(),
813
+ version: z.string(),
814
+ description: z.string().nullable().optional(),
815
+ categories: z.array(z.string()).optional(),
816
+ preview: z.string().optional(),
817
+ active: z.boolean().optional()
818
+ })
819
+ )
820
+ }
821
+ },
822
+ async ({ projectId, themeRoot }) => {
823
+ const structuredContent = { themes: await listAgentThemes({ projectId, themeRoot }) };
824
+ return mcpStructured("Available Suda themes.", structuredContent);
825
+ }
826
+ );
827
+ server.registerTool(
828
+ "describe_theme",
829
+ {
830
+ description: "Return the raw Suda agent manifest for a theme.",
831
+ inputSchema: {
832
+ theme: z.string(),
833
+ version: z.string().optional(),
834
+ projectId: z.string().optional(),
835
+ themeRoot: z.string().optional()
836
+ },
837
+ outputSchema: {
838
+ agentManifest: z.unknown()
839
+ }
840
+ },
841
+ async ({ theme, version, projectId, themeRoot }) => {
842
+ const structuredContent = {
843
+ agentManifest: await fetchAgentManifest(theme, { version, projectId, themeRoot })
844
+ };
845
+ return mcpStructured("Raw Suda theme agent manifest.", structuredContent);
846
+ }
847
+ );
848
+ server.registerTool(
849
+ "get_page_schema",
850
+ {
851
+ description: "Return the AI-first output schema for generating Suda page content, including all section schemas.",
852
+ inputSchema: {
853
+ theme: z.string(),
854
+ version: z.string().optional(),
855
+ projectId: z.string().optional(),
856
+ themeRoot: z.string().optional(),
857
+ includeExample: z.boolean().optional()
858
+ },
859
+ outputSchema: agentPageSchemaOutputSchema.shape
860
+ },
861
+ async ({ theme, version, projectId, themeRoot, includeExample }) => {
862
+ const manifest = await fetchAgentManifest(theme, { version, projectId, themeRoot });
863
+ const structuredContent = createAgentPageSchemaOutput(manifest, {
864
+ includeExample: includeExample === true
865
+ });
866
+ return mcpStructured("AI-first Suda page content output schema.", structuredContent);
867
+ }
868
+ );
869
+ server.registerTool(
870
+ "get_section_schema",
871
+ {
872
+ description: "Return the AI-first output schema for one section component in a theme.",
873
+ inputSchema: {
874
+ theme: z.string(),
875
+ section: z.string(),
876
+ version: z.string().optional(),
877
+ projectId: z.string().optional(),
878
+ themeRoot: z.string().optional()
879
+ },
880
+ outputSchema: agentComponentOutputSchema.shape
881
+ },
882
+ async ({ theme, section, version, projectId, themeRoot }) => {
883
+ const manifest = await fetchAgentManifest(theme, { version, projectId, themeRoot });
884
+ const structuredContent = createSectionSchema(manifest, section);
885
+ return mcpStructured("AI-first Suda section output schema.", structuredContent);
886
+ }
887
+ );
888
+ server.registerTool(
889
+ "validate_page_config",
890
+ {
891
+ description: "Validate AI-generated Suda page content against a theme.",
892
+ inputSchema: {
893
+ theme: z.string(),
894
+ data: z.unknown(),
895
+ version: z.string().optional(),
896
+ projectId: z.string().optional(),
897
+ themeRoot: z.string().optional()
898
+ },
899
+ outputSchema: agentValidationResultSchema.shape
900
+ },
901
+ async ({ theme, data, version, projectId, themeRoot }) => {
902
+ const manifest = await fetchAgentManifest(theme, { version, projectId, themeRoot });
903
+ const structuredContent = agentValidationResultSchema.parse(
904
+ validateAgentPageContentWithManifest(data, manifest)
905
+ );
906
+ return mcpStructured("Suda page content validation result.", structuredContent);
907
+ }
908
+ );
909
+ server.registerTool(
910
+ "create_page_draft",
911
+ {
912
+ description: "Create a workspace page draft from AI-generated Suda page content.",
913
+ inputSchema: {
914
+ projectId: z.string(),
915
+ title: z.string(),
916
+ slug: z.string(),
917
+ theme: z.string(),
918
+ data: z.unknown(),
919
+ version: z.string().optional()
920
+ },
921
+ outputSchema: createPageDraftOutputSchema
922
+ },
923
+ async ({ projectId, title, slug, theme, data, version }) => {
924
+ const manifest = await fetchAgentManifest(theme, { version, projectId });
925
+ const validation = validateAgentPageContentWithManifest(data, manifest);
926
+ if (!validation.valid) {
927
+ return mcpStructured(
928
+ "Suda page content validation failed. Fix issues before creating a draft.",
929
+ createPageDraftOutputSchema.parse({
930
+ status: "invalid",
931
+ valid: false,
932
+ issues: validation.issues
933
+ })
934
+ );
935
+ }
936
+ const auth = await requireCliBaseUrl();
937
+ const response = await fetchJson(
938
+ `${auth.baseUrl}/api/cli/agent/projects/${encodeURIComponent(projectId)}/pages`,
939
+ {
940
+ method: "POST",
941
+ token: auth.token,
942
+ headers: { "Content-Type": "application/json" },
943
+ body: JSON.stringify({
944
+ title,
945
+ slug,
946
+ themeKey: manifest.manifest.key,
947
+ themeVersion: manifest.manifest.version,
948
+ data: createPageDataFromAgentContent(data)
949
+ })
950
+ }
951
+ );
952
+ return mcpStructured(
953
+ "Created Suda page draft.",
954
+ createPageDraftOutputSchema.parse({ status: "created", ...response })
955
+ );
956
+ }
957
+ );
958
+ await server.connect(new StdioServerTransport());
959
+ }
620
960
  async function writeThemeArtifacts(theme) {
621
961
  const dist = path2.join(theme.root, "dist");
962
+ const agentManifest = createThemeAgentManifest(theme.module);
622
963
  await writeFile(
623
964
  path2.join(dist, "manifest.json"),
624
965
  `${JSON.stringify(theme.module.manifest, null, 2)}
@@ -632,6 +973,11 @@ async function writeThemeArtifacts(theme) {
632
973
  await writeFile(
633
974
  path2.join(dist, "default-layout.json"),
634
975
  `${JSON.stringify(theme.module.defaultLayout, null, 2)}
976
+ `
977
+ );
978
+ await writeFile(
979
+ path2.join(dist, "agent-manifest.json"),
980
+ `${JSON.stringify(agentManifest, null, 2)}
635
981
  `
636
982
  );
637
983
  }
@@ -709,8 +1055,7 @@ async function publishTheme(root, skipBuild) {
709
1055
  if (!config) {
710
1056
  throw new Error("Not logged in. Run `suda auth login` to authenticate first.");
711
1057
  }
712
- const protocol = config.host.includes("localhost") || config.host.includes("127.0.0.1") ? "http" : "https";
713
- const baseUrl = `${protocol}://${config.host}`;
1058
+ const baseUrl = `${protocolForHost(config.host)}://${config.host}`;
714
1059
  const { key, version } = theme.module.manifest;
715
1060
  const intentRes = await fetch(`${baseUrl}/api/cli/themes/publish-intent`, {
716
1061
  method: "POST",
@@ -755,6 +1100,7 @@ async function publishTheme(root, skipBuild) {
755
1100
  }
756
1101
  const previewArtifactRelative = "dist/preview/desktop.png";
757
1102
  const previewArtifactExists = await pathExists(path2.join(theme.root, previewArtifactRelative));
1103
+ const agentManifest = createThemeAgentManifest(theme.module);
758
1104
  const completeRes = await fetch(`${baseUrl}/api/cli/themes/publish-complete`, {
759
1105
  method: "POST",
760
1106
  headers: {
@@ -766,6 +1112,7 @@ async function publishTheme(root, skipBuild) {
766
1112
  version,
767
1113
  checksum: digest,
768
1114
  manifest: theme.module.manifest,
1115
+ agentManifest,
769
1116
  bundleArtifactRelative,
770
1117
  previewArtifactRelative: previewArtifactExists ? previewArtifactRelative : void 0
771
1118
  })
@@ -834,22 +1181,109 @@ export const Hero: ComponentConfig = {
834
1181
  eyebrow: { type: "text", label: "Eyebrow" },
835
1182
  title: { type: "text", label: "Title" },
836
1183
  description: { type: "textarea", label: "Description" },
1184
+ primaryLabel: { type: "text", label: "Primary button label" },
1185
+ primaryHref: { type: "text", label: "Primary button link" },
837
1186
  },
838
1187
  defaultProps: {
839
1188
  eyebrow: "New theme",
840
1189
  title: "Build with SudaCloud",
841
1190
  description: "Edit this starter section in the visual editor.",
1191
+ primaryLabel: "Get started",
1192
+ primaryHref: "#contact",
842
1193
  },
843
- render: ({ eyebrow, title, description }) => (
844
- <section className="${key}-hero">
845
- <p>{eyebrow}</p>
1194
+ render: ({ eyebrow, title, description, primaryLabel, primaryHref }) => (
1195
+ <section className="${key}-section ${key}-hero">
1196
+ <p className="${key}-eyebrow">{eyebrow}</p>
846
1197
  <h1>{title}</h1>
847
- <div>{description}</div>
1198
+ <p>{description}</p>
1199
+ <a className="${key}-button" href={primaryHref}>{primaryLabel}</a>
1200
+ </section>
1201
+ ),
1202
+ };
1203
+
1204
+ export const FeatureGrid: ComponentConfig = {
1205
+ label: "Feature grid",
1206
+ fields: {
1207
+ title: { type: "text", label: "Title" },
1208
+ description: { type: "textarea", label: "Description" },
1209
+ features: {
1210
+ type: "array",
1211
+ label: "Features",
1212
+ arrayFields: {
1213
+ title: { type: "text", label: "Title" },
1214
+ description: { type: "textarea", label: "Description" },
1215
+ },
1216
+ },
1217
+ },
1218
+ defaultProps: {
1219
+ title: "Everything you need to launch",
1220
+ description: "Use this section to explain the core value of the project.",
1221
+ features: [
1222
+ { title: "Fast setup", description: "Start from a clean theme contract." },
1223
+ { title: "Visual editing", description: "Expose content fields through Puck." },
1224
+ { title: "Publish ready", description: "Build and publish with the Suda CLI." },
1225
+ ],
1226
+ },
1227
+ render: ({ title, description, features = [] }) => (
1228
+ <section className="${key}-section">
1229
+ <h2>{title}</h2>
1230
+ <p>{description}</p>
1231
+ <div className="${key}-grid">
1232
+ {features.map((feature: { title?: string; description?: string }, index: number) => (
1233
+ <article className="${key}-card" key={index}>
1234
+ <h3>{feature.title}</h3>
1235
+ <p>{feature.description}</p>
1236
+ </article>
1237
+ ))}
1238
+ </div>
848
1239
  </section>
849
1240
  ),
850
1241
  };
851
1242
 
852
- export const SECTION_COMPONENTS = { Hero };
1243
+ export const Testimonial: ComponentConfig = {
1244
+ label: "Testimonial",
1245
+ fields: {
1246
+ quote: { type: "textarea", label: "Quote" },
1247
+ author: { type: "text", label: "Author" },
1248
+ role: { type: "text", label: "Role" },
1249
+ },
1250
+ defaultProps: {
1251
+ quote: "SudaCloud gives our team a practical editing workflow without giving up theme control.",
1252
+ author: "Alex Chen",
1253
+ role: "Founder",
1254
+ },
1255
+ render: ({ quote, author, role }) => (
1256
+ <section className="${key}-section ${key}-quote">
1257
+ <blockquote>{quote}</blockquote>
1258
+ <p>{author} \xB7 {role}</p>
1259
+ </section>
1260
+ ),
1261
+ };
1262
+
1263
+ export const CallToAction: ComponentConfig = {
1264
+ label: "Call to action",
1265
+ fields: {
1266
+ title: { type: "text", label: "Title" },
1267
+ description: { type: "textarea", label: "Description" },
1268
+ buttonLabel: { type: "text", label: "Button label" },
1269
+ buttonHref: { type: "text", label: "Button link" },
1270
+ },
1271
+ defaultProps: {
1272
+ title: "Ready to build your next page?",
1273
+ description: "Use this section as the final conversion block.",
1274
+ buttonLabel: "Contact us",
1275
+ buttonHref: "#contact",
1276
+ },
1277
+ render: ({ title, description, buttonLabel, buttonHref }) => (
1278
+ <section className="${key}-section ${key}-cta" id="contact">
1279
+ <h2>{title}</h2>
1280
+ <p>{description}</p>
1281
+ <a className="${key}-button" href={buttonHref}>{buttonLabel}</a>
1282
+ </section>
1283
+ ),
1284
+ };
1285
+
1286
+ export const SECTION_COMPONENTS = { Hero, FeatureGrid, Testimonial, CallToAction };
853
1287
  `
854
1288
  );
855
1289
  await writeFile(
@@ -939,6 +1373,90 @@ export const starterPages: ThemeStarterPage[] = [
939
1373
  eyebrow: "Starter page",
940
1374
  title: "Welcome to ${key}",
941
1375
  description: "This page was generated by suda theme init.",
1376
+ primaryLabel: "Explore features",
1377
+ primaryHref: "#features",
1378
+ },
1379
+ },
1380
+ {
1381
+ type: "FeatureGrid",
1382
+ props: {
1383
+ id: "FeatureGrid-1",
1384
+ title: "Designed for editable sites",
1385
+ description: "Starter sections show agents and editors how this theme is structured.",
1386
+ features: [
1387
+ { title: "Typed fields", description: "Each section exposes a clear field schema." },
1388
+ { title: "Starter pages", description: "Templates show realistic section composition." },
1389
+ { title: "CLI workflow", description: "Build, validate, preview, and publish from one tool." },
1390
+ ],
1391
+ },
1392
+ },
1393
+ {
1394
+ type: "CallToAction",
1395
+ props: {
1396
+ id: "CallToAction-1",
1397
+ title: "Launch your first page",
1398
+ description: "Customize this starter template or let an agent generate a new draft.",
1399
+ buttonLabel: "Get in touch",
1400
+ buttonHref: "/contact",
1401
+ },
1402
+ },
1403
+ ],
1404
+ },
1405
+ },
1406
+ {
1407
+ slug: "about",
1408
+ title: "About",
1409
+ data: {
1410
+ root: { props: {} },
1411
+ content: [
1412
+ {
1413
+ type: "Hero",
1414
+ props: {
1415
+ id: "Hero-About",
1416
+ eyebrow: "About",
1417
+ title: "A clean starting point for your story",
1418
+ description: "Use this page to introduce the project, audience, and promise.",
1419
+ primaryLabel: "Contact us",
1420
+ primaryHref: "/contact",
1421
+ },
1422
+ },
1423
+ {
1424
+ type: "Testimonial",
1425
+ props: {
1426
+ id: "Testimonial-About",
1427
+ quote: "This starter theme keeps the editable surface focused and predictable.",
1428
+ author: "SudaCloud",
1429
+ role: "Theme team",
1430
+ },
1431
+ },
1432
+ ],
1433
+ },
1434
+ },
1435
+ {
1436
+ slug: "contact",
1437
+ title: "Contact",
1438
+ data: {
1439
+ root: { props: {} },
1440
+ content: [
1441
+ {
1442
+ type: "Hero",
1443
+ props: {
1444
+ id: "Hero-Contact",
1445
+ eyebrow: "Contact",
1446
+ title: "Let's talk",
1447
+ description: "Tell visitors how to reach you and what happens next.",
1448
+ primaryLabel: "Email us",
1449
+ primaryHref: "mailto:hello@example.com",
1450
+ },
1451
+ },
1452
+ {
1453
+ type: "CallToAction",
1454
+ props: {
1455
+ id: "CallToAction-Contact",
1456
+ title: "Start the conversation",
1457
+ description: "Replace this copy with your preferred contact details or form link.",
1458
+ buttonLabel: "Send an email",
1459
+ buttonHref: "mailto:hello@example.com",
942
1460
  },
943
1461
  },
944
1462
  ],
@@ -971,8 +1489,67 @@ export { manifest, pageConfig, layoutConfig, defaultLayout, starterPages };
971
1489
  path2.join(target, "src", "runtime.client.ts"),
972
1490
  '"use client";\n\nimport theme from "./index.js";\n\nexport default theme;\n'
973
1491
  );
974
- await writeFile(path2.join(target, "styles.css"), "\n");
1492
+ await writeFile(
1493
+ path2.join(target, "styles.css"),
1494
+ `.${key}-root { font-family: Inter, ui-sans-serif, system-ui, sans-serif; color: #111827; background: #ffffff; }
1495
+ .${key}-header, .${key}-footer { padding: 20px clamp(20px, 5vw, 64px); border-bottom: 1px solid #e5e7eb; }
1496
+ .${key}-footer { border-top: 1px solid #e5e7eb; border-bottom: 0; color: #6b7280; }
1497
+ .${key}-section { padding: 64px clamp(20px, 5vw, 64px); }
1498
+ .${key}-hero { background: #f8fafc; }
1499
+ .${key}-eyebrow { text-transform: uppercase; letter-spacing: 0.08em; font-size: 12px; color: #2563eb; font-weight: 700; }
1500
+ .${key}-section h1 { max-width: 780px; font-size: clamp(40px, 7vw, 72px); line-height: 0.95; margin: 0 0 20px; }
1501
+ .${key}-section h2 { max-width: 720px; font-size: 36px; line-height: 1.05; margin: 0 0 16px; }
1502
+ .${key}-section p { max-width: 680px; line-height: 1.7; color: #4b5563; }
1503
+ .${key}-button { display: inline-flex; align-items: center; min-height: 42px; padding: 0 18px; border-radius: 8px; background: #111827; color: #ffffff; text-decoration: none; font-weight: 700; }
1504
+ .${key}-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; margin-top: 28px; }
1505
+ .${key}-card { border: 1px solid #e5e7eb; border-radius: 8px; padding: 20px; }
1506
+ .${key}-quote blockquote { max-width: 780px; font-size: 28px; line-height: 1.25; margin: 0 0 16px; }
1507
+ .${key}-cta { background: #111827; color: #ffffff; }
1508
+ .${key}-cta p { color: #d1d5db; }
1509
+ .${key}-cta .${key}-button { background: #ffffff; color: #111827; }
1510
+ `
1511
+ );
975
1512
  await writeFile(path2.join(target, ".gitignore"), "node_modules\ndist\n*.tsbuildinfo\n");
1513
+ await writeFile(
1514
+ path2.join(target, "AGENTS.md"),
1515
+ `# Suda Theme Agent Guide
1516
+
1517
+ This directory is a Suda theme source project generated by \`suda theme init\`.
1518
+
1519
+ ## What to edit
1520
+
1521
+ - Add page sections in \`src/sections.tsx\` and register them in \`SECTION_COMPONENTS\`.
1522
+ - Add starter pages in \`src/templates.ts\` to show realistic section combinations.
1523
+ - Keep shared site chrome in \`src/layout.tsx\`; \`PageOutlet\` is where page content renders.
1524
+ - Keep \`src/index.tsx\` exporting the source \`ThemeModule\`. The final artifact is produced by \`suda theme build\`.
1525
+
1526
+ ## Page content rules
1527
+
1528
+ - Agent page files passed to \`suda agent page validate/create\` contain \`content\` and optional \`zones\` only.
1529
+ - Every \`content[]\` item must use a section type from \`SECTION_COMPONENTS\`.
1530
+ - Every \`content[]\` item must include \`props.id\`.
1531
+ - Prefer editing section props over changing render code when generating pages.
1532
+ - Do not edit files under \`dist/\`; they are generated.
1533
+
1534
+ ## Useful commands
1535
+
1536
+ \`\`\`bash
1537
+ suda theme build
1538
+ suda theme validate
1539
+ suda theme preview
1540
+ suda agent theme describe local --theme-root .
1541
+ suda agent section schema --theme local --section Hero --theme-root .
1542
+ suda agent page validate --theme local --input ./page.json --theme-root .
1543
+ \`\`\`
1544
+ `
1545
+ );
1546
+ await writeFile(
1547
+ path2.join(target, "CLAUDE.md"),
1548
+ `# Claude Code
1549
+
1550
+ Read \`AGENTS.md\` first. It is the canonical guide for this Suda theme project.
1551
+ `
1552
+ );
976
1553
  await writeFile(
977
1554
  path2.join(target, "README.md"),
978
1555
  `# ${key}
@@ -982,10 +1559,19 @@ A Suda theme scaffolded with \`suda theme init\`.
982
1559
  ## Develop
983
1560
 
984
1561
  \`\`\`bash
1562
+ suda theme build
985
1563
  suda theme dev # rebuild the browser runtime on change
986
1564
  suda theme preview # build and preview the home starter page
987
1565
  \`\`\`
988
1566
 
1567
+ ## Agent tooling
1568
+
1569
+ \`\`\`bash
1570
+ suda agent theme describe local --theme-root .
1571
+ suda agent page schema local --theme-root .
1572
+ suda agent section schema --theme local --section Hero --theme-root .
1573
+ \`\`\`
1574
+
989
1575
  ## Publish
990
1576
 
991
1577
  \`\`\`bash
@@ -997,7 +1583,7 @@ suda theme publish
997
1583
  );
998
1584
  console.log(`created theme scaffold at ${target}`);
999
1585
  }
1000
- async function main() {
1586
+ function buildProgram() {
1001
1587
  const program = new Command();
1002
1588
  program.name("suda").description("Suda CLI for managing themes, sites, posts and AI tooling.").version("0.0.0");
1003
1589
  const theme = program.command("theme").description("Manage Suda theme artifacts.");
@@ -1032,8 +1618,38 @@ async function main() {
1032
1618
  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.").action(async (options) => {
1033
1619
  await publishTheme(resolveThemeRoot(options), options.skipBuild === true);
1034
1620
  });
1621
+ const agent = program.command("agent").description("Agent-friendly theme and page tooling.");
1622
+ const agentThemes = agent.command("themes").description("Manage agent-visible themes.");
1623
+ agentThemes.command("list").description("List themes visible to the current CLI user.").option("--project-id <projectId>", "Scope results to a project.").option("--theme-root <path>", "Describe a local theme instead of the remote catalog.").action(async (options) => {
1624
+ const themes = await listAgentThemes(options);
1625
+ printJson({ themes });
1626
+ });
1627
+ const agentTheme = agent.command("theme").description("Inspect a single theme.");
1628
+ 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) => {
1629
+ const manifest = await fetchAgentManifest(themeKey, options);
1630
+ printJson(createAgentPageSchemaOutput(manifest, { includeExample: options.example === true }));
1631
+ });
1632
+ const agentPage = agent.command("page").description("AI-first page content tooling.");
1633
+ 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) => {
1634
+ const manifest = await fetchAgentManifest(themeKey, options);
1635
+ printJson(createAgentPageSchemaOutput(manifest, { includeExample: options.example === true }));
1636
+ });
1637
+ 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) => {
1638
+ await validateAgentPage(options.theme, options.input, options);
1639
+ });
1640
+ agentPage.command("create").description("Create a workspace page draft from AI-generated Suda page content JSON.").requiredOption("--project-id <projectId>", "Project id.").requiredOption("--title <title>", "Page title.").requiredOption("--slug <slug>", "Page slug.").requiredOption("--theme <theme>", "Theme key.").requiredOption("--input <file>", "JSON file containing Suda page content.").option("--version <version>", "Theme version.").action(async (options) => {
1641
+ await createAgentPage(options);
1642
+ });
1643
+ const agentSection = agent.command("section").description("Inspect a single section.");
1644
+ agentSection.command("schema").description("Print one AI-first section output schema for a theme.").requiredOption("--theme <theme>", "Theme key.").requiredOption("--section <section>", "Section type.").option("--version <version>", "Theme version.").option("--project-id <projectId>", "Project scope.").option("--theme-root <path>", "Read a local theme.").action(async (options) => {
1645
+ const manifest = await fetchAgentManifest(options.theme, options);
1646
+ printJson(createSectionSchema(manifest, options.section));
1647
+ });
1648
+ program.command("mcp").description("Run the Suda local MCP server over stdio.").action(async () => {
1649
+ await startMcpServer();
1650
+ });
1035
1651
  const authCmd = program.command("auth").description("Manage Suda authentication.");
1036
- authCmd.command("login").description("Authenticate Suda CLI with a SudaCloud workspace.").option("--host <host>", "The SudaCloud workspace host to authenticate against.", "workspace.sudacloud.com").action(async (options) => {
1652
+ 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) => {
1037
1653
  await login(options.host);
1038
1654
  });
1039
1655
  authCmd.command("status").description("Check current authentication status.").action(async () => {
@@ -1043,12 +1659,14 @@ async function main() {
1043
1659
  await logout();
1044
1660
  });
1045
1661
  program.showHelpAfterError();
1662
+ return program;
1663
+ }
1664
+ async function main() {
1665
+ const program = buildProgram();
1046
1666
  const argv = process.argv.filter((arg, index) => index < 2 || arg !== "--");
1047
1667
  await program.parseAsync(argv);
1048
1668
  }
1049
- main().catch((error) => {
1050
- console.error(error instanceof Error ? error.message : error);
1051
- process.exitCode = 1;
1052
- });
1669
+
1670
+ export { buildProgram, main };
1053
1671
  //# sourceMappingURL=index.js.map
1054
1672
  //# sourceMappingURL=index.js.map