@cyber-dash-tech/revela 0.4.0 → 0.4.1

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.
@@ -0,0 +1,167 @@
1
+ export interface DesignsNewArgs {
2
+ name: string
3
+ base: string
4
+ }
5
+
6
+ export type DesignsNewParseResult =
7
+ | { ok: true; name: string; base: string }
8
+ | { ok: false; error: string }
9
+
10
+ export type DesignsEditParseResult =
11
+ | { ok: true; name: string }
12
+ | { ok: false; error: string }
13
+
14
+ const USAGE =
15
+ "**Usage:** `/revela designs-new <kebab-case-name> [--base starter]`\n" +
16
+ "Example: `/revela designs-new neon-finance --base starter`"
17
+
18
+ const EDIT_USAGE =
19
+ "**Usage:** `/revela designs-edit <kebab-case-name>`\n" +
20
+ "Example: `/revela designs-edit neon-finance`"
21
+
22
+ function isValidDesignName(name: string): boolean {
23
+ return /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(name)
24
+ }
25
+
26
+ export function parseDesignsNewArgs(input: string): DesignsNewParseResult {
27
+ const tokens = input.trim().split(/\s+/).filter(Boolean)
28
+ if (tokens.length === 0) return { ok: false, error: USAGE }
29
+
30
+ const name = tokens[0]
31
+ if (!isValidDesignName(name)) {
32
+ return {
33
+ ok: false,
34
+ error: `${USAGE}\n\nDesign name must be kebab-case using lowercase letters, numbers, and hyphens.`,
35
+ }
36
+ }
37
+
38
+ let base = "starter"
39
+ for (let i = 1; i < tokens.length; i++) {
40
+ const token = tokens[i]
41
+ if (token === "--base") {
42
+ const value = tokens[i + 1]
43
+ if (!value) return { ok: false, error: `${USAGE}\n\nMissing value for --base.` }
44
+ if (!isValidDesignName(value)) {
45
+ return { ok: false, error: `${USAGE}\n\nBase design must be a valid kebab-case design name.` }
46
+ }
47
+ base = value
48
+ i++
49
+ continue
50
+ }
51
+ return { ok: false, error: `${USAGE}\n\nUnknown option: \`${token}\`` }
52
+ }
53
+
54
+ return { ok: true, name, base }
55
+ }
56
+
57
+ export function parseDesignsEditArgs(input: string): DesignsEditParseResult {
58
+ const tokens = input.trim().split(/\s+/).filter(Boolean)
59
+ if (tokens.length !== 1) return { ok: false, error: EDIT_USAGE }
60
+
61
+ const name = tokens[0]
62
+ if (!isValidDesignName(name)) {
63
+ return {
64
+ ok: false,
65
+ error: `${EDIT_USAGE}\n\nDesign name must be kebab-case using lowercase letters, numbers, and hyphens.`,
66
+ }
67
+ }
68
+
69
+ return { ok: true, name }
70
+ }
71
+
72
+ const VISUAL_QUALITY_RULES = `Visual extraction and CSS quality rules:
73
+ - Before writing CSS, extract a visual schema from the user's references. Include reference type, composition, scale, anchoring, typography relationship, decorative language, must-preserve, and must-avoid.
74
+ - Preserve composition, not just colors and shapes. If the reference is a bottom strip, compact badge, side rail, or sparse header mark, keep that scale and anchoring; do not enlarge it into a full-slide mascot or background unless requested.
75
+ - Do not rewrite the entire base layout system from scratch. Preserve base layout/container CSS where possible; mainly change tokens, typography, component skins, and small decorative components.
76
+ - Keep CSS scoped and boring. Prefer CSS variables and reusable classes over many one-off absolute-positioned selectors.
77
+ - If a reference is flat vector, doodle, mascot, blob, line-art, or geometric illustration, prefer a self-contained SVG component with a fixed viewBox. CSS should place the SVG; the SVG should draw the motif.
78
+ - If a reference is photography, UI screenshot, webpage, or product surface, do not convert it to SVG. Extract palette, type scale, spacing, layout rhythm, borders, and image treatment instead.
79
+ - For SVG motifs: set a viewBox, keep all eyes/mouths/decorations inside that coordinate system, and document intended placement/scale in the component notes.
80
+ - Before saving, review the preview for text overlap, scale drift, lost anchoring, overflow, and whether the preview preserves the reference composition.`
81
+
82
+ export function buildDesignsNewPrompt({ name, base }: DesignsNewArgs): string {
83
+ return `You are creating a new Revela visual design package.
84
+
85
+ Target design:
86
+ - name: ${name}
87
+ - base design: ${base}
88
+
89
+ Use the base design only as a structural scaffold. Preserve its structure, not its visual identity.
90
+
91
+ You must preserve from the base design:
92
+ - marker structure: @design, @layout, and @component blocks
93
+ - layout taxonomy and component coverage
94
+ - HTML skeleton and SlidePresentation JavaScript architecture
95
+ - 1920x1080 fixed canvas behavior
96
+ - slide-qa usage on every slide
97
+ - QA-friendly class vocabulary discipline
98
+
99
+ You must replace unless the user explicitly requests otherwise:
100
+ - palette
101
+ - typography
102
+ - imagery direction
103
+ - decorative language
104
+ - tone
105
+ - composition rules
106
+ - preview content
107
+
108
+ ${VISUAL_QUALITY_RULES}
109
+
110
+ Workflow:
111
+ 1. Do not generate or save files immediately.
112
+ 2. Interview the user first. Ask for visual references such as screenshots/images, webpage URLs, text descriptions, brands, or decks they like.
113
+ 3. Extract a visual schema from the references before proposing CSS or components.
114
+ 4. Collect a concise design brief covering intent, tone, density, content mode, industry fit, references, visual schema, must-have, and must-avoid.
115
+ 5. Summarize the brief and visual schema, then ask the user to confirm them.
116
+ 6. After confirmation, inspect the base design using the \`revela-designs\` tool.
117
+ 7. Generate a complete \`DESIGN.md\` and matching \`preview.html\`.
118
+ 8. Self-review the preview against the visual schema before saving.
119
+ 9. Save the package with \`revela-designs-author\` using action \`create\`.
120
+ 10. Validate it with \`revela-designs-author\` using action \`validate\`.
121
+ 11. Report the saved path and activation command: \`/revela designs ${name}\`.
122
+
123
+ Hard requirements:
124
+ - \`DESIGN.md\` must include frontmatter with name, description, author, and version.
125
+ - \`DESIGN.md\` must include valid \`@design\`, \`@layout\`, and \`@component\` markers.
126
+ - \`DESIGN.md\` must include at least \`@design:foundation\`, \`@design:rules\`, one layout, and one component.
127
+ - \`preview.html\` must be self-contained and directly openable in a browser.
128
+ - Every preview slide must include \`slide-qa="true"\` or \`slide-qa="false"\`.
129
+ - Do not save anything until the user confirms the brief.
130
+
131
+ Start now by interviewing the user. Keep the first question concise.`
132
+ }
133
+
134
+ export function buildDesignsEditPrompt({ name }: { name: string }): string {
135
+ return `You are editing an existing Revela visual design package.
136
+
137
+ Target design:
138
+ - name: ${name}
139
+
140
+ Goal:
141
+ - Refine the existing design without losing its useful layout/component coverage.
142
+ - Preserve the design's established structure unless the user explicitly asks to change it.
143
+ - Save the edited design only after the user confirms the edit brief.
144
+
145
+ ${VISUAL_QUALITY_RULES}
146
+
147
+ Workflow:
148
+ 1. Do not save files immediately.
149
+ 2. Ask the user what they want to change. Accept text descriptions, screenshots/images, webpage URLs, or specific complaints about the current preview.
150
+ 3. Inspect the existing design using \`revela-designs\` with action \`read\` and name \`${name}\`. Fetch relevant layouts/components as needed.
151
+ 4. Summarize an edit brief covering current issue, desired direction, visual schema, must-preserve, and must-avoid.
152
+ 5. Ask the user to confirm the edit brief.
153
+ 6. After confirmation, generate the updated complete \`DESIGN.md\` and updated complete \`preview.html\`.
154
+ 7. Self-review the preview for text overlap, scale drift, lost anchoring, overflow, and whether the requested change is visible.
155
+ 8. Save with \`revela-designs-author\` using action \`create\`, name \`${name}\`, and overwrite=true.
156
+ 9. Validate with \`revela-designs-author\` using action \`validate\`.
157
+ 10. Report the saved path and activation command: \`/revela designs ${name}\`.
158
+
159
+ Hard requirements:
160
+ - Preserve valid frontmatter and marker structure.
161
+ - Preserve at least \`@design:foundation\`, \`@design:rules\`, one layout, and one component.
162
+ - \`preview.html\` must be self-contained and directly openable in a browser.
163
+ - Every preview slide must include \`slide-qa="true"\` or \`slide-qa="false"\`.
164
+ - Do not save anything until the user confirms the edit brief.
165
+
166
+ Start now by asking what the user wants to change in \`${name}\`.`
167
+ }
@@ -0,0 +1,36 @@
1
+ import { resolveDesignPreview } from "../design/designs"
2
+
3
+ function openFile(filePath: string): void {
4
+ if (process.platform === "darwin") {
5
+ const proc = Bun.spawnSync(["open", filePath])
6
+ if (proc.exitCode !== 0) throw new Error(proc.stderr.toString() || "Failed to open preview")
7
+ return
8
+ }
9
+
10
+ if (process.platform === "win32") {
11
+ const proc = Bun.spawnSync(["cmd", "/c", "start", "", filePath])
12
+ if (proc.exitCode !== 0) throw new Error(proc.stderr.toString() || "Failed to open preview")
13
+ return
14
+ }
15
+
16
+ const proc = Bun.spawnSync(["xdg-open", filePath])
17
+ if (proc.exitCode !== 0) throw new Error(proc.stderr.toString() || "Failed to open preview")
18
+ }
19
+
20
+ export async function handleDesignsPreview(
21
+ name: string,
22
+ send: (text: string) => Promise<void>,
23
+ ): Promise<void> {
24
+ try {
25
+ const preview = resolveDesignPreview(name || undefined)
26
+ if (!preview.hasPreview) {
27
+ await send(`Design \`${preview.name}\` has no \`preview.html\`.`)
28
+ return
29
+ }
30
+
31
+ openFile(preview.previewPath)
32
+ await send(`Opened preview for design \`${preview.name}\`: \`${preview.previewPath}\``)
33
+ } catch (e: any) {
34
+ await send(`**Preview failed:** ${e.message || String(e)}`)
35
+ }
36
+ }
@@ -28,6 +28,9 @@ export async function handleHelp(
28
28
  `\`/revela disable\` — disable slide generation mode\n` +
29
29
  `\`/revela designs\` — list installed designs\n` +
30
30
  `\`/revela designs <name>\` — activate a design\n` +
31
+ `\`/revela designs-new <name>\` — create a new custom design with AI\n` +
32
+ `\`/revela designs-edit <name>\` — refine an existing custom design with AI\n` +
33
+ `\`/revela designs-preview [name]\` — open a design preview in browser\n` +
31
34
  `\`/revela domains\` — list installed domains\n` +
32
35
  `\`/revela domains <name>\` — activate a domain\n` +
33
36
  `\`/revela designs-add <url>\` — install a design from URL / github:user/repo\n` +
@@ -39,9 +39,51 @@ export interface DesignInfo {
39
39
  description: string
40
40
  author: string
41
41
  version: string
42
+ internal: boolean
42
43
  skillText: string
43
44
  }
44
45
 
46
+ export interface ListDesignsOptions {
47
+ includeInternal?: boolean
48
+ }
49
+
50
+ export interface CreateDesignPackageArgs {
51
+ name: string
52
+ base?: string
53
+ designMd: string
54
+ previewHtml: string
55
+ overwrite?: boolean
56
+ }
57
+
58
+ export interface CreateDesignPackageResult {
59
+ ok: true
60
+ name: string
61
+ path: string
62
+ files: string[]
63
+ base?: string
64
+ overwritten: boolean
65
+ }
66
+
67
+ export interface ValidateDesignPackageResult {
68
+ ok: boolean
69
+ name: string
70
+ path: string
71
+ hasDesignMd: boolean
72
+ hasPreview: boolean
73
+ hasMarkers: boolean
74
+ sections: string[]
75
+ layouts: string[]
76
+ components: string[]
77
+ errors: string[]
78
+ }
79
+
80
+ export interface DesignPreviewInfo {
81
+ name: string
82
+ designDir: string
83
+ previewPath: string
84
+ hasPreview: boolean
85
+ }
86
+
45
87
  // ---------------------------------------------------------------------------
46
88
  // Seed
47
89
  // ---------------------------------------------------------------------------
@@ -80,6 +122,7 @@ export function parseDesignFile(filePath: string): DesignInfo | null {
80
122
  description: meta.description || "",
81
123
  author: meta.author || "unknown",
82
124
  version: meta.version || "0.0.0",
125
+ internal: meta.internal === "true",
83
126
  skillText: body,
84
127
  }
85
128
  } catch (e) {
@@ -95,10 +138,11 @@ export function parseDesignFile(filePath: string): DesignInfo | null {
95
138
  // Public API
96
139
  // ---------------------------------------------------------------------------
97
140
 
98
- /** List all installed designs, sorted by name. */
99
- export function listDesigns(): DesignInfo[] {
141
+ /** List installed designs, sorted by name. Internal designs are hidden by default. */
142
+ export function listDesigns(options: ListDesignsOptions = {}): DesignInfo[] {
100
143
  if (!existsSync(DESIGNS_DIR)) return []
101
144
  const results: DesignInfo[] = []
145
+ const includeInternal = options.includeInternal ?? false
102
146
 
103
147
  for (const entry of readdirSync(DESIGNS_DIR).sort()) {
104
148
  const dir = join(DESIGNS_DIR, entry)
@@ -106,7 +150,7 @@ export function listDesigns(): DesignInfo[] {
106
150
  const mdPath = join(dir, "DESIGN.md")
107
151
  if (!existsSync(mdPath)) continue
108
152
  const info = parseDesignFile(mdPath)
109
- if (info) results.push(info)
153
+ if (info && (includeInternal || !info.internal)) results.push(info)
110
154
  }
111
155
  return results
112
156
  }
@@ -141,6 +185,135 @@ export function getDesignSkillMd(name?: string): string {
141
185
  return info.skillText
142
186
  }
143
187
 
188
+ /** Resolve a design's preview.html path. Throws if the design is not installed. */
189
+ export function resolveDesignPreview(name?: string): DesignPreviewInfo {
190
+ const designName = normalizeDesignName(name || activeDesign())
191
+ const designDir = join(DESIGNS_DIR, designName)
192
+ const mdPath = join(designDir, "DESIGN.md")
193
+ if (!existsSync(designDir) || !existsSync(mdPath)) {
194
+ throw new Error(`Design '${designName}' is not installed`)
195
+ }
196
+
197
+ const previewPath = join(designDir, "preview.html")
198
+ return {
199
+ name: designName,
200
+ designDir,
201
+ previewPath,
202
+ hasPreview: existsSync(previewPath),
203
+ }
204
+ }
205
+
206
+ /** Normalize and validate a design package name. */
207
+ export function normalizeDesignName(name: string): string {
208
+ const normalized = name.trim().toLowerCase()
209
+ if (!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(normalized)) {
210
+ throw new Error("Design name must be kebab-case using lowercase letters, numbers, and hyphens")
211
+ }
212
+ return normalized
213
+ }
214
+
215
+ /** Create a local design package in ~/.config/revela/designs/<name>/. */
216
+ export function createDesignPackage(args: CreateDesignPackageArgs): CreateDesignPackageResult {
217
+ const name = normalizeDesignName(args.name)
218
+ const designMd = args.designMd?.trim()
219
+ const previewHtml = args.previewHtml?.trim()
220
+
221
+ if (!designMd) throw new Error("designMd is required")
222
+ if (!previewHtml) throw new Error("previewHtml is required")
223
+
224
+ const target = join(DESIGNS_DIR, name)
225
+ const existed = existsSync(target)
226
+ if (existed && !args.overwrite) {
227
+ throw new Error(`Design '${name}' already exists. Pass overwrite=true to replace it.`)
228
+ }
229
+
230
+ mkdirSync(DESIGNS_DIR, { recursive: true })
231
+ if (existed) {
232
+ rmSync(target, { recursive: true, force: true })
233
+ }
234
+ mkdirSync(target, { recursive: true })
235
+ writeFileSync(join(target, "DESIGN.md"), `${designMd}\n`, "utf-8")
236
+ writeFileSync(join(target, "preview.html"), `${previewHtml}\n`, "utf-8")
237
+
238
+ const validation = validateDesignPackage(name)
239
+ if (!validation.ok) {
240
+ throw new Error(`Created design package is invalid: ${validation.errors.join("; ")}`)
241
+ }
242
+
243
+ return {
244
+ ok: true,
245
+ name,
246
+ path: target,
247
+ files: ["DESIGN.md", "preview.html"],
248
+ base: args.base,
249
+ overwritten: existed,
250
+ }
251
+ }
252
+
253
+ /** Validate a local design package for the minimum Revela design contract. */
254
+ export function validateDesignPackage(nameInput: string): ValidateDesignPackageResult {
255
+ let name = nameInput
256
+ const errors: string[] = []
257
+ try {
258
+ name = normalizeDesignName(nameInput)
259
+ } catch (e) {
260
+ errors.push(e instanceof Error ? e.message : String(e))
261
+ }
262
+
263
+ const dir = join(DESIGNS_DIR, name)
264
+ const mdPath = join(dir, "DESIGN.md")
265
+ const previewPath = join(dir, "preview.html")
266
+ const hasDesignMd = existsSync(mdPath)
267
+ const hasPreview = existsSync(previewPath)
268
+ let hasMarkers = false
269
+ let sections: string[] = []
270
+ let layouts: string[] = []
271
+ let components: string[] = []
272
+
273
+ if (!existsSync(dir)) errors.push(`Design directory does not exist: ${dir}`)
274
+ if (!hasDesignMd) errors.push("DESIGN.md is missing")
275
+ if (!hasPreview) errors.push("preview.html is missing")
276
+
277
+ if (hasDesignMd) {
278
+ const info = parseDesignFile(mdPath)
279
+ if (!info) {
280
+ errors.push("DESIGN.md could not be parsed")
281
+ } else {
282
+ const parsed = parseDesignSections(info.skillText)
283
+ hasMarkers = parsed.hasMarkers
284
+ sections = Object.keys(parsed.sections)
285
+ layouts = Object.keys(parsed.layouts)
286
+ components = Object.keys(parsed.components)
287
+
288
+ if (!hasMarkers) errors.push("DESIGN.md must include marker sections")
289
+ if (!parsed.sections.foundation) errors.push("@design:foundation section is missing")
290
+ if (!parsed.sections.rules) errors.push("@design:rules section is missing")
291
+ if (layouts.length === 0) errors.push("At least one @layout section is required")
292
+ if (components.length === 0) errors.push("At least one @component section is required")
293
+ }
294
+ }
295
+
296
+ if (hasPreview) {
297
+ const preview = readFileSync(previewPath, "utf-8")
298
+ if (!preview.includes('<section class="slide"')) errors.push("preview.html must include slide sections")
299
+ if (!preview.includes("slide-qa=")) errors.push("preview.html slides must include slide-qa attributes")
300
+ if (!preview.includes("slide-canvas")) errors.push("preview.html must include .slide-canvas")
301
+ }
302
+
303
+ return {
304
+ ok: errors.length === 0,
305
+ name,
306
+ path: dir,
307
+ hasDesignMd,
308
+ hasPreview,
309
+ hasMarkers,
310
+ sections,
311
+ layouts,
312
+ components,
313
+ errors,
314
+ }
315
+ }
316
+
144
317
  // ---------------------------------------------------------------------------
145
318
  // Marker-based section / component parsing
146
319
  // ---------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyber-dash-tech/revela",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "OpenCode plugin that turns AI into an HTML slide deck generator",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -13,8 +13,12 @@
13
13
  "tools/",
14
14
  "skill/",
15
15
  "designs/aurora/DESIGN.md",
16
+ "designs/starter/DESIGN.md",
17
+ "designs/starter/preview.html",
16
18
  "designs/summit/DESIGN.md",
19
+ "designs/summit/preview.html",
17
20
  "designs/monet/DESIGN.md",
21
+ "designs/monet/preview.html",
18
22
  "domains/general/INDUSTRY.md",
19
23
  "domains/deeptech-investment/INDUSTRY.md",
20
24
  "domains/consulting/INDUSTRY.md",
package/plugin.ts CHANGED
@@ -45,6 +45,14 @@ import {
45
45
  } from "./lib/commands/domains"
46
46
  import { handlePdf } from "./lib/commands/pdf"
47
47
  import { handlePptx } from "./lib/commands/pptx"
48
+ import { handleDesignsPreview } from "./lib/commands/designs-preview"
49
+ import {
50
+ parseDesignsNewArgs,
51
+ buildDesignsNewPrompt,
52
+ parseDesignsEditArgs,
53
+ buildDesignsEditPrompt,
54
+ } from "./lib/commands/designs-new"
55
+ import designsAuthorTool from "./tools/designs-author"
48
56
  import designsTool from "./tools/designs"
49
57
  import domainsTool from "./tools/domains"
50
58
  import mediaBatchSaveTool from "./tools/media-batch-save"
@@ -203,6 +211,36 @@ const server: Plugin = (async (pluginCtx) => {
203
211
  await handleDesignsAdd(param, send)
204
212
  throw new Error("__REVELA_DESIGNS_ADD_HANDLED__")
205
213
  }
214
+ if (sub === "designs-new") {
215
+ const parsed = parseDesignsNewArgs(param)
216
+ if (!parsed.ok) {
217
+ await send(parsed.error)
218
+ throw new Error("__REVELA_DESIGNS_NEW_USAGE_HANDLED__")
219
+ }
220
+ output.parts.length = 0
221
+ output.parts.push({
222
+ type: "text",
223
+ text: buildDesignsNewPrompt({ name: parsed.name, base: parsed.base }),
224
+ } as any)
225
+ return
226
+ }
227
+ if (sub === "designs-edit") {
228
+ const parsed = parseDesignsEditArgs(param)
229
+ if (!parsed.ok) {
230
+ await send(parsed.error)
231
+ throw new Error("__REVELA_DESIGNS_EDIT_USAGE_HANDLED__")
232
+ }
233
+ output.parts.length = 0
234
+ output.parts.push({
235
+ type: "text",
236
+ text: buildDesignsEditPrompt({ name: parsed.name }),
237
+ } as any)
238
+ return
239
+ }
240
+ if (sub === "designs-preview") {
241
+ await handleDesignsPreview(param, send)
242
+ throw new Error("__REVELA_DESIGNS_PREVIEW_HANDLED__")
243
+ }
206
244
  if (sub === "domains-add") {
207
245
  await handleDomainsAdd(param, send)
208
246
  throw new Error("__REVELA_DOMAINS_ADD_HANDLED__")
@@ -231,6 +269,7 @@ const server: Plugin = (async (pluginCtx) => {
231
269
  // ── LLM tools: designs, domains, research, document materials, qa ─────
232
270
  tool: {
233
271
  "revela-designs": designsTool,
272
+ "revela-designs-author": designsAuthorTool,
234
273
  "revela-domains": domainsTool,
235
274
  "revela-media-batch-save": mediaBatchSaveTool,
236
275
  "revela-media-save": mediaSaveTool,
@@ -0,0 +1,62 @@
1
+ import { tool } from "@opencode-ai/plugin"
2
+ import { createDesignPackage, validateDesignPackage } from "../lib/design/designs"
3
+
4
+ export default tool({
5
+ description:
6
+ "Create and validate local Revela design packages generated by the AI design authoring workflow. " +
7
+ "Use action 'create' to save DESIGN.md and preview.html into ~/.config/revela/designs/{name}/. " +
8
+ "Use action 'validate' to check a generated design package before asking the user to activate it.",
9
+ args: {
10
+ action: tool.schema
11
+ .enum(["create", "validate"])
12
+ .describe("Operation to perform"),
13
+ name: tool.schema
14
+ .string()
15
+ .optional()
16
+ .describe("Design name in kebab-case. Required for create and validate."),
17
+ base: tool.schema
18
+ .string()
19
+ .optional()
20
+ .describe("Base design used as structural scaffold, e.g. 'summit'. Recorded in the result only."),
21
+ designMd: tool.schema
22
+ .string()
23
+ .optional()
24
+ .describe("Complete DESIGN.md content. Required for create."),
25
+ previewHtml: tool.schema
26
+ .string()
27
+ .optional()
28
+ .describe("Complete preview.html content. Required for create."),
29
+ overwrite: tool.schema
30
+ .boolean()
31
+ .optional()
32
+ .describe("Whether to replace an existing local design package. Defaults to false."),
33
+ },
34
+ async execute(args) {
35
+ try {
36
+ if (!args.name) return JSON.stringify({ error: "name is required" })
37
+
38
+ switch (args.action) {
39
+ case "create": {
40
+ if (!args.designMd) return JSON.stringify({ error: "designMd is required for create" })
41
+ if (!args.previewHtml) return JSON.stringify({ error: "previewHtml is required for create" })
42
+ const result = createDesignPackage({
43
+ name: args.name,
44
+ base: args.base,
45
+ designMd: args.designMd,
46
+ previewHtml: args.previewHtml,
47
+ overwrite: args.overwrite ?? false,
48
+ })
49
+ return JSON.stringify(result, null, 2)
50
+ }
51
+ case "validate": {
52
+ return JSON.stringify(validateDesignPackage(args.name), null, 2)
53
+ }
54
+ default: {
55
+ return JSON.stringify({ error: `Unknown action: ${args.action}` })
56
+ }
57
+ }
58
+ } catch (e: any) {
59
+ return JSON.stringify({ error: e.message || String(e) })
60
+ }
61
+ },
62
+ })