@yosai/cli 0.0.0-stage → 0.2.1-test.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/LICENSE +201 -0
- package/README.md +66 -2
- package/dist/cli.js +188 -0
- package/dist/commands/context.js +6 -0
- package/dist/commands/docs-download.js +85 -0
- package/dist/commands/doctor.js +102 -0
- package/dist/commands/domain.js +16 -0
- package/dist/commands/index.js +10 -0
- package/dist/commands/kb-upload.js +210 -0
- package/dist/commands/kb.js +165 -0
- package/dist/commands/login.js +35 -0
- package/dist/commands/ping.js +40 -0
- package/dist/commands/skill.js +22 -0
- package/dist/commands/tool.js +129 -0
- package/dist/commands/update.js +73 -0
- package/dist/commands/version.js +44 -0
- package/dist/config.js +55 -0
- package/dist/domain-commands.js +477 -0
- package/dist/domain-executor.js +257 -0
- package/dist/endpoints.js +1 -0
- package/dist/errors.js +37 -0
- package/dist/index.js +5 -0
- package/dist/npm-meta.js +96 -0
- package/dist/openapis-client.js +112 -0
- package/dist/output.js +21 -0
- package/dist/prompt.js +40 -0
- package/dist/skill-sync.js +34 -0
- package/dist/skills/yos/SKILL.md +86 -0
- package/dist/skills/yos-install/SKILL.md +162 -0
- package/dist/skills/yos-kb/SKILL.md +388 -0
- package/dist/skills/yos-retrieval/SKILL.md +244 -0
- package/dist/skills/yos-retrieval/references/examples.md +152 -0
- package/dist/skills/yos-shared/SKILL.md +185 -0
- package/dist/skills/yos-support/SKILL.md +141 -0
- package/dist/skills/yos-transcript/SKILL.md +92 -0
- package/dist/tool-domains.js +38 -0
- package/dist/version-state.js +30 -0
- package/package.json +30 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "{}"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2026 YOS (Yuou)
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -1,3 +1,67 @@
|
|
|
1
|
-
#
|
|
1
|
+
# yos-cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
YOS 能力的 Agent 执行入口:终端直调 YOS 工具 + 知识库文件上传。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
- 本地开发:`npm install && npm run build`,用 `node dist/index.js <命令>` 或 `npm link` 后使用 `yos`
|
|
8
|
+
- 公网安装:`npm install -g @yosai/cli`(需 Node ≥ 18;装完 `yos --version` 验证)
|
|
9
|
+
|
|
10
|
+
## 快速开始
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
yos login # 配置访问令牌(客户端「个人设置-访问令牌」页创建并复制)与端点
|
|
14
|
+
yos doctor # 诊断配置与连通(显示当前身份)
|
|
15
|
+
yos ping # 探活:连接与令牌有效性(退出码即结论,客户端对接用)
|
|
16
|
+
yos kb list # 我的知识库
|
|
17
|
+
yos kb upload ./report.pdf --kb KBxxx # 上传文件到知识库
|
|
18
|
+
yos docs tree KBxxx # 知识库目录树
|
|
19
|
+
yos docs download NDxxx --out ./ # 下载上传文件类型的原文件
|
|
20
|
+
yos retrieve search '关键词' --kb KBxxx # 语义检索
|
|
21
|
+
yos retrieve read 'cortex://kb/KBxxx/doc/NDxxx:1-50' # 读原文(区间简写)
|
|
22
|
+
yos <领域> --help # 域内全部命令(kb / docs / retrieve / support / transcript)
|
|
23
|
+
yos tool list # 底层工具通道(领域命令未覆盖时兜底)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 命令清单
|
|
27
|
+
|
|
28
|
+
| 命令 | 说明 |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `yos login` | 配置访问令牌与端点(校验身份后保存) |
|
|
31
|
+
| `yos doctor` | 诊断配置、访问令牌、连通、Skill 状态与 CLI 版本 |
|
|
32
|
+
| `yos ping` | 探活:检查连接是否成功、令牌是否有效(退出码 0/2/3/4 即结论,客户端检测对接用) |
|
|
33
|
+
| `yos kb <动作>` | 知识库:`list` / `detail` / `update` / `upload`(建库、删库请到 YOS 客户端) |
|
|
34
|
+
| `yos docs <动作>` | 文档与节点:`tree` / `content` / `download` / `count` / `mkdir` / `create` / `update` / `rename` / `move` / `delete` / `restore` |
|
|
35
|
+
| `yos retrieve <动作>` | 检索:`search` / `grep` / `read`(支持 `<target>:起-止`) / `neighbors` / `outline` |
|
|
36
|
+
| `yos support <动作>` | 支持:`manual` / `manual-read` / `manual-search` / `upgrade` / `upgrade-log` / `feedback` |
|
|
37
|
+
| `yos transcript <动作>` | 转写:`list` / `detail` |
|
|
38
|
+
| `yos tool list` / `schema` / `call` | 底层工具通道(领域命令未覆盖时兜底) |
|
|
39
|
+
| `yos version [--check]` | CLI 版本(`--check` 比对 npmjs 最新版) |
|
|
40
|
+
| `yos update [--check]` | 升级到 npmjs 最新版并自动重同步 Skill |
|
|
41
|
+
| `yos skill install [--target <dir>]` | 安装内置 Skill |
|
|
42
|
+
|
|
43
|
+
所有命令支持 `--json` 输出与统一退出码(0 成功 / 1 业务失败 / 2 参数错误 / 3 网络 / 4 鉴权)。
|
|
44
|
+
|
|
45
|
+
## 给 Agent 使用
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
yos skill install # 安装 7 个内置 Skill(总入口 + 领域 + 安装引导)到 Claude Code / Cursor / Codex / Qoder / WorkBuddy 等技能目录
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
安装后重启 Agent 工具,即可用自然语言驱动(如「把这份报告上传到知识库」)。`yos` 为能力总入口(何时使用 / 能力地图 / 命令使用方式),Agent 据此路由到各领域细则;`yos-shared` 收录跨领域约定(输出解析、退出码契约、仍需自行校验内容的场景、安全纪律);`yos-install` 是自包含的安装配置引导,可单独下载到发布仓库供用户引导装机。
|
|
52
|
+
|
|
53
|
+
需要让 Agent 宿主(Cursor / Claude Desktop 等)直连 YOS MCP 时,在宿主中配置远程 MCP 端点 `<endpoint>/mcp` 并携带 `Authorization: Bearer <密钥>`(CLI 不代写宿主配置)。
|
|
54
|
+
|
|
55
|
+
## 配置
|
|
56
|
+
|
|
57
|
+
- 配置文件:`~/.config/yos/config.json`(权限 0600;目录可用 `YOS_CONFIG_DIR` 覆盖)
|
|
58
|
+
- 环境变量(均优先于配置文件,适合 CI/容器场景):`YOS_API_KEY` 访问令牌、`YOS_ENDPOINT` 服务端点
|
|
59
|
+
- 端点:默认生产环境 `https://yos-bff.linkduoo.com`,`yos login --endpoint <url>` 持久切换(本地联调可用 http://localhost:8080);`yos doctor` 显示当前生效端点及其来源
|
|
60
|
+
|
|
61
|
+
## 故障排查
|
|
62
|
+
|
|
63
|
+
- 退出码 4(鉴权失败):访问令牌无效/已吊销/已过期,重新 `yos login`
|
|
64
|
+
- 退出码 2(参数错误):CLI 本地拦截、未发请求,按 `提示:` 里的用法改写参数
|
|
65
|
+
- 上传报 20MB 上限:单文件上限 20MB,更大文件暂不支持
|
|
66
|
+
- 下载报「没有该资源的访问权限:DOWNLOAD.xxx」:只有阅读权限的库不支持下载原文件,需库管理员授权
|
|
67
|
+
- 退出码 3(网络):确认端点可达;`yos version --check` 报网络错则是 npm 或 registry 不可达
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
import { DOMAIN_META } from './domain-commands.js';
|
|
3
|
+
import { CliError, EXIT, UsageError } from './errors.js';
|
|
4
|
+
import { print, printErr, renderRows } from './output.js';
|
|
5
|
+
const registry = [];
|
|
6
|
+
const GROUP_ORDER = ['知识库 kb', '文档 docs', '检索 retrieve', '支持 support', '转写 transcript', '工具通道', 'Agent 接入', '基础'];
|
|
7
|
+
const RISK_LABEL = { read: '只读', write: '写入', danger: '高危' };
|
|
8
|
+
export function register(command) {
|
|
9
|
+
registry.push(command);
|
|
10
|
+
}
|
|
11
|
+
export function renderHelp() {
|
|
12
|
+
const lines = [
|
|
13
|
+
'yos - YOS 能力的 Agent 执行入口',
|
|
14
|
+
'',
|
|
15
|
+
'推荐流程:',
|
|
16
|
+
' 0. yos <领域> --help 按领域浏览能力(kb / docs / retrieve / support / transcript)',
|
|
17
|
+
' 1. yos tool list 动态发现全部工具(领域命令未覆盖时)',
|
|
18
|
+
' 2. 领域命令未覆盖的场景用 yos tool schema / tool call 兜底',
|
|
19
|
+
' 3. yos skill install 安装内置 Skill,接入本机 AI 助手(Claude Code / Cursor / Qoder 等)',
|
|
20
|
+
'',
|
|
21
|
+
'约定:',
|
|
22
|
+
' - 输出默认人类可读;--json 输出结构化结果(工具返回原文在 text 字段内)',
|
|
23
|
+
' - 退出码:0 成功 / 1 业务失败 / 2 参数错误 / 3 网络错误 / 4 鉴权失败',
|
|
24
|
+
' - 删除类操作前先用 yos docs count <node_code> 查询影响范围,并取得用户明确确认',
|
|
25
|
+
'',
|
|
26
|
+
'环境变量(均优先于配置文件,便于 CI 免落盘):',
|
|
27
|
+
' YOS_API_KEY 访问令牌',
|
|
28
|
+
' YOS_ENDPOINT BFF 服务地址',
|
|
29
|
+
' YOS_CONFIG_DIR 配置目录(缺省 ~/.config/yos)',
|
|
30
|
+
'',
|
|
31
|
+
'示例:',
|
|
32
|
+
' yos doctor',
|
|
33
|
+
' yos kb list',
|
|
34
|
+
" yos retrieve search '报销标准' --kb KBxxxx",
|
|
35
|
+
" yos retrieve read 'cortex://kb/KBxxxx/doc/NDxxxx:10-60'",
|
|
36
|
+
' yos docs create "周报" --kb KBxxxx --content "正文内容"',
|
|
37
|
+
' yos kb upload ./报告.pdf --kb KBxxxx',
|
|
38
|
+
' yos docs download NDxxxx --out ./报告.pdf',
|
|
39
|
+
'',
|
|
40
|
+
];
|
|
41
|
+
for (const group of GROUP_ORDER) {
|
|
42
|
+
const commands = registry.filter((command) => command.group === group);
|
|
43
|
+
if (commands.length === 0) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
lines.push(`${group}:`);
|
|
47
|
+
lines.push(indentRows(renderRows(commands.map((command) => [command.name, command.description]))));
|
|
48
|
+
lines.push('');
|
|
49
|
+
}
|
|
50
|
+
const ungrouped = registry.filter((command) => !command.group);
|
|
51
|
+
if (ungrouped.length > 0) {
|
|
52
|
+
lines.push(indentRows(renderRows(ungrouped.map((command) => [command.name, command.description]))));
|
|
53
|
+
lines.push('');
|
|
54
|
+
}
|
|
55
|
+
lines.push("运行 'yos <命令> --help' 查看命令用法");
|
|
56
|
+
return lines.join('\n');
|
|
57
|
+
}
|
|
58
|
+
export function renderDomainHelp(domain) {
|
|
59
|
+
const meta = DOMAIN_META[domain];
|
|
60
|
+
if (!meta) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const commands = registry.filter((command) => command.name.startsWith(`${domain} `));
|
|
64
|
+
return [
|
|
65
|
+
`yos ${domain} - ${meta.description}`,
|
|
66
|
+
'',
|
|
67
|
+
indentRows(renderRows(commands.map((command) => [command.name, command.description]))),
|
|
68
|
+
'',
|
|
69
|
+
`运行 'yos ${domain} <动作> --help' 查看子命令用法`,
|
|
70
|
+
].join('\n');
|
|
71
|
+
}
|
|
72
|
+
export function listCommands() {
|
|
73
|
+
return [...registry];
|
|
74
|
+
}
|
|
75
|
+
export function renderCommandHelp(command) {
|
|
76
|
+
const lines = [`用法: ${command.usage}`, `说明: ${command.description}`];
|
|
77
|
+
if (command.paramNotes && command.paramNotes.length > 0) {
|
|
78
|
+
lines.push('', '参数:');
|
|
79
|
+
lines.push(indentRows(renderRows(command.paramNotes)));
|
|
80
|
+
}
|
|
81
|
+
if (command.risk) {
|
|
82
|
+
const dangerHint = command.risk === 'danger' ? '(删除类操作先执行 yos docs count <node_code> 确认影响面)' : '';
|
|
83
|
+
lines.push('', `风险: ${RISK_LABEL[command.risk]}${dangerHint}`);
|
|
84
|
+
}
|
|
85
|
+
return lines.join('\n');
|
|
86
|
+
}
|
|
87
|
+
function indentRows(text) {
|
|
88
|
+
return text.split('\n').map((line) => ` ${line}`).join('\n');
|
|
89
|
+
}
|
|
90
|
+
function matchCommand(argv) {
|
|
91
|
+
const two = argv.slice(0, 2).join(' ');
|
|
92
|
+
const hit = registry.find((c) => c.name === two) ?? registry.find((c) => c.name === argv[0]);
|
|
93
|
+
if (!hit) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return { command: hit, rest: argv.slice(hit.name.split(' ').length) };
|
|
97
|
+
}
|
|
98
|
+
const PARSE_ERROR_RULES = [
|
|
99
|
+
{ pattern: /Unknown option '([^']+)'/, rewrite: (matched) => `未知参数 ${matched[1]}` },
|
|
100
|
+
{ pattern: /Option '([^' ]+)(?: <[^>]+>)?' argument missing/, rewrite: (matched) => `参数 ${matched[1]} 缺少取值` },
|
|
101
|
+
{
|
|
102
|
+
pattern: /Option '([^' ]+)' argument is ambiguous/,
|
|
103
|
+
rewrite: (matched) => `参数 ${matched[1]} 的取值以 - 开头,需写成 ${matched[1]}=<值>`,
|
|
104
|
+
},
|
|
105
|
+
{ pattern: /Unexpected argument '([^']+)'/, rewrite: (matched) => `该命令不接受位置参数:${matched[1]}` },
|
|
106
|
+
];
|
|
107
|
+
function parseCommandArgs(command, rest) {
|
|
108
|
+
try {
|
|
109
|
+
const parsed = parseArgs({
|
|
110
|
+
args: rest,
|
|
111
|
+
options: command.options ?? {},
|
|
112
|
+
strict: true,
|
|
113
|
+
allowPositionals: true,
|
|
114
|
+
});
|
|
115
|
+
return { positionals: parsed.positionals, flags: parsed.values };
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
119
|
+
throw new UsageError(describeParseError(detail), buildUnknownOptionHint(command, rest) ?? `用法: ${command.usage}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export function describeParseError(detail) {
|
|
123
|
+
for (const rule of PARSE_ERROR_RULES) {
|
|
124
|
+
const matched = rule.pattern.exec(detail);
|
|
125
|
+
if (matched) {
|
|
126
|
+
return rule.rewrite(matched);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return '参数格式有误';
|
|
130
|
+
}
|
|
131
|
+
export function buildUnknownOptionHint(command, rest) {
|
|
132
|
+
const known = new Set(Object.keys(command.options ?? {}));
|
|
133
|
+
for (const token of rest) {
|
|
134
|
+
const matched = /^--([a-z0-9_-]+)(?:=|$)/.exec(token);
|
|
135
|
+
if (!matched || known.has(matched[1])) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const alias = command.paramAlias?.[matched[1]];
|
|
139
|
+
if (alias) {
|
|
140
|
+
return `--${matched[1]} 是底层工具参数名,命令内请用 --${alias}(yos ${command.name} --help 查看完整参数)`;
|
|
141
|
+
}
|
|
142
|
+
return `yos ${command.name} --help 查看完整参数`;
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
export async function dispatch(argv) {
|
|
147
|
+
if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h') {
|
|
148
|
+
print(renderHelp());
|
|
149
|
+
return EXIT.OK;
|
|
150
|
+
}
|
|
151
|
+
const normalized = argv[0] === '--version' ? ['version', ...argv.slice(1)] : argv;
|
|
152
|
+
const matched = matchCommand(normalized);
|
|
153
|
+
if (!matched) {
|
|
154
|
+
const domainHelp = renderDomainHelp(argv[0]);
|
|
155
|
+
if (domainHelp) {
|
|
156
|
+
if (argv.length > 1 && !argv.includes('--help') && !argv.includes('-h')) {
|
|
157
|
+
printErr(`未知子命令: ${argv.slice(1).join(' ')}`);
|
|
158
|
+
printErr(domainHelp);
|
|
159
|
+
return EXIT.USAGE;
|
|
160
|
+
}
|
|
161
|
+
print(domainHelp);
|
|
162
|
+
return EXIT.OK;
|
|
163
|
+
}
|
|
164
|
+
printErr(`未知命令: ${argv.join(' ')}`);
|
|
165
|
+
printErr(renderHelp());
|
|
166
|
+
return EXIT.USAGE;
|
|
167
|
+
}
|
|
168
|
+
const { command, rest } = matched;
|
|
169
|
+
if (rest.includes('--help') || rest.includes('-h')) {
|
|
170
|
+
print(renderCommandHelp(command));
|
|
171
|
+
return EXIT.OK;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
const exitCode = await command.run(parseCommandArgs(command, rest));
|
|
175
|
+
return typeof exitCode === 'number' ? exitCode : EXIT.OK;
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (error instanceof CliError) {
|
|
179
|
+
printErr(`错误: ${error.message}`);
|
|
180
|
+
if (error.hint) {
|
|
181
|
+
printErr(`提示: ${error.hint}`);
|
|
182
|
+
}
|
|
183
|
+
return error.exitCode;
|
|
184
|
+
}
|
|
185
|
+
printErr(`错误: ${error instanceof Error ? error.message : String(error)}`);
|
|
186
|
+
return EXIT.BUSINESS;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { loadConfig, resolveEndpoint, resolveKey } from '../config.js';
|
|
2
|
+
import { OpenApisClient } from '../openapis-client.js';
|
|
3
|
+
export function clientFromConfig() {
|
|
4
|
+
const config = loadConfig();
|
|
5
|
+
return new OpenApisClient(`${resolveEndpoint(config)}/open-apis`, resolveKey(config));
|
|
6
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { mkdir, open, stat } from 'node:fs/promises';
|
|
2
|
+
import { basename, dirname, resolve as resolvePath } from 'node:path';
|
|
3
|
+
import { register } from '../cli.js';
|
|
4
|
+
import { BusinessError, EXIT, NetworkError, UsageError } from '../errors.js';
|
|
5
|
+
import { parseToolJson } from '../openapis-client.js';
|
|
6
|
+
import { print, printJson } from '../output.js';
|
|
7
|
+
import { clientFromConfig } from './context.js';
|
|
8
|
+
register({
|
|
9
|
+
name: 'docs download',
|
|
10
|
+
description: '下载文档原文件到本地(仅上传文件类型;需下载权限,只读角色不可下载)',
|
|
11
|
+
usage: 'yos docs download <node_code> [--out <本地路径>] [--json]',
|
|
12
|
+
group: '文档 docs',
|
|
13
|
+
risk: 'read',
|
|
14
|
+
options: {
|
|
15
|
+
out: { type: 'string' },
|
|
16
|
+
json: { type: 'boolean' },
|
|
17
|
+
},
|
|
18
|
+
paramNotes: [
|
|
19
|
+
['<node_code>', '文档节点编码(docType=file)'],
|
|
20
|
+
['--out', '保存路径:已存在的目录则按原文件名落盘,否则视为目标文件路径(缺省当前目录 + 原文件名)'],
|
|
21
|
+
['--json', '结构化输出(nodeCode、fileName、savedPath、size)'],
|
|
22
|
+
],
|
|
23
|
+
async run({ positionals, flags }) {
|
|
24
|
+
const nodeCode = positionals[0];
|
|
25
|
+
if (!nodeCode) {
|
|
26
|
+
throw new UsageError('缺少文档节点编码', '用法: yos docs download <node_code> [--out <本地路径>];节点编码可用 `yos docs tree <kb_code>` 查看');
|
|
27
|
+
}
|
|
28
|
+
const json = flags.json === true;
|
|
29
|
+
const out = flags.out;
|
|
30
|
+
const result = await clientFromConfig().callTool('docs_download', { node_code: nodeCode });
|
|
31
|
+
if (result.isError) {
|
|
32
|
+
throw new BusinessError(result.text);
|
|
33
|
+
}
|
|
34
|
+
const payload = parseToolJson(result);
|
|
35
|
+
if (!payload.downloadUrl) {
|
|
36
|
+
throw new BusinessError(`服务未返回下载地址:nodeCode=${nodeCode}`);
|
|
37
|
+
}
|
|
38
|
+
const savedPath = resolvePath(await resolveDownloadPath(out, payload.fileName, nodeCode));
|
|
39
|
+
const size = await downloadToFile(payload.downloadUrl, savedPath);
|
|
40
|
+
if (json) {
|
|
41
|
+
printJson({ nodeCode, fileName: payload.fileName ?? null, savedPath, size });
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
print(`已下载:${basename(savedPath)} → ${savedPath}(${size} 字节)`);
|
|
45
|
+
}
|
|
46
|
+
return EXIT.OK;
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
export async function resolveDownloadPath(out, fileName, nodeCode) {
|
|
50
|
+
const name = (fileName === undefined ? '' : basename(fileName).trim()) || nodeCode;
|
|
51
|
+
if (out === undefined) {
|
|
52
|
+
return name;
|
|
53
|
+
}
|
|
54
|
+
const isDir = (await stat(out).catch(() => undefined))?.isDirectory() === true;
|
|
55
|
+
return isDir ? resolvePath(out, name) : out;
|
|
56
|
+
}
|
|
57
|
+
async function downloadToFile(url, filePath) {
|
|
58
|
+
let response;
|
|
59
|
+
try {
|
|
60
|
+
response = await fetch(url);
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
throw new NetworkError(`无法连接文件存储:${e.message}`);
|
|
64
|
+
}
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
throw new BusinessError(`文件下载失败:HTTP ${response.status}`, '预签名地址有时效,请重新执行 yos docs download 获取新地址');
|
|
67
|
+
}
|
|
68
|
+
if (!response.body) {
|
|
69
|
+
throw new BusinessError(`文件下载失败:响应无内容,url=${url.slice(0, 120)}`);
|
|
70
|
+
}
|
|
71
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
72
|
+
const handle = await open(filePath, 'w');
|
|
73
|
+
try {
|
|
74
|
+
let size = 0;
|
|
75
|
+
for await (const chunk of response.body) {
|
|
76
|
+
const bytes = chunk;
|
|
77
|
+
await handle.write(bytes);
|
|
78
|
+
size += bytes.byteLength;
|
|
79
|
+
}
|
|
80
|
+
return size;
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
await handle.close();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { register } from '../cli.js';
|
|
2
|
+
import { loadConfig, maskKey, resolveEndpoint, resolveKey } from '../config.js';
|
|
3
|
+
import { CliError, EXIT } from '../errors.js';
|
|
4
|
+
import { formatIdentity, OpenApisClient } from '../openapis-client.js';
|
|
5
|
+
import { print, printJson } from '../output.js';
|
|
6
|
+
import { readPackageMeta } from '../npm-meta.js';
|
|
7
|
+
import { loadVersionState } from '../version-state.js';
|
|
8
|
+
register({
|
|
9
|
+
name: 'doctor',
|
|
10
|
+
description: '诊断配置、访问令牌、连通、Skill 状态与 CLI 版本',
|
|
11
|
+
usage: 'yos doctor [--json]',
|
|
12
|
+
group: '基础',
|
|
13
|
+
options: { json: { type: 'boolean' } },
|
|
14
|
+
paramNotes: [['--json', '结构化输出(检查项列表)']],
|
|
15
|
+
async run({ flags }) {
|
|
16
|
+
const checks = [];
|
|
17
|
+
const config = loadConfig();
|
|
18
|
+
checks.push({
|
|
19
|
+
name: '配置文件',
|
|
20
|
+
ok: config !== null,
|
|
21
|
+
detail: config ? '已找到' : '未找到,请执行 yos login',
|
|
22
|
+
});
|
|
23
|
+
checks.push({
|
|
24
|
+
name: '服务端点',
|
|
25
|
+
ok: true,
|
|
26
|
+
detail: `${resolveEndpoint(config)}(来自 ${endpointSource(config)})`,
|
|
27
|
+
});
|
|
28
|
+
let key = null;
|
|
29
|
+
try {
|
|
30
|
+
key = resolveKey(config);
|
|
31
|
+
checks.push({ name: '访问令牌', ok: true, detail: maskKey(key) });
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
checks.push({ name: '访问令牌', ok: false, detail: e.message });
|
|
35
|
+
}
|
|
36
|
+
let connectionFailure = null;
|
|
37
|
+
if (!key) {
|
|
38
|
+
checks.push({ name: '服务连通', ok: false, detail: '未配置访问令牌,未验证(先执行 yos login)' });
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
try {
|
|
42
|
+
const identity = await new OpenApisClient(`${resolveEndpoint(config)}/open-apis`, key).whoami();
|
|
43
|
+
checks.push({ name: '服务连通', ok: true, detail: `身份 ${formatIdentity(identity)}` });
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
const failure = e instanceof CliError ? e : new CliError(String(e), EXIT.BUSINESS);
|
|
47
|
+
connectionFailure = failure;
|
|
48
|
+
checks.push({ name: '服务连通', ok: false, detail: failure.message });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const skillDirs = config?.installedSkillDirs ?? [];
|
|
52
|
+
checks.push({
|
|
53
|
+
name: 'Skill 安装',
|
|
54
|
+
ok: skillDirs.length > 0,
|
|
55
|
+
detail: skillDirs.length > 0 ? skillDirs.join(', ') : '尚未安装,执行 yos skill install',
|
|
56
|
+
});
|
|
57
|
+
const meta = readPackageMeta();
|
|
58
|
+
checks.push({
|
|
59
|
+
name: 'CLI 版本',
|
|
60
|
+
ok: meta !== null,
|
|
61
|
+
detail: meta ? `${meta.version}(endpoint=${resolveEndpoint(config)})` : 'unknown(未找到 package.json)',
|
|
62
|
+
});
|
|
63
|
+
const state = loadVersionState();
|
|
64
|
+
checks.push({
|
|
65
|
+
name: 'Skill 同步',
|
|
66
|
+
ok: meta !== null && state?.version === meta.version && skillDirs.length > 0,
|
|
67
|
+
detail: skillSyncDetail(meta, state, skillDirs),
|
|
68
|
+
});
|
|
69
|
+
if (flags.json) {
|
|
70
|
+
printJson({ checks });
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
print(checks.map((c) => `${c.ok ? '✓' : '✗'} ${c.name}: ${c.detail}`).join('\n'));
|
|
74
|
+
}
|
|
75
|
+
const firstFail = checks.find((c) => !c.ok);
|
|
76
|
+
if (!firstFail) {
|
|
77
|
+
return EXIT.OK;
|
|
78
|
+
}
|
|
79
|
+
return connectionFailure?.exitCode ?? EXIT.USAGE;
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
function endpointSource(config) {
|
|
83
|
+
if (process.env.YOS_ENDPOINT) {
|
|
84
|
+
return 'YOS_ENDPOINT';
|
|
85
|
+
}
|
|
86
|
+
return config?.endpoint ? '配置文件' : '内置缺省';
|
|
87
|
+
}
|
|
88
|
+
function skillSyncDetail(meta, state, skillDirs) {
|
|
89
|
+
if (skillDirs.length === 0) {
|
|
90
|
+
return '尚未安装,执行 yos skill install';
|
|
91
|
+
}
|
|
92
|
+
if (meta === null) {
|
|
93
|
+
return '无法比对(CLI 版本未知)';
|
|
94
|
+
}
|
|
95
|
+
if (state === null) {
|
|
96
|
+
return `缺少版本状态(Skill 目录 ${skillDirs.length} 个),执行 yos update 重同步`;
|
|
97
|
+
}
|
|
98
|
+
if (state.version === meta.version) {
|
|
99
|
+
return `与 CLI 一致(${skillDirs.length} 个目录)`;
|
|
100
|
+
}
|
|
101
|
+
return `Skill 与 CLI 版本不一致(状态 ${state.version},CLI ${meta.version}),执行 yos update`;
|
|
102
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { register } from '../cli.js';
|
|
2
|
+
import { DOMAIN_COMMANDS, DOMAIN_META } from '../domain-commands.js';
|
|
3
|
+
import { buildOptionsForSpec, buildParamAliasForSpec, buildParamNotesForSpec, buildUsageForSpec, runDomainCommand } from '../domain-executor.js';
|
|
4
|
+
for (const spec of DOMAIN_COMMANDS) {
|
|
5
|
+
register({
|
|
6
|
+
name: spec.name,
|
|
7
|
+
description: spec.description,
|
|
8
|
+
usage: buildUsageForSpec(spec),
|
|
9
|
+
group: DOMAIN_META[spec.domain].groupLabel,
|
|
10
|
+
risk: spec.risk,
|
|
11
|
+
options: buildOptionsForSpec(spec),
|
|
12
|
+
paramNotes: buildParamNotesForSpec(spec),
|
|
13
|
+
paramAlias: buildParamAliasForSpec(spec),
|
|
14
|
+
run: (context) => runDomainCommand(spec, context),
|
|
15
|
+
});
|
|
16
|
+
}
|