@yosai/cli 0.2.1-test.3 → 0.2.1-test.7
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/README.md +10 -8
- package/dist/cli.js +5 -2
- package/dist/commands/doctor.js +7 -9
- package/dist/commands/login.js +7 -12
- package/dist/commands/ping.js +4 -4
- package/dist/commands/update.js +8 -7
- package/dist/commands/version.js +8 -8
- package/dist/config.js +12 -23
- package/dist/endpoints.js +14 -9
- package/dist/errors.js +6 -0
- package/dist/npm-meta.js +88 -11
- package/dist/openapis-client.js +25 -6
- package/dist/skill-sync.js +2 -2
- package/dist/skills/yos/SKILL.md +9 -9
- package/dist/skills/yos-install/SKILL.md +8 -6
- package/dist/skills/{yos-kb → yos-knowledge}/SKILL.md +1 -1
- package/dist/skills/yos-shared/SKILL.md +3 -2
- package/dist/update-check-worker.js +98 -0
- package/dist/update-notice.js +88 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ YOS 能力的 Agent 执行入口:终端直调 YOS 工具 + 知识库文件上
|
|
|
10
10
|
## 快速开始
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
yos login #
|
|
13
|
+
yos login # 配置访问令牌(客户端「个人设置-访问令牌」页创建并复制)与服务环境
|
|
14
14
|
yos doctor # 诊断配置与连通(显示当前身份)
|
|
15
15
|
yos ping # 探活:连接与令牌有效性(退出码即结论,客户端对接用)
|
|
16
16
|
yos kb list # 我的知识库
|
|
@@ -27,7 +27,7 @@ yos tool list # 底层工具通道(领域命令未覆盖
|
|
|
27
27
|
|
|
28
28
|
| 命令 | 说明 |
|
|
29
29
|
|---|---|
|
|
30
|
-
| `yos login` |
|
|
30
|
+
| `yos login` | 配置访问令牌与服务环境(校验身份后保存) |
|
|
31
31
|
| `yos doctor` | 诊断配置、访问令牌、连通、Skill 状态与 CLI 版本 |
|
|
32
32
|
| `yos ping` | 探活:检查连接是否成功、令牌是否有效(退出码 0/2/3/4 即结论,客户端检测对接用) |
|
|
33
33
|
| `yos kb <动作>` | 知识库:`list` / `detail` / `update` / `upload`(建库、删库请到 YOS 客户端) |
|
|
@@ -36,11 +36,11 @@ yos tool list # 底层工具通道(领域命令未覆盖
|
|
|
36
36
|
| `yos support <动作>` | 支持:`manual` / `manual-read` / `manual-search` / `upgrade` / `upgrade-log` / `feedback` |
|
|
37
37
|
| `yos transcript <动作>` | 转写:`list` / `detail` |
|
|
38
38
|
| `yos tool list` / `schema` / `call` | 底层工具通道(领域命令未覆盖时兜底) |
|
|
39
|
-
| `yos version [--check]` | CLI 版本(`--check`
|
|
40
|
-
| `yos update [--check]` |
|
|
39
|
+
| `yos version [--check]` | CLI 版本(`--check` 比对对应通道最新版) |
|
|
40
|
+
| `yos update [--check]` | 手动升级到对应通道最新版并自动重同步 Skill(平时后台自动更新,一般无需执行) |
|
|
41
41
|
| `yos skill install [--target <dir>]` | 安装内置 Skill |
|
|
42
42
|
|
|
43
|
-
所有命令支持 `--json` 输出与统一退出码(0 成功 / 1 业务失败 / 2 参数错误 / 3 网络 / 4
|
|
43
|
+
所有命令支持 `--json` 输出与统一退出码(0 成功 / 1 业务失败 / 2 参数错误 / 3 网络 / 4 鉴权 / 5 版本过低)。
|
|
44
44
|
|
|
45
45
|
## 给 Agent 使用
|
|
46
46
|
|
|
@@ -55,13 +55,15 @@ yos skill install # 安装 7 个内置 Skill(总入口 + 领域 + 安装
|
|
|
55
55
|
## 配置
|
|
56
56
|
|
|
57
57
|
- 配置文件:`~/.config/yos/config.json`(权限 0600;目录可用 `YOS_CONFIG_DIR` 覆盖)
|
|
58
|
-
- 环境变量(均优先于配置文件,适合 CI/容器场景):`YOS_API_KEY` 访问令牌、`
|
|
59
|
-
-
|
|
58
|
+
- 环境变量(均优先于配置文件,适合 CI/容器场景):`YOS_API_KEY` 访问令牌、`YOS_ENV` 服务环境(prod / test / dev)
|
|
59
|
+
- 自动更新:命令执行后会在后台检查对应通道并静默更新到最新版(下次运行生效,自动重同步 Skill);升级通道跟随服务环境——测试环境跟随 test 通道,dev 不自动更新;设置 `YOS_NO_AUTO_UPDATE=1` 可关闭
|
|
60
|
+
- 服务环境:缺省生产;`yos login --env test` 切到测试环境,`--env dev` 指向本机开发服务(http://localhost:8080);`yos doctor` 显示当前生效环境及其来源
|
|
60
61
|
|
|
61
62
|
## 故障排查
|
|
62
63
|
|
|
63
64
|
- 退出码 4(鉴权失败):访问令牌无效/已吊销/已过期,重新 `yos login`
|
|
64
65
|
- 退出码 2(参数错误):CLI 本地拦截、未发请求,按 `提示:` 里的用法改写参数
|
|
66
|
+
- 退出码 5(版本过低):服务端要求的 CLI 最低版本高于当前版本,执行 `yos update` 升级后重试
|
|
65
67
|
- 上传报 20MB 上限:单文件上限 20MB,更大文件暂不支持
|
|
66
68
|
- 下载报「没有该资源的访问权限:DOWNLOAD.xxx」:只有阅读权限的库不支持下载原文件,需库管理员授权
|
|
67
|
-
- 退出码 3
|
|
69
|
+
- 退出码 3(网络):确认服务地址可达;`yos version --check` 报网络错则是 npm 或 registry 不可达
|
package/dist/cli.js
CHANGED
|
@@ -2,6 +2,7 @@ import { parseArgs } from 'node:util';
|
|
|
2
2
|
import { DOMAIN_META } from './domain-commands.js';
|
|
3
3
|
import { CliError, EXIT, UsageError } from './errors.js';
|
|
4
4
|
import { print, printErr, renderRows } from './output.js';
|
|
5
|
+
import { handleUpdateNotice } from './update-notice.js';
|
|
5
6
|
const registry = [];
|
|
6
7
|
const GROUP_ORDER = ['知识库 kb', '文档 docs', '检索 retrieve', '支持 support', '转写 transcript', '工具通道', 'Agent 接入', '基础'];
|
|
7
8
|
const RISK_LABEL = { read: '只读', write: '写入', danger: '高危' };
|
|
@@ -20,13 +21,12 @@ export function renderHelp() {
|
|
|
20
21
|
'',
|
|
21
22
|
'约定:',
|
|
22
23
|
' - 输出默认人类可读;--json 输出结构化结果(工具返回原文在 text 字段内)',
|
|
23
|
-
' - 退出码:0 成功 / 1 业务失败 / 2 参数错误 / 3 网络错误 / 4 鉴权失败',
|
|
24
|
+
' - 退出码:0 成功 / 1 业务失败 / 2 参数错误 / 3 网络错误 / 4 鉴权失败 / 5 版本过低需升级',
|
|
24
25
|
' - 删除类操作前先用 yos docs count <node_code> 查询影响范围,并取得用户明确确认',
|
|
25
26
|
'',
|
|
26
27
|
'环境变量(均优先于配置文件,便于 CI 免落盘):',
|
|
27
28
|
' YOS_API_KEY 访问令牌',
|
|
28
29
|
' YOS_ENV 服务环境(prod / test / dev;缺省生产)',
|
|
29
|
-
' YOS_ENDPOINT 自定义服务地址(CI / 本地联调,一般无需设置)',
|
|
30
30
|
' YOS_CONFIG_DIR 配置目录(缺省 ~/.config/yos)',
|
|
31
31
|
'',
|
|
32
32
|
'示例:',
|
|
@@ -186,4 +186,7 @@ export async function dispatch(argv) {
|
|
|
186
186
|
printErr(`错误: ${error instanceof Error ? error.message : String(error)}`);
|
|
187
187
|
return EXIT.BUSINESS;
|
|
188
188
|
}
|
|
189
|
+
finally {
|
|
190
|
+
handleUpdateNotice(argv, command.name);
|
|
191
|
+
}
|
|
189
192
|
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { register } from '../cli.js';
|
|
2
|
-
import { loadConfig, maskKey,
|
|
3
|
-
import { envLabel } from '../endpoints.js';
|
|
2
|
+
import { loadConfig, maskKey, resolveEnv, resolveKey } from '../config.js';
|
|
3
|
+
import { endpointOfEnv, envLabel } from '../endpoints.js';
|
|
4
4
|
import { CliError, EXIT } from '../errors.js';
|
|
5
5
|
import { formatIdentity, OpenApisClient } from '../openapis-client.js';
|
|
6
6
|
import { print, printJson } from '../output.js';
|
|
@@ -16,7 +16,8 @@ register({
|
|
|
16
16
|
async run({ flags }) {
|
|
17
17
|
const checks = [];
|
|
18
18
|
const config = loadConfig();
|
|
19
|
-
const
|
|
19
|
+
const env = resolveEnv(config);
|
|
20
|
+
const endpoint = endpointOfEnv(env);
|
|
20
21
|
checks.push({
|
|
21
22
|
name: '配置文件',
|
|
22
23
|
ok: config !== null,
|
|
@@ -25,7 +26,7 @@ register({
|
|
|
25
26
|
checks.push({
|
|
26
27
|
name: '服务环境',
|
|
27
28
|
ok: true,
|
|
28
|
-
detail: `${envLabel(
|
|
29
|
+
detail: `${envLabel(env)}(来自 ${endpointSource(config)})`,
|
|
29
30
|
});
|
|
30
31
|
let key = null;
|
|
31
32
|
try {
|
|
@@ -60,7 +61,7 @@ register({
|
|
|
60
61
|
checks.push({
|
|
61
62
|
name: 'CLI 版本',
|
|
62
63
|
ok: meta !== null,
|
|
63
|
-
detail: meta ? `${meta.version}(环境=${envLabel(
|
|
64
|
+
detail: meta ? `${meta.version}(环境=${envLabel(env)})` : 'unknown(未找到 package.json)',
|
|
64
65
|
});
|
|
65
66
|
const state = loadVersionState();
|
|
66
67
|
checks.push({
|
|
@@ -82,13 +83,10 @@ register({
|
|
|
82
83
|
},
|
|
83
84
|
});
|
|
84
85
|
function endpointSource(config) {
|
|
85
|
-
if (process.env.YOS_ENDPOINT) {
|
|
86
|
-
return 'YOS_ENDPOINT';
|
|
87
|
-
}
|
|
88
86
|
if (process.env.YOS_ENV) {
|
|
89
87
|
return 'YOS_ENV';
|
|
90
88
|
}
|
|
91
|
-
return config?.
|
|
89
|
+
return config?.env ? '配置文件' : '内置缺省';
|
|
92
90
|
}
|
|
93
91
|
function skillSyncDetail(meta, state, skillDirs) {
|
|
94
92
|
if (skillDirs.length === 0) {
|
package/dist/commands/login.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { register } from '../cli.js';
|
|
2
|
-
import { loadConfig, maskKey,
|
|
3
|
-
import {
|
|
2
|
+
import { loadConfig, maskKey, resolveEnv, saveConfig } from '../config.js';
|
|
3
|
+
import { endpointOfEnv, envLabel } from '../endpoints.js';
|
|
4
4
|
import { UsageError } from '../errors.js';
|
|
5
5
|
import { formatIdentity, OpenApisClient } from '../openapis-client.js';
|
|
6
6
|
import { print } from '../output.js';
|
|
@@ -8,24 +8,20 @@ import { readHiddenLine } from '../prompt.js';
|
|
|
8
8
|
register({
|
|
9
9
|
name: 'login',
|
|
10
10
|
description: '配置访问令牌与服务环境(校验身份后保存)',
|
|
11
|
-
usage: 'yos login [--env <name>] [--
|
|
11
|
+
usage: 'yos login [--env <name>] [--key <key>]',
|
|
12
12
|
group: '基础',
|
|
13
13
|
options: {
|
|
14
14
|
env: { type: 'string' },
|
|
15
|
-
endpoint: { type: 'string' },
|
|
16
15
|
key: { type: 'string' },
|
|
17
16
|
},
|
|
18
17
|
paramNotes: [
|
|
19
18
|
['--env', '服务环境(prod / test / dev;缺省读 YOS_ENV,再缺省生产)'],
|
|
20
|
-
['--endpoint', '自定义服务地址(本地联调等;优先于 --env)'],
|
|
21
19
|
['--key', '访问令牌(40 位小写 hex,缺省读 YOS_API_KEY,否则交互输入)'],
|
|
22
20
|
],
|
|
23
21
|
async run({ flags }) {
|
|
24
22
|
const config = loadConfig();
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
env: flags.env,
|
|
28
|
-
});
|
|
23
|
+
const env = resolveEnv(config, { env: flags.env });
|
|
24
|
+
const endpoint = endpointOfEnv(env);
|
|
29
25
|
const key = flags.key
|
|
30
26
|
?? process.env.YOS_API_KEY
|
|
31
27
|
?? await readHiddenLine('请粘贴访问令牌(输入不回显,客户端「个人设置-访问令牌」页创建并复制): ');
|
|
@@ -33,8 +29,7 @@ register({
|
|
|
33
29
|
throw new UsageError('令牌格式不正确(应为 40 位小写 hex)', '请在客户端「个人设置-访问令牌」页创建并复制完整令牌');
|
|
34
30
|
}
|
|
35
31
|
const identity = await new OpenApisClient(`${endpoint}/open-apis`, key).whoami();
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
print(`已保存配置:环境=${envLabel(endpoint)},key=${maskKey(key)},身份 ${formatIdentity(identity)}`);
|
|
32
|
+
saveConfig({ ...config, env, apiKey: key });
|
|
33
|
+
print(`已保存配置:环境=${envLabel(env)},key=${maskKey(key)},身份 ${formatIdentity(identity)}`);
|
|
39
34
|
},
|
|
40
35
|
});
|
package/dist/commands/ping.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { register } from '../cli.js';
|
|
2
|
-
import { loadConfig,
|
|
3
|
-
import {
|
|
2
|
+
import { loadConfig, resolveEnv, resolveKey } from '../config.js';
|
|
3
|
+
import { endpointOfEnv } from '../endpoints.js';
|
|
4
4
|
import { CliError, EXIT } from '../errors.js';
|
|
5
5
|
import { formatIdentity, OpenApisClient } from '../openapis-client.js';
|
|
6
6
|
import { print, printErr, printJson } from '../output.js';
|
|
@@ -13,8 +13,8 @@ register({
|
|
|
13
13
|
paramNotes: [['--json', '结构化输出(ok / env / userCode / userName / error)']],
|
|
14
14
|
async run({ flags }) {
|
|
15
15
|
const config = loadConfig();
|
|
16
|
-
const
|
|
17
|
-
const
|
|
16
|
+
const env = resolveEnv(config);
|
|
17
|
+
const endpoint = endpointOfEnv(env);
|
|
18
18
|
try {
|
|
19
19
|
const identity = await new OpenApisClient(`${endpoint}/open-apis`, resolveKey(config)).whoami();
|
|
20
20
|
if (flags.json) {
|
package/dist/commands/update.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { register } from '../cli.js';
|
|
2
|
-
import { loadConfig } from '../config.js';
|
|
2
|
+
import { loadConfig, resolveChannel } from '../config.js';
|
|
3
3
|
import { BusinessError } from '../errors.js';
|
|
4
4
|
import { compareVersions, detectGlobalInstall, fetchLatestVersion, PUBLIC_REGISTRY, readPackageMeta, runNpm } from '../npm-meta.js';
|
|
5
5
|
import { print, printJson } from '../output.js';
|
|
@@ -7,7 +7,7 @@ import { installSkillsTo } from '../skill-sync.js';
|
|
|
7
7
|
import { saveVersionState } from '../version-state.js';
|
|
8
8
|
register({
|
|
9
9
|
name: 'update',
|
|
10
|
-
description: '检查并升级 CLI
|
|
10
|
+
description: '检查并升级 CLI 到对应通道最新版(升级后自动重同步 Skill)',
|
|
11
11
|
usage: 'yos update [--check] [--json]',
|
|
12
12
|
group: '基础',
|
|
13
13
|
risk: 'write',
|
|
@@ -21,11 +21,12 @@ register({
|
|
|
21
21
|
if (!meta) {
|
|
22
22
|
throw new BusinessError('无法确定包名(未找到 package.json)', '请通过 npm 全局安装后重试');
|
|
23
23
|
}
|
|
24
|
-
const
|
|
24
|
+
const channel = resolveChannel(loadConfig()) ?? 'latest';
|
|
25
|
+
const latest = await fetchLatestVersion(meta.name, channel);
|
|
25
26
|
const outdated = compareVersions(meta.version, latest) < 0;
|
|
26
27
|
if (flags.check) {
|
|
27
28
|
if (flags.json) {
|
|
28
|
-
printJson({ version: meta.version, latest, outdated });
|
|
29
|
+
printJson({ version: meta.version, latest, outdated, channel });
|
|
29
30
|
}
|
|
30
31
|
else {
|
|
31
32
|
print(outdated
|
|
@@ -43,15 +44,15 @@ register({
|
|
|
43
44
|
const shape = await detectGlobalInstall(meta);
|
|
44
45
|
if (!shape.global) {
|
|
45
46
|
print(`当前安装不在 npm 全局目录(${meta.dir}),不代跑升级`);
|
|
46
|
-
throw new BusinessError('非全局安装,已跳过自动升级', `手动升级:npm install -g ${meta.name}
|
|
47
|
+
throw new BusinessError('非全局安装,已跳过自动升级', `手动升级:npm install -g ${meta.name}@${channel} --registry=${PUBLIC_REGISTRY};或先 npm uninstall -g ${meta.name} 再全局安装`);
|
|
47
48
|
}
|
|
48
|
-
const install = await runNpm(['install', '-g', `${meta.name}
|
|
49
|
+
const install = await runNpm(['install', '-g', `${meta.name}@${channel}`, `--registry=${PUBLIC_REGISTRY}`]);
|
|
49
50
|
if (!install.ok) {
|
|
50
51
|
const output = `${install.stderr}\n${install.stdout}`;
|
|
51
52
|
if (/EACCES|EPERM|permission denied/i.test(output)) {
|
|
52
53
|
throw new BusinessError('npm 全局目录没有写权限', '把 npm 前缀改到用户目录后重试(npm config set prefix ~/.npm-global 并把其 bin 加入 PATH),不要 sudo npm install -g');
|
|
53
54
|
}
|
|
54
|
-
throw new BusinessError(`npm install -g ${meta.name}
|
|
55
|
+
throw new BusinessError(`npm install -g ${meta.name}@${channel} 失败:${firstLine(install.stderr) || firstLine(install.stdout) || '无输出'}`);
|
|
55
56
|
}
|
|
56
57
|
syncSkills();
|
|
57
58
|
saveVersionState(latest);
|
package/dist/commands/version.js
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
import { register } from '../cli.js';
|
|
2
|
-
import { loadConfig,
|
|
3
|
-
import { envNameOf } from '../endpoints.js';
|
|
2
|
+
import { loadConfig, resolveChannel, resolveEnv } from '../config.js';
|
|
4
3
|
import { BusinessError } from '../errors.js';
|
|
5
4
|
import { compareVersions, detectGlobalInstall, fetchLatestVersion, readPackageMeta } from '../npm-meta.js';
|
|
6
5
|
import { print, printJson } from '../output.js';
|
|
7
6
|
register({
|
|
8
7
|
name: 'version',
|
|
9
|
-
description: '显示 CLI 版本(--check
|
|
8
|
+
description: '显示 CLI 版本(--check 比对对应通道最新版)',
|
|
10
9
|
usage: 'yos version [--json] [--check]',
|
|
11
10
|
group: '基础',
|
|
12
11
|
risk: 'read',
|
|
13
12
|
options: { json: { type: 'boolean' }, check: { type: 'boolean' } },
|
|
14
13
|
paramNotes: [
|
|
15
|
-
['--check', '
|
|
14
|
+
['--check', '与对应通道最新版本比对是否可升级(只读,不写入任何状态)'],
|
|
16
15
|
['--json', '结构化输出'],
|
|
17
16
|
],
|
|
18
17
|
async run({ flags }) {
|
|
19
18
|
const meta = readPackageMeta();
|
|
20
19
|
const version = meta?.version ?? 'unknown';
|
|
21
|
-
const
|
|
22
|
-
const env =
|
|
20
|
+
const config = loadConfig();
|
|
21
|
+
const env = resolveEnv(config);
|
|
23
22
|
if (!flags.check) {
|
|
24
23
|
if (flags.json) {
|
|
25
24
|
const installedGlobally = meta ? (await detectGlobalInstall(meta)).global : false;
|
|
@@ -33,10 +32,11 @@ register({
|
|
|
33
32
|
if (!meta) {
|
|
34
33
|
throw new BusinessError('无法确定包名(未找到 package.json)', '请通过 npm 全局安装后重试');
|
|
35
34
|
}
|
|
36
|
-
const
|
|
35
|
+
const channel = resolveChannel(config) ?? 'latest';
|
|
36
|
+
const latest = await fetchLatestVersion(meta.name, channel);
|
|
37
37
|
const outdated = compareVersions(meta.version, latest) < 0;
|
|
38
38
|
if (flags.json) {
|
|
39
|
-
printJson({ version, latest, outdated, installedGlobally: (await detectGlobalInstall(meta)).global, env });
|
|
39
|
+
printJson({ version, latest, outdated, installedGlobally: (await detectGlobalInstall(meta)).global, env, channel });
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
print(outdated
|
package/dist/config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import { channelOfEnv, DEFAULT_ENV, endpointOfEnv } from './endpoints.js';
|
|
5
5
|
import { UsageError } from './errors.js';
|
|
6
6
|
export function configDir() {
|
|
7
7
|
return process.env.YOS_CONFIG_DIR ?? join(homedir(), '.config', 'yos');
|
|
@@ -15,7 +15,9 @@ export function loadConfig() {
|
|
|
15
15
|
return null;
|
|
16
16
|
}
|
|
17
17
|
try {
|
|
18
|
-
|
|
18
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
19
|
+
delete parsed.endpoint;
|
|
20
|
+
return parsed;
|
|
19
21
|
}
|
|
20
22
|
catch {
|
|
21
23
|
return null;
|
|
@@ -35,29 +37,16 @@ export function resolveKey(config) {
|
|
|
35
37
|
}
|
|
36
38
|
throw new UsageError('未配置访问令牌', '请执行 `yos login` 或设置环境变量 YOS_API_KEY');
|
|
37
39
|
}
|
|
40
|
+
export function resolveEnv(config, flags) {
|
|
41
|
+
const env = flags?.env || process.env.YOS_ENV || config?.env || DEFAULT_ENV;
|
|
42
|
+
endpointOfEnv(env);
|
|
43
|
+
return env;
|
|
44
|
+
}
|
|
38
45
|
export function resolveEndpoint(config, flags) {
|
|
39
|
-
|
|
40
|
-
return normalizeEndpoint(flags.endpoint);
|
|
41
|
-
}
|
|
42
|
-
if (flags?.env) {
|
|
43
|
-
return endpointOfEnv(flags.env);
|
|
44
|
-
}
|
|
45
|
-
if (process.env.YOS_ENDPOINT) {
|
|
46
|
-
return normalizeEndpoint(process.env.YOS_ENDPOINT);
|
|
47
|
-
}
|
|
48
|
-
if (process.env.YOS_ENV) {
|
|
49
|
-
return endpointOfEnv(process.env.YOS_ENV);
|
|
50
|
-
}
|
|
51
|
-
if (config?.endpoint) {
|
|
52
|
-
return normalizeEndpoint(config.endpoint);
|
|
53
|
-
}
|
|
54
|
-
if (config?.env) {
|
|
55
|
-
return endpointOfEnv(config.env);
|
|
56
|
-
}
|
|
57
|
-
return DEFAULT_ENDPOINT;
|
|
46
|
+
return endpointOfEnv(resolveEnv(config, flags));
|
|
58
47
|
}
|
|
59
|
-
export function
|
|
60
|
-
return
|
|
48
|
+
export function resolveChannel(config) {
|
|
49
|
+
return channelOfEnv(resolveEnv(config));
|
|
61
50
|
}
|
|
62
51
|
export function maskKey(key) {
|
|
63
52
|
return key.length <= 12 ? '****' : `${key.slice(0, 8)}****${key.slice(-4)}`;
|
package/dist/endpoints.js
CHANGED
|
@@ -5,7 +5,6 @@ export const ENVIRONMENTS = {
|
|
|
5
5
|
dev: { url: 'http://localhost:8080', label: '开发' },
|
|
6
6
|
};
|
|
7
7
|
export const DEFAULT_ENV = 'prod';
|
|
8
|
-
export const DEFAULT_ENDPOINT = ENVIRONMENTS[DEFAULT_ENV].url;
|
|
9
8
|
export function endpointOfEnv(env) {
|
|
10
9
|
const found = ENVIRONMENTS[env];
|
|
11
10
|
if (!found) {
|
|
@@ -13,16 +12,22 @@ export function endpointOfEnv(env) {
|
|
|
13
12
|
}
|
|
14
13
|
return found.url;
|
|
15
14
|
}
|
|
16
|
-
|
|
15
|
+
const CHANNEL_OF_ENV = {
|
|
16
|
+
prod: 'latest',
|
|
17
|
+
test: 'test',
|
|
18
|
+
};
|
|
19
|
+
export function channelOfEnv(env) {
|
|
20
|
+
return CHANNEL_OF_ENV[env] ?? null;
|
|
21
|
+
}
|
|
22
|
+
export function envLabel(env) {
|
|
23
|
+
return ENVIRONMENTS[env]?.label ?? env;
|
|
24
|
+
}
|
|
25
|
+
export function labelOfEndpoint(endpoint) {
|
|
17
26
|
const normalized = endpoint.replace(/\/+$/, '');
|
|
18
|
-
for (const
|
|
27
|
+
for (const env of Object.values(ENVIRONMENTS)) {
|
|
19
28
|
if (env.url === normalized) {
|
|
20
|
-
return
|
|
29
|
+
return env.label;
|
|
21
30
|
}
|
|
22
31
|
}
|
|
23
|
-
return
|
|
24
|
-
}
|
|
25
|
-
export function envLabel(endpoint) {
|
|
26
|
-
const name = envNameOf(endpoint);
|
|
27
|
-
return name ? ENVIRONMENTS[name].label : endpoint;
|
|
32
|
+
return endpoint;
|
|
28
33
|
}
|
package/dist/errors.js
CHANGED
|
@@ -4,6 +4,7 @@ export const EXIT = {
|
|
|
4
4
|
USAGE: 2,
|
|
5
5
|
NETWORK: 3,
|
|
6
6
|
AUTH: 4,
|
|
7
|
+
UPGRADE: 5,
|
|
7
8
|
};
|
|
8
9
|
export class CliError extends Error {
|
|
9
10
|
exitCode;
|
|
@@ -35,3 +36,8 @@ export class AuthError extends CliError {
|
|
|
35
36
|
super(message, EXIT.AUTH, '请执行 `yos login` 重新配置访问令牌');
|
|
36
37
|
}
|
|
37
38
|
}
|
|
39
|
+
export class UpgradeError extends CliError {
|
|
40
|
+
constructor(message, hint) {
|
|
41
|
+
super(message, EXIT.UPGRADE, hint ?? '执行 `yos update` 升级后重试');
|
|
42
|
+
}
|
|
43
|
+
}
|
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
|
|
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}
|
|
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}
|
|
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 =
|
|
62
|
-
const right =
|
|
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:
|
|
146
|
+
global: isPackageUnderPrefix(meta.dir, prefix),
|
|
80
147
|
prefix,
|
|
81
148
|
};
|
|
82
149
|
}
|
|
83
|
-
function
|
|
84
|
-
|
|
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 {
|
package/dist/openapis-client.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { readFile, stat } from 'node:fs/promises';
|
|
2
2
|
import { basename } from 'node:path';
|
|
3
|
-
import {
|
|
4
|
-
import { AuthError, BusinessError, NetworkError } from './errors.js';
|
|
3
|
+
import { labelOfEndpoint } from './endpoints.js';
|
|
4
|
+
import { AuthError, BusinessError, NetworkError, UpgradeError } from './errors.js';
|
|
5
|
+
import { getCliVersion } from './npm-meta.js';
|
|
5
6
|
export const MAX_UPLOAD_SIZE_BYTES = 20 * 1024 * 1024;
|
|
7
|
+
export const CLI_VERSION_HEADER = 'X-Yos-Cli-Version';
|
|
6
8
|
export function formatIdentity(identity) {
|
|
7
9
|
return identity.userName ? `${identity.userName}(${identity.userCode})` : identity.userCode;
|
|
8
10
|
}
|
|
@@ -34,6 +36,7 @@ export class OpenApisClient {
|
|
|
34
36
|
headers: {
|
|
35
37
|
'Content-Type': 'application/json',
|
|
36
38
|
'Authorization': `Bearer ${this.apiKey}`,
|
|
39
|
+
[CLI_VERSION_HEADER]: getCliVersion(),
|
|
37
40
|
},
|
|
38
41
|
body: JSON.stringify(body ?? {}),
|
|
39
42
|
});
|
|
@@ -45,7 +48,7 @@ export class OpenApisClient {
|
|
|
45
48
|
}
|
|
46
49
|
}
|
|
47
50
|
function envLabelOfBase(base) {
|
|
48
|
-
return
|
|
51
|
+
return labelOfEndpoint(base.replace(/\/open-apis$/, ''));
|
|
49
52
|
}
|
|
50
53
|
async function parseEnvelope(response) {
|
|
51
54
|
if (response.status === 401) {
|
|
@@ -61,7 +64,23 @@ async function parseEnvelope(response) {
|
|
|
61
64
|
throw new AuthError(`鉴权失败:${reason}`);
|
|
62
65
|
}
|
|
63
66
|
if (response.status === 404) {
|
|
64
|
-
throw new NetworkError(`HTTP 404:服务端不存在该接口(${response.url})`, '
|
|
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);
|
|
65
84
|
}
|
|
66
85
|
let payload;
|
|
67
86
|
try {
|
|
@@ -105,12 +124,12 @@ export async function uploadKnowledgeFile(endpoint, apiKey, filePath, kbCode, pa
|
|
|
105
124
|
try {
|
|
106
125
|
response = await fetch(`${endpoint}/open-apis/knowledge/file_upload`, {
|
|
107
126
|
method: 'POST',
|
|
108
|
-
headers: { 'Authorization': `Bearer ${apiKey}
|
|
127
|
+
headers: { 'Authorization': `Bearer ${apiKey}`, [CLI_VERSION_HEADER]: getCliVersion() },
|
|
109
128
|
body: form,
|
|
110
129
|
});
|
|
111
130
|
}
|
|
112
131
|
catch (e) {
|
|
113
|
-
throw new NetworkError(`无法连接 YOS 服务(${
|
|
132
|
+
throw new NetworkError(`无法连接 YOS 服务(${labelOfEndpoint(endpoint)}),原因:${e.message}`);
|
|
114
133
|
}
|
|
115
134
|
return parseEnvelope(response);
|
|
116
135
|
}
|
package/dist/skill-sync.js
CHANGED
|
@@ -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-
|
|
9
|
-
const AGENT_SKILL_DIRS = ['.claude/skills', '.workbuddy/skills', '.cursor/skills', '.codex/skills', '.qoder/skills', '.qoder-cn/skills', '.
|
|
8
|
+
export const BUILTIN_SKILLS = ['yos', 'yos-shared', 'yos-knowledge', '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');
|
package/dist/skills/yos/SKILL.md
CHANGED
|
@@ -11,23 +11,23 @@ YOS 是企业 AI 底座:公司知识、会议录音、帮助手册等能力都
|
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
13
|
yos doctor # 七项 ✓ 才继续:配置文件 / 服务环境 / 访问令牌 / 服务连通(回显身份)/ Skill 安装 / CLI 版本 / Skill 同步
|
|
14
|
-
yos ping # 只要探活,退出码即结论(0 通 / 3 网络 / 4
|
|
14
|
+
yos ping # 只要探活,退出码即结论(0 通 / 3 网络 / 4 鉴权 / 5 版本过低)
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
- `command not found: yos` 或 `yos doctor` 报「未找到配置文件」→ 按 `yos-install` skill 引导用户装 CLI 与登录,不要继续猜命令
|
|
18
18
|
- 已装但七项没全 ✓ → 先修环境再执行任务;`yos doctor` 的失败项自带修复提示
|
|
19
|
-
- 查版本用 `yos version`(或 `yos --version
|
|
19
|
+
- 查版本用 `yos version`(或 `yos --version`);CLI 会后台自动更新(跟随当前环境通道,无需手动升级),需要立即升级用 `yos update`
|
|
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-
|
|
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-
|
|
29
|
-
| 把知识库里的上传文件取回本地 | `docs download <node_code> --out <路径>` | yos-
|
|
30
|
-
| 上传本地文件或目录、新建文档/文件夹、改名、移动、删除、回收站恢复 | `kb upload` `docs create` `docs mkdir` `docs update` `docs rename` `docs move` `docs delete` `docs restore` | yos-
|
|
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
31
|
| 会议录音转写、纪要、逐句发言 | `transcript list` → `transcript detail` | yos-transcript |
|
|
32
32
|
| 「YOS 怎么用」、客户端最新版本、提交问题反馈 | `support manual*` `support upgrade*` `support feedback` | yos-support |
|
|
33
33
|
| 领域命令没覆盖的能力 | `tool list` → `tool schema <name>` → `tool call <name> --params '{...}'` | yos-shared |
|
|
@@ -49,9 +49,9 @@ yos ping # 只要探活,退出码即结论(0 通 / 3 网络 / 4 鉴
|
|
|
49
49
|
## 3. 登录与配置
|
|
50
50
|
|
|
51
51
|
1. 用户在 YOS 客户端「个人设置-访问令牌」页创建并复制令牌(40 位小写 hex)
|
|
52
|
-
2. `yos login` 交互粘贴(不回显);Agent 代执行时用 `yos login --key
|
|
52
|
+
2. `yos login` 交互粘贴(不回显);Agent 代执行时用 `yos login --key <令牌>`
|
|
53
53
|
3. 配置文件 `~/.config/yos/config.json`(0600)。环境变量**优先于配置文件**:`YOS_CONFIG_DIR`(换配置目录)、`YOS_API_KEY`(换令牌)、`YOS_ENV`(换服务环境,如 test)
|
|
54
|
-
4. 服务环境缺省生产,切换用 `yos login --env test`(临时可用 `YOS_ENV=test
|
|
54
|
+
4. 服务环境缺省生产,切换用 `yos login --env test`(临时可用 `YOS_ENV=test`);开发者本地调试用 `--env dev`(指向本机 8080 起的服务);`yos doctor` 会显示当前生效环境及其来源
|
|
55
55
|
5. 退出码 4 = 令牌无效/已吊销/已过期 → 引导重新 `yos login`,不要反复重试同一命令
|
|
56
56
|
|
|
57
57
|
## 4. 常用编排(已实测)
|
|
@@ -82,5 +82,5 @@ yos support upgrade --platform MAC_ARM64 # 客户端最新版
|
|
|
82
82
|
|
|
83
83
|
- 默认输出人类可读文本;需要字段时**加 `--json` 再解析**,不要正则刮文本
|
|
84
84
|
- 分页壳不统一:`kb list` / `support upgrade-log` 为 `records` / `total` / `page` / `size`;`transcript list` 为 `list` / `current` / `size` / `pages` / `total`(转写域解析细则见 yos-transcript)
|
|
85
|
-
- 命令报错时先看退出码:`2` 用法错误(CLI 本地拦截,未发请求,按 `提示:` 改参数)、`1` 业务失败(服务端结论,文案里带具体编码,可直接引给用户)、`3` 网络、`4`
|
|
85
|
+
- 命令报错时先看退出码:`2` 用法错误(CLI 本地拦截,未发请求,按 `提示:` 改参数)、`1` 业务失败(服务端结论,文案里带具体编码,可直接引给用户)、`3` 网络、`4` 鉴权、`5` 版本过低(先 `yos update` 升级再原样重试)
|
|
86
86
|
- 领域命令找不到对应能力时,`yos tool list` 看全量工具,`yos tool schema <name> --json` 拿完整参数说明(不带 `--json` 会截断描述),再 `yos tool call`
|
|
@@ -12,7 +12,7 @@ description: 引导用户在本机安装并配置 YOS CLI(环境检查 → npm
|
|
|
12
12
|
## 0. 引导纪律
|
|
13
13
|
|
|
14
14
|
- 一次只执行一步,把该步命令的**输出原文**给用户,再决定下一步;不要连跑多条命令后统一汇报
|
|
15
|
-
- 需要访问令牌时:优先让用户自己在终端输入(`yos login` 的输入不回显);确需 Agent 代跑时用 `--key
|
|
15
|
+
- 需要访问令牌时:优先让用户自己在终端输入(`yos login` 的输入不回显);确需 Agent 代跑时用 `--key`/环境变量
|
|
16
16
|
- **凭据零外泄**:访问令牌不写进代码仓库、日志、聊天正文或文档;引用时一律掩码(形如 `xxxxxxxx****xxxx`,即原令牌前 8 位 + `****` + 后 4 位)
|
|
17
17
|
- 不擅自改用户的全局配置:写 `~/.npmrc`、改 `npm prefix`、删 skill 目录这类动作先取得用户同意
|
|
18
18
|
- 全程不需要 `sudo`。权限报错按第 7 节处置,不要 `sudo npm install -g`(会把 root 拥有的文件混进全局目录,后续更难维护)
|
|
@@ -76,7 +76,7 @@ yos login --key '<40 位令牌>'
|
|
|
76
76
|
yos skill install
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
- 自动探测下列目录,**只对已存在的目录写入**(一台机器可能同时命中多个):`~/.claude/skills`、`~/.workbuddy/skills`、`~/.cursor/skills`、`~/.codex/skills`、`~/.qoder/skills`、`~/.qoder-cn/skills`、`~/.
|
|
79
|
+
- 自动探测下列目录,**只对已存在的目录写入**(一台机器可能同时命中多个):`~/.claude/skills`、`~/.workbuddy/skills`、`~/.cursor/skills`、`~/.codex/skills`、`~/.qoder/skills`、`~/.qoder-cn/skills`、`~/.agents/skills`
|
|
80
80
|
- 成功输出(数字随内置 Skill 数量变化):
|
|
81
81
|
|
|
82
82
|
```
|
|
@@ -124,13 +124,15 @@ yos kb list --json # 端到端实跑一条只读命令收尾
|
|
|
124
124
|
|
|
125
125
|
## 6. 升级与卸载
|
|
126
126
|
|
|
127
|
+
CLI 平时无需手动升级:每次命令执行后会在后台自动检查对应通道并静默更新(升级后自动重同步 Skill),下次运行生效。升级通道跟随服务环境:生产跟 latest 正式版,测试环境(`login --env test`)自动跟 test 通道,dev 不自动更新。
|
|
128
|
+
|
|
127
129
|
```bash
|
|
128
|
-
yos update #
|
|
130
|
+
yos update # 需要立即升级时执行(到当前通道最新版并自动重同步 Skill)
|
|
129
131
|
yos update --check # 只查是否有新版本,不安装
|
|
130
|
-
npm install -g @yosai/cli@latest #
|
|
132
|
+
npm install -g @yosai/cli@latest # 等价手动写法(正式版);走这条路后需再跑 yos skill install 刷新 Skill
|
|
131
133
|
```
|
|
132
134
|
|
|
133
|
-
(0.2.0 之前的版本没有 `yos update`,直接用 `npm install -g @yosai/cli@latest`
|
|
135
|
+
(0.2.0 之前的版本没有 `yos update`,直接用 `npm install -g @yosai/cli@latest` 升级;如需关闭后台自动更新,设置环境变量 `YOS_NO_AUTO_UPDATE=1`)
|
|
134
136
|
|
|
135
137
|
卸载(破坏性,逐项取得用户确认后再动手):
|
|
136
138
|
|
|
@@ -156,7 +158,7 @@ ls ~/.claude/skills | grep -E '^yos' # 先看要删哪些,
|
|
|
156
158
|
|
|
157
159
|
## 8. 装好之后
|
|
158
160
|
|
|
159
|
-
- 让用户从 `yos` skill(总入口)开始使用;命令细节看各领域 skill:`yos-
|
|
161
|
+
- 让用户从 `yos` skill(总入口)开始使用;命令细节看各领域 skill:`yos-knowledge`(知识库管理)、`yos-retrieval`(检索与引用)、`yos-transcript`(录音转写)、`yos-support`(帮助/版本/反馈)、`yos-shared`(共享约定)
|
|
160
162
|
- 本机可用能力:28 个领域命令(kb 4 / docs 11 / retrieve 5 / support 6 / transcript 2)+ `yos tool` 兜底通道,覆盖知识库检索与管理、文档上传下载、会议转写查询、帮助手册、客户端版本与反馈;新建与删除知识库不在 CLI 内,需到 YOS 客户端手动操作
|
|
161
163
|
- 边界(别过度承诺):不能实时控制转写、不能申请/变更权限、反馈不带附件、只能看到当前令牌归属用户可见的数据
|
|
162
164
|
- 排查建议:任何失败先跑 `yos doctor`;退出码 `4` 一律重新登录,`3` 先看「服务环境」项与网络
|
|
@@ -35,9 +35,9 @@ yos doctor # 七项全 ✓ 才能继续;任一项 ✗ 按下方「未登
|
|
|
35
35
|
|
|
36
36
|
配置细节(排障时才需要):
|
|
37
37
|
|
|
38
|
-
- 配置文件 `~/.config/yos/config.json`(目录 0700、文件 0600),字段 `{env, apiKey, installedSkillDirs}
|
|
38
|
+
- 配置文件 `~/.config/yos/config.json`(目录 0700、文件 0600),字段 `{env, apiKey, installedSkillDirs}`
|
|
39
39
|
- 环境变量优先级:`YOS_API_KEY` > 配置文件 `apiKey`;`YOS_ENV` > 配置文件 `env`;`YOS_CONFIG_DIR` 覆盖配置目录(测试隔离用)
|
|
40
|
-
- 服务环境缺省生产,切换用 `yos login --env test`(临时用 `YOS_ENV=test`);开发者本地调试用 `--env dev`(指向本机 8080
|
|
40
|
+
- 服务环境缺省生产,切换用 `yos login --env test`(临时用 `YOS_ENV=test`);开发者本地调试用 `--env dev`(指向本机 8080 起的服务);`yos doctor` 的「服务环境」项显示当前环境与来源
|
|
41
41
|
- 令牌格式先本地校验:`yos login` 粘贴了非 40 位小写 hex 会直接报 `错误: 令牌格式不正确(应为 40 位小写 hex)`,不会落盘
|
|
42
42
|
|
|
43
43
|
**安全**:绝不把令牌写进命令历史可见的日志、代码仓库或对用户的回复里;`--key` 传令牌仅用于无交互场景,回复中一律用掩码形式(`xxxxxxxx****xxxx`)。
|
|
@@ -88,6 +88,7 @@ CLI 对两套编码原样透出不做映射。遇到 cortex 侧编码时:可
|
|
|
88
88
|
| 2 | 参数/用法错误(CLI 本地拦截,未发请求) | `错误: 缺少 --kind`<br>`提示: 用法: yos retrieve neighbors <target> --kind <值> ...` | 照 `提示:` 里的用法改写 |
|
|
89
89
|
| 3 | 网络错误/服务不可达 | `错误: 无法连接 YOS 服务(生产),原因:fetch failed` | 确认服务环境与网络连通,`yos ping` 复测 |
|
|
90
90
|
| 4 | 鉴权失败 | `错误: 鉴权失败...`<br>`提示: 请执行 \`yos login\` 重新配置访问令牌` | 令牌无效/吊销/过期 → 让用户重新创建后 `yos login` |
|
|
91
|
+
| 5 | 版本过低(服务端最低版本门槛拒绝,请求未生效) | `错误: CLI 版本过低(当前 0.2.0,最低要求 0.3.0),请升级后重试`<br>`提示: 执行 \`yos update\` 升级后重试` | 先执行 `yos update` 升级再原样重试;不要改参数反复试探 |
|
|
91
92
|
|
|
92
93
|
`1` 与 `2` 的关键差别:**2 根本没发请求**,改法一定写在 `提示:` 里;**1 是服务端结论**,文案里带具体编码,直接引用给用户。
|
|
93
94
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { mkdirSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { configDir, loadConfig, resolveChannel } from './config.js';
|
|
5
|
+
import { compareVersions, detectGlobalInstall, fetchLatestVersion, PUBLIC_REGISTRY, readPackageMeta, runNpm, } from './npm-meta.js';
|
|
6
|
+
import { installSkillsTo } from './skill-sync.js';
|
|
7
|
+
import { saveCheckState } from './update-notice.js';
|
|
8
|
+
import { saveVersionState } from './version-state.js';
|
|
9
|
+
const LOCK_STALE_MS = 10 * 60 * 1000;
|
|
10
|
+
export async function runUpdateCheck() {
|
|
11
|
+
const meta = readPackageMeta();
|
|
12
|
+
if (!meta) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const channel = resolveChannel(loadConfig());
|
|
16
|
+
if (channel === null) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const latest = await fetchLatestVersion(meta.name, channel);
|
|
21
|
+
if (compareVersions(meta.version, latest) < 0 && !autoUpdateDisabled() && acquireUpdateLock()) {
|
|
22
|
+
try {
|
|
23
|
+
await silentUpdate(meta, latest, channel);
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
releaseUpdateLock();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
saveCheckState(latest, channel);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function silentUpdate(meta, latest, channel) {
|
|
35
|
+
const shape = await detectGlobalInstall(meta);
|
|
36
|
+
if (!shape.global) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const install = await runNpm(['install', '-g', `${meta.name}@${channel}`, `--registry=${PUBLIC_REGISTRY}`]);
|
|
40
|
+
if (!install.ok) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const dirs = loadConfig()?.installedSkillDirs ?? [];
|
|
44
|
+
if (dirs.length > 0) {
|
|
45
|
+
installSkillsTo(dirs);
|
|
46
|
+
}
|
|
47
|
+
saveVersionState(latest);
|
|
48
|
+
}
|
|
49
|
+
function autoUpdateDisabled() {
|
|
50
|
+
return Boolean(process.env.YOS_NO_AUTO_UPDATE);
|
|
51
|
+
}
|
|
52
|
+
function updateLockPath() {
|
|
53
|
+
return join(configDir(), 'update.lock');
|
|
54
|
+
}
|
|
55
|
+
function acquireUpdateLock() {
|
|
56
|
+
mkdirSync(configDir(), { recursive: true, mode: 0o700 });
|
|
57
|
+
const path = updateLockPath();
|
|
58
|
+
try {
|
|
59
|
+
writeFileSync(path, `${Date.now()}`, { flag: 'wx', mode: 0o600 });
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (error.code !== 'EEXIST') {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
if (Date.now() - statSync(path).mtimeMs > LOCK_STALE_MS) {
|
|
69
|
+
rmSync(path, { force: true });
|
|
70
|
+
writeFileSync(path, `${Date.now()}`, { flag: 'wx', mode: 0o600 });
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
function releaseUpdateLock() {
|
|
79
|
+
try {
|
|
80
|
+
rmSync(updateLockPath(), { force: true });
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export function isDirectRun(argv1, moduleUrl) {
|
|
86
|
+
if (!argv1) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
return realpathSync(argv1) === realpathSync(fileURLToPath(moduleUrl));
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (isDirectRun(process.argv[1], import.meta.url)) {
|
|
97
|
+
await runUpdateCheck();
|
|
98
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
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
|
+
const CACHE_TTL_MS = 24 * 60 * 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 input.tty && !input.argv.includes('--json') ? 'notify' : 'skip';
|
|
49
|
+
}
|
|
50
|
+
export function handleUpdateNotice(argv, 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
|
+
argv,
|
|
60
|
+
commandName,
|
|
61
|
+
state,
|
|
62
|
+
tty: Boolean(process.stderr.isTTY),
|
|
63
|
+
now: new Date(),
|
|
64
|
+
currentVersion,
|
|
65
|
+
});
|
|
66
|
+
if (decision === 'spawn') {
|
|
67
|
+
spawnUpdateCheckWorker();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (decision === 'notify' && state) {
|
|
71
|
+
printErr(`提示: 有新版本 ${state.latest}(当前 ${currentVersion}),执行 yos update 升级`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function isFresh(state, now) {
|
|
78
|
+
const checkedAt = Date.parse(state.checkedAt);
|
|
79
|
+
return !Number.isNaN(checkedAt) && now.getTime() - checkedAt < CACHE_TTL_MS;
|
|
80
|
+
}
|
|
81
|
+
function spawnUpdateCheckWorker() {
|
|
82
|
+
try {
|
|
83
|
+
const workerPath = fileURLToPath(new URL('./update-check-worker.js', import.meta.url));
|
|
84
|
+
spawn(process.execPath, [workerPath], { detached: true, stdio: 'ignore', windowsHide: true }).unref();
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
}
|
|
88
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yosai/cli",
|
|
3
|
-
"version": "0.2.1-test.
|
|
3
|
+
"version": "0.2.1-test.7",
|
|
4
4
|
"description": "YOS 能力的 Agent 执行入口:工具调用、知识库管理与文件上传",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
"build": "tsc && node scripts/copy-assets.mjs && chmod +x dist/index.js",
|
|
21
21
|
"typecheck": "tsc --noEmit",
|
|
22
22
|
"test": "vitest run",
|
|
23
|
-
"release": "node scripts/release.mjs"
|
|
23
|
+
"release": "node scripts/release.mjs",
|
|
24
|
+
"publish:test": "node scripts/release.mjs --publish --tag test --bump",
|
|
25
|
+
"publish:latest": "node scripts/release.mjs --publish --tag latest --bump"
|
|
24
26
|
},
|
|
25
27
|
"license": "Apache-2.0",
|
|
26
28
|
"private": false,
|