@yosai/cli 0.2.1-test.8 → 0.2.1-test.9

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,404 @@
1
+ /**
2
+ * CLI 用纯 ESM 校验 SheetWorkbook(IWorkbookData 精简契约)。
3
+ * 供 ./yos-sheet.mjs 使用(同步自 @yos/sheet-editor@0.0.41)
4
+ */
5
+
6
+ /** 与 @yos/sheet-editor MAX_SHEET_ROW_COUNT 对齐 */
7
+ export const AGENT_MAX_ROW_COUNT = 10000;
8
+
9
+ /** Excel 工作表名上限 */
10
+ export const AGENT_MAX_SHEET_NAME_LENGTH = 31;
11
+
12
+ /** CellValueType */
13
+ export const AGENT_CELL_VALUE_TYPES = [1, 2, 3, 4];
14
+
15
+ /** tier A 单元格允许字段 */
16
+ export const AGENT_TIER_A_CELL_KEYS = ["v", "t", "f"];
17
+
18
+ /** tier B 额外单元格字段 */
19
+ export const AGENT_TIER_B_CELL_KEYS = ["s"];
20
+
21
+ /** 永远禁止的单元格字段(富文本 / 内部公式 id 等) */
22
+ export const AGENT_FORBIDDEN_CELL_KEYS = ["p", "si", "ref", "xf"];
23
+
24
+ /**
25
+ * @param {"A"|"B"} tier
26
+ * @returns {Set<string>}
27
+ */
28
+ export function allowedCellKeysForTier(tier) {
29
+ const keys = new Set(AGENT_TIER_A_CELL_KEYS);
30
+ if (tier === "B") {
31
+ for (const key of AGENT_TIER_B_CELL_KEYS) {
32
+ keys.add(key);
33
+ }
34
+ }
35
+ return keys;
36
+ }
37
+
38
+ /**
39
+ * @typedef {{ path: string, message: string }} SheetIssue
40
+ * @typedef {{ ok: boolean, issues: SheetIssue[] }} SheetValidateResult
41
+ */
42
+
43
+ /**
44
+ * @param {unknown} value
45
+ * @returns {value is Record<string, unknown>}
46
+ */
47
+ function isRecord(value) {
48
+ return typeof value === "object" && value !== null && !Array.isArray(value);
49
+ }
50
+
51
+ /**
52
+ * @param {SheetIssue[]} issues
53
+ * @param {string} path
54
+ * @param {string} message
55
+ */
56
+ function push(issues, path, message) {
57
+ issues.push({ path, message });
58
+ }
59
+
60
+ /**
61
+ * @param {unknown} key
62
+ * @returns {boolean}
63
+ */
64
+ function isIndexKey(key) {
65
+ const n = Number(key);
66
+ return Number.isInteger(n) && n >= 0 && String(n) === String(key);
67
+ }
68
+
69
+ /**
70
+ * @param {SheetIssue[]} issues
71
+ * @param {string} path
72
+ * @param {unknown} cell
73
+ * @param {Set<string>} allowedKeys
74
+ * @param {"A"|"B"} tier
75
+ * @param {Set<string>} styleIds
76
+ */
77
+ function validateCell(issues, path, cell, allowedKeys, tier, styleIds) {
78
+ if (cell === null || cell === undefined) {
79
+ return;
80
+ }
81
+ if (!isRecord(cell)) {
82
+ push(issues, path, "单元格必须是对象或 null");
83
+ return;
84
+ }
85
+
86
+ for (const key of Object.keys(cell)) {
87
+ if (AGENT_FORBIDDEN_CELL_KEYS.includes(key)) {
88
+ push(issues, `${path}.${key}`, `禁止字段 ${key}(Agent 勿生成)`);
89
+ continue;
90
+ }
91
+ if (!allowedKeys.has(key)) {
92
+ push(
93
+ issues,
94
+ `${path}.${key}`,
95
+ tier === "A"
96
+ ? `tier A 不允许字段 ${key}(样式请用 --tier B)`
97
+ : `未知单元格字段 ${key}`,
98
+ );
99
+ }
100
+ }
101
+
102
+ if ("v" in cell) {
103
+ const v = cell.v;
104
+ if (
105
+ v !== null &&
106
+ typeof v !== "string" &&
107
+ typeof v !== "number" &&
108
+ typeof v !== "boolean"
109
+ ) {
110
+ push(issues, `${path}.v`, "v 须为 string | number | boolean | null");
111
+ }
112
+ }
113
+
114
+ if ("t" in cell) {
115
+ if (!AGENT_CELL_VALUE_TYPES.includes(cell.t)) {
116
+ push(issues, `${path}.t`, "t 须为 1|2|3|4(STRING|NUMBER|BOOLEAN|FORCE_STRING)");
117
+ }
118
+ }
119
+
120
+ if ("f" in cell) {
121
+ const f = cell.f;
122
+ if (f !== null && typeof f !== "string") {
123
+ push(issues, `${path}.f`, "f 须为 string | null");
124
+ } else if (typeof f === "string") {
125
+ if (!f.trim()) {
126
+ push(issues, `${path}.f`, "公式不能为空字符串");
127
+ } else if (!f.trim().startsWith("=")) {
128
+ push(issues, `${path}.f`, "公式须含前导 =");
129
+ }
130
+ }
131
+ }
132
+
133
+ if (!("v" in cell) && !("f" in cell)) {
134
+ push(issues, path, "单元格至少需要 v 或 f");
135
+ }
136
+
137
+ if ("s" in cell && tier === "B") {
138
+ const s = cell.s;
139
+ if (s === null) {
140
+ // ok
141
+ } else if (typeof s === "string") {
142
+ if (s && styleIds.size > 0 && !styleIds.has(s)) {
143
+ push(issues, `${path}.s`, `样式 id "${s}" 未在根 styles 中定义`);
144
+ }
145
+ } else if (!isRecord(s)) {
146
+ push(issues, `${path}.s`, "s 须为样式 id 字符串、样式对象或 null");
147
+ }
148
+ }
149
+ }
150
+
151
+ /**
152
+ * @param {SheetIssue[]} issues
153
+ * @param {string} path
154
+ * @param {unknown} sheet
155
+ * @param {string} expectedId
156
+ * @param {"A"|"B"} tier
157
+ * @param {Set<string>} styleIds
158
+ */
159
+ function validateSheet(issues, path, sheet, expectedId, tier, styleIds) {
160
+ if (!isRecord(sheet)) {
161
+ push(issues, path, "sheet 必须是对象");
162
+ return;
163
+ }
164
+
165
+ if (typeof sheet.id !== "string" || !sheet.id) {
166
+ push(issues, `${path}.id`, "id 必须是非空字符串");
167
+ } else if (sheet.id !== expectedId) {
168
+ push(issues, `${path}.id`, `建议与 sheets 的 key 一致(当前 key=${expectedId})`);
169
+ }
170
+
171
+ if (typeof sheet.name !== "string" || !sheet.name.trim()) {
172
+ push(issues, `${path}.name`, "name 必须是非空字符串");
173
+ } else if (sheet.name.length > AGENT_MAX_SHEET_NAME_LENGTH) {
174
+ push(
175
+ issues,
176
+ `${path}.name`,
177
+ `name 长度不能超过 ${AGENT_MAX_SHEET_NAME_LENGTH}`,
178
+ );
179
+ }
180
+
181
+ if ("rowCount" in sheet) {
182
+ if (typeof sheet.rowCount !== "number" || !Number.isInteger(sheet.rowCount)) {
183
+ push(issues, `${path}.rowCount`, "rowCount 须为正整数");
184
+ } else if (sheet.rowCount < 1 || sheet.rowCount > AGENT_MAX_ROW_COUNT) {
185
+ push(
186
+ issues,
187
+ `${path}.rowCount`,
188
+ `rowCount 须在 1~${AGENT_MAX_ROW_COUNT}`,
189
+ );
190
+ }
191
+ }
192
+
193
+ if ("columnCount" in sheet) {
194
+ if (
195
+ typeof sheet.columnCount !== "number" ||
196
+ !Number.isInteger(sheet.columnCount) ||
197
+ sheet.columnCount < 1
198
+ ) {
199
+ push(issues, `${path}.columnCount`, "columnCount 须为正整数");
200
+ }
201
+ }
202
+
203
+ const tierBOnly = [
204
+ "styles",
205
+ "mergeData",
206
+ "freeze",
207
+ "rowData",
208
+ "columnData",
209
+ "tabColor",
210
+ "defaultStyle",
211
+ ];
212
+ if (tier === "A") {
213
+ for (const key of tierBOnly) {
214
+ if (key in sheet && sheet[key] !== undefined) {
215
+ const emptyArray =
216
+ key === "mergeData" &&
217
+ Array.isArray(sheet[key]) &&
218
+ sheet[key].length === 0;
219
+ const emptyObject =
220
+ (key === "rowData" || key === "columnData") &&
221
+ isRecord(sheet[key]) &&
222
+ Object.keys(sheet[key]).length === 0;
223
+ const emptyTab = key === "tabColor" && sheet[key] === "";
224
+ if (emptyArray || emptyObject || emptyTab) {
225
+ continue;
226
+ }
227
+ push(issues, `${path}.${key}`, `tier A 不允许 ${key}(请用 --tier B)`);
228
+ }
229
+ }
230
+ }
231
+
232
+ if ("mergeData" in sheet && sheet.mergeData !== undefined) {
233
+ if (!Array.isArray(sheet.mergeData)) {
234
+ push(issues, `${path}.mergeData`, "mergeData 须为数组");
235
+ } else if (tier === "B") {
236
+ sheet.mergeData.forEach((range, index) => {
237
+ const rPath = `${path}.mergeData[${index}]`;
238
+ if (!isRecord(range)) {
239
+ push(issues, rPath, "合并区须为对象");
240
+ return;
241
+ }
242
+ for (const key of [
243
+ "startRow",
244
+ "startColumn",
245
+ "endRow",
246
+ "endColumn",
247
+ ]) {
248
+ if (typeof range[key] !== "number" || !Number.isInteger(range[key])) {
249
+ push(issues, `${rPath}.${key}`, `${key} 须为整数`);
250
+ }
251
+ }
252
+ });
253
+ }
254
+ }
255
+
256
+ if ("freeze" in sheet && sheet.freeze !== undefined && tier === "B") {
257
+ if (!isRecord(sheet.freeze)) {
258
+ push(issues, `${path}.freeze`, "freeze 须为对象");
259
+ }
260
+ }
261
+
262
+ if (!("cellData" in sheet) || sheet.cellData === undefined) {
263
+ push(issues, `${path}.cellData`, "缺少 cellData");
264
+ return;
265
+ }
266
+ if (!isRecord(sheet.cellData)) {
267
+ push(issues, `${path}.cellData`, "cellData 须为对象(稀疏矩阵)");
268
+ return;
269
+ }
270
+
271
+ const allowedKeys = allowedCellKeysForTier(tier);
272
+ for (const [rowKey, row] of Object.entries(sheet.cellData)) {
273
+ if (!isIndexKey(rowKey)) {
274
+ push(issues, `${path}.cellData.${rowKey}`, "行 key 须为非负整数");
275
+ continue;
276
+ }
277
+ if (row === null || row === undefined) {
278
+ continue;
279
+ }
280
+ if (!isRecord(row)) {
281
+ push(issues, `${path}.cellData.${rowKey}`, "行须为对象");
282
+ continue;
283
+ }
284
+ for (const [colKey, cell] of Object.entries(row)) {
285
+ if (!isIndexKey(colKey)) {
286
+ push(
287
+ issues,
288
+ `${path}.cellData.${rowKey}.${colKey}`,
289
+ "列 key 须为非负整数",
290
+ );
291
+ continue;
292
+ }
293
+ validateCell(
294
+ issues,
295
+ `${path}.cellData.${rowKey}.${colKey}`,
296
+ cell,
297
+ allowedKeys,
298
+ tier,
299
+ styleIds,
300
+ );
301
+ }
302
+ }
303
+ }
304
+
305
+ /**
306
+ * 校验 Agent 生成的 SheetWorkbook 快照。
307
+ * @param {Record<string, unknown>} workbook
308
+ * @param {{ tier?: "A"|"B" }} [options]
309
+ * @returns {SheetValidateResult}
310
+ */
311
+ export function validateSheetWorkbook(workbook, options = {}) {
312
+ const tier = options.tier === "B" ? "B" : "A";
313
+ /** @type {SheetIssue[]} */
314
+ const issues = [];
315
+
316
+ if (!isRecord(workbook)) {
317
+ return { ok: false, issues: [{ path: "", message: "根必须是对象" }] };
318
+ }
319
+
320
+ if (typeof workbook.id !== "string" || !workbook.id) {
321
+ push(issues, "id", "id 必须是非空字符串");
322
+ }
323
+ if (typeof workbook.name !== "string" || !workbook.name.trim()) {
324
+ push(issues, "name", "name 必须是非空字符串");
325
+ }
326
+
327
+ if ("resources" in workbook && workbook.resources !== undefined) {
328
+ if (!Array.isArray(workbook.resources)) {
329
+ push(issues, "resources", "resources 须为数组");
330
+ } else if (workbook.resources.length > 0) {
331
+ push(
332
+ issues,
333
+ "resources",
334
+ "禁止非空 resources(勿编造插件状态;请用 [] 或省略)",
335
+ );
336
+ }
337
+ }
338
+
339
+ /** @type {Set<string>} */
340
+ const styleIds = new Set();
341
+ if ("styles" in workbook && workbook.styles !== undefined) {
342
+ if (tier === "A") {
343
+ if (isRecord(workbook.styles) && Object.keys(workbook.styles).length === 0) {
344
+ // 空 styles 允许
345
+ } else {
346
+ push(issues, "styles", "tier A 不允许 styles(请用 --tier B)");
347
+ }
348
+ } else if (!isRecord(workbook.styles)) {
349
+ push(issues, "styles", "styles 须为对象");
350
+ } else {
351
+ for (const id of Object.keys(workbook.styles)) {
352
+ styleIds.add(id);
353
+ const style = workbook.styles[id];
354
+ if (style !== null && !isRecord(style)) {
355
+ push(issues, `styles.${id}`, "样式须为对象或 null");
356
+ }
357
+ }
358
+ }
359
+ }
360
+
361
+ if (!Array.isArray(workbook.sheetOrder) || workbook.sheetOrder.length === 0) {
362
+ push(issues, "sheetOrder", "sheetOrder 须为非空字符串数组");
363
+ } else {
364
+ workbook.sheetOrder.forEach((id, index) => {
365
+ if (typeof id !== "string" || !id) {
366
+ push(issues, `sheetOrder[${index}]`, "须为非空字符串");
367
+ }
368
+ });
369
+ }
370
+
371
+ if (!isRecord(workbook.sheets)) {
372
+ push(issues, "sheets", "sheets 须为对象");
373
+ } else {
374
+ const order = Array.isArray(workbook.sheetOrder) ? workbook.sheetOrder : [];
375
+ for (const id of order) {
376
+ if (typeof id !== "string") {
377
+ continue;
378
+ }
379
+ if (!(id in workbook.sheets)) {
380
+ push(issues, `sheets.${id}`, "sheetOrder 中的 id 在 sheets 中不存在");
381
+ continue;
382
+ }
383
+ validateSheet(
384
+ issues,
385
+ `sheets.${id}`,
386
+ workbook.sheets[id],
387
+ id,
388
+ tier,
389
+ styleIds,
390
+ );
391
+ }
392
+ for (const id of Object.keys(workbook.sheets)) {
393
+ if (!order.includes(id)) {
394
+ push(
395
+ issues,
396
+ `sheets.${id}`,
397
+ "存在于 sheets 但不在 sheetOrder 中(请加入 sheetOrder)",
398
+ );
399
+ }
400
+ }
401
+ }
402
+
403
+ return { ok: issues.length === 0, issues };
404
+ }
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * yos-doc — DocDraft schema / validate CLI(Node ≥ 18)
4
+ * 实现:./validate-runtime-doc.mjs(同步自 @yos/doc-editor@0.0.41)
5
+ */
6
+ import { readFileSync } from "node:fs";
7
+ import { dirname, join, resolve } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+
10
+ import {
11
+ AGENT_ALLOWED_MARKS,
12
+ AGENT_FORBIDDEN_MARKS,
13
+ AGENT_FORBIDDEN_NODES,
14
+ AGENT_TIER_A_NODES,
15
+ AGENT_TIER_B_NODES,
16
+ validateDocDraft,
17
+ } from "./validate-runtime-doc.mjs";
18
+
19
+ const __dirname = dirname(fileURLToPath(import.meta.url));
20
+ const REFERENCES_DIR = resolve(__dirname, "..", "references");
21
+
22
+ function printHelp() {
23
+ process.stdout.write(`yos-doc — YOS DocDraft 工具(Agent / CI)
24
+
25
+ 用法:
26
+ yos-doc schema [--tier A|B]
27
+ yos-doc validate <draft.json> [--tier A|B]
28
+ yos-doc paths
29
+
30
+ 命令:
31
+ schema 打印当前档位白名单(JSON)
32
+ validate 校验 DocDraft JSON 文件;通过 exit 0,失败 exit 1
33
+ paths 打印 AGENT_SCHEMA / few-shot / 全量文档路径
34
+
35
+ 选项:
36
+ --tier A|B 默认 A;B 额外允许 columnBlock / column / image
37
+ -h, --help 帮助
38
+ `);
39
+ }
40
+
41
+ function parseTier(argv) {
42
+ const index = argv.indexOf("--tier");
43
+ if (index === -1) {
44
+ return "A";
45
+ }
46
+ const value = argv[index + 1];
47
+ if (value === "A" || value === "B") {
48
+ return value;
49
+ }
50
+ process.stderr.write(`无效 --tier: ${String(value)}(仅 A|B)\n`);
51
+ process.exit(2);
52
+ }
53
+
54
+ function cmdSchema(tier) {
55
+ const payload = {
56
+ tier,
57
+ root: {
58
+ title: "string",
59
+ content: { type: "doc", content: [] },
60
+ attachments: [],
61
+ },
62
+ nodes: {
63
+ A: [...AGENT_TIER_A_NODES],
64
+ B: tier === "B" ? [...AGENT_TIER_B_NODES] : [],
65
+ forbidden: [...AGENT_FORBIDDEN_NODES],
66
+ },
67
+ marks: {
68
+ allowed: [...AGENT_ALLOWED_MARKS],
69
+ forbidden: [...AGENT_FORBIDDEN_MARKS],
70
+ },
71
+ docs: {
72
+ agentSchema: "references/AGENT_SCHEMA.doc.md",
73
+ fewShot: "references/few-shot.draft.json",
74
+ fullApi: "references/DOC_DATA_API.md",
75
+ },
76
+ };
77
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
78
+ }
79
+
80
+ function cmdPaths() {
81
+ const paths = {
82
+ agentSchema: join(REFERENCES_DIR, "AGENT_SCHEMA.doc.md"),
83
+ fewShot: join(REFERENCES_DIR, "few-shot.draft.json"),
84
+ fullApi: join(REFERENCES_DIR, "DOC_DATA_API.md"),
85
+ template: join(REFERENCES_DIR, "doc-draft.template.json"),
86
+ };
87
+ process.stdout.write(`${JSON.stringify(paths, null, 2)}\n`);
88
+ }
89
+
90
+ function cmdValidate(fileArg, tier) {
91
+ if (!fileArg) {
92
+ process.stderr.write("validate 需要 <draft.json> 路径\n");
93
+ process.exit(2);
94
+ }
95
+ const filePath = resolve(process.cwd(), fileArg);
96
+ let raw;
97
+ try {
98
+ raw = readFileSync(filePath, "utf8");
99
+ } catch (error) {
100
+ process.stderr.write(`无法读取: ${filePath}\n${String(error)}\n`);
101
+ process.exit(2);
102
+ }
103
+ let draft;
104
+ try {
105
+ draft = JSON.parse(raw);
106
+ } catch (error) {
107
+ process.stderr.write(`JSON 解析失败: ${String(error)}\n`);
108
+ process.exit(1);
109
+ }
110
+ if (typeof draft !== "object" || draft === null || Array.isArray(draft)) {
111
+ process.stderr.write("根必须是对象(DocDraft)\n");
112
+ process.exit(1);
113
+ }
114
+ const result = validateDocDraft(draft, { tier });
115
+ if (result.ok) {
116
+ process.stdout.write(`OK (${filePath}) tier=${tier}\n`);
117
+ process.exit(0);
118
+ }
119
+ process.stderr.write(
120
+ `FAIL (${filePath}) tier=${tier} — ${result.issues.length} issue(s)\n`,
121
+ );
122
+ for (const issue of result.issues) {
123
+ process.stderr.write(` - ${issue.path}: ${issue.message}\n`);
124
+ }
125
+ process.exit(1);
126
+ }
127
+
128
+ function main() {
129
+ const argv = process.argv.slice(2);
130
+ if (argv.length === 0 || argv.includes("-h") || argv.includes("--help")) {
131
+ printHelp();
132
+ process.exit(argv.length === 0 ? 2 : 0);
133
+ }
134
+ const command = argv[0];
135
+ const tier = parseTier(argv);
136
+ if (command === "schema") {
137
+ cmdSchema(tier);
138
+ return;
139
+ }
140
+ if (command === "paths") {
141
+ cmdPaths();
142
+ return;
143
+ }
144
+ if (command === "validate") {
145
+ const fileArg = argv.find(
146
+ (item, index) =>
147
+ index > 0 && !item.startsWith("-") && argv[index - 1] !== "--tier",
148
+ );
149
+ cmdValidate(fileArg, tier);
150
+ return;
151
+ }
152
+ process.stderr.write(`未知命令: ${String(command)}\n`);
153
+ printHelp();
154
+ process.exit(2);
155
+ }
156
+
157
+ main();
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * yos-sheet — SheetWorkbook schema / validate CLI(Node ≥ 18)
4
+ * 实现:./validate-runtime-sheet.mjs(同步自 @yos/sheet-editor@0.0.41)
5
+ */
6
+ import { readFileSync } from "node:fs";
7
+ import { dirname, join, resolve } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+
10
+ import {
11
+ AGENT_FORBIDDEN_CELL_KEYS,
12
+ AGENT_MAX_ROW_COUNT,
13
+ AGENT_TIER_A_CELL_KEYS,
14
+ AGENT_TIER_B_CELL_KEYS,
15
+ validateSheetWorkbook,
16
+ } from "./validate-runtime-sheet.mjs";
17
+
18
+ const __dirname = dirname(fileURLToPath(import.meta.url));
19
+ const REFERENCES_DIR = resolve(__dirname, "..", "references");
20
+
21
+ function printHelp() {
22
+ process.stdout.write(`yos-sheet — YOS SheetWorkbook 工具(Agent / CI)
23
+
24
+ 用法:
25
+ yos-sheet schema [--tier A|B]
26
+ yos-sheet validate <workbook.json> [--tier A|B]
27
+ yos-sheet paths
28
+
29
+ 命令:
30
+ schema 打印当前档位白名单(JSON)
31
+ validate 校验工作簿 JSON;通过 exit 0,失败 exit 1
32
+ paths 打印 AGENT_SCHEMA / few-shot / 全量文档路径
33
+
34
+ 选项:
35
+ --tier A|B 默认 A;B 额外允许 styles / merge / freeze / 行列宽
36
+ -h, --help 帮助
37
+ `);
38
+ }
39
+
40
+ function parseTier(argv) {
41
+ const index = argv.indexOf("--tier");
42
+ if (index === -1) {
43
+ return "A";
44
+ }
45
+ const value = argv[index + 1];
46
+ if (value === "A" || value === "B") {
47
+ return value;
48
+ }
49
+ process.stderr.write(`无效 --tier: ${String(value)}(仅 A|B)\n`);
50
+ process.exit(2);
51
+ }
52
+
53
+ function cmdSchema(tier) {
54
+ const payload = {
55
+ tier,
56
+ root: {
57
+ id: "string",
58
+ name: "string",
59
+ sheetOrder: ["sheet-1"],
60
+ sheets: {},
61
+ resources: [],
62
+ },
63
+ cells: {
64
+ A: [...AGENT_TIER_A_CELL_KEYS],
65
+ B: tier === "B" ? [...AGENT_TIER_B_CELL_KEYS] : [],
66
+ forbidden: [...AGENT_FORBIDDEN_CELL_KEYS],
67
+ },
68
+ limits: {
69
+ maxRowCount: AGENT_MAX_ROW_COUNT,
70
+ },
71
+ docs: {
72
+ agentSchema: "references/AGENT_SCHEMA.sheet.md",
73
+ fewShot: "references/few-shot.workbook.json",
74
+ fullApi: "references/SHEET_DATA_API.md",
75
+ },
76
+ };
77
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
78
+ }
79
+
80
+ function cmdPaths() {
81
+ const paths = {
82
+ agentSchema: join(REFERENCES_DIR, "AGENT_SCHEMA.sheet.md"),
83
+ fewShot: join(REFERENCES_DIR, "few-shot.workbook.json"),
84
+ fullApi: join(REFERENCES_DIR, "SHEET_DATA_API.md"),
85
+ template: join(REFERENCES_DIR, "sheet-workbook.template.json"),
86
+ };
87
+ process.stdout.write(`${JSON.stringify(paths, null, 2)}\n`);
88
+ }
89
+
90
+ function cmdValidate(fileArg, tier) {
91
+ if (!fileArg) {
92
+ process.stderr.write("validate 需要 <workbook.json> 路径\n");
93
+ process.exit(2);
94
+ }
95
+ const filePath = resolve(process.cwd(), fileArg);
96
+ let raw;
97
+ try {
98
+ raw = readFileSync(filePath, "utf8");
99
+ } catch (error) {
100
+ process.stderr.write(`无法读取: ${filePath}\n${String(error)}\n`);
101
+ process.exit(2);
102
+ }
103
+ let workbook;
104
+ try {
105
+ workbook = JSON.parse(raw);
106
+ } catch (error) {
107
+ process.stderr.write(`JSON 解析失败: ${String(error)}\n`);
108
+ process.exit(1);
109
+ }
110
+ if (typeof workbook !== "object" || workbook === null || Array.isArray(workbook)) {
111
+ process.stderr.write("根必须是对象(SheetWorkbook)\n");
112
+ process.exit(1);
113
+ }
114
+ const result = validateSheetWorkbook(workbook, { tier });
115
+ if (result.ok) {
116
+ process.stdout.write(`OK (${filePath}) tier=${tier}\n`);
117
+ process.exit(0);
118
+ }
119
+ process.stderr.write(
120
+ `FAIL (${filePath}) tier=${tier} — ${result.issues.length} issue(s)\n`,
121
+ );
122
+ for (const issue of result.issues) {
123
+ process.stderr.write(` - ${issue.path}: ${issue.message}\n`);
124
+ }
125
+ process.exit(1);
126
+ }
127
+
128
+ function main() {
129
+ const argv = process.argv.slice(2);
130
+ if (argv.length === 0 || argv.includes("-h") || argv.includes("--help")) {
131
+ printHelp();
132
+ process.exit(argv.length === 0 ? 2 : 0);
133
+ }
134
+ const command = argv[0];
135
+ const tier = parseTier(argv);
136
+ if (command === "schema") {
137
+ cmdSchema(tier);
138
+ return;
139
+ }
140
+ if (command === "paths") {
141
+ cmdPaths();
142
+ return;
143
+ }
144
+ if (command === "validate") {
145
+ const fileArg = argv.find(
146
+ (item, index) =>
147
+ index > 0 && !item.startsWith("-") && argv[index - 1] !== "--tier",
148
+ );
149
+ cmdValidate(fileArg, tier);
150
+ return;
151
+ }
152
+ process.stderr.write(`未知命令: ${String(command)}\n`);
153
+ printHelp();
154
+ process.exit(2);
155
+ }
156
+
157
+ main();