@lemonppt/cli 0.1.7 → 0.2.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/SKILL.md +189 -97
- package/agents/codex.yaml +99 -0
- package/agents/cursor.yaml +47 -0
- package/agents/openai.yaml +176 -0
- package/dist/cli.js +199 -1
- package/dist/index.d.ts +98 -1
- package/dist/index.js +320 -8
- package/package.json +7 -6
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# lemonPPT - AI-powered presentation generation
|
|
2
|
+
# Copyright (c) 2026 lemonforme
|
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
name: lemonppt
|
|
6
|
+
description: |
|
|
7
|
+
lemonPPT 是一个结构化 goal.json → 视觉 PPT 的渲染与导出引擎。
|
|
8
|
+
支持自然语言生成 PPT、按角色自动选版式、导出 PPTX/PDF,以及程序化选页与字段校验。
|
|
9
|
+
|
|
10
|
+
functions:
|
|
11
|
+
- name: generate_goal
|
|
12
|
+
description: 根据一句话需求生成完整的 goal.json(端到端入口)
|
|
13
|
+
parameters:
|
|
14
|
+
type: object
|
|
15
|
+
properties:
|
|
16
|
+
input:
|
|
17
|
+
type: string
|
|
18
|
+
description: 用户的 PPT 需求描述
|
|
19
|
+
pageCount:
|
|
20
|
+
type: integer
|
|
21
|
+
description: 期望页数,默认 8
|
|
22
|
+
default: 8
|
|
23
|
+
theme:
|
|
24
|
+
type: string
|
|
25
|
+
description: 主题 ID,如 theme01、theme06
|
|
26
|
+
default: theme01
|
|
27
|
+
language:
|
|
28
|
+
type: string
|
|
29
|
+
enum: [zh, en]
|
|
30
|
+
description: 输出语言
|
|
31
|
+
default: zh
|
|
32
|
+
required: [input]
|
|
33
|
+
|
|
34
|
+
- name: render_goal
|
|
35
|
+
description: 将 goal.json 渲染为离线可打开的 HTML deck
|
|
36
|
+
parameters:
|
|
37
|
+
type: object
|
|
38
|
+
properties:
|
|
39
|
+
goal:
|
|
40
|
+
type: object
|
|
41
|
+
description: 符合 lemonPPT 规范的 DeckGoal 对象
|
|
42
|
+
editable:
|
|
43
|
+
type: boolean
|
|
44
|
+
description: 是否生成浏览器端可编辑版本
|
|
45
|
+
default: false
|
|
46
|
+
required: [goal]
|
|
47
|
+
|
|
48
|
+
- name: export_pptx
|
|
49
|
+
description: 将 goal.json 导出为可编辑 PPTX
|
|
50
|
+
parameters:
|
|
51
|
+
type: object
|
|
52
|
+
properties:
|
|
53
|
+
goal:
|
|
54
|
+
type: object
|
|
55
|
+
description: 符合 lemonPPT 规范的 DeckGoal 对象
|
|
56
|
+
required: [goal]
|
|
57
|
+
|
|
58
|
+
- name: export_pdf
|
|
59
|
+
description: 将 goal.json 导出为截图 PDF
|
|
60
|
+
parameters:
|
|
61
|
+
type: object
|
|
62
|
+
properties:
|
|
63
|
+
goal:
|
|
64
|
+
type: object
|
|
65
|
+
description: 符合 lemonPPT 规范的 DeckGoal 对象
|
|
66
|
+
required: [goal]
|
|
67
|
+
|
|
68
|
+
- name: list_themes
|
|
69
|
+
description: 列出所有可用主题
|
|
70
|
+
parameters:
|
|
71
|
+
type: object
|
|
72
|
+
properties: {}
|
|
73
|
+
|
|
74
|
+
- name: layout_query
|
|
75
|
+
description: 按主题与角色查询候选版式,供 Agent 程序化选页
|
|
76
|
+
parameters:
|
|
77
|
+
type: object
|
|
78
|
+
properties:
|
|
79
|
+
theme:
|
|
80
|
+
type: string
|
|
81
|
+
description: 主题 ID
|
|
82
|
+
role:
|
|
83
|
+
type: string
|
|
84
|
+
description: 页面角色,如 cover、metric、chart、process
|
|
85
|
+
keyword:
|
|
86
|
+
type: string
|
|
87
|
+
description: 关键词过滤
|
|
88
|
+
needsMedia:
|
|
89
|
+
type: boolean
|
|
90
|
+
description: 是否只返回需要媒体的版式
|
|
91
|
+
limit:
|
|
92
|
+
type: integer
|
|
93
|
+
description: 返回数量上限
|
|
94
|
+
default: 8
|
|
95
|
+
required: [theme, role]
|
|
96
|
+
|
|
97
|
+
- name: inspect_layout
|
|
98
|
+
description: 查看指定版式的字段契约、默认值与媒体槽位
|
|
99
|
+
parameters:
|
|
100
|
+
type: object
|
|
101
|
+
properties:
|
|
102
|
+
layoutId:
|
|
103
|
+
type: string
|
|
104
|
+
description: 完整版式 ID,如 theme06_metric_hero_v1
|
|
105
|
+
required: [layoutId]
|
|
106
|
+
|
|
107
|
+
- name: goal_scaffold
|
|
108
|
+
description: 生成只含 role 的 goal.json 骨架,由外层 Agent 填充内容
|
|
109
|
+
parameters:
|
|
110
|
+
type: object
|
|
111
|
+
properties:
|
|
112
|
+
title:
|
|
113
|
+
type: string
|
|
114
|
+
description: PPT 标题
|
|
115
|
+
goal:
|
|
116
|
+
type: string
|
|
117
|
+
description: PPT 目标/核心信息
|
|
118
|
+
audience:
|
|
119
|
+
type: string
|
|
120
|
+
description: 目标受众
|
|
121
|
+
owner:
|
|
122
|
+
type: string
|
|
123
|
+
description: 作者或团队
|
|
124
|
+
theme:
|
|
125
|
+
type: string
|
|
126
|
+
description: 主题 ID
|
|
127
|
+
default: theme01
|
|
128
|
+
pages:
|
|
129
|
+
type: integer
|
|
130
|
+
description: 页数
|
|
131
|
+
default: 8
|
|
132
|
+
language:
|
|
133
|
+
type: string
|
|
134
|
+
enum: [zh, en]
|
|
135
|
+
description: 语言
|
|
136
|
+
default: zh
|
|
137
|
+
required: [title, goal]
|
|
138
|
+
|
|
139
|
+
- name: write_safe_props
|
|
140
|
+
description: 规范化 goal.json 的 props,填充默认值并校验未知字段
|
|
141
|
+
parameters:
|
|
142
|
+
type: object
|
|
143
|
+
properties:
|
|
144
|
+
goal:
|
|
145
|
+
type: object
|
|
146
|
+
description: DeckGoal 对象
|
|
147
|
+
required: [goal]
|
|
148
|
+
|
|
149
|
+
- name: validate_goal_spec
|
|
150
|
+
description: 独立校验 goal.json 是否符合规范
|
|
151
|
+
parameters:
|
|
152
|
+
type: object
|
|
153
|
+
properties:
|
|
154
|
+
goal:
|
|
155
|
+
type: object
|
|
156
|
+
description: DeckGoal 对象
|
|
157
|
+
strict:
|
|
158
|
+
type: boolean
|
|
159
|
+
description: 是否将内容警告视为错误
|
|
160
|
+
default: false
|
|
161
|
+
required: [goal]
|
|
162
|
+
|
|
163
|
+
usage:
|
|
164
|
+
- |
|
|
165
|
+
端到端生成:
|
|
166
|
+
1. generate_goal(input="面向企业客户的 AI 助手产品发布会...", pageCount=8, theme="theme01")
|
|
167
|
+
2. export_pptx(goal=result.goal)
|
|
168
|
+
- |
|
|
169
|
+
精细编排:
|
|
170
|
+
1. list_themes()
|
|
171
|
+
2. layout_query(theme="theme06", role="metric", limit=3)
|
|
172
|
+
3. inspect_layout(layoutId="theme06_metric_hero_v1")
|
|
173
|
+
4. goal_scaffold(title="...", goal="...", theme="theme06", pages=10)
|
|
174
|
+
5. write_safe_props(goal=...)
|
|
175
|
+
6. validate_goal_spec(goal=...)
|
|
176
|
+
7. export_pptx(goal=...)
|
package/dist/cli.js
CHANGED
|
@@ -2,14 +2,25 @@
|
|
|
2
2
|
// lemonPPT - AI-powered presentation generation
|
|
3
3
|
// Copyright (c) 2026 lemonforme
|
|
4
4
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
-
import {
|
|
5
|
+
import { createServer } from 'node:http';
|
|
6
|
+
import { stat, readFile } from 'node:fs/promises';
|
|
7
|
+
import { extname, join, resolve } from 'node:path';
|
|
8
|
+
import { exportGoalToPdf, exportGoalToPptx, generateGoalToFile, inspectLayout, listThemes, queryLayouts, readGoalFromFile, renderGoalToDir, scaffoldGoalToFile, validateGoalSpec, writeSafePropsToFile, } from './index.js';
|
|
6
9
|
import { installSkill } from './install-skill.js';
|
|
7
10
|
function printUsage() {
|
|
8
11
|
console.log(`Usage:
|
|
9
12
|
lemonppt generate "<input>" [--pages N] [--theme <id>] [--language zh|en] [--out goal.json] [--api-key KEY]
|
|
10
13
|
lemonppt render <goal.json> [--out ./output] [--editable]
|
|
11
14
|
lemonppt export <goal.json> --pptx out.pptx [--pdf out.pdf]
|
|
15
|
+
lemonppt serve [<dir>] [--port N]
|
|
12
16
|
lemonppt install-skill [--claude] [--codex] [--cursor] [--all]
|
|
17
|
+
|
|
18
|
+
lemonppt list-themes
|
|
19
|
+
lemonppt layout-query --theme <id> --role <role> [--keyword K] [--needs-media] [--limit N] [--seed S]
|
|
20
|
+
lemonppt inspect-layout <layoutId> [--compact]
|
|
21
|
+
lemonppt goal-scaffold --title T --goal G --theme <id> --pages N [--out goal.json]
|
|
22
|
+
lemonppt write-safe-props <goal.json> [--write]
|
|
23
|
+
lemonppt validate-goal-spec <goal.json> [--strict]
|
|
13
24
|
`);
|
|
14
25
|
}
|
|
15
26
|
function parseArgs(argv) {
|
|
@@ -34,6 +45,71 @@ function parseArgs(argv) {
|
|
|
34
45
|
}
|
|
35
46
|
return { positional, options };
|
|
36
47
|
}
|
|
48
|
+
async function serveDir(dir, port) {
|
|
49
|
+
const root = resolve(dir);
|
|
50
|
+
const mimeTypes = {
|
|
51
|
+
'.html': 'text/html; charset=utf-8',
|
|
52
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
53
|
+
'.css': 'text/css; charset=utf-8',
|
|
54
|
+
'.json': 'application/json; charset=utf-8',
|
|
55
|
+
'.png': 'image/png',
|
|
56
|
+
'.jpg': 'image/jpeg',
|
|
57
|
+
'.jpeg': 'image/jpeg',
|
|
58
|
+
'.gif': 'image/gif',
|
|
59
|
+
'.svg': 'image/svg+xml',
|
|
60
|
+
'.woff': 'font/woff',
|
|
61
|
+
'.woff2': 'font/woff2',
|
|
62
|
+
'.ttf': 'font/ttf',
|
|
63
|
+
'.otf': 'font/otf',
|
|
64
|
+
'.eot': 'application/vnd.ms-fontobject',
|
|
65
|
+
};
|
|
66
|
+
const server = createServer(async (req, res) => {
|
|
67
|
+
const url = new URL(req.url || '/', `http://${req.headers.host}`);
|
|
68
|
+
let pathname = decodeURIComponent(url.pathname);
|
|
69
|
+
if (pathname === '/') {
|
|
70
|
+
// 优先 editable 输出(editor.html),回退到非 editable 输出(index.html)
|
|
71
|
+
for (const fallback of ['editor.html', 'index.html']) {
|
|
72
|
+
try {
|
|
73
|
+
const s = await stat(join(root, fallback));
|
|
74
|
+
if (s.isFile()) {
|
|
75
|
+
pathname = '/' + fallback;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch { }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const filePath = join(root, pathname);
|
|
83
|
+
if (!filePath.startsWith(root + '/') && filePath !== root) {
|
|
84
|
+
res.writeHead(403);
|
|
85
|
+
res.end('Forbidden');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const s = await stat(filePath);
|
|
90
|
+
if (!s.isFile()) {
|
|
91
|
+
res.writeHead(404);
|
|
92
|
+
res.end('Not found');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const ext = extname(filePath).toLowerCase();
|
|
96
|
+
const contentType = mimeTypes[ext] || 'application/octet-stream';
|
|
97
|
+
const data = await readFile(filePath);
|
|
98
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
99
|
+
res.end(data);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
res.writeHead(404);
|
|
103
|
+
res.end('Not found');
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
return new Promise((resolve) => {
|
|
107
|
+
server.listen(port, () => {
|
|
108
|
+
console.log(`Serving ${root} at http://localhost:${port}`);
|
|
109
|
+
resolve();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}
|
|
37
113
|
async function main() {
|
|
38
114
|
const argv = process.argv.slice(2);
|
|
39
115
|
if (argv.length === 0) {
|
|
@@ -99,6 +175,128 @@ async function main() {
|
|
|
99
175
|
}
|
|
100
176
|
break;
|
|
101
177
|
}
|
|
178
|
+
case 'serve': {
|
|
179
|
+
const dir = positional[0] || './output';
|
|
180
|
+
const port = args.options.port ? Number(args.options.port) : 3456;
|
|
181
|
+
await serveDir(dir, port);
|
|
182
|
+
// 保持进程运行
|
|
183
|
+
await new Promise(() => { });
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case 'list-themes': {
|
|
187
|
+
const themes = listThemes();
|
|
188
|
+
console.log(JSON.stringify(themes, null, 2));
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case 'layout-query': {
|
|
192
|
+
const theme = args.options.theme;
|
|
193
|
+
const role = args.options.role;
|
|
194
|
+
if (!theme || !role) {
|
|
195
|
+
console.error('Error: layout-query requires --theme and --role.');
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
const result = queryLayouts({
|
|
199
|
+
theme,
|
|
200
|
+
role,
|
|
201
|
+
keyword: args.options.keyword,
|
|
202
|
+
needsMedia: args.options['needs-media'] === true,
|
|
203
|
+
limit: args.options.limit ? Number(args.options.limit) : 8,
|
|
204
|
+
seed: args.options.seed,
|
|
205
|
+
});
|
|
206
|
+
console.log(JSON.stringify(result, null, 2));
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case 'inspect-layout': {
|
|
210
|
+
const layoutId = positional[0];
|
|
211
|
+
if (!layoutId) {
|
|
212
|
+
console.error('Error: inspect-layout requires a layoutId.');
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
const info = inspectLayout(layoutId);
|
|
216
|
+
if (!info) {
|
|
217
|
+
console.error(`Error: layout "${layoutId}" not found.`);
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
if (args.options.compact === true) {
|
|
221
|
+
console.log(JSON.stringify({
|
|
222
|
+
layout: info.layout,
|
|
223
|
+
displayName: info.displayName,
|
|
224
|
+
role: info.role,
|
|
225
|
+
description: info.description,
|
|
226
|
+
needsMedia: info.needsMedia,
|
|
227
|
+
mediaSlots: info.mediaSlots,
|
|
228
|
+
tags: info.tags,
|
|
229
|
+
contentShape: info.contentShape,
|
|
230
|
+
fields: info.fields,
|
|
231
|
+
}, null, 2));
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
console.log(JSON.stringify(info, null, 2));
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
case 'goal-scaffold': {
|
|
239
|
+
const title = args.options.title;
|
|
240
|
+
const goal = args.options.goal;
|
|
241
|
+
if (!title || !goal) {
|
|
242
|
+
console.error('Error: goal-scaffold requires --title and --goal.');
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
const result = await scaffoldGoalToFile({
|
|
246
|
+
title,
|
|
247
|
+
goal,
|
|
248
|
+
audience: args.options.audience,
|
|
249
|
+
owner: args.options.owner,
|
|
250
|
+
theme: args.options.theme,
|
|
251
|
+
pages: args.options.pages ? Number(args.options.pages) : 8,
|
|
252
|
+
language: args.options.language,
|
|
253
|
+
seed: args.options.seed,
|
|
254
|
+
outFile: args.options.out || './goal.json',
|
|
255
|
+
});
|
|
256
|
+
console.log(`Scaffolded goal: ${result.title} (${result.slides.length} slides)`);
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
case 'write-safe-props': {
|
|
260
|
+
const goalPath = positional[0];
|
|
261
|
+
if (!goalPath) {
|
|
262
|
+
console.error('Error: write-safe-props requires a goal.json path.');
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
const result = await writeSafePropsToFile({
|
|
266
|
+
goalPath,
|
|
267
|
+
write: args.options.write === true,
|
|
268
|
+
});
|
|
269
|
+
if (args.options.write !== true) {
|
|
270
|
+
console.log(JSON.stringify({
|
|
271
|
+
valid: result.valid,
|
|
272
|
+
layoutChanges: result.layoutChanges,
|
|
273
|
+
unknownFields: result.unknownFields,
|
|
274
|
+
}, null, 2));
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
console.log(`Safe props written to ${goalPath}`);
|
|
278
|
+
if (result.layoutChanges.length > 0) {
|
|
279
|
+
console.warn(`Layout changes: ${result.layoutChanges.length}`);
|
|
280
|
+
}
|
|
281
|
+
if (result.unknownFields.length > 0) {
|
|
282
|
+
console.warn(`Unknown fields: ${result.unknownFields.length}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
case 'validate-goal-spec': {
|
|
288
|
+
const goalPath = positional[0];
|
|
289
|
+
if (!goalPath) {
|
|
290
|
+
console.error('Error: validate-goal-spec requires a goal.json path.');
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
const result = await validateGoalSpec(goalPath, args.options.strict === true);
|
|
294
|
+
console.log(JSON.stringify(result, null, 2));
|
|
295
|
+
if (!result.valid) {
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
102
300
|
case 'install-skill': {
|
|
103
301
|
const agents = [];
|
|
104
302
|
if (args.options.claude)
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface GenerateCliOptions {
|
|
|
5
5
|
input: string;
|
|
6
6
|
/** 页数,默认 8 */
|
|
7
7
|
pageCount?: number;
|
|
8
|
-
/** 主题 ID,默认
|
|
8
|
+
/** 主题 ID,默认 theme01 */
|
|
9
9
|
theme?: string;
|
|
10
10
|
/** 语言,默认 zh */
|
|
11
11
|
language?: 'zh' | 'en';
|
|
@@ -53,3 +53,100 @@ export declare function exportGoalToPptx(goal: DeckGoal, options: ExportCliOptio
|
|
|
53
53
|
* 导出 goal 为 PDF。
|
|
54
54
|
*/
|
|
55
55
|
export declare function exportGoalToPdf(goal: DeckGoal, options: ExportCliOptions): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* 列出所有可用主题。
|
|
58
|
+
*/
|
|
59
|
+
export declare function listThemes(): {
|
|
60
|
+
id: string;
|
|
61
|
+
name: string;
|
|
62
|
+
}[];
|
|
63
|
+
/**
|
|
64
|
+
* 按主题与角色查询候选版式。
|
|
65
|
+
*/
|
|
66
|
+
export declare function queryLayouts(options: {
|
|
67
|
+
theme: string;
|
|
68
|
+
role: string;
|
|
69
|
+
keyword?: string;
|
|
70
|
+
needsMedia?: boolean;
|
|
71
|
+
limit?: number;
|
|
72
|
+
seed?: string;
|
|
73
|
+
}): {
|
|
74
|
+
theme: string;
|
|
75
|
+
role: string;
|
|
76
|
+
count: number;
|
|
77
|
+
layouts: {
|
|
78
|
+
layout: string;
|
|
79
|
+
displayName: string;
|
|
80
|
+
description: string;
|
|
81
|
+
needsMedia: boolean;
|
|
82
|
+
}[];
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* 查看指定版式的字段契约。
|
|
86
|
+
*/
|
|
87
|
+
export declare function inspectLayout(layoutId: string): {
|
|
88
|
+
layout: string;
|
|
89
|
+
displayName: string;
|
|
90
|
+
theme: string;
|
|
91
|
+
role: string;
|
|
92
|
+
description: string;
|
|
93
|
+
needsMedia: boolean;
|
|
94
|
+
mediaSlots: {
|
|
95
|
+
name: string;
|
|
96
|
+
fieldPath: string;
|
|
97
|
+
canPresetMedia: boolean;
|
|
98
|
+
}[];
|
|
99
|
+
tags: string[];
|
|
100
|
+
contentShape: string;
|
|
101
|
+
fields: {
|
|
102
|
+
key: string;
|
|
103
|
+
label: string;
|
|
104
|
+
type: string;
|
|
105
|
+
defaultValue?: unknown;
|
|
106
|
+
}[];
|
|
107
|
+
} | null;
|
|
108
|
+
/**
|
|
109
|
+
* 生成只含 role 的 goal.json 骨架。
|
|
110
|
+
*/
|
|
111
|
+
export declare function scaffoldGoalToFile(options: {
|
|
112
|
+
title: string;
|
|
113
|
+
goal: string;
|
|
114
|
+
audience?: string;
|
|
115
|
+
owner?: string;
|
|
116
|
+
theme?: string;
|
|
117
|
+
pages?: number;
|
|
118
|
+
language?: 'zh' | 'en';
|
|
119
|
+
seed?: string;
|
|
120
|
+
outFile?: string;
|
|
121
|
+
}): Promise<DeckGoal>;
|
|
122
|
+
/**
|
|
123
|
+
* 规范化 goal.json 的 props。
|
|
124
|
+
*/
|
|
125
|
+
export declare function writeSafePropsToFile(options: {
|
|
126
|
+
goalPath: string;
|
|
127
|
+
write?: boolean;
|
|
128
|
+
}): Promise<{
|
|
129
|
+
valid: boolean;
|
|
130
|
+
layoutChanges: {
|
|
131
|
+
index: number;
|
|
132
|
+
from: string;
|
|
133
|
+
to: string;
|
|
134
|
+
}[];
|
|
135
|
+
unknownFields: {
|
|
136
|
+
index: number;
|
|
137
|
+
layout: string;
|
|
138
|
+
keys: string[];
|
|
139
|
+
}[];
|
|
140
|
+
goal: DeckGoal;
|
|
141
|
+
}>;
|
|
142
|
+
/**
|
|
143
|
+
* 校验 goal.json 规范。
|
|
144
|
+
*/
|
|
145
|
+
export declare function validateGoalSpec(goalPath: string, strict?: boolean): Promise<{
|
|
146
|
+
valid: boolean;
|
|
147
|
+
errors: {
|
|
148
|
+
type: string;
|
|
149
|
+
detail: unknown;
|
|
150
|
+
}[];
|
|
151
|
+
warnings: string[];
|
|
152
|
+
}>;
|