@yixinkj/cli 1.0.7 → 1.0.9
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 +16 -74
- package/index.js +43 -360
- package/package.json +3 -6
package/README.md
CHANGED
|
@@ -1,94 +1,36 @@
|
|
|
1
1
|
# @yixinkj/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
译心跨境原生 CLI 启动器。通过 `npx -y` 下载并缓存当前平台的原生二进制,然后把参数直接转发给 `alibaba-cli`。
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
配置方式:`Stdio`
|
|
8
|
-
|
|
9
|
-
命令:
|
|
5
|
+
## 使用
|
|
10
6
|
|
|
11
7
|
```bash
|
|
12
|
-
npx -y @yixinkj/cli@latest
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
推荐环境变量:
|
|
16
|
-
|
|
17
|
-
```text
|
|
18
|
-
YIXIN_PHONE=你的手机号
|
|
19
|
-
YIXIN_AUTH_URL=http://47.237.81.135:3000/v1/token
|
|
8
|
+
npx -y @yixinkj/cli@latest --help
|
|
9
|
+
npx -y @yixinkj/cli@latest --ads --period last-week
|
|
20
10
|
```
|
|
21
11
|
|
|
22
|
-
|
|
12
|
+
首次运行会从公开 GitHub Release 下载二进制到 `~/.yixin/bin`。
|
|
23
13
|
|
|
24
|
-
|
|
14
|
+
macOS 下下载完成后会自动处理每个二进制:
|
|
25
15
|
|
|
26
|
-
|
|
16
|
+
- 清除 `com.apple.quarantine`
|
|
17
|
+
- 设置可执行权限
|
|
27
18
|
|
|
28
|
-
|
|
19
|
+
授权、手机号保存、token 缓存、广告数据抓取和 HTML 报告生成均由原生 `alibaba-cli` 内置完成。
|
|
29
20
|
|
|
30
|
-
|
|
31
|
-
{
|
|
32
|
-
"phone": "客户授权手机号",
|
|
33
|
-
"authUrl": "http://47.237.81.135:3000/v1/token",
|
|
34
|
-
"skill": "ads"
|
|
35
|
-
}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
执行流程:
|
|
39
|
-
|
|
40
|
-
1. 使用传入的 `phone`、`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` 读取授权手机号;授权服务地址默认使用 `http://47.237.81.135:3000/v1/token`,也可由配置覆盖。
|
|
60
|
-
4. 向授权服务提交 `{ phone, skill, version }`。
|
|
61
|
-
5. 返回自检结果;不会生成广告报告。
|
|
62
|
-
|
|
63
|
-
### run_ads_report
|
|
64
|
-
|
|
65
|
-
参数:
|
|
66
|
-
|
|
67
|
-
```json
|
|
68
|
-
{
|
|
69
|
-
"period": "last-week"
|
|
70
|
-
}
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
执行流程:
|
|
74
|
-
|
|
75
|
-
1. 从环境变量或 `~/.yixin/config.json` 读取授权手机号;授权服务地址默认使用 `http://47.237.81.135:3000/v1/token`,也可由配置覆盖。
|
|
76
|
-
2. 向授权服务提交 `{ phone, skill: "ads", version }`。
|
|
77
|
-
3. 获取短期授权 token。
|
|
78
|
-
4. 如当前版本二进制尚未安装,则从公开 GitHub Release 下载对应平台包并解压到 `~/.yixin/bin`。
|
|
79
|
-
5. 使用以下参数运行 `~/.yixin/bin/alibaba-cli-{platform}`:
|
|
21
|
+
## 环境变量
|
|
80
22
|
|
|
81
23
|
```text
|
|
82
|
-
|
|
24
|
+
YIXIN_PHONE 可选。授权手机号;未设置时 CLI 会读取 ~/.yixin/config.json,仍没有则交互式询问。
|
|
25
|
+
YIXIN_AUTH_URL 可选。授权服务地址,默认已内置。
|
|
26
|
+
YIXIN_RELEASE_REPO 可选。默认 yixinkj/cli-releases。
|
|
27
|
+
YIXIN_RELEASE_BASE_URL 可选。覆盖二进制下载基础 URL。
|
|
28
|
+
YIXIN_GITHUB_TOKEN 可选。下载私有 Release 时使用;正式公开分发通常不需要。
|
|
83
29
|
```
|
|
84
30
|
|
|
85
|
-
6. 将原生二进制的 stdout 返回给 MCP 客户端。
|
|
86
|
-
|
|
87
31
|
## 二进制发布包
|
|
88
32
|
|
|
89
33
|
默认从 `yixinkj/cli-releases` 下载与 npm 包版本一致的 release asset:
|
|
90
34
|
|
|
91
35
|
- `darwin-arm64` -> `yixin-mac-arm64.tar.gz`
|
|
92
|
-
- `win32-x64` -> `yixin-
|
|
93
|
-
|
|
94
|
-
可通过 `YIXIN_RELEASE_REPO` 或 `YIXIN_RELEASE_BASE_URL` 覆盖下载来源。
|
|
36
|
+
- `win32-x64` -> `yixin-win32-x64.zip`
|
package/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import http from 'node:http';
|
|
4
3
|
import https from 'node:https';
|
|
5
4
|
import fs from 'node:fs';
|
|
6
5
|
import os from 'node:os';
|
|
@@ -8,64 +7,40 @@ import path from 'node:path';
|
|
|
8
7
|
import { spawnSync } from 'node:child_process';
|
|
9
8
|
import { createRequire } from 'node:module';
|
|
10
9
|
|
|
11
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
12
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
13
|
-
import { z } from 'zod';
|
|
14
|
-
|
|
15
10
|
const require = createRequire(import.meta.url);
|
|
16
11
|
const packageJson = require('./package.json');
|
|
17
12
|
|
|
18
13
|
const VERSION = packageJson.version;
|
|
19
14
|
const RELEASE_REPO = process.env.YIXIN_RELEASE_REPO || 'yixinkj/cli-releases';
|
|
20
15
|
const CACHE_ROOT = path.join(os.homedir(), '.yixin');
|
|
21
|
-
const BIN_DIR = path.join(
|
|
16
|
+
const BIN_DIR = path.join(CACHE_ROOT, 'bin');
|
|
22
17
|
const VERSION_FILE = path.join(CACHE_ROOT, 'version');
|
|
23
|
-
const CONFIG_FILE = path.join(CACHE_ROOT, 'config.json');
|
|
24
|
-
const DEFAULT_AUTH_URL = process.env.YIXIN_DEFAULT_AUTH_URL || 'http://47.237.81.135:3000/v1/token';
|
|
25
18
|
|
|
26
19
|
const PLATFORM_TARGETS = {
|
|
27
20
|
'darwin-arm64': {
|
|
28
21
|
id: 'mac-arm64',
|
|
29
22
|
archive: 'yixin-mac-arm64.tar.gz',
|
|
30
23
|
mainBinary: 'alibaba-cli-mac-arm64',
|
|
31
|
-
binaries: ['alibaba-cli-mac-arm64', '
|
|
24
|
+
binaries: ['alibaba-cli-mac-arm64', 'browser-bridge-mac-arm64']
|
|
32
25
|
},
|
|
33
26
|
'win32-x64': {
|
|
34
|
-
id: '
|
|
35
|
-
archive: 'yixin-
|
|
36
|
-
mainBinary: 'alibaba-cli-
|
|
37
|
-
binaries: ['alibaba-cli-
|
|
27
|
+
id: 'win32-x64',
|
|
28
|
+
archive: 'yixin-win32-x64.zip',
|
|
29
|
+
mainBinary: 'alibaba-cli-win32-x64.exe',
|
|
30
|
+
binaries: ['alibaba-cli-win32-x64.exe', 'browser-bridge-win32-x64.exe']
|
|
38
31
|
}
|
|
39
32
|
};
|
|
40
33
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
'上月': 'last-month',
|
|
46
|
-
'last_month': 'last-month',
|
|
47
|
-
'last-month': 'last-month',
|
|
48
|
-
'昨日': 'yesterday',
|
|
49
|
-
'昨天': 'yesterday',
|
|
50
|
-
'yesterday': 'yesterday',
|
|
51
|
-
'今日': 'today',
|
|
52
|
-
'今天': 'today',
|
|
53
|
-
'today': 'today',
|
|
54
|
-
'近7天': 'last-7-days',
|
|
55
|
-
'最近7天': 'last-7-days',
|
|
56
|
-
'last_7_days': 'last-7-days',
|
|
57
|
-
'last-7-days': 'last-7-days',
|
|
58
|
-
'近30天': 'last-30-days',
|
|
59
|
-
'最近30天': 'last-30-days',
|
|
60
|
-
'last_30_days': 'last-30-days',
|
|
61
|
-
'last-30-days': 'last-30-days'
|
|
62
|
-
};
|
|
34
|
+
function fail(message) {
|
|
35
|
+
console.error(`[yixin] ${message}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
63
38
|
|
|
64
39
|
function detectTarget() {
|
|
65
40
|
const platformKey = `${process.platform}-${process.arch}`;
|
|
66
41
|
const target = PLATFORM_TARGETS[platformKey];
|
|
67
42
|
if (!target) {
|
|
68
|
-
throw new Error(`暂不支持当前平台:${process.platform}/${process.arch}。已支持:mac-arm64、
|
|
43
|
+
throw new Error(`暂不支持当前平台:${process.platform}/${process.arch}。已支持:mac-arm64、win32-x64。`);
|
|
69
44
|
}
|
|
70
45
|
return target;
|
|
71
46
|
}
|
|
@@ -91,51 +66,6 @@ function releaseUrl(target) {
|
|
|
91
66
|
return `https://github.com/${RELEASE_REPO}/releases/download/v${VERSION}/${target.archive}`;
|
|
92
67
|
}
|
|
93
68
|
|
|
94
|
-
function readLocalConfig() {
|
|
95
|
-
if (!fs.existsSync(CONFIG_FILE)) {
|
|
96
|
-
return {};
|
|
97
|
-
}
|
|
98
|
-
try {
|
|
99
|
-
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
100
|
-
} catch (error) {
|
|
101
|
-
throw new Error(`本地授权配置文件无效:${CONFIG_FILE},${error.message}`);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function writeLocalConfig(config) {
|
|
106
|
-
fs.mkdirSync(CACHE_ROOT, { recursive: true });
|
|
107
|
-
const tempPath = `${CONFIG_FILE}.${process.pid}.tmp`;
|
|
108
|
-
fs.writeFileSync(tempPath, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
|
|
109
|
-
fs.renameSync(tempPath, CONFIG_FILE);
|
|
110
|
-
try {
|
|
111
|
-
fs.chmodSync(CONFIG_FILE, 0o600);
|
|
112
|
-
} catch (_) {
|
|
113
|
-
// Windows 可能会忽略 POSIX 权限位。
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function getAuthSettings() {
|
|
118
|
-
const localConfig = readLocalConfig();
|
|
119
|
-
const envPhone = String(process.env.YIXIN_PHONE || '').trim();
|
|
120
|
-
const envLegacyKey = String(process.env.YIXIN_KEY || '').trim();
|
|
121
|
-
const envAuthUrl = String(process.env.YIXIN_AUTH_URL || '').trim();
|
|
122
|
-
const localPhone = String(localConfig.yixinPhone || localConfig.yixinKey || '').trim();
|
|
123
|
-
const localAuthUrl = String(localConfig.yixinAuthUrl || '').trim();
|
|
124
|
-
const phone = envPhone || envLegacyKey || localPhone;
|
|
125
|
-
const authUrl = envAuthUrl || localAuthUrl || DEFAULT_AUTH_URL;
|
|
126
|
-
|
|
127
|
-
return {
|
|
128
|
-
phone,
|
|
129
|
-
authUrl,
|
|
130
|
-
phoneSource: envPhone
|
|
131
|
-
? 'env:YIXIN_PHONE'
|
|
132
|
-
: (envLegacyKey ? 'env:YIXIN_KEY' : (localPhone ? CONFIG_FILE : 'missing')),
|
|
133
|
-
authUrlSource: envAuthUrl
|
|
134
|
-
? 'env:YIXIN_AUTH_URL'
|
|
135
|
-
: (localAuthUrl ? CONFIG_FILE : 'default')
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
|
|
139
69
|
function downloadFile(url, outputPath, redirectsLeft = 5) {
|
|
140
70
|
return new Promise((resolve, reject) => {
|
|
141
71
|
const requestUrl = new URL(url);
|
|
@@ -173,9 +103,7 @@ function downloadFile(url, outputPath, redirectsLeft = 5) {
|
|
|
173
103
|
|
|
174
104
|
const file = fs.createWriteStream(outputPath);
|
|
175
105
|
response.pipe(file);
|
|
176
|
-
file.on('finish', () =>
|
|
177
|
-
file.close(resolve);
|
|
178
|
-
});
|
|
106
|
+
file.on('finish', () => file.close(resolve));
|
|
179
107
|
file.on('error', reject);
|
|
180
108
|
}).on('error', reject);
|
|
181
109
|
});
|
|
@@ -222,34 +150,40 @@ function extractArchive(archivePath, target) {
|
|
|
222
150
|
throw new Error(`暂不支持的压缩包类型:${target.archive}`);
|
|
223
151
|
}
|
|
224
152
|
|
|
225
|
-
function
|
|
226
|
-
if (process.platform
|
|
153
|
+
function prepareMacBinary(filePath) {
|
|
154
|
+
if (process.platform !== 'darwin' || !fs.existsSync(filePath)) {
|
|
227
155
|
return;
|
|
228
156
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
157
|
+
|
|
158
|
+
spawnSync('xattr', ['-dr', 'com.apple.quarantine', filePath], {
|
|
159
|
+
encoding: 'utf8',
|
|
160
|
+
stdio: 'ignore'
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const chmod = spawnSync('chmod', ['+x', filePath], {
|
|
164
|
+
encoding: 'utf8',
|
|
165
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
166
|
+
});
|
|
167
|
+
if (chmod.error) {
|
|
168
|
+
throw chmod.error;
|
|
169
|
+
}
|
|
170
|
+
if (chmod.status !== 0) {
|
|
171
|
+
throw new Error((chmod.stderr || chmod.stdout || `chmod +x failed: ${filePath}`).trim());
|
|
234
172
|
}
|
|
235
173
|
}
|
|
236
174
|
|
|
237
|
-
function
|
|
238
|
-
if (process.platform !== 'darwin'
|
|
175
|
+
function prepareInstalledBinaries(target) {
|
|
176
|
+
if (process.platform !== 'darwin') {
|
|
239
177
|
return;
|
|
240
178
|
}
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
});
|
|
244
|
-
if (result.error && result.error.code !== 'ENOENT') {
|
|
245
|
-
console.error(`[yixin] macOS 安全隔离标记清理失败:${result.error.message}`);
|
|
179
|
+
for (const name of target.binaries) {
|
|
180
|
+
prepareMacBinary(path.join(BIN_DIR, name));
|
|
246
181
|
}
|
|
247
182
|
}
|
|
248
183
|
|
|
249
184
|
async function ensureInstalled(target) {
|
|
250
185
|
if (isInstalled(target)) {
|
|
251
|
-
|
|
252
|
-
chmodExecutables(target);
|
|
186
|
+
prepareInstalledBinaries(target);
|
|
253
187
|
return;
|
|
254
188
|
}
|
|
255
189
|
|
|
@@ -262,8 +196,7 @@ async function ensureInstalled(target) {
|
|
|
262
196
|
console.error(`[yixin] 正在下载 ${url}`);
|
|
263
197
|
await downloadFile(url, archivePath);
|
|
264
198
|
extractArchive(archivePath, target);
|
|
265
|
-
|
|
266
|
-
chmodExecutables(target);
|
|
199
|
+
prepareInstalledBinaries(target);
|
|
267
200
|
|
|
268
201
|
for (const name of target.binaries) {
|
|
269
202
|
const filePath = path.join(BIN_DIR, name);
|
|
@@ -278,271 +211,21 @@ async function ensureInstalled(target) {
|
|
|
278
211
|
}
|
|
279
212
|
}
|
|
280
213
|
|
|
281
|
-
async function
|
|
282
|
-
const normalizedAuthUrl = String(authUrl || '').trim();
|
|
283
|
-
if (!normalizedAuthUrl) {
|
|
284
|
-
throw new Error('未配置授权服务器地址,请添加 YIXIN_AUTH_URL');
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const url = new URL(normalizedAuthUrl);
|
|
288
|
-
const body = JSON.stringify({ phone, skill, version });
|
|
289
|
-
const client = url.protocol === 'https:' ? https : http;
|
|
290
|
-
|
|
291
|
-
return new Promise((resolve, reject) => {
|
|
292
|
-
const request = client.request({
|
|
293
|
-
method: 'POST',
|
|
294
|
-
protocol: url.protocol,
|
|
295
|
-
hostname: url.hostname,
|
|
296
|
-
port: url.port || undefined,
|
|
297
|
-
path: `${url.pathname}${url.search}`,
|
|
298
|
-
headers: {
|
|
299
|
-
'Content-Type': 'application/json',
|
|
300
|
-
'Content-Length': Buffer.byteLength(body),
|
|
301
|
-
'User-Agent': '@yixinkj/cli'
|
|
302
|
-
}
|
|
303
|
-
}, (response) => {
|
|
304
|
-
let data = '';
|
|
305
|
-
response.setEncoding('utf8');
|
|
306
|
-
response.on('data', (chunk) => {
|
|
307
|
-
data += chunk;
|
|
308
|
-
});
|
|
309
|
-
response.on('end', () => {
|
|
310
|
-
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
311
|
-
reject(new Error(`授权服务器请求失败:HTTP ${response.statusCode}`));
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
try {
|
|
315
|
-
resolve(JSON.parse(data));
|
|
316
|
-
} catch (error) {
|
|
317
|
-
reject(new Error(`授权服务器返回了无效 JSON:${error.message}`));
|
|
318
|
-
}
|
|
319
|
-
});
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
request.setTimeout(15000, () => {
|
|
323
|
-
request.destroy(new Error('授权服务器请求超时'));
|
|
324
|
-
});
|
|
325
|
-
request.on('error', reject);
|
|
326
|
-
request.write(body);
|
|
327
|
-
request.end();
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
async function authorize(skill) {
|
|
332
|
-
const settings = getAuthSettings();
|
|
333
|
-
const phone = settings.phone;
|
|
334
|
-
if (!phone) {
|
|
335
|
-
throw new Error('未配置授权手机号,请在 MCP 客户端环境变量里添加 YIXIN_PHONE=手机号,或调用 configure_auth 写入本机配置');
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
const auth = await requestToken({ phone, skill, version: VERSION, authUrl: settings.authUrl });
|
|
339
|
-
if (!auth || auth.valid !== true || !auth.token) {
|
|
340
|
-
throw new Error(auth && auth.reason ? auth.reason : '授权失败');
|
|
341
|
-
}
|
|
342
|
-
return auth.token;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
async function runNative({ target, args, token }) {
|
|
346
|
-
await ensureInstalled(target);
|
|
347
|
-
const executablePath = binaryPath(target);
|
|
348
|
-
const result = spawnSync(executablePath, args, {
|
|
349
|
-
encoding: 'utf8',
|
|
350
|
-
env: {
|
|
351
|
-
...process.env,
|
|
352
|
-
YIXIN_TOKEN: token
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
if (result.error) {
|
|
357
|
-
throw result.error;
|
|
358
|
-
}
|
|
359
|
-
if (result.status !== 0) {
|
|
360
|
-
const detail = [result.stderr, result.stdout].filter(Boolean).join('\n').trim();
|
|
361
|
-
throw new Error(detail || `${executablePath} exited with code ${result.status}`);
|
|
362
|
-
}
|
|
363
|
-
return result.stdout || '';
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
async function runAdsReport({ period }) {
|
|
367
|
-
const normalizedPeriod = normalizePeriod(period);
|
|
368
|
-
if (!normalizedPeriod) {
|
|
369
|
-
throw new Error('period 不能为空');
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const token = await authorize('ads');
|
|
373
|
-
const target = detectTarget();
|
|
374
|
-
const stdout = await runNative({
|
|
375
|
-
target,
|
|
376
|
-
args: ['--ads', '--period', normalizedPeriod],
|
|
377
|
-
token
|
|
378
|
-
});
|
|
379
|
-
return stdout;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
async function checkAuth({ skill }) {
|
|
383
|
-
const normalizedSkill = String(skill || 'ads').trim() || 'ads';
|
|
214
|
+
async function main() {
|
|
384
215
|
const target = detectTarget();
|
|
385
216
|
await ensureInstalled(target);
|
|
386
|
-
const settings = getAuthSettings();
|
|
387
|
-
await authorize(normalizedSkill);
|
|
388
|
-
|
|
389
|
-
return [
|
|
390
|
-
'Yixin MCP 自检通过',
|
|
391
|
-
`version: ${VERSION}`,
|
|
392
|
-
`skill: ${normalizedSkill}`,
|
|
393
|
-
`platform: ${target.id}`,
|
|
394
|
-
`binary: ${binaryPath(target)}`,
|
|
395
|
-
`phone_source: ${settings.phoneSource}`,
|
|
396
|
-
`auth_url_source: ${settings.authUrlSource}`,
|
|
397
|
-
`config_file: ${CONFIG_FILE}`
|
|
398
|
-
].join('\n');
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
async function configureAuth({ phone, authUrl, skill }) {
|
|
402
|
-
const normalizedPhone = String(phone || '').trim();
|
|
403
|
-
const hasCustomAuthUrl = authUrl !== undefined && authUrl !== null && String(authUrl).trim() !== '';
|
|
404
|
-
const normalizedAuthUrl = hasCustomAuthUrl ? String(authUrl).trim() : DEFAULT_AUTH_URL;
|
|
405
|
-
const normalizedSkill = String(skill || 'ads').trim() || 'ads';
|
|
406
217
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
if (!normalizedAuthUrl) {
|
|
411
|
-
throw new Error('authUrl 不能为空');
|
|
412
|
-
}
|
|
413
|
-
try {
|
|
414
|
-
new URL(normalizedAuthUrl);
|
|
415
|
-
} catch (error) {
|
|
416
|
-
throw new Error(`authUrl 不是有效 URL:${error.message}`);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
const auth = await requestToken({
|
|
420
|
-
phone: normalizedPhone,
|
|
421
|
-
skill: normalizedSkill,
|
|
422
|
-
version: VERSION,
|
|
423
|
-
authUrl: normalizedAuthUrl
|
|
218
|
+
const result = spawnSync(binaryPath(target), process.argv.slice(2), {
|
|
219
|
+
stdio: 'inherit',
|
|
220
|
+
env: process.env
|
|
424
221
|
});
|
|
425
|
-
if (!auth || auth.valid !== true || !auth.token) {
|
|
426
|
-
throw new Error(auth && auth.reason ? auth.reason : '授权验证失败,未写入本地配置');
|
|
427
|
-
}
|
|
428
222
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
...existingConfig,
|
|
432
|
-
yixinPhone: normalizedPhone,
|
|
433
|
-
updatedAt: new Date().toISOString()
|
|
434
|
-
};
|
|
435
|
-
delete nextConfig.yixinKey;
|
|
436
|
-
if (hasCustomAuthUrl) {
|
|
437
|
-
nextConfig.yixinAuthUrl = normalizedAuthUrl;
|
|
438
|
-
} else {
|
|
439
|
-
delete nextConfig.yixinAuthUrl;
|
|
223
|
+
if (result.error) {
|
|
224
|
+
throw result.error;
|
|
440
225
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
return [
|
|
444
|
-
'Yixin 授权配置已写入本机',
|
|
445
|
-
`skill: ${normalizedSkill}`,
|
|
446
|
-
`auth_url: ${hasCustomAuthUrl ? normalizedAuthUrl : `${DEFAULT_AUTH_URL} (default)`}`,
|
|
447
|
-
`config_file: ${CONFIG_FILE}`,
|
|
448
|
-
'后续 check_auth/run_ads_report 会优先使用环境变量;环境变量缺失时使用本机配置'
|
|
449
|
-
].join('\n');
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
function normalizePeriod(period) {
|
|
453
|
-
const value = String(period || '').trim();
|
|
454
|
-
return PERIOD_ALIASES[value] || PERIOD_ALIASES[value.toLowerCase()] || value;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
function registerTools(server) {
|
|
458
|
-
server.registerTool(
|
|
459
|
-
'configure_auth',
|
|
460
|
-
{
|
|
461
|
-
title: 'Configure Auth',
|
|
462
|
-
description: '验证并保存译心授权手机号到本机配置;授权服务地址默认内置,也可用 authUrl 或 YIXIN_AUTH_URL 覆盖。',
|
|
463
|
-
inputSchema: {
|
|
464
|
-
phone: z
|
|
465
|
-
.string()
|
|
466
|
-
.min(1)
|
|
467
|
-
.describe('客户授权手机号。'),
|
|
468
|
-
authUrl: z
|
|
469
|
-
.string()
|
|
470
|
-
.optional()
|
|
471
|
-
.describe(`授权服务地址,默认 ${DEFAULT_AUTH_URL}。`),
|
|
472
|
-
skill: z
|
|
473
|
-
.string()
|
|
474
|
-
.optional()
|
|
475
|
-
.describe('授权能力名,默认 ads。')
|
|
476
|
-
}
|
|
477
|
-
},
|
|
478
|
-
async ({ phone, authUrl, skill }) => {
|
|
479
|
-
const stdout = await configureAuth({ phone, authUrl, skill });
|
|
480
|
-
return {
|
|
481
|
-
content: [{ type: 'text', text: stdout }]
|
|
482
|
-
};
|
|
483
|
-
}
|
|
484
|
-
);
|
|
485
|
-
|
|
486
|
-
server.registerTool(
|
|
487
|
-
'check_auth',
|
|
488
|
-
{
|
|
489
|
-
title: 'Check Auth',
|
|
490
|
-
description: '检查译心 MCP 环境、二进制安装和授权手机号是否可用;不生成广告报告。',
|
|
491
|
-
inputSchema: {
|
|
492
|
-
skill: z
|
|
493
|
-
.string()
|
|
494
|
-
.optional()
|
|
495
|
-
.describe('授权能力名,默认 ads。')
|
|
496
|
-
}
|
|
497
|
-
},
|
|
498
|
-
async ({ skill }) => {
|
|
499
|
-
const stdout = await checkAuth({ skill });
|
|
500
|
-
return {
|
|
501
|
-
content: [{ type: 'text', text: stdout }]
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
|
-
);
|
|
505
|
-
|
|
506
|
-
server.registerTool(
|
|
507
|
-
'run_ads_report',
|
|
508
|
-
{
|
|
509
|
-
title: 'Run Ads Report',
|
|
510
|
-
description: '运行本地译心广告报告二进制。period 可用 last-week(上周)、last-month(上月)、yesterday(昨日)、today(今日)、last-7-days(近7天)、last-30-days(近30天),也支持中文别名和下划线别名。',
|
|
511
|
-
inputSchema: {
|
|
512
|
-
period: z
|
|
513
|
-
.string()
|
|
514
|
-
.min(1)
|
|
515
|
-
.describe('相对时间范围。推荐值:last-week、last-month、yesterday、today、last-7-days、last-30-days。')
|
|
516
|
-
}
|
|
517
|
-
},
|
|
518
|
-
async ({ period }) => {
|
|
519
|
-
const stdout = await runAdsReport({ period });
|
|
520
|
-
return {
|
|
521
|
-
content: [{ type: 'text', text: stdout }]
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
);
|
|
525
|
-
|
|
526
|
-
// 后续新增能力时,在这里继续注册 server.registerTool(...)。
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
async function startMcpServer() {
|
|
530
|
-
const server = new McpServer({
|
|
531
|
-
name: 'yixin-cli',
|
|
532
|
-
version: VERSION
|
|
533
|
-
});
|
|
534
|
-
registerTools(server);
|
|
535
|
-
|
|
536
|
-
const transport = new StdioServerTransport();
|
|
537
|
-
await server.connect(transport);
|
|
538
|
-
console.error(`Yixin MCP Server v${VERSION} running on stdio`);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
async function main() {
|
|
542
|
-
await startMcpServer();
|
|
226
|
+
process.exit(result.status === null ? 1 : result.status);
|
|
543
227
|
}
|
|
544
228
|
|
|
545
229
|
main().catch((error) => {
|
|
546
|
-
|
|
547
|
-
process.exit(1);
|
|
230
|
+
fail(error.message);
|
|
548
231
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yixinkj/cli",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Yixin
|
|
3
|
+
"version": "1.0.9",
|
|
4
|
+
"description": "Yixin native CLI launcher.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"yixin": "index.js"
|
|
@@ -10,10 +10,7 @@
|
|
|
10
10
|
"index.js",
|
|
11
11
|
"README.md"
|
|
12
12
|
],
|
|
13
|
-
"dependencies": {
|
|
14
|
-
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
15
|
-
"zod": "^3.25.76"
|
|
16
|
-
},
|
|
13
|
+
"dependencies": {},
|
|
17
14
|
"engines": {
|
|
18
15
|
"node": ">=18"
|
|
19
16
|
},
|