@yixinkj/cli 1.0.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.
Files changed (3) hide show
  1. package/README.md +51 -0
  2. package/index.js +217 -0
  3. package/package.json +25 -0
package/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # @yixinkj/cli
2
+
3
+ 译心跨境本地 MCP stdio 服务器,用于在客户端通过 `npx -y` 启动 MCP 服务并调用本地 Rust 能力。
4
+
5
+ ## Accio Work 配置
6
+
7
+ 配置方式:`Stdio`
8
+
9
+ 命令:
10
+
11
+ ```bash
12
+ npx -y @yixinkj/cli@latest
13
+ ```
14
+
15
+ 环境变量:
16
+
17
+ ```text
18
+ YIXIN_KEY=你的key
19
+ YIXIN_AUTH_URL=https://你的授权服务域名/v1/token
20
+ ```
21
+
22
+ ## 工具
23
+
24
+ ### run_ads_report
25
+
26
+ 参数:
27
+
28
+ ```json
29
+ {
30
+ "period": "last-week"
31
+ }
32
+ ```
33
+
34
+ 执行流程:
35
+
36
+ 1. 从环境变量读取 `YIXIN_KEY`。
37
+ 2. 向 `YIXIN_AUTH_URL` 提交 `{ key, skill: "ads", version }`。
38
+ 3. 获取短期授权 token。
39
+ 4. 使用以下参数运行 `~/.yixin/bin/alibaba-cli-{platform}`:
40
+
41
+ ```text
42
+ --ads --period <period>
43
+ ```
44
+
45
+ 5. 将原生二进制的 stdout 返回给 Accio Work。
46
+
47
+ ## 本地二进制名称
48
+
49
+ - `darwin-arm64` -> `~/.yixin/bin/alibaba-cli-mac-arm64`
50
+ - `darwin-x64` -> `~/.yixin/bin/alibaba-cli-mac-x64`
51
+ - `win32-x64` -> `~/.yixin/bin/alibaba-cli-win-x64.exe`
package/index.js ADDED
@@ -0,0 +1,217 @@
1
+ #!/usr/bin/env node
2
+
3
+ import http from 'node:http';
4
+ import https from 'node:https';
5
+ import fs from 'node:fs';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+ import { spawnSync } from 'node:child_process';
9
+ import { createRequire } from 'node:module';
10
+
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
+ const require = createRequire(import.meta.url);
16
+ const packageJson = require('./package.json');
17
+
18
+ const VERSION = packageJson.version;
19
+ const BIN_DIR = path.join(os.homedir(), '.yixin', 'bin');
20
+
21
+ const PLATFORM_BINARIES = {
22
+ 'darwin-arm64': 'alibaba-cli-mac-arm64',
23
+ 'darwin-x64': 'alibaba-cli-mac-x64',
24
+ 'win32-x64': 'alibaba-cli-win-x64.exe'
25
+ };
26
+
27
+ const PERIOD_ALIASES = {
28
+ '上周': 'last-week',
29
+ 'last_week': 'last-week',
30
+ 'last-week': 'last-week',
31
+ '上月': 'last-month',
32
+ 'last_month': 'last-month',
33
+ 'last-month': 'last-month',
34
+ '昨日': 'yesterday',
35
+ '昨天': 'yesterday',
36
+ 'yesterday': 'yesterday',
37
+ '今日': 'today',
38
+ '今天': 'today',
39
+ 'today': 'today',
40
+ '近7天': 'last-7-days',
41
+ '最近7天': 'last-7-days',
42
+ 'last_7_days': 'last-7-days',
43
+ 'last-7-days': 'last-7-days',
44
+ '近30天': 'last-30-days',
45
+ '最近30天': 'last-30-days',
46
+ 'last_30_days': 'last-30-days',
47
+ 'last-30-days': 'last-30-days'
48
+ };
49
+
50
+ function detectBinary() {
51
+ const platformKey = `${process.platform}-${process.arch}`;
52
+ const binaryName = PLATFORM_BINARIES[platformKey];
53
+ if (!binaryName) {
54
+ throw new Error(`暂不支持当前平台:${process.platform}/${process.arch}。已支持:mac-arm64、mac-x64、win-x64。`);
55
+ }
56
+ return path.join(BIN_DIR, binaryName);
57
+ }
58
+
59
+ function ensureBinary(binaryPath) {
60
+ if (!fs.existsSync(binaryPath)) {
61
+ throw new Error(`未找到本地二进制:${binaryPath}`);
62
+ }
63
+ if (process.platform !== 'win32') {
64
+ try {
65
+ fs.chmodSync(binaryPath, 0o755);
66
+ } catch (_) {
67
+ // 继续执行,让 spawnSync 在真正启动失败时返回具体错误。
68
+ }
69
+ }
70
+ }
71
+
72
+ async function requestToken({ key, skill, version }) {
73
+ const authUrl = String(process.env.YIXIN_AUTH_URL || '').trim();
74
+ if (!authUrl) {
75
+ throw new Error('未配置授权服务器地址,请添加 YIXIN_AUTH_URL');
76
+ }
77
+
78
+ const url = new URL(authUrl);
79
+ const body = JSON.stringify({ key, skill, version });
80
+ const client = url.protocol === 'https:' ? https : http;
81
+
82
+ return new Promise((resolve, reject) => {
83
+ const request = client.request({
84
+ method: 'POST',
85
+ protocol: url.protocol,
86
+ hostname: url.hostname,
87
+ port: url.port || undefined,
88
+ path: `${url.pathname}${url.search}`,
89
+ headers: {
90
+ 'Content-Type': 'application/json',
91
+ 'Content-Length': Buffer.byteLength(body),
92
+ 'User-Agent': '@yixinkj/cli'
93
+ }
94
+ }, (response) => {
95
+ let data = '';
96
+ response.setEncoding('utf8');
97
+ response.on('data', (chunk) => {
98
+ data += chunk;
99
+ });
100
+ response.on('end', () => {
101
+ if (response.statusCode < 200 || response.statusCode >= 300) {
102
+ reject(new Error(`授权服务器请求失败:HTTP ${response.statusCode}`));
103
+ return;
104
+ }
105
+ try {
106
+ resolve(JSON.parse(data));
107
+ } catch (error) {
108
+ reject(new Error(`授权服务器返回了无效 JSON:${error.message}`));
109
+ }
110
+ });
111
+ });
112
+
113
+ request.setTimeout(15000, () => {
114
+ request.destroy(new Error('授权服务器请求超时'));
115
+ });
116
+ request.on('error', reject);
117
+ request.write(body);
118
+ request.end();
119
+ });
120
+ }
121
+
122
+ async function authorize(skill) {
123
+ const key = String(process.env.YIXIN_KEY || '').trim();
124
+ if (!key) {
125
+ throw new Error('未配置授权 key,请在 Accio Work 环境变量里添加 YIXIN_KEY=你的key');
126
+ }
127
+
128
+ const auth = await requestToken({ key, skill, version: VERSION });
129
+ if (!auth || auth.valid !== true || !auth.token) {
130
+ throw new Error(auth && auth.reason ? auth.reason : '授权失败');
131
+ }
132
+ return auth.token;
133
+ }
134
+
135
+ function runNative({ binaryPath, args, token }) {
136
+ ensureBinary(binaryPath);
137
+ const result = spawnSync(binaryPath, args, {
138
+ encoding: 'utf8',
139
+ env: {
140
+ ...process.env,
141
+ YIXIN_TOKEN: token
142
+ }
143
+ });
144
+
145
+ if (result.error) {
146
+ throw result.error;
147
+ }
148
+ if (result.status !== 0) {
149
+ const detail = [result.stderr, result.stdout].filter(Boolean).join('\n').trim();
150
+ throw new Error(detail || `${binaryPath} exited with code ${result.status}`);
151
+ }
152
+ return result.stdout || '';
153
+ }
154
+
155
+ async function runAdsReport({ period }) {
156
+ const normalizedPeriod = normalizePeriod(period);
157
+ if (!normalizedPeriod) {
158
+ throw new Error('period 不能为空');
159
+ }
160
+
161
+ const token = await authorize('ads');
162
+ const stdout = runNative({
163
+ binaryPath: detectBinary(),
164
+ args: ['--ads', '--period', normalizedPeriod],
165
+ token
166
+ });
167
+ return stdout;
168
+ }
169
+
170
+ function normalizePeriod(period) {
171
+ const value = String(period || '').trim();
172
+ return PERIOD_ALIASES[value] || PERIOD_ALIASES[value.toLowerCase()] || value;
173
+ }
174
+
175
+ function registerTools(server) {
176
+ server.registerTool(
177
+ 'run_ads_report',
178
+ {
179
+ title: 'Run Ads Report',
180
+ description: '运行本地译心广告报告二进制。period 可用 last-week(上周)、last-month(上月)、yesterday(昨日)、today(今日)、last-7-days(近7天)、last-30-days(近30天),也支持中文别名和下划线别名。',
181
+ inputSchema: {
182
+ period: z
183
+ .string()
184
+ .min(1)
185
+ .describe('相对时间范围。推荐值:last-week、last-month、yesterday、today、last-7-days、last-30-days。')
186
+ }
187
+ },
188
+ async ({ period }) => {
189
+ const stdout = await runAdsReport({ period });
190
+ return {
191
+ content: [{ type: 'text', text: stdout }]
192
+ };
193
+ }
194
+ );
195
+
196
+ // 后续新增能力时,在这里继续注册 server.registerTool(...)。
197
+ }
198
+
199
+ async function startMcpServer() {
200
+ const server = new McpServer({
201
+ name: 'yixin-cli',
202
+ version: VERSION
203
+ });
204
+ registerTools(server);
205
+
206
+ const transport = new StdioServerTransport();
207
+ await server.connect(transport);
208
+ }
209
+
210
+ async function main() {
211
+ await startMcpServer();
212
+ }
213
+
214
+ main().catch((error) => {
215
+ console.error(`[yixin] ${error.message}`);
216
+ process.exit(1);
217
+ });
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@yixinkj/cli",
3
+ "version": "1.0.0",
4
+ "description": "Yixin local MCP server and CLI launcher for Accio Work.",
5
+ "type": "module",
6
+ "bin": {
7
+ "yixin": "index.js"
8
+ },
9
+ "files": [
10
+ "index.js",
11
+ "README.md"
12
+ ],
13
+ "dependencies": {
14
+ "@modelcontextprotocol/sdk": "^1.25.2",
15
+ "zod": "^3.25.76"
16
+ },
17
+ "engines": {
18
+ "node": ">=18"
19
+ },
20
+ "license": "UNLICENSED",
21
+ "private": false,
22
+ "publishConfig": {
23
+ "access": "public"
24
+ }
25
+ }