@yosai/cli 0.2.1-test.0 → 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 +4 -2
  3. package/dist/commands/doctor.js +17 -11
  4. package/dist/commands/login.js +11 -10
  5. package/dist/commands/ping.js +7 -5
  6. package/dist/commands/update.js +52 -19
  7. package/dist/commands/version.js +9 -7
  8. package/dist/config.js +15 -11
  9. package/dist/domain-commands.js +13 -4
  10. package/dist/domain-executor.js +3 -0
  11. package/dist/endpoints.js +33 -1
  12. package/dist/errors.js +6 -0
  13. package/dist/npm-meta.js +88 -11
  14. package/dist/openapis-client.js +28 -5
  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 +48 -21
  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 +24 -15
  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
package/dist/npm-meta.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { execFile } from 'node:child_process';
2
- import { existsSync, readFileSync } from 'node:fs';
2
+ import { existsSync, readFileSync, realpathSync } from 'node:fs';
3
3
  import { dirname, join, resolve, sep } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { promisify } from 'node:util';
@@ -23,6 +23,11 @@ export function readPackageMeta() {
23
23
  dir = parent;
24
24
  }
25
25
  }
26
+ let cliVersionCache;
27
+ export function getCliVersion() {
28
+ cliVersionCache ??= readPackageMeta()?.version ?? 'unknown';
29
+ return cliVersionCache;
30
+ }
26
31
  export async function runNpm(args) {
27
32
  const useCmdWrapper = process.platform === 'win32';
28
33
  const command = useCmdWrapper ? (process.env.ComSpec ?? 'cmd.exe') : 'npm';
@@ -45,21 +50,39 @@ export async function runNpm(args) {
45
50
  };
46
51
  }
47
52
  }
48
- export async function fetchLatestVersion(name) {
49
- const result = await runNpm(['view', name, 'version', `--registry=${PUBLIC_REGISTRY}`]);
53
+ export async function fetchLatestVersion(name, channel = 'latest') {
54
+ const result = await runNpm(['view', `${name}@${channel}`, 'version', `--registry=${PUBLIC_REGISTRY}`]);
50
55
  if (!result.ok) {
51
56
  const reason = result.stderr.split(/\r?\n/)[0]?.trim() || result.stdout.split(/\r?\n/)[0]?.trim() || '';
52
- throw new NetworkError(`无法获知 ${name} 的最新版本:npm view 调用失败`, `${reason ? `${reason};` : ''}离线或 npm 不可用时,手动升级请执行 npm install -g ${name}@latest`);
57
+ throw new NetworkError(`无法获知 ${name}(${channel} 通道)的最新版本:npm view 调用失败`, `${reason ? `${reason};` : ''}离线或 npm 不可用时,手动升级请执行 npm install -g ${name}@${channel}`);
53
58
  }
54
59
  const latest = result.stdout.split(/\r?\n/)[0]?.trim() ?? '';
55
60
  if (!/^\d+\.\d+\.\d+/.test(latest)) {
56
- throw new NetworkError(`无法解析 ${name} 的最新版本输出`, `npm view 返回:${result.stdout.slice(0, 120)}`);
61
+ throw new NetworkError(`无法解析 ${name}(${channel} 通道)的最新版本输出`, `npm view 返回:${result.stdout.slice(0, 120)}`);
57
62
  }
58
63
  return latest;
59
64
  }
60
65
  export function compareVersions(a, b) {
61
- const left = toSegments(a);
62
- const right = toSegments(b);
66
+ const left = splitVersion(a);
67
+ const right = splitVersion(b);
68
+ const coreDiff = compareCoreSegments(left.core, right.core);
69
+ if (coreDiff !== 0) {
70
+ return coreDiff;
71
+ }
72
+ return comparePreRelease(left.pre, right.pre);
73
+ }
74
+ function splitVersion(version) {
75
+ const trimmed = version.trim();
76
+ const dash = trimmed.indexOf('-');
77
+ const coreText = dash === -1 ? trimmed : trimmed.slice(0, dash);
78
+ const core = coreText.split('.').map((part) => Number.parseInt(part, 10) || 0);
79
+ if (dash === -1) {
80
+ return { core, pre: null };
81
+ }
82
+ const pre = trimmed.slice(dash + 1).split('.').filter((part) => part !== '');
83
+ return { core, pre: pre.length > 0 ? pre : null };
84
+ }
85
+ function compareCoreSegments(left, right) {
63
86
  for (let i = 0; i < Math.max(left.length, right.length); i += 1) {
64
87
  const diff = (left[i] ?? 0) - (right[i] ?? 0);
65
88
  if (diff !== 0) {
@@ -68,20 +91,74 @@ export function compareVersions(a, b) {
68
91
  }
69
92
  return 0;
70
93
  }
94
+ function comparePreRelease(left, right) {
95
+ if (!left && !right) {
96
+ return 0;
97
+ }
98
+ if (!left) {
99
+ return 1;
100
+ }
101
+ if (!right) {
102
+ return -1;
103
+ }
104
+ for (let i = 0; i < Math.max(left.length, right.length); i += 1) {
105
+ const l = left[i];
106
+ const r = right[i];
107
+ if (l === undefined) {
108
+ return -1;
109
+ }
110
+ if (r === undefined) {
111
+ return 1;
112
+ }
113
+ const diff = comparePreIdentifier(l, r);
114
+ if (diff !== 0) {
115
+ return diff;
116
+ }
117
+ }
118
+ return 0;
119
+ }
120
+ function comparePreIdentifier(left, right) {
121
+ const leftNumeric = /^\d+$/.test(left);
122
+ const rightNumeric = /^\d+$/.test(right);
123
+ if (leftNumeric && rightNumeric) {
124
+ const l = Number(left);
125
+ const r = Number(right);
126
+ if (l === r) {
127
+ return 0;
128
+ }
129
+ return l < r ? -1 : 1;
130
+ }
131
+ if (leftNumeric !== rightNumeric) {
132
+ return leftNumeric ? -1 : 1;
133
+ }
134
+ if (left === right) {
135
+ return 0;
136
+ }
137
+ return left < right ? -1 : 1;
138
+ }
71
139
  export async function detectGlobalInstall(meta) {
72
140
  const result = await runNpm(['prefix', '-g']);
73
141
  if (!result.ok || result.stdout.length === 0) {
74
142
  return { global: false, prefix: '' };
75
143
  }
76
144
  const prefix = stripTrailingSep(resolve(result.stdout));
77
- const packageDir = stripTrailingSep(meta.dir);
78
145
  return {
79
- global: packageDir === prefix || packageDir.startsWith(`${prefix}${sep}`),
146
+ global: isPackageUnderPrefix(meta.dir, prefix),
80
147
  prefix,
81
148
  };
82
149
  }
83
- function toSegments(version) {
84
- return version.trim().split('-')[0].split('.').map((part) => Number.parseInt(part, 10) || 0);
150
+ export function isPackageUnderPrefix(packageDir, prefix) {
151
+ const dir = stripTrailingSep(realpathOrSelf(packageDir));
152
+ const base = stripTrailingSep(realpathOrSelf(prefix));
153
+ return dir === base || dir.startsWith(`${base}${sep}`);
154
+ }
155
+ function realpathOrSelf(path) {
156
+ try {
157
+ return realpathSync(path);
158
+ }
159
+ catch {
160
+ return path;
161
+ }
85
162
  }
86
163
  function tryParseJson(path) {
87
164
  try {
@@ -1,7 +1,10 @@
1
1
  import { readFile, stat } from 'node:fs/promises';
2
2
  import { basename } from 'node:path';
3
- import { AuthError, BusinessError, NetworkError } from './errors.js';
3
+ import { envNoteOfEndpoint } from './endpoints.js';
4
+ import { AuthError, BusinessError, NetworkError, UpgradeError } from './errors.js';
5
+ import { getCliVersion } from './npm-meta.js';
4
6
  export const MAX_UPLOAD_SIZE_BYTES = 20 * 1024 * 1024;
7
+ export const CLI_VERSION_HEADER = 'X-Yos-Cli-Version';
5
8
  export function formatIdentity(identity) {
6
9
  return identity.userName ? `${identity.userName}(${identity.userCode})` : identity.userCode;
7
10
  }
@@ -33,16 +36,20 @@ export class OpenApisClient {
33
36
  headers: {
34
37
  'Content-Type': 'application/json',
35
38
  'Authorization': `Bearer ${this.apiKey}`,
39
+ [CLI_VERSION_HEADER]: getCliVersion(),
36
40
  },
37
41
  body: JSON.stringify(body ?? {}),
38
42
  });
39
43
  }
40
44
  catch (e) {
41
- throw new NetworkError(`无法连接 YOS 服务:endpoint=${this.base},原因:${e.message}`);
45
+ throw new NetworkError(`无法连接 YOS 服务${envNoteOfBase(this.base)},原因:${e.message}`);
42
46
  }
43
47
  return parseEnvelope(response);
44
48
  }
45
49
  }
50
+ function envNoteOfBase(base) {
51
+ return envNoteOfEndpoint(base.replace(/\/open-apis$/, ''));
52
+ }
46
53
  async function parseEnvelope(response) {
47
54
  if (response.status === 401) {
48
55
  let reason = '访问令牌无效或已过期';
@@ -57,7 +64,23 @@ async function parseEnvelope(response) {
57
64
  throw new AuthError(`鉴权失败:${reason}`);
58
65
  }
59
66
  if (response.status === 404) {
60
- throw new NetworkError(`HTTP 404:服务端不存在该接口(${response.url})`, '确认服务端已部署开放接口通道;端点配错时用 yos login --endpoint <url> --key <key> 重新指定');
67
+ throw new NetworkError(`HTTP 404:服务端不存在该接口(${response.url})`, '确认服务端已部署开放接口通道;环境选择有误时用 yos login --env 重新指定');
68
+ }
69
+ if (response.status === 426) {
70
+ let message = 'CLI 版本过低,请升级后重试';
71
+ let hint;
72
+ try {
73
+ const body = await response.json();
74
+ if (body.error) {
75
+ message = body.error;
76
+ }
77
+ if (body.upgradeCommand) {
78
+ hint = `执行 \`${body.upgradeCommand}\` 升级后重试`;
79
+ }
80
+ }
81
+ catch {
82
+ }
83
+ throw new UpgradeError(message, hint);
61
84
  }
62
85
  let payload;
63
86
  try {
@@ -101,12 +124,12 @@ export async function uploadKnowledgeFile(endpoint, apiKey, filePath, kbCode, pa
101
124
  try {
102
125
  response = await fetch(`${endpoint}/open-apis/knowledge/file_upload`, {
103
126
  method: 'POST',
104
- headers: { 'Authorization': `Bearer ${apiKey}` },
127
+ headers: { 'Authorization': `Bearer ${apiKey}`, [CLI_VERSION_HEADER]: getCliVersion() },
105
128
  body: form,
106
129
  });
107
130
  }
108
131
  catch (e) {
109
- throw new NetworkError(`无法连接 YOS 服务:endpoint=${endpoint},原因:${e.message}`);
132
+ throw new NetworkError(`无法连接 YOS 服务${envNoteOfEndpoint(endpoint)},原因:${e.message}`);
110
133
  }
111
134
  return parseEnvelope(response);
112
135
  }
@@ -5,8 +5,8 @@ import { fileURLToPath } from 'node:url';
5
5
  import { readPackageMeta } from './npm-meta.js';
6
6
  import { updateConfig } from './config.js';
7
7
  import { saveVersionState } from './version-state.js';
8
- export const BUILTIN_SKILLS = ['yos', 'yos-shared', 'yos-kb', 'yos-retrieval', 'yos-transcript', 'yos-support', 'yos-install'];
9
- const AGENT_SKILL_DIRS = ['.claude/skills', '.workbuddy/skills', '.cursor/skills', '.codex/skills', '.qoder/skills', '.qoder-cn/skills', '.agentskill'];
8
+ export const BUILTIN_SKILLS = ['yos', 'yos-shared', 'yos-knowledge', 'yos-doc', 'yos-sheet', 'yos-retrieval', 'yos-transcript', 'yos-support', 'yos-install'];
9
+ const AGENT_SKILL_DIRS = ['.claude/skills', '.workbuddy/skills', '.cursor/skills', '.codex/skills', '.qoder/skills', '.qoder-cn/skills', '.agents/skills'];
10
10
  export function skillsSourceDir() {
11
11
  const here = dirname(fileURLToPath(import.meta.url));
12
12
  const distCandidate = join(here, 'skills');
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: yos
3
- description: YOS 企业 AI 能力总入口:公司知识库检索、文档管理、录音转写、帮助手册、客户端升级与反馈。用户问公司制度/流程/资料、要查或整理知识库文档、要看会议录音转写、需要 YOS 客户端帮助时使用;不确定该用哪个 YOS 能力时先读本文件
3
+ description: YOS 企业 AI 能力总入口:公司/部门/工作/个人等各类知识的检索与文档管理、云文档/云表格编写、录音转写、帮助手册、客户端升级与反馈。用户提到 yos/YOS 即使用本技能;用户问制度/流程/规范、要查或整理资料文档、要写云文档/云表格、要看会议录音转写、需要 YOS 客户端帮助时使用;不确定该用哪个 YOS 能力时先读本文件
4
4
  ---
5
5
 
6
6
  # YOS 能力总入口
@@ -10,24 +10,26 @@ YOS 是企业 AI 底座:公司知识、会议录音、帮助手册等能力都
10
10
  ## 0. 开工前先确认环境(30 秒)
11
11
 
12
12
  ```bash
13
- yos doctor # 七项 ✓ 才继续:配置文件 / 服务端点 / 访问令牌 / 服务连通(回显身份)/ Skill 安装 / CLI 版本 / Skill 同步
14
- yos ping # 只要探活,退出码即结论(0 通 / 3 网络 / 4 鉴权)
13
+ yos doctor # 全部 ✓ 才继续:配置文件 / 访问令牌 / 服务连通(回显身份)/ Skill 安装 / CLI 版本 / Skill 同步
14
+ yos ping # 只要探活,退出码即结论(0 通 / 3 网络 / 4 鉴权 / 5 版本过低)
15
15
  ```
16
16
 
17
17
  - `command not found: yos` 或 `yos doctor` 报「未找到配置文件」→ 按 `yos-install` skill 引导用户装 CLI 与登录,不要继续猜命令
18
- - 已装但七项没全 ✓ → 先修环境再执行任务;`yos doctor` 的失败项自带修复提示
19
- - 查版本用 `yos version`(或 `yos --version`);升级到 npmjs 最新版用 `yos update`
18
+ - 已装但检查项没全 ✓ → 先修好再执行任务;`yos doctor` 的失败项自带修复提示
19
+ - 查版本用 `yos version`(或 `yos --version`);命令输出前 stderr 若出现「提示: 有新版本」,先执行 `yos update` 升级再继续任务(约定详见 yos-shared)
20
20
 
21
21
  ## 1. 意图路由
22
22
 
23
23
  | 用户在问什么 | 用哪个命令 | 先读哪本 skill |
24
24
  |---|---|---|
25
25
  | 「公司制度/流程/规范/报销标准/产品资料」等**要一个公司里的答案** | `retrieve search` → `retrieve grep` 定位 → `retrieve read` 读原文 | yos-retrieval |
26
- | 「有哪些文档 / 共几份 / 文档目录结构」 | `retrieve outline`(检索层)或 `docs tree`(管理层) | yos-retrieval / yos-kb |
26
+ | 「有哪些文档 / 共几份 / 文档目录结构」 | `retrieve outline`(检索层)或 `docs tree`(管理层) | yos-retrieval / yos-knowledge |
27
27
  | 找**配套/相关**材料、跨文档关系 | `retrieve neighbors`、`retrieve search --entity/--topic` | yos-retrieval |
28
- | 浏览/搜索自己的知识库与文档内容 | `kb list` `kb detail` `docs tree` `docs content` | yos-kb |
29
- | 把知识库里的上传文件取回本地 | `docs download <node_code> --out <路径>` | yos-kb |
30
- | 上传本地文件或目录、新建文档/文件夹、改名、移动、删除、回收站恢复 | `kb upload` `docs create` `docs mkdir` `docs update` `docs rename` `docs move` `docs delete` `docs restore` | yos-kb |
28
+ | 浏览/搜索自己的知识库与文档内容 | `kb list` `kb detail` `docs tree` `docs content` | yos-knowledge |
29
+ | 把知识库里的上传文件取回本地 | `docs download <node_code> --out <路径>` | yos-knowledge |
30
+ | 上传本地文件或目录、新建文档/文件夹、改名、移动、删除、回收站恢复 | `kb upload` `docs create` `docs mkdir` `docs update` `docs rename` `docs move` `docs delete` `docs restore` | yos-knowledge |
31
+ | 往知识库**写云文档**(在线文档;非办公文件) | `docs create --type cloud`、`docs update` | **先读 yos-doc**(正文是结构化 JSON,结构与编写规范都在这本) |
32
+ | 往知识库**写云表格**(在线表格;非办公文件) | `docs create --type sheet`、`docs update` | **先读 yos-sheet**(正文是结构化 JSON,结构与列宽等推荐做法都在这本) |
31
33
  | 会议录音转写、纪要、逐句发言 | `transcript list` → `transcript detail` | yos-transcript |
32
34
  | 「YOS 怎么用」、客户端最新版本、提交问题反馈 | `support manual*` `support upgrade*` `support feedback` | yos-support |
33
35
  | 领域命令没覆盖的能力 | `tool list` → `tool schema <name>` → `tool call <name> --params '{...}'` | yos-shared |
@@ -41,7 +43,7 @@ yos ping # 只要探活,退出码即结论(0 通 / 3 网络 / 4 鉴
41
43
  1. **`--json` 是双层包装**:外层 `{"text":"<JSON 字符串>","isError":false}`,要拿字段必须对 `text` 再 `JSON.parse` 一次;`isError:true` 时 `text` 是错误文案而非数据
42
44
  2. **参数错误一律 exit=2 且不发请求**:非法枚举、越界 `--size`、日期格式非法或区间倒置、URN 非法、必填缺失都由 CLI 本地拦下,照 `提示:` 改参数即可,不要重试原命令。exit=0 仍需自行校验内容的场景见 yos-shared
43
45
  3. **引用作答必须读原文**:`retrieve search` 的 `snippet` 只是线索,不能作为引用依据;未 `retrieve read` 之前不下结论
44
- 4. **写操作先复述再执行**:`kb update`、`docs create/update/mkdir/rename/move`、`kb upload`、`support feedback` 都要让用户确认最终文案;`docs update` 是**整体覆盖**(正文传什么就替换什么)
46
+ 4. **写操作先复述再执行**:`kb update`、`docs create/update/mkdir/rename/move`、`kb upload`、`support feedback` 都要让用户确认最终文案;`docs update` 是**整体覆盖**(正文传什么就替换什么);写云文档/云表格(`--type cloud/sheet`)的正文前**先读 yos-doc(云文档)/ yos-sheet(云表格)技能**——正文是结构化 JSON 且服务端不校验,写错要到客户端打开才暴露
45
47
  5. **删除必须两步**:`yos docs count <node_code>` 查受影响文档数 → 展示给用户 → 用户明确同意后才 `yos docs delete`。未经确认不得调用
46
48
  6. **令牌不出现在任何输出里**:不写进日志、命令、对用户的回复;只以掩码形式(`xxxxxxxx****xxxx`,即前 8 位 + `****` + 后 4 位)引用
47
49
  7. **日期参数分两类,别混用**:`transcript list --start/--end`、`support upgrade-log --start/--end` **只收 `yyyy-MM-dd`**,中文表达由 CLI 本地拒绝(`错误: --start 日期格式应为 yyyy-MM-dd:本周`,exit 2),须自己换算;`retrieve search/outline` 的 `--occurred-*`/`--effective-*` **收自然语言**(`上季度`、`8 月` 直接传,由服务端解析),`retrieve search` 还会把 `--query` 文本里的时间表达解析为时间窗并在 `time_window` 回显
@@ -49,9 +51,9 @@ yos ping # 只要探活,退出码即结论(0 通 / 3 网络 / 4 鉴
49
51
  ## 3. 登录与配置
50
52
 
51
53
  1. 用户在 YOS 客户端「个人设置-访问令牌」页创建并复制令牌(40 位小写 hex)
52
- 2. `yos login` 交互粘贴(不回显);Agent 代执行时用 `yos login --key <令牌>`(注意令牌会进入 shell 历史,优先让用户自己在终端执行,或用 `YOS_API_KEY`)
53
- 3. 配置文件 `~/.config/yos/config.json`(0600)。三个环境变量**优先于配置文件**:`YOS_CONFIG_DIR`(换配置目录)、`YOS_API_KEY`(换令牌)、`YOS_ENDPOINT`(换端点)
54
- 4. 端点缺省生产环境 `https://yos-bff.linkduoo.com`,切换用 `yos login --endpoint <url>`(本地联调 `http://localhost:8080`);临时切换可用 `YOS_ENDPOINT`,`yos doctor` 会显示当前生效端点及其来源
54
+ 2. `yos login` 交互粘贴(不回显);Agent 代执行时用 `yos login --key <令牌>`
55
+ 3. 配置文件 `~/.config/yos/config.json`(0600)。环境变量**优先于配置文件**:`YOS_CONFIG_DIR`(换配置目录)、`YOS_API_KEY`(换令牌)、`YOS_ENV`(换服务环境)
56
+ 4. 切换服务环境(测试/开发用途):`yos login --env test`(临时可用 `YOS_ENV=test`),本地调试 `--env dev`(指向本机 8080 起的服务)
55
57
  5. 退出码 4 = 令牌无效/已吊销/已过期 → 引导重新 `yos login`,不要反复重试同一命令
56
58
 
57
59
  ## 4. 常用编排(已实测)
@@ -69,6 +71,13 @@ yos kb upload ./报告.pdf --kb KB20261009000001 # 单文件上传
69
71
  yos docs download ND20261009000012 --out ./归档/ # 取回上传的原文件(仅 docType=file)
70
72
  yos docs count ND20261009000012 # 删除前查影响范围
71
73
 
74
+ # 往知识库写云文档 / 云表格(正文是结构化 JSON 而非文本;动手前先读 yos-doc / yos-sheet 技能)
75
+ # 场景:会议结论/方案等沉淀为在线文档 → 云文档 --type cloud;登记表/对照表/排期表 → 云表格 --type sheet
76
+ yos docs create "上线检查清单" --kb KB20261009000001 --type cloud --file draft.yosdoc.json
77
+ yos docs create "值班排期表" --kb KB20261009000001 --type sheet --file draft.sheet.json
78
+ yos docs content ND20261010000030 --json # 写后自查:读回确认正文完整落库、结构未变形
79
+ yos docs update ND20261010000030 --file edited.yosdoc.json # 改正文=整体覆盖:先读回全文,本地编辑后整体回传
80
+
72
81
  # 会议内容
73
82
  yos transcript list --json # 取 transcriptionUserId(不是 TT 开头编码)
74
83
  yos transcript detail 2105108032979103745 --json # 取 summary / sentences
@@ -82,5 +91,5 @@ yos support upgrade --platform MAC_ARM64 # 客户端最新版
82
91
 
83
92
  - 默认输出人类可读文本;需要字段时**加 `--json` 再解析**,不要正则刮文本
84
93
  - 分页壳不统一:`kb list` / `support upgrade-log` 为 `records` / `total` / `page` / `size`;`transcript list` 为 `list` / `current` / `size` / `pages` / `total`(转写域解析细则见 yos-transcript)
85
- - 命令报错时先看退出码:`2` 用法错误(CLI 本地拦截,未发请求,按 `提示:` 改参数)、`1` 业务失败(服务端结论,文案里带具体编码,可直接引给用户)、`3` 网络、`4` 鉴权
94
+ - 命令报错时先看退出码:`2` 用法错误(CLI 本地拦截,未发请求,按 `提示:` 改参数)、`1` 业务失败(服务端结论,文案里带具体编码,可直接引给用户)、`3` 网络、`4` 鉴权、`5` 版本过低(先 `yos update` 升级再原样重试)
86
95
  - 领域命令找不到对应能力时,`yos tool list` 看全量工具,`yos tool schema <name> --json` 拿完整参数说明(不带 `--json` 会截断描述),再 `yos tool call`
@@ -0,0 +1,221 @@
1
+ ---
2
+ name: yos-doc
3
+ description: YOS 云文档(在线文档)的正文编写规范(DocDraft JSON 数据结构)。用户提到 yos/YOS 且要新建或修改知识库里的云文档(在线文档,非办公文件;即 docs create --type cloud 与 docs update 改云文档正文)时,先读本技能确定正文结构;云表格(在线表格)走 yos-sheet 技能;上传办公文件(kb upload)与写 Markdown 文档(--type md)不需要本技能
4
+ ---
5
+
6
+ # YOS 云文档编写(DocDraft)
7
+
8
+ **往 YOS 知识库写"云文档"(在线文档)的正文内容(新建或修改)前,必须先读本技能。** 云文档正文不是纯文本,而是结构化 JSON(不是 Markdown / HTML),且服务端**不做 schema 校验**(原样存储)——结构写错要到客户端打开时才暴露;本技能自带官方校验器(§4),草稿写完先本地校验再上传。
9
+
10
+ 云表格(在线表格)的正文结构完全不同,见 `yos-sheet` 技能。
11
+
12
+ ## 0. 适用边界(先分清楚再动手)
13
+
14
+ | 场景 | 走哪个命令 | 用本技能? |
15
+ |---|---|---|
16
+ | 上传办公文件(docx/xlsx/pdf/png…) | `yos kb upload`(落库为 docType=file) | 否 |
17
+ | 写 Markdown 文档 | `yos docs create`(缺省 `--type md`) | 否 |
18
+ | **写云文档(在线文档)** | `yos docs create --type cloud`、`yos docs update` | **是,先读本技能** |
19
+ | 写云表格(在线表格) | `yos docs create --type sheet`、`yos docs update` | 否(先读 yos-sheet 技能) |
20
+ | 目录管理(建文件夹 / 改名 / 移动 / 删除 / 恢复) | `yos docs mkdir/rename/move/delete/restore` | 否(见 yos-knowledge) |
21
+
22
+ 判断一个已有节点属于哪类:`docs tree` / `docs content` 的 `docType` 字段——`cloud` = 云文档、`sheet` = 云表格、`md` = Markdown、`file` = 上传的办公文件。
23
+
24
+ ## 1. 三步流程
25
+
26
+ 1. **写 JSON 草稿**:按 §2 的 DocDraft 结构写成本地文件(多行 JSON 不要用 `--content` 拼字符串),写好后先用技能自带校验器过一遍(§4)
27
+ 2. **上传**:
28
+
29
+ ```bash
30
+ # 新建(--parent 为目标文件夹编码,缺省库根;--kb 与 --parent 至少一个)
31
+ yos docs create "<标题>" --kb <kbCode> [--parent <folderCode>] --type cloud --file <草稿.json>
32
+
33
+ # 修改已有(正文整体覆盖:先读回完整正文,本地编辑后整体回传)
34
+ yos docs update <nodeCode> --title "<新标题>" --file <草稿.json>
35
+ ```
36
+
37
+ 3. **读回自查**:`yos docs content <nodeCode> --json` 确认 `docType=cloud` 与正文 JSON 完整落库、结构未变形
38
+
39
+ ## 2. DocDraft JSON(--type cloud)
40
+
41
+ 对齐编辑器:`@yos/doc-editor`。
42
+
43
+ ### 2.1 顶层结构
44
+
45
+ ```json
46
+ {
47
+ "title": "文档标题",
48
+ "attachments": [],
49
+ "content": { "type": "doc", "content": [] }
50
+ }
51
+ ```
52
+
53
+ - `title`:与 `docs create` 的标题参数保持一致;**正文 `content` 不需要再写文档标题**(文档名由 title 单独展示,正文里重复写标题会显示两遍,从正文内容直接开始即可)
54
+ - `attachments`:必须为 `[]`(禁止非空、禁止编造 mediaId)
55
+ - `content`:TipTap 文档树,`type` 固定为 `doc`,节点放 `content` 数组
56
+
57
+ ### 2.2 节点档位
58
+
59
+ | 档 | 节点 | 用法 |
60
+ | -- | ---- | ---- |
61
+ | **tier A(默认)** | `paragraph` `heading` `blockquote` `horizontalRule` `hardBreak` `bulletList` `orderedList` `listItem` `taskList` `taskItem` `codeBlock` `callout` `table` `tableRow` `tableHeader` `tableCell` `text` | 直接写 |
62
+ | **tier B(用户明确要求)** | `columnBlock` `column`(分栏)、`image`(外链图) | 仅在明确要求时写 |
63
+ | **禁止(永远不写)** | `mindmap` `flowchart` `video` `audio` `file` | — |
64
+
65
+ 行内 marks 允许:`bold` `italic` `strike` `underline` `code` `link` `textStyle` `highlight`;禁止 `glossaryTerm`。
66
+
67
+ ### 2.3 常用节点示例
68
+
69
+ 段落与文本(`attrs` 建议保留为下述形态):
70
+
71
+ ```json
72
+ {"type":"paragraph","attrs":{"indent":0,"textAlign":null},"content":[{"type":"text","text":"正文内容"}]}
73
+ ```
74
+
75
+ 标题(`level` 1~6):
76
+
77
+ ```json
78
+ {"type":"heading","attrs":{"level":2,"indent":0,"textAlign":null,"collapsed":false},"content":[{"type":"text","text":"小节标题"}]}
79
+ ```
80
+
81
+ 无序列表(`bulletList` → `listItem` → `paragraph`,可嵌套;有序列表把 type 换成 `orderedList` 并加 `attrs":{"start":1,"type":null}`,其余相同):
82
+
83
+ ```json
84
+ {"type":"bulletList","content":[
85
+ {"type":"listItem","attrs":{"collapsed":false},"content":[
86
+ {"type":"paragraph","attrs":{"indent":0,"textAlign":null},"content":[{"type":"text","text":"列表项"}]}
87
+ ]}
88
+ ]}
89
+ ```
90
+
91
+ 任务列表(`taskList` → `taskItem` → `paragraph`,`checked` 控制勾选态):
92
+
93
+ ```json
94
+ {"type":"taskList","content":[
95
+ {"type":"taskItem","attrs":{"checked":false},"content":[
96
+ {"type":"paragraph","attrs":{"indent":0,"textAlign":null},"content":[{"type":"text","text":"待办项"}]}
97
+ ]}
98
+ ]}
99
+ ```
100
+
101
+ 引用与提示块:
102
+
103
+ ```json
104
+ {"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"textAlign":null},"content":[{"type":"text","text":"引用内容"}]}]}
105
+ {"type":"callout","attrs":{"emoji":"💡","backgroundColor":null,"borderColor":null},"content":[{"type":"paragraph","attrs":{"indent":0,"textAlign":null},"content":[{"type":"text","text":"提示内容"}]}]}
106
+ ```
107
+
108
+ 代码块(换行用 `\n` 写在同一 `text` 内):
109
+
110
+ ```json
111
+ {"type":"codeBlock","attrs":{"language":"plaintext","wrap":false,"caption":"代码块","collapsed":false,"regionFolds":[]},"content":[{"type":"text","text":"yos kb list\nyos doctor"}]}
112
+ ```
113
+
114
+ 表格(`table` → `tableRow` → `tableHeader`/`tableCell` → `paragraph`;首行用 `tableHeader`,数据行用 `tableCell`):
115
+
116
+ ```json
117
+ {"type":"table","content":[
118
+ {"type":"tableRow","attrs":{"height":null},"content":[
119
+ {"type":"tableHeader","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"align":null,"backgroundColor":null},"content":[
120
+ {"type":"paragraph","attrs":{"indent":0,"textAlign":null},"content":[{"type":"text","text":"列 A"}]}
121
+ ]}
122
+ ]},
123
+ {"type":"tableRow","attrs":{"height":null},"content":[
124
+ {"type":"tableCell","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"align":null,"backgroundColor":null},"content":[
125
+ {"type":"paragraph","attrs":{"indent":0,"textAlign":null},"content":[{"type":"text","text":"1"}]}
126
+ ]}
127
+ ]}
128
+ ]}
129
+ ```
130
+
131
+ 分隔线与强制换行:`{"type":"horizontalRule"}`(无 content);`{"type":"hardBreak"}` 放在 paragraph / heading 的 content 内。
132
+
133
+ ### 2.4 节点 attrs 速查
134
+
135
+ | type | attrs |
136
+ | ---- | ----- |
137
+ | `paragraph` | `{ "indent": 0, "textAlign": null }`;indent 0~8;textAlign null/left/center/right/justify |
138
+ | `heading` | `{ "level": 1, "indent": 0, "textAlign": null, "collapsed": false }`;level 1~6 |
139
+ | `orderedList` | `{ "start": 1, "type": null }` |
140
+ | `listItem` | `{ "collapsed": false }` |
141
+ | `taskItem` | `{ "checked": false }` |
142
+ | `codeBlock` | `{ "language": "plaintext", "wrap": false, "caption": "代码块", "collapsed": false, "regionFolds": [] }` |
143
+ | `callout` | `{ "emoji": "💡", "backgroundColor": null, "borderColor": null }` |
144
+ | `tableRow` | `{ "height": null }` |
145
+ | `tableHeader` / `tableCell` | `{ "colspan": 1, "rowspan": 1, "colwidth": null, "align": null, "backgroundColor": null }` |
146
+ | `blockquote` `bulletList` `taskList` `table` `horizontalRule` `hardBreak` | 无 attrs |
147
+
148
+ tier B 节点(用户明确要求时):
149
+
150
+ - `columnBlock`:attrs `{ "widths": [50, 50] }`,含 2~5 个 `column`(`column` 无 attrs,内放块节点)
151
+ - `image`:attrs 完整字段 `{ "src": "https://…", "mediaId": "", "alt": null, "title": null, "width": null, "height": null, "align": "left", "caption": "", "originalSrc": null, "uploadStatus": null, "uploadId": "" }`——`src` 仅 http(s);**勿编造 `mediaId` / `upload*`**(媒体由客户端宿主上传)
152
+
153
+ ### 2.5 行内 marks
154
+
155
+ ```json
156
+ {"type":"text","marks":[{"type":"bold"}],"text":"加粗"}
157
+ {"type":"text","marks":[{"type":"italic"}],"text":"斜体"}
158
+ {"type":"text","marks":[{"type":"strike"}],"text":"删除线"}
159
+ {"type":"text","marks":[{"type":"underline"}],"text":"下划线"}
160
+ {"type":"text","marks":[{"type":"code"}],"text":"行内代码"}
161
+ {"type":"text","marks":[{"type":"textStyle","attrs":{"color":"#3370ff"}}],"text":"彩色文字"}
162
+ {"type":"text","marks":[{"type":"highlight","attrs":{"color":"#faf1d1"}}],"text":"高亮"}
163
+ {"type":"text","marks":[{"type":"link","attrs":{"href":"https://example.com","target":"_blank","rel":"noopener noreferrer nofollow","class":"yos-docs-editor-link","title":null}}],"text":"链接"}
164
+ ```
165
+
166
+ 可叠加:加粗+斜体同放数组,如 `[{"type":"bold"},{"type":"italic"}]`。
167
+
168
+ ### 2.6 禁止与注意事项
169
+
170
+ - 禁止节点:`mindmap`、`flowchart`、`video`、`audio`、`file`;禁止 mark:`glossaryTerm`
171
+ - 默认只写 tier A 结构;分栏、外链图等 tier B 仅在用户明确要求时使用
172
+ - **不要以 Markdown 作为最终产物**:先定内容结构再落成上述节点 JSON;转换时逐段映射(小节标题→heading、列表→bulletList、有序列表→orderedList、待办→taskList、引用→blockquote、表格→table、提示→callout);文档大标题(如 `# 标题`)不映射进正文,由 title 承载(见 §2.1)
173
+
174
+ ## 3. 常见任务编排
175
+
176
+ **新建云文档并落到指定文件夹**
177
+
178
+ ```bash
179
+ yos kb list --keyword <库名> # 1. 拿 kbCode
180
+ yos docs tree KBxxxx --json # 2. 找目标文件夹 resourceCode(须 resourceType=folder)
181
+ # 3. 写草稿(§2 结构),如 draft.yosdoc.json
182
+ yos docs create "文档标题" --parent NDxxxx --type cloud --file draft.yosdoc.json
183
+ yos docs content NDyyyy --json # 4. 读回自查
184
+ ```
185
+
186
+ **修改已有云文档的正文**(整体覆盖,保留未改动部分)
187
+
188
+ ```bash
189
+ yos docs content NDyyyy --json # 1. 取回完整正文 content
190
+ # 2. 本地编辑目标段落,其余节点原样保留
191
+ yos docs update NDyyyy --title "标题" --file edited.yosdoc.json # 3. 整体回传
192
+ ```
193
+
194
+ ## 4. 校验与避坑(实测)
195
+
196
+ ### 4.1 本地校验器(技能自带,先校验再上传)
197
+
198
+ 本技能自带与官方编辑器同源的校验器(Node ≥ 18,纯 ESM),用法(`<技能目录>` 即本 SKILL.md 所在目录):
199
+
200
+ ```bash
201
+ node "<技能目录>/scripts/yos-doc.mjs" validate draft.json --tier A|B
202
+ ```
203
+
204
+ - 默认 tier A;草稿含 tier B 节点(分栏、外链图)时必须显式 `--tier B`,否则校验器按字段路径逐条拒绝
205
+ - 通过 exit 0;失败 exit 1 并逐条输出 `字段路径: 原因`;参数错误 exit 2
206
+ - `schema` 子命令打印当前档位白名单(JSON);`paths` 子命令打印 references 各文档的绝对路径
207
+
208
+ ### 4.2 参考文档(references/,全量契约)
209
+
210
+ - `references/AGENT_SCHEMA.doc.md`:官方 Agent 契约完整版(本文档的同源展开)
211
+ - `references/DOC_DATA_API.md`:编辑器全量数据 API(本文档未覆盖的字段查这里)
212
+ - `references/few-shot.draft.json`:可直接参考的草稿示例(过 tier A 校验)
213
+ - `references/doc-draft.template.json` 仅作字段形态参考、不要照抄:它是编辑器全量能力演示(含 attachments、glossaryTerm、禁止节点),任何档位都无法直接通过校验,其中 `attachments` 非空仅为展示、写草稿必须为 `[]`
214
+
215
+ ### 4.3 避坑
216
+
217
+ - **没有在线校验**:CLI 与服务端都不校验正文 schema,写入即原样存储。自查手段:草稿先过 4.1 校验器或本地 JSON 语法(如 `jq . draft.json`)→ 上传后 `docs content --json` 读回核对结构
218
+ - `docs content` 读回的 `content` 是 **JSON 字符串**,需对 `text` 再 `JSON.parse` 两次(外层信封 + content 字符串)
219
+ - `docs update` 的正文是**整体覆盖**不是局部替换——改一处也要先取全文再整体回传,否则其余内容会丢
220
+ - 渲染效果(尤其高级结构、代码块、表格样式)以 YOS 客户端打开为准;发现渲染异常先确认草稿结构,必要时用 `yos support feedback` 反馈
221
+ - 同名云文档可并存,创建前可先 `docs tree` 看目标位置是否已有同名节点
@@ -0,0 +1,69 @@
1
+ # Agent DocDraft 精简契约
2
+
3
+ > 完整字段见同目录 [`DOC_DATA_API.md`](./DOC_DATA_API.md)。Agent **只读本文 + few-shot**,勿整份塞入 prompt。
4
+ > 校验:`node "<技能目录>/scripts/yos-doc.mjs" validate <file.json> [--tier A|B]`
5
+
6
+ ## 输出根形状
7
+
8
+ ```json
9
+ {
10
+ "title": "文档标题",
11
+ "attachments": [],
12
+ "content": { "type": "doc", "content": [/* 块节点 */] }
13
+ }
14
+ ```
15
+
16
+ - `attachments` 必须为 `[]`(媒体由宿主 `DocMediaHooks` 上传)。
17
+ - `content.type` 必须为 `"doc"`。
18
+ - 正文 `content` 不需要再写文档标题(文档名由 `title` 承载);正文从小节或正文内容直接开始。
19
+
20
+ ## 档位
21
+
22
+ | 档 | 节点 | 默认 |
23
+ | -- | ---- | ---- |
24
+ | **A** | `paragraph` `heading` `blockquote` `horizontalRule` `hardBreak` `bulletList` `orderedList` `listItem` `taskList` `taskItem` `codeBlock` `callout` `table` `tableRow` `tableHeader` `tableCell` `text` | 默认开启 |
25
+ | **B** | + `columnBlock` `column` `image` | `--tier B` |
26
+ | **C 禁止** | `mindmap` `flowchart` `video` `audio` `file` | 永远禁止 |
27
+
28
+ Marks 允许:`bold` `italic` `strike` `underline` `code` `link` `textStyle` `highlight`。
29
+ 禁止:`glossaryTerm`。
30
+
31
+ ## 常用节点默认 attrs
32
+
33
+ | type | attrs 默认 / 约束 |
34
+ | ---- | ----------------- |
35
+ | `paragraph` | `{ "indent": 0, "textAlign": null }`;indent 0~8;textAlign null\|left\|center\|right\|justify |
36
+ | `heading` | `{ "level": 1, "indent": 0, "textAlign": null, "collapsed": false }`;level 1~6 |
37
+ | `orderedList` | `{ "start": 1, "type": null }` |
38
+ | `listItem` | `{ "collapsed": false }`;content 多为 paragraph,可再嵌套列表 |
39
+ | `taskItem` | `{ "checked": false }` |
40
+ | `codeBlock` | `{ "language": "plaintext", "wrap": false, "caption": "代码块", "collapsed": false, "regionFolds": [] }`;content 单 `text` |
41
+ | `callout` | `{ "emoji": "💡", "backgroundColor": null, "borderColor": null }` |
42
+ | `tableRow` | `{ "height": null }` |
43
+ | `tableHeader` / `tableCell` | `{ "colspan": 1, "rowspan": 1, "colwidth": null, "align": null, "backgroundColor": null }`;格内多为 paragraph |
44
+ | `blockquote` / `bulletList` / `taskList` / `table` / `horizontalRule` | 无 attrs;HR 无 content |
45
+ | `hardBreak` | 无 attrs;落在 paragraph/heading 的 content 内 |
46
+ | `columnBlock`(B) | `{ "widths": [50, 50] }`;2~5 个 `column`;widths.length = 列数 |
47
+ | `column`(B) | 无 attrs;`block+` |
48
+ | `image`(B) | `{ "src": "https://…", "mediaId": "", "alt": null, "title": null, "width": null, "height": null, "align": "left", "caption": "", "originalSrc": null, "uploadStatus": null, "uploadId": "" }`;**勿编造 mediaId / upload\***;src 仅 http(s) |
49
+
50
+ ## text / marks
51
+
52
+ ```json
53
+ { "type": "text", "text": "字", "marks": [{ "type": "bold" }] }
54
+ ```
55
+
56
+ - `link`:`attrs` 含非空 `href`;建议 `target: "_blank"`, `rel: "noopener noreferrer nofollow"`, `class: "yos-docs-editor-link"`, `title: null`
57
+ - `textStyle`:`{ "color": "#3370ff" }`
58
+ - `highlight`:`{ "color": "#faf1d1" }`
59
+
60
+ ## 结构要点
61
+
62
+ 1. 列表:`bulletList`/`orderedList` → `listItem` → `paragraph`(再可选嵌套列表)。
63
+ 2. 任务:`taskList` → `taskItem` → `paragraph`。
64
+ 3. 表格:`table` → `tableRow` → `tableHeader`|`tableCell` → `paragraph`。
65
+ 4. 每个块尽量带齐默认 attrs(与编辑器 dump 一致,避免缺字段)。
66
+
67
+ ## few-shot
68
+
69
+ 见 [`few-shot.draft.json`](./few-shot.draft.json)(已通过 tier A 校验)。