neuxnbcp 0.1.3 → 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,5 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
2
3
  import { createInterface } from 'node:readline/promises';
4
+ import { DEFAULT_API_BASE_URL, DEFAULT_PERIODS, maskToken, renderMcpConfigSnippet, resolveConfig, saveLocalConfig, validateConfig, } from './config.js';
3
5
  import { renderNbcpBanner, shouldShowBanner } from './banner.js';
4
6
  const LOTTERY_MCP_TOOLS = [
5
7
  'lottery.latest',
@@ -8,62 +10,136 @@ const LOTTERY_MCP_TOOLS = [
8
10
  'lottery.summary',
9
11
  ];
10
12
  const MCP_SERVER_TRANSPORT = 'stdio';
13
+ const WEBSITE_URL = 'https://www.neuxsbot.com';
11
14
  const TOKEN_PAGE_URL = 'https://www.neuxsbot.com/member/api-keys';
12
15
  const MENU_TEXT = `请选择操作:
13
- 1. 立即使用(启动 MCP 服务)
14
- 2. 全局安装
15
- 3. 获取 Token 页面
16
- 4. 查看帮助
16
+ 1. 注册/登录并获取 Token
17
+ 2. 配置接口地址、Token、默认期数
18
+ 3. 生成 MCP 配置片段
19
+ 4. 查看当前配置
20
+ 5. 启动 MCP 服务
17
21
  0. 退出`;
18
22
  const HELP_TEXT = `临时打开菜单:
19
23
  npx --yes neuxnbcp@latest
20
24
 
21
- 直接查看帮助:
22
- npx --yes neuxnbcp@latest --help
23
-
24
25
  全局安装:
25
26
  npm i -g neuxnbcp
26
27
 
27
- 安装完成后:
28
- nbcp --help
29
-
30
28
  使用方法:
31
- nbcp <command>
32
- nbcp --help
29
+ 1. 先注册/登录官网并获取 Token
30
+ 2. 再配置 API_BASE_URL / TOKEN / DEFAULT_PERIODS
31
+ 3. 复制 MCP 配置片段到支持 MCP 的 AI 工具
32
+ 4. 在 AI 对话里动态选择彩种和期数
33
33
 
34
34
  可用命令:
35
35
  serve 启动 MCP stdio 服务
36
36
  init 生成本地配置文件
37
- doctor 检查本地配置与网站连通性
37
+ doctor 查看当前配置摘要
38
38
  login 打开网站 Token 页面
39
39
 
40
40
  当前版本:
41
41
  官网: www.neuxsbot.com
42
42
  传输方式: ${MCP_SERVER_TRANSPORT}
43
+ 彩种: 由 AI 对话动态传入,不写死在本地配置
43
44
  工具列表: ${LOTTERY_MCP_TOOLS.join(', ')}
44
45
  `;
45
- const QUICK_START_TEXT = `立即使用:
46
- 临时运行:
47
- npx --yes neuxnbcp@latest serve
48
-
49
- 全局安装后:
50
- nbcp serve
51
- `;
52
- const INSTALL_TEXT = `全局安装:
53
- npm i -g neuxnbcp
46
+ const TOKEN_TEXT = `注册/登录并获取 Token:
47
+ 官网首页: ${WEBSITE_URL}
48
+ Token 页面: ${TOKEN_PAGE_URL}
54
49
 
55
- 安装完成后:
56
- nbcp --help
57
- nbcp serve
50
+ 说明:
51
+ Token 用于识别会员权限、可查期数、升级状态。
52
+ 彩种不在本地写死,由 AI 对话触发工具时动态传入。
58
53
  `;
59
- const TOKEN_TEXT = `获取 Token 页面:
60
- ${TOKEN_PAGE_URL}
54
+ const renderConfigSummary = (config) => `当前配置:
55
+ API_BASE_URL: ${config.apiBaseUrl || '(未设置)'}
56
+ TOKEN: ${maskToken(config.token || '')}
57
+ DEFAULT_PERIODS: ${config.defaultPeriods || '(未设置)'}
61
58
  `;
62
- const runScaffoldedCommand = (name) => {
63
- console.log(`命令 "${name}" 已预留,下一阶段接入实际功能。`);
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));
64
77
  return 0;
65
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
+ };
66
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
+ };
67
143
  const runStartupMenu = async () => {
68
144
  console.log(MENU_TEXT);
69
145
  if (!canShowInteractiveMenu()) {
@@ -79,17 +155,16 @@ const runStartupMenu = async () => {
79
155
  console.log('');
80
156
  switch (selection) {
81
157
  case '1':
82
- console.log(QUICK_START_TEXT);
158
+ console.log(TOKEN_TEXT);
83
159
  return 0;
84
160
  case '2':
85
- console.log(INSTALL_TEXT);
86
- return 0;
161
+ return promptForConfig();
87
162
  case '3':
88
- console.log(TOKEN_TEXT);
89
- return 0;
163
+ return printConfigSnippet();
90
164
  case '4':
91
- console.log(HELP_TEXT);
92
- return 0;
165
+ return runDoctor();
166
+ case '5':
167
+ return runServe();
93
168
  case '0':
94
169
  console.log('已退出。');
95
170
  return 0;
@@ -115,8 +190,18 @@ const main = async () => {
115
190
  console.log(HELP_TEXT);
116
191
  return 0;
117
192
  }
118
- if (['serve', 'init', 'doctor', 'login'].includes(command)) {
119
- return runScaffoldedCommand(command);
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;
120
205
  }
121
206
  console.error(`未知命令: ${command}`);
122
207
  console.log(HELP_TEXT);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neuxnbcp",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "NEUXSBOT lottery MCP command line entrypoint",