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.
@@ -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 HELP_TEXT = `临时运行:
11
- npx --yes neuxnbcp@latest --help
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
- nbcp <command>
21
- nbcp --help
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
- if (shouldShowBanner(command)) {
37
- process.stdout.write(renderNbcpBanner());
38
- }
39
- if (!command || command === '--help' || command === '-h') {
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
- process.exit(0);
42
- }
43
- if (['serve', 'init', 'doctor', 'login'].includes(command)) {
44
- console.log(`命令 "${command}" 已预留,下一阶段接入实际功能。`);
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neuxnbcp",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "NEUXSBOT lottery MCP command line entrypoint",