@yosai/cli 0.2.1-test.1 → 0.2.1-test.10

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.
Files changed (39) hide show
  1. package/README.md +11 -9
  2. package/dist/cli.js +3 -2
  3. package/dist/commands/doctor.js +14 -13
  4. package/dist/commands/login.js +8 -12
  5. package/dist/commands/ping.js +4 -4
  6. package/dist/commands/update.js +52 -19
  7. package/dist/commands/version.js +8 -8
  8. package/dist/config.js +12 -23
  9. package/dist/domain-commands.js +13 -4
  10. package/dist/domain-executor.js +3 -0
  11. package/dist/endpoints.js +13 -8
  12. package/dist/errors.js +6 -0
  13. package/dist/npm-meta.js +88 -11
  14. package/dist/openapis-client.js +27 -8
  15. package/dist/skill-sync.js +2 -2
  16. package/dist/skills/yos/SKILL.md +23 -14
  17. package/dist/skills/yos-doc/SKILL.md +221 -0
  18. package/dist/skills/yos-doc/references/AGENT_SCHEMA.doc.md +69 -0
  19. package/dist/skills/yos-doc/references/DOC_DATA_API.md +624 -0
  20. package/dist/skills/yos-doc/references/doc-draft.template.json +721 -0
  21. package/dist/skills/yos-doc/references/few-shot.draft.json +195 -0
  22. package/dist/skills/yos-doc/scripts/validate-runtime-doc.mjs +370 -0
  23. package/dist/skills/yos-doc/scripts/yos-doc.mjs +157 -0
  24. package/dist/skills/yos-install/SKILL.md +46 -20
  25. package/dist/skills/{yos-kb → yos-knowledge}/SKILL.md +21 -8
  26. package/dist/skills/yos-retrieval/SKILL.md +1 -1
  27. package/dist/skills/yos-shared/SKILL.md +23 -14
  28. package/dist/skills/yos-sheet/SKILL.md +233 -0
  29. package/dist/skills/yos-sheet/references/AGENT_SCHEMA.sheet.md +105 -0
  30. package/dist/skills/yos-sheet/references/SHEET_DATA_API.md +356 -0
  31. package/dist/skills/yos-sheet/references/few-shot.workbook.json +50 -0
  32. package/dist/skills/yos-sheet/references/sheet-workbook.template.json +147 -0
  33. package/dist/skills/yos-sheet/scripts/validate-runtime-sheet.mjs +404 -0
  34. package/dist/skills/yos-sheet/scripts/yos-sheet.mjs +157 -0
  35. package/dist/skills/yos-support/SKILL.md +1 -1
  36. package/dist/skills/yos-transcript/SKILL.md +1 -1
  37. package/dist/update-check-worker.js +35 -0
  38. package/dist/update-notice.js +86 -0
  39. package/package.json +4 -2
@@ -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-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();
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: yos-support
3
- description: YOS 帮助与支持(manual/manual-read/manual-search 帮助手册,upgrade/upgrade-log 客户端版本,feedback 提交反馈)。用户问 YOS 怎么用、要客户端最新版本、要报问题时使用
3
+ description: YOS 帮助与支持(manual/manual-read/manual-search 帮助手册,upgrade/upgrade-log 客户端版本,feedback 提交反馈)。用户提到 yos/YOS 且要查 YOS 使用手册、客户端最新版本、提交反馈时使用
4
4
  ---
5
5
 
6
6
  # YOS 帮助与支持(support)
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: yos-transcript
3
- description: YOS 录音转写查询(transcript list 列表 / transcript detail 详情,含分段、纪要、思维导图、音频地址与字段坑)。用户想看会议录音转写、要纪要、查某次会议内容时使用
3
+ description: YOS 录音转写查询(transcript list 列表 / transcript detail 详情,含分段、纪要、思维导图、音频地址与字段坑)。用户提到 yos/YOS 且要看会议录音转写、要纪要、查某次会议内容时使用
4
4
  ---
5
5
 
6
6
  # YOS 录音转写(transcript)
@@ -0,0 +1,35 @@
1
+ import { realpathSync } from 'node:fs';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { loadConfig, resolveChannel } from './config.js';
4
+ import { fetchLatestVersion, readPackageMeta } from './npm-meta.js';
5
+ import { saveCheckState } from './update-notice.js';
6
+ export async function runUpdateCheck() {
7
+ const meta = readPackageMeta();
8
+ if (!meta) {
9
+ return;
10
+ }
11
+ const channel = resolveChannel(loadConfig());
12
+ if (channel === null) {
13
+ return;
14
+ }
15
+ try {
16
+ const latest = await fetchLatestVersion(meta.name, channel);
17
+ saveCheckState(latest, channel);
18
+ }
19
+ catch {
20
+ }
21
+ }
22
+ export function isDirectRun(argv1, moduleUrl) {
23
+ if (!argv1) {
24
+ return false;
25
+ }
26
+ try {
27
+ return realpathSync(argv1) === realpathSync(fileURLToPath(moduleUrl));
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ }
33
+ if (isDirectRun(process.argv[1], import.meta.url)) {
34
+ await runUpdateCheck();
35
+ }
@@ -0,0 +1,86 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { configDir, loadConfig, resolveChannel } from './config.js';
6
+ import { compareVersions, getCliVersion } from './npm-meta.js';
7
+ import { printErr } from './output.js';
8
+ export const CACHE_TTL_MS = 10 * 60 * 1000;
9
+ const SKIP_COMMANDS = new Set(['update', 'version']);
10
+ function checkStatePath() {
11
+ return join(configDir(), 'update-check.json');
12
+ }
13
+ export function loadCheckState(channel) {
14
+ const path = checkStatePath();
15
+ if (!existsSync(path)) {
16
+ return null;
17
+ }
18
+ try {
19
+ const parsed = JSON.parse(readFileSync(path, 'utf8'));
20
+ if (typeof parsed.latest !== 'string' || parsed.latest === '' || parsed.channel !== channel) {
21
+ return null;
22
+ }
23
+ return {
24
+ latest: parsed.latest,
25
+ channel: parsed.channel,
26
+ checkedAt: typeof parsed.checkedAt === 'string' ? parsed.checkedAt : '',
27
+ };
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ }
33
+ export function saveCheckState(latest, channel) {
34
+ mkdirSync(configDir(), { recursive: true, mode: 0o700 });
35
+ const state = { latest, channel, checkedAt: new Date().toISOString() };
36
+ writeFileSync(checkStatePath(), `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 });
37
+ }
38
+ export function evaluateNotice(input) {
39
+ if (input.commandName && SKIP_COMMANDS.has(input.commandName)) {
40
+ return 'skip';
41
+ }
42
+ if (!input.state || !isFresh(input.state, input.now)) {
43
+ return 'spawn';
44
+ }
45
+ if (compareVersions(input.currentVersion, input.state.latest) >= 0) {
46
+ return 'skip';
47
+ }
48
+ return 'notify';
49
+ }
50
+ export function handleUpdateNotice(commandName) {
51
+ try {
52
+ const channel = resolveChannel(loadConfig());
53
+ if (channel === null) {
54
+ return;
55
+ }
56
+ const currentVersion = getCliVersion();
57
+ const state = loadCheckState(channel);
58
+ const decision = evaluateNotice({
59
+ commandName,
60
+ state,
61
+ now: new Date(),
62
+ currentVersion,
63
+ });
64
+ if (decision === 'spawn') {
65
+ spawnUpdateCheckWorker();
66
+ return;
67
+ }
68
+ if (decision === 'notify' && state) {
69
+ printErr(`提示: 有新版本 ${state.latest}(当前 ${currentVersion}),请先执行 yos update 升级再继续任务`);
70
+ }
71
+ }
72
+ catch {
73
+ }
74
+ }
75
+ function isFresh(state, now) {
76
+ const checkedAt = Date.parse(state.checkedAt);
77
+ return !Number.isNaN(checkedAt) && now.getTime() - checkedAt < CACHE_TTL_MS;
78
+ }
79
+ function spawnUpdateCheckWorker() {
80
+ try {
81
+ const workerPath = fileURLToPath(new URL('./update-check-worker.js', import.meta.url));
82
+ spawn(process.execPath, [workerPath], { detached: true, stdio: 'ignore', windowsHide: true }).unref();
83
+ }
84
+ catch {
85
+ }
86
+ }