neuxnbcp 0.1.2 → 0.1.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/dist/config.d.ts +16 -0
- package/dist/config.js +66 -0
- package/dist/index.js +183 -21
- package/package.json +1 -1
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type NbcpConfig = {
|
|
2
|
+
apiBaseUrl: string;
|
|
3
|
+
token: string;
|
|
4
|
+
defaultPeriods: string;
|
|
5
|
+
};
|
|
6
|
+
export declare const DEFAULT_API_BASE_URL = "https://www.neuxsbot.com";
|
|
7
|
+
export declare const DEFAULT_PERIODS = "100";
|
|
8
|
+
export declare const CONFIG_DIRNAME = ".neuxsbot";
|
|
9
|
+
export declare const CONFIG_FILENAME = "cp.config.json";
|
|
10
|
+
export declare const getConfigPath: () => string;
|
|
11
|
+
export declare const loadLocalConfig: () => Promise<Partial<NbcpConfig>>;
|
|
12
|
+
export declare const resolveConfig: () => Promise<Partial<NbcpConfig>>;
|
|
13
|
+
export declare const saveLocalConfig: (config: NbcpConfig) => Promise<void>;
|
|
14
|
+
export declare const validateConfig: (config: Partial<NbcpConfig>) => string[];
|
|
15
|
+
export declare const maskToken: (token: string) => string;
|
|
16
|
+
export declare const renderMcpConfigSnippet: (config: NbcpConfig) => string;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
export const DEFAULT_API_BASE_URL = 'https://www.neuxsbot.com';
|
|
5
|
+
export const DEFAULT_PERIODS = '100';
|
|
6
|
+
export const CONFIG_DIRNAME = '.neuxsbot';
|
|
7
|
+
export const CONFIG_FILENAME = 'cp.config.json';
|
|
8
|
+
export const getConfigPath = () => process.env.NBCP_CONFIG_PATH || path.join(os.homedir(), CONFIG_DIRNAME, CONFIG_FILENAME);
|
|
9
|
+
export const loadLocalConfig = async () => {
|
|
10
|
+
try {
|
|
11
|
+
const configText = await readFile(getConfigPath(), 'utf8');
|
|
12
|
+
const parsed = JSON.parse(configText);
|
|
13
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
export const resolveConfig = async () => {
|
|
20
|
+
const localConfig = await loadLocalConfig();
|
|
21
|
+
return {
|
|
22
|
+
apiBaseUrl: process.env.NEUXSBOT_API_BASE_URL || localConfig.apiBaseUrl || DEFAULT_API_BASE_URL,
|
|
23
|
+
token: process.env.NEUXSBOT_TOKEN || localConfig.token || '',
|
|
24
|
+
defaultPeriods: process.env.NEUXSBOT_DEFAULT_PERIODS || localConfig.defaultPeriods || DEFAULT_PERIODS,
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
export const saveLocalConfig = async (config) => {
|
|
28
|
+
const configPath = getConfigPath();
|
|
29
|
+
await mkdir(path.dirname(configPath), { recursive: true });
|
|
30
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
31
|
+
};
|
|
32
|
+
export const validateConfig = (config) => {
|
|
33
|
+
const missing = [];
|
|
34
|
+
if (!config.apiBaseUrl?.trim()) {
|
|
35
|
+
missing.push('API_BASE_URL');
|
|
36
|
+
}
|
|
37
|
+
if (!config.token?.trim()) {
|
|
38
|
+
missing.push('TOKEN');
|
|
39
|
+
}
|
|
40
|
+
if (!config.defaultPeriods?.trim() || !/^\d+$/.test(config.defaultPeriods.trim())) {
|
|
41
|
+
missing.push('DEFAULT_PERIODS');
|
|
42
|
+
}
|
|
43
|
+
return missing;
|
|
44
|
+
};
|
|
45
|
+
export const maskToken = (token) => {
|
|
46
|
+
if (!token) {
|
|
47
|
+
return '(未设置)';
|
|
48
|
+
}
|
|
49
|
+
if (token.length <= 8) {
|
|
50
|
+
return `${token.slice(0, 2)}***`;
|
|
51
|
+
}
|
|
52
|
+
return `${token.slice(0, 4)}***${token.slice(-4)}`;
|
|
53
|
+
};
|
|
54
|
+
export const renderMcpConfigSnippet = (config) => JSON.stringify({
|
|
55
|
+
mcpServers: {
|
|
56
|
+
'neuxsbot-cp': {
|
|
57
|
+
command: 'npx',
|
|
58
|
+
args: ['-y', 'neuxnbcp@latest', 'serve'],
|
|
59
|
+
env: {
|
|
60
|
+
NEUXSBOT_API_BASE_URL: config.apiBaseUrl,
|
|
61
|
+
NEUXSBOT_TOKEN: config.token,
|
|
62
|
+
NEUXSBOT_DEFAULT_PERIODS: config.defaultPeriods,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
}, null, 2);
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { createInterface } from 'node:readline/promises';
|
|
4
|
+
import { DEFAULT_API_BASE_URL, DEFAULT_PERIODS, maskToken, renderMcpConfigSnippet, resolveConfig, saveLocalConfig, validateConfig, } from './config.js';
|
|
2
5
|
import { renderNbcpBanner, shouldShowBanner } from './banner.js';
|
|
3
6
|
const LOTTERY_MCP_TOOLS = [
|
|
4
7
|
'lottery.latest',
|
|
@@ -7,43 +10,202 @@ const LOTTERY_MCP_TOOLS = [
|
|
|
7
10
|
'lottery.summary',
|
|
8
11
|
];
|
|
9
12
|
const MCP_SERVER_TRANSPORT = 'stdio';
|
|
10
|
-
const
|
|
11
|
-
|
|
13
|
+
const WEBSITE_URL = 'https://www.neuxsbot.com';
|
|
14
|
+
const TOKEN_PAGE_URL = 'https://www.neuxsbot.com/member/api-keys';
|
|
15
|
+
const MENU_TEXT = `请选择操作:
|
|
16
|
+
1. 注册/登录并获取 Token
|
|
17
|
+
2. 配置接口地址、Token、默认期数
|
|
18
|
+
3. 生成 MCP 配置片段
|
|
19
|
+
4. 查看当前配置
|
|
20
|
+
5. 启动 MCP 服务
|
|
21
|
+
0. 退出`;
|
|
22
|
+
const HELP_TEXT = `临时打开菜单:
|
|
23
|
+
npx --yes neuxnbcp@latest
|
|
12
24
|
|
|
13
25
|
全局安装:
|
|
14
26
|
npm i -g neuxnbcp
|
|
15
27
|
|
|
16
|
-
安装完成后:
|
|
17
|
-
nbcp --help
|
|
18
|
-
|
|
19
28
|
使用方法:
|
|
20
|
-
|
|
21
|
-
|
|
29
|
+
1. 先注册/登录官网并获取 Token
|
|
30
|
+
2. 再配置 API_BASE_URL / TOKEN / DEFAULT_PERIODS
|
|
31
|
+
3. 复制 MCP 配置片段到支持 MCP 的 AI 工具
|
|
32
|
+
4. 在 AI 对话里动态选择彩种和期数
|
|
22
33
|
|
|
23
34
|
可用命令:
|
|
24
35
|
serve 启动 MCP stdio 服务
|
|
25
36
|
init 生成本地配置文件
|
|
26
|
-
doctor
|
|
37
|
+
doctor 查看当前配置摘要
|
|
27
38
|
login 打开网站 Token 页面
|
|
28
39
|
|
|
29
40
|
当前版本:
|
|
30
41
|
官网: www.neuxsbot.com
|
|
31
42
|
传输方式: ${MCP_SERVER_TRANSPORT}
|
|
43
|
+
彩种: 由 AI 对话动态传入,不写死在本地配置
|
|
32
44
|
工具列表: ${LOTTERY_MCP_TOOLS.join(', ')}
|
|
33
45
|
`;
|
|
46
|
+
const TOKEN_TEXT = `注册/登录并获取 Token:
|
|
47
|
+
官网首页: ${WEBSITE_URL}
|
|
48
|
+
Token 页面: ${TOKEN_PAGE_URL}
|
|
49
|
+
|
|
50
|
+
说明:
|
|
51
|
+
Token 用于识别会员权限、可查期数、升级状态。
|
|
52
|
+
彩种不在本地写死,由 AI 对话触发工具时动态传入。
|
|
53
|
+
`;
|
|
54
|
+
const renderConfigSummary = (config) => `当前配置:
|
|
55
|
+
API_BASE_URL: ${config.apiBaseUrl || '(未设置)'}
|
|
56
|
+
TOKEN: ${maskToken(config.token || '')}
|
|
57
|
+
DEFAULT_PERIODS: ${config.defaultPeriods || '(未设置)'}
|
|
58
|
+
`;
|
|
59
|
+
const isPositiveInteger = (value) => /^\d+$/.test(value.trim());
|
|
60
|
+
const buildNextConfig = (currentConfig, input) => ({
|
|
61
|
+
apiBaseUrl: input.apiBaseUrl?.trim() || currentConfig.apiBaseUrl || DEFAULT_API_BASE_URL,
|
|
62
|
+
token: input.token?.trim() || currentConfig.token || '',
|
|
63
|
+
defaultPeriods: input.defaultPeriods?.trim() || currentConfig.defaultPeriods || DEFAULT_PERIODS,
|
|
64
|
+
});
|
|
65
|
+
const persistConfig = async (nextConfig) => {
|
|
66
|
+
if (!nextConfig.token.trim()) {
|
|
67
|
+
console.error('Token 不能为空。');
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
if (!isPositiveInteger(nextConfig.defaultPeriods)) {
|
|
71
|
+
console.error('默认期数必须是正整数。');
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
await saveLocalConfig(nextConfig);
|
|
75
|
+
console.log('\n配置已保存。');
|
|
76
|
+
console.log(renderConfigSummary(nextConfig));
|
|
77
|
+
return 0;
|
|
78
|
+
};
|
|
79
|
+
const promptForConfig = async () => {
|
|
80
|
+
const currentConfig = await resolveConfig();
|
|
81
|
+
if (!process.stdin.isTTY) {
|
|
82
|
+
const pipedInput = readFileSync(0, 'utf8').split(/\r?\n/);
|
|
83
|
+
const nextConfig = buildNextConfig(currentConfig, {
|
|
84
|
+
apiBaseUrl: pipedInput[0],
|
|
85
|
+
token: pipedInput[1],
|
|
86
|
+
defaultPeriods: pipedInput[2],
|
|
87
|
+
});
|
|
88
|
+
return persistConfig(nextConfig);
|
|
89
|
+
}
|
|
90
|
+
const rl = createInterface({
|
|
91
|
+
input: process.stdin,
|
|
92
|
+
output: process.stdout,
|
|
93
|
+
});
|
|
94
|
+
try {
|
|
95
|
+
const apiBaseUrlInput = (await rl.question(`接口地址 [${currentConfig.apiBaseUrl || DEFAULT_API_BASE_URL}]: `)).trim();
|
|
96
|
+
const tokenInput = (await rl.question(`Token [${currentConfig.token ? maskToken(currentConfig.token) : '必填'}]: `)).trim();
|
|
97
|
+
const defaultPeriodsInput = (await rl.question(`默认期数 [${currentConfig.defaultPeriods || DEFAULT_PERIODS}]: `)).trim();
|
|
98
|
+
const nextConfig = buildNextConfig(currentConfig, {
|
|
99
|
+
apiBaseUrl: apiBaseUrlInput,
|
|
100
|
+
token: tokenInput,
|
|
101
|
+
defaultPeriods: defaultPeriodsInput,
|
|
102
|
+
});
|
|
103
|
+
return persistConfig(nextConfig);
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
rl.close();
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const canShowInteractiveMenu = (stdin = process.stdin, stdout = process.stdout) => process.env.NBCP_FORCE_MENU === '1' || (Boolean(stdin.isTTY) && Boolean(stdout.isTTY));
|
|
110
|
+
const printConfigSnippet = async () => {
|
|
111
|
+
const config = await resolveConfig();
|
|
112
|
+
const missing = validateConfig(config);
|
|
113
|
+
if (missing.length > 0) {
|
|
114
|
+
console.error(`未检测到完整配置,请先完成接入向导。缺少: ${missing.join(', ')}`);
|
|
115
|
+
return 1;
|
|
116
|
+
}
|
|
117
|
+
console.log('将下面这段 MCP 配置粘贴到支持 MCP 的 AI 工具中:\n');
|
|
118
|
+
console.log(renderMcpConfigSnippet(config));
|
|
119
|
+
return 0;
|
|
120
|
+
};
|
|
121
|
+
const runDoctor = async () => {
|
|
122
|
+
const config = await resolveConfig();
|
|
123
|
+
const missing = validateConfig(config);
|
|
124
|
+
console.log(renderConfigSummary(config));
|
|
125
|
+
if (missing.length > 0) {
|
|
126
|
+
console.log(`缺少: ${missing.join(', ')}`);
|
|
127
|
+
return 1;
|
|
128
|
+
}
|
|
129
|
+
console.log('配置完整,可用于生成 MCP 配置片段。');
|
|
130
|
+
return 0;
|
|
131
|
+
};
|
|
132
|
+
const runServe = async () => {
|
|
133
|
+
const config = await resolveConfig();
|
|
134
|
+
const missing = validateConfig(config);
|
|
135
|
+
if (missing.length > 0) {
|
|
136
|
+
console.error(`未检测到完整配置,请先运行 init 或默认菜单完成接入。缺少: ${missing.join(', ')}`);
|
|
137
|
+
return 1;
|
|
138
|
+
}
|
|
139
|
+
console.log('MCP stdio 服务接入参数已就绪。');
|
|
140
|
+
console.log('当前版本先完成网站鉴权接入与宿主配置生成,真实服务实现下一步接入。');
|
|
141
|
+
return 0;
|
|
142
|
+
};
|
|
143
|
+
const runStartupMenu = async () => {
|
|
144
|
+
console.log(MENU_TEXT);
|
|
145
|
+
if (!canShowInteractiveMenu()) {
|
|
146
|
+
console.log('\n当前为非交互环境,请追加 --help 查看完整帮助。');
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
const rl = createInterface({
|
|
150
|
+
input: process.stdin,
|
|
151
|
+
output: process.stdout,
|
|
152
|
+
});
|
|
153
|
+
try {
|
|
154
|
+
const selection = (await rl.question('\n请输入数字: ')).trim();
|
|
155
|
+
console.log('');
|
|
156
|
+
switch (selection) {
|
|
157
|
+
case '1':
|
|
158
|
+
console.log(TOKEN_TEXT);
|
|
159
|
+
return 0;
|
|
160
|
+
case '2':
|
|
161
|
+
return promptForConfig();
|
|
162
|
+
case '3':
|
|
163
|
+
return printConfigSnippet();
|
|
164
|
+
case '4':
|
|
165
|
+
return runDoctor();
|
|
166
|
+
case '5':
|
|
167
|
+
return runServe();
|
|
168
|
+
case '0':
|
|
169
|
+
console.log('已退出。');
|
|
170
|
+
return 0;
|
|
171
|
+
default:
|
|
172
|
+
console.error(`无效选择: ${selection || '(空)'}`);
|
|
173
|
+
return 1;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
rl.close();
|
|
178
|
+
}
|
|
179
|
+
};
|
|
34
180
|
const args = process.argv.slice(2);
|
|
35
181
|
const command = args[0];
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
182
|
+
const main = async () => {
|
|
183
|
+
if (shouldShowBanner(command)) {
|
|
184
|
+
process.stdout.write(renderNbcpBanner());
|
|
185
|
+
}
|
|
186
|
+
if (!command) {
|
|
187
|
+
return runStartupMenu();
|
|
188
|
+
}
|
|
189
|
+
if (command === '--help' || command === '-h') {
|
|
190
|
+
console.log(HELP_TEXT);
|
|
191
|
+
return 0;
|
|
192
|
+
}
|
|
193
|
+
if (command === 'serve') {
|
|
194
|
+
return runServe();
|
|
195
|
+
}
|
|
196
|
+
if (command === 'init') {
|
|
197
|
+
return promptForConfig();
|
|
198
|
+
}
|
|
199
|
+
if (command === 'doctor') {
|
|
200
|
+
return runDoctor();
|
|
201
|
+
}
|
|
202
|
+
if (command === 'login') {
|
|
203
|
+
console.log(TOKEN_TEXT);
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
console.error(`未知命令: ${command}`);
|
|
40
207
|
console.log(HELP_TEXT);
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
process.exit(0);
|
|
46
|
-
}
|
|
47
|
-
console.error(`未知命令: ${command}`);
|
|
48
|
-
console.log(HELP_TEXT);
|
|
49
|
-
process.exit(1);
|
|
208
|
+
return 1;
|
|
209
|
+
};
|
|
210
|
+
const exitCode = await main();
|
|
211
|
+
process.exit(exitCode);
|