@yixinkj/cli 1.0.3 → 1.0.4
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 +42 -3
- package/index.js +160 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,15 +12,54 @@
|
|
|
12
12
|
npx -y @yixinkj/cli@latest
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
推荐环境变量:
|
|
16
16
|
|
|
17
17
|
```text
|
|
18
18
|
YIXIN_KEY=你的key
|
|
19
19
|
YIXIN_AUTH_URL=https://你的授权服务域名/v1/token
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
也可以通过 MCP tool `configure_auth` 写入本机配置;环境变量优先于本机配置。
|
|
23
|
+
|
|
22
24
|
## 工具
|
|
23
25
|
|
|
26
|
+
### configure_auth
|
|
27
|
+
|
|
28
|
+
参数:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"key": "客户授权 key",
|
|
33
|
+
"authUrl": "https://你的授权服务域名/v1/token",
|
|
34
|
+
"skill": "ads"
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
执行流程:
|
|
39
|
+
|
|
40
|
+
1. 使用传入的 `key`、`authUrl` 向授权服务换取一次短期 token。
|
|
41
|
+
2. 验证成功后写入 `~/.yixin/config.json`。
|
|
42
|
+
3. 文件权限尽量设置为 `0600`。
|
|
43
|
+
4. 环境变量仍然优先;环境变量缺失时才读取本机配置。
|
|
44
|
+
|
|
45
|
+
### check_auth
|
|
46
|
+
|
|
47
|
+
参数可省略:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"skill": "ads"
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
执行流程:
|
|
56
|
+
|
|
57
|
+
1. 检查当前平台是否支持。
|
|
58
|
+
2. 如当前版本二进制尚未安装,则从公开 GitHub Release 下载对应平台包并解压到 `~/.yixin/bin`。
|
|
59
|
+
3. 从环境变量或 `~/.yixin/config.json` 读取授权 key 和授权服务地址。
|
|
60
|
+
4. 向授权服务提交 `{ key, skill, version }`。
|
|
61
|
+
5. 返回自检结果;不会生成广告报告。
|
|
62
|
+
|
|
24
63
|
### run_ads_report
|
|
25
64
|
|
|
26
65
|
参数:
|
|
@@ -33,8 +72,8 @@ YIXIN_AUTH_URL=https://你的授权服务域名/v1/token
|
|
|
33
72
|
|
|
34
73
|
执行流程:
|
|
35
74
|
|
|
36
|
-
1.
|
|
37
|
-
2.
|
|
75
|
+
1. 从环境变量或 `~/.yixin/config.json` 读取授权 key 和授权服务地址。
|
|
76
|
+
2. 向授权服务提交 `{ key, skill: "ads", version }`。
|
|
38
77
|
3. 获取短期授权 token。
|
|
39
78
|
4. 如当前版本二进制尚未安装,则从公开 GitHub Release 下载对应平台包并解压到 `~/.yixin/bin`。
|
|
40
79
|
5. 使用以下参数运行 `~/.yixin/bin/alibaba-cli-{platform}`:
|
package/index.js
CHANGED
|
@@ -20,6 +20,7 @@ const RELEASE_REPO = process.env.YIXIN_RELEASE_REPO || 'yixinkj/cli-releases';
|
|
|
20
20
|
const CACHE_ROOT = path.join(os.homedir(), '.yixin');
|
|
21
21
|
const BIN_DIR = path.join(os.homedir(), '.yixin', 'bin');
|
|
22
22
|
const VERSION_FILE = path.join(CACHE_ROOT, 'version');
|
|
23
|
+
const CONFIG_FILE = path.join(CACHE_ROOT, 'config.json');
|
|
23
24
|
|
|
24
25
|
const PLATFORM_TARGETS = {
|
|
25
26
|
'darwin-arm64': {
|
|
@@ -89,6 +90,44 @@ function releaseUrl(target) {
|
|
|
89
90
|
return `https://github.com/${RELEASE_REPO}/releases/download/v${VERSION}/${target.archive}`;
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
function readLocalConfig() {
|
|
94
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
95
|
+
return {};
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
99
|
+
} catch (error) {
|
|
100
|
+
throw new Error(`本地授权配置文件无效:${CONFIG_FILE},${error.message}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function writeLocalConfig(config) {
|
|
105
|
+
fs.mkdirSync(CACHE_ROOT, { recursive: true });
|
|
106
|
+
const tempPath = `${CONFIG_FILE}.${process.pid}.tmp`;
|
|
107
|
+
fs.writeFileSync(tempPath, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
|
|
108
|
+
fs.renameSync(tempPath, CONFIG_FILE);
|
|
109
|
+
try {
|
|
110
|
+
fs.chmodSync(CONFIG_FILE, 0o600);
|
|
111
|
+
} catch (_) {
|
|
112
|
+
// Windows 可能会忽略 POSIX 权限位。
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function getAuthSettings() {
|
|
117
|
+
const localConfig = readLocalConfig();
|
|
118
|
+
const envKey = String(process.env.YIXIN_KEY || '').trim();
|
|
119
|
+
const envAuthUrl = String(process.env.YIXIN_AUTH_URL || '').trim();
|
|
120
|
+
const localKey = String(localConfig.yixinKey || '').trim();
|
|
121
|
+
const localAuthUrl = String(localConfig.yixinAuthUrl || '').trim();
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
key: envKey || localKey,
|
|
125
|
+
authUrl: envAuthUrl || localAuthUrl,
|
|
126
|
+
keySource: envKey ? 'env:YIXIN_KEY' : (localKey ? CONFIG_FILE : 'missing'),
|
|
127
|
+
authUrlSource: envAuthUrl ? 'env:YIXIN_AUTH_URL' : (localAuthUrl ? CONFIG_FILE : 'missing')
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
92
131
|
function downloadFile(url, outputPath, redirectsLeft = 5) {
|
|
93
132
|
return new Promise((resolve, reject) => {
|
|
94
133
|
const requestUrl = new URL(url);
|
|
@@ -217,13 +256,13 @@ async function ensureInstalled(target) {
|
|
|
217
256
|
}
|
|
218
257
|
}
|
|
219
258
|
|
|
220
|
-
async function requestToken({ key, skill, version }) {
|
|
221
|
-
const
|
|
222
|
-
if (!
|
|
259
|
+
async function requestToken({ key, skill, version, authUrl }) {
|
|
260
|
+
const normalizedAuthUrl = String(authUrl || '').trim();
|
|
261
|
+
if (!normalizedAuthUrl) {
|
|
223
262
|
throw new Error('未配置授权服务器地址,请添加 YIXIN_AUTH_URL');
|
|
224
263
|
}
|
|
225
264
|
|
|
226
|
-
const url = new URL(
|
|
265
|
+
const url = new URL(normalizedAuthUrl);
|
|
227
266
|
const body = JSON.stringify({ key, skill, version });
|
|
228
267
|
const client = url.protocol === 'https:' ? https : http;
|
|
229
268
|
|
|
@@ -268,12 +307,16 @@ async function requestToken({ key, skill, version }) {
|
|
|
268
307
|
}
|
|
269
308
|
|
|
270
309
|
async function authorize(skill) {
|
|
271
|
-
const
|
|
310
|
+
const settings = getAuthSettings();
|
|
311
|
+
const key = settings.key;
|
|
272
312
|
if (!key) {
|
|
273
|
-
throw new Error('未配置授权 key,请在 MCP 客户端环境变量里添加 YIXIN_KEY=你的key');
|
|
313
|
+
throw new Error('未配置授权 key,请在 MCP 客户端环境变量里添加 YIXIN_KEY=你的key,或调用 configure_auth 写入本机配置');
|
|
314
|
+
}
|
|
315
|
+
if (!settings.authUrl) {
|
|
316
|
+
throw new Error('未配置授权服务器地址,请在 MCP 客户端环境变量里添加 YIXIN_AUTH_URL,或调用 configure_auth 写入本机配置');
|
|
274
317
|
}
|
|
275
318
|
|
|
276
|
-
const auth = await requestToken({ key, skill, version: VERSION });
|
|
319
|
+
const auth = await requestToken({ key, skill, version: VERSION, authUrl: settings.authUrl });
|
|
277
320
|
if (!auth || auth.valid !== true || !auth.token) {
|
|
278
321
|
throw new Error(auth && auth.reason ? auth.reason : '授权失败');
|
|
279
322
|
}
|
|
@@ -317,12 +360,122 @@ async function runAdsReport({ period }) {
|
|
|
317
360
|
return stdout;
|
|
318
361
|
}
|
|
319
362
|
|
|
363
|
+
async function checkAuth({ skill }) {
|
|
364
|
+
const normalizedSkill = String(skill || 'ads').trim() || 'ads';
|
|
365
|
+
const target = detectTarget();
|
|
366
|
+
await ensureInstalled(target);
|
|
367
|
+
const settings = getAuthSettings();
|
|
368
|
+
await authorize(normalizedSkill);
|
|
369
|
+
|
|
370
|
+
return [
|
|
371
|
+
'Yixin MCP 自检通过',
|
|
372
|
+
`version: ${VERSION}`,
|
|
373
|
+
`skill: ${normalizedSkill}`,
|
|
374
|
+
`platform: ${target.id}`,
|
|
375
|
+
`binary: ${binaryPath(target)}`,
|
|
376
|
+
`key_source: ${settings.keySource}`,
|
|
377
|
+
`auth_url_source: ${settings.authUrlSource}`,
|
|
378
|
+
`config_file: ${CONFIG_FILE}`
|
|
379
|
+
].join('\n');
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async function configureAuth({ key, authUrl, skill }) {
|
|
383
|
+
const normalizedKey = String(key || '').trim();
|
|
384
|
+
const normalizedAuthUrl = String(authUrl || '').trim();
|
|
385
|
+
const normalizedSkill = String(skill || 'ads').trim() || 'ads';
|
|
386
|
+
|
|
387
|
+
if (!normalizedKey) {
|
|
388
|
+
throw new Error('key 不能为空');
|
|
389
|
+
}
|
|
390
|
+
if (!normalizedAuthUrl) {
|
|
391
|
+
throw new Error('authUrl 不能为空');
|
|
392
|
+
}
|
|
393
|
+
try {
|
|
394
|
+
new URL(normalizedAuthUrl);
|
|
395
|
+
} catch (error) {
|
|
396
|
+
throw new Error(`authUrl 不是有效 URL:${error.message}`);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const auth = await requestToken({
|
|
400
|
+
key: normalizedKey,
|
|
401
|
+
skill: normalizedSkill,
|
|
402
|
+
version: VERSION,
|
|
403
|
+
authUrl: normalizedAuthUrl
|
|
404
|
+
});
|
|
405
|
+
if (!auth || auth.valid !== true || !auth.token) {
|
|
406
|
+
throw new Error(auth && auth.reason ? auth.reason : '授权验证失败,未写入本地配置');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const existingConfig = readLocalConfig();
|
|
410
|
+
writeLocalConfig({
|
|
411
|
+
...existingConfig,
|
|
412
|
+
yixinKey: normalizedKey,
|
|
413
|
+
yixinAuthUrl: normalizedAuthUrl,
|
|
414
|
+
updatedAt: new Date().toISOString()
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
return [
|
|
418
|
+
'Yixin 授权配置已写入本机',
|
|
419
|
+
`skill: ${normalizedSkill}`,
|
|
420
|
+
`config_file: ${CONFIG_FILE}`,
|
|
421
|
+
'后续 check_auth/run_ads_report 会优先使用环境变量;环境变量缺失时使用本机配置'
|
|
422
|
+
].join('\n');
|
|
423
|
+
}
|
|
424
|
+
|
|
320
425
|
function normalizePeriod(period) {
|
|
321
426
|
const value = String(period || '').trim();
|
|
322
427
|
return PERIOD_ALIASES[value] || PERIOD_ALIASES[value.toLowerCase()] || value;
|
|
323
428
|
}
|
|
324
429
|
|
|
325
430
|
function registerTools(server) {
|
|
431
|
+
server.registerTool(
|
|
432
|
+
'configure_auth',
|
|
433
|
+
{
|
|
434
|
+
title: 'Configure Auth',
|
|
435
|
+
description: '验证并保存译心授权 key 和授权服务地址到本机配置;环境变量仍优先于本机配置。',
|
|
436
|
+
inputSchema: {
|
|
437
|
+
key: z
|
|
438
|
+
.string()
|
|
439
|
+
.min(1)
|
|
440
|
+
.describe('客户授权 key。'),
|
|
441
|
+
authUrl: z
|
|
442
|
+
.string()
|
|
443
|
+
.min(1)
|
|
444
|
+
.describe('授权服务地址,例如 https://example.com/v1/token。'),
|
|
445
|
+
skill: z
|
|
446
|
+
.string()
|
|
447
|
+
.optional()
|
|
448
|
+
.describe('授权能力名,默认 ads。')
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
async ({ key, authUrl, skill }) => {
|
|
452
|
+
const stdout = await configureAuth({ key, authUrl, skill });
|
|
453
|
+
return {
|
|
454
|
+
content: [{ type: 'text', text: stdout }]
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
server.registerTool(
|
|
460
|
+
'check_auth',
|
|
461
|
+
{
|
|
462
|
+
title: 'Check Auth',
|
|
463
|
+
description: '检查译心 MCP 环境、二进制安装和授权 key 是否可用;不生成广告报告。',
|
|
464
|
+
inputSchema: {
|
|
465
|
+
skill: z
|
|
466
|
+
.string()
|
|
467
|
+
.optional()
|
|
468
|
+
.describe('授权能力名,默认 ads。')
|
|
469
|
+
}
|
|
470
|
+
},
|
|
471
|
+
async ({ skill }) => {
|
|
472
|
+
const stdout = await checkAuth({ skill });
|
|
473
|
+
return {
|
|
474
|
+
content: [{ type: 'text', text: stdout }]
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
);
|
|
478
|
+
|
|
326
479
|
server.registerTool(
|
|
327
480
|
'run_ads_report',
|
|
328
481
|
{
|