@dalehkx/quote-cli 0.2.0 → 0.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dalehkx/quote-cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "通用询报价 CLI 工具",
5
5
  "type": "module",
6
6
  "bin": {
@@ -123,11 +123,19 @@ export function registerInquiryCommands(program) {
123
123
  .description('监听询价单报价状态(有新报价时提醒)')
124
124
  .argument('<id>', '询价单 ID')
125
125
  .option('-i, --interval <seconds>', '轮询间隔(秒)', '30')
126
+ .option('--timeout <seconds>', '最长等待时间(秒),超时后退出,0=不限制', '0')
127
+ .option('--exit-on-quotes <n>', '收到 n 条报价后自动退出', '0')
128
+ .option('--json', '以 JSON 格式输出结果(适合脚本/AI 调用)')
126
129
  .action(async (id, opts) => {
127
- const adapter = getAdapter();
128
- const interval = Math.max(10, parseInt(opts.interval, 10)) * 1000;
129
-
130
- console.log(`监听询价单 ${id},每 ${interval / 1000} 秒检查一次,Ctrl+C 退出\n`);
130
+ const adapter = getAdapter();
131
+ const interval = Math.max(10, parseInt(opts.interval, 10)) * 1000;
132
+ const timeout = parseInt(opts.timeout, 10) * 1000;
133
+ const exitOnQuotes = parseInt(opts.exitOnQuotes, 10);
134
+ const startTime = Date.now();
135
+
136
+ if (!opts.json) {
137
+ console.log(`监听询价单 ${id},每 ${interval / 1000} 秒检查一次,Ctrl+C 退出\n`);
138
+ }
131
139
 
132
140
  let lastQuoteCount = 0;
133
141
  let lastStatus = '';
@@ -139,20 +147,43 @@ export function registerInquiryCommands(program) {
139
147
  const count = replies.length;
140
148
  const time = new Date().toLocaleTimeString();
141
149
 
150
+ // 超时退出
151
+ if (timeout > 0 && Date.now() - startTime >= timeout) {
152
+ if (opts.json) {
153
+ console.log(JSON.stringify({ inquiryId: id, status: record.status, quotes: replies, timedOut: true }));
154
+ } else {
155
+ console.log(`\n[${time}] 等待超时,共 ${count} 条报价`);
156
+ }
157
+ process.exit(0);
158
+ }
159
+
160
+ // 达到目标报价数退出
161
+ if (exitOnQuotes > 0 && count >= exitOnQuotes) {
162
+ if (opts.json) {
163
+ console.log(JSON.stringify({ inquiryId: id, status: record.status, quotes: replies }));
164
+ } else {
165
+ console.log(`\n[${time}] 已收到 ${count} 条报价,退出监听`);
166
+ replies.forEach(r => console.log(` ${r.supplier} | ¥${r.price} | ${r.brand || '-'} | ${r.delivery ? r.delivery + '天' : '货期未知'}`));
167
+ }
168
+ process.exit(0);
169
+ }
170
+
142
171
  if (count > lastQuoteCount) {
143
- console.log(`\n[${time}] 🔔 新报价!共 ${count} 条`);
144
- replies.slice(lastQuoteCount).forEach(r => {
145
- console.log(` ${r.supplier} | ¥${r.price} | ${r.brand || '-'} | ${r.delivery ? r.delivery + '天' : '货期未知'}`);
146
- });
172
+ if (!opts.json) {
173
+ console.log(`\n[${time}] 🔔 新报价!共 ${count} 条`);
174
+ replies.slice(lastQuoteCount).forEach(r => {
175
+ console.log(` ${r.supplier} | ¥${r.price} | ${r.brand || '-'} | ${r.delivery ? r.delivery + '天' : '货期未知'}`);
176
+ });
177
+ }
147
178
  lastQuoteCount = count;
148
179
  } else if (record.status !== lastStatus) {
149
- console.log(`[${time}] 状态变更: ${lastStatus || '-'} → ${record.status} 报价: ${count} 条`);
180
+ if (!opts.json) console.log(`[${time}] 状态变更: ${lastStatus || '-'} → ${record.status} 报价: ${count} 条`);
150
181
  lastStatus = record.status;
151
182
  } else {
152
- process.stdout.write(`\r[${time}] 等待报价... 当前 ${count} 条`);
183
+ if (!opts.json) process.stdout.write(`\r[${time}] 等待报价... 当前 ${count} 条`);
153
184
  }
154
185
  } catch (e) {
155
- console.error(`\n[错误] ${e.message}`);
186
+ if (!opts.json) console.error(`\n[错误] ${e.message}`);
156
187
  }
157
188
  };
158
189
 
@@ -28,7 +28,6 @@ export function registerLoginCommands(program) {
28
28
  try {
29
29
  let tokenData;
30
30
 
31
- // --test 强制走短信登录,手机号固定为测试账号
32
31
  if (opts.test || opts.sms) {
33
32
  const cellphone = opts.test ? TEST_PHONE : (opts.username || await ask('手机号: '));
34
33
 
@@ -56,10 +55,8 @@ export function registerLoginCommands(program) {
56
55
 
57
56
  tokenData = await loginWithCellphone(apiBase, cellphone, verifyCode);
58
57
  } else {
59
- // 账号密码登录
60
58
  const username = opts.username || await ask('账号: ');
61
59
 
62
- // 检查账号是否存在
63
60
  process.stdout.write('正在检查账号...');
64
61
  const exists = await checkAccountExists(apiBase, username);
65
62
  if (!exists) {
@@ -93,12 +90,32 @@ export function registerLoginCommands(program) {
93
90
  .option('-u, --username <name>', '用户名')
94
91
  .option('--phone <phone>', '手机号')
95
92
  .option('-p, --password <pwd>', '密码')
93
+ .option('--code <code>', '验证码(非交互:先用 --send-code 发送)')
94
+ .option('--send-code', '仅发送注册验证码,不完成注册')
95
+ .option('--company <name>', '公司/门店名称')
96
+ .option('--province-id <id>', '省级 geoId(如 CN-11)')
97
+ .option('--province-name <name>', '省名称(如 北京市)')
98
+ .option('--city-id <id>', '市级 geoId(如 1351)')
99
+ .option('--city-name <name>', '市名称')
100
+ .option('--county-id <id>', '区县 geoId(如 9629)')
101
+ .option('--county-name <name>', '区县名称')
96
102
  .option('--api-base <url>', 'API 地址', API_BASE_DEFAULT)
97
103
  .action(async (opts) => {
98
104
  const store = new Store();
99
105
  const config = store.getConfig();
100
106
  const apiBase = opts.apiBase || config.apiBase || API_BASE_DEFAULT;
101
107
 
108
+ // 仅发送验证码模式
109
+ if (opts.sendCode) {
110
+ if (!opts.phone) { console.error('✗ 请提供 --phone <手机号>'); process.exit(1); }
111
+ process.stdout.write('正在发送验证码...');
112
+ await sendRegisterCode(apiBase, opts.phone);
113
+ console.log(' 已发送,请查收短信后带 --code 完成注册');
114
+ return;
115
+ }
116
+
117
+ const isNonInteractive = opts.phone && opts.password && opts.username && opts.code;
118
+
102
119
  const rl = createInterface({ input: process.stdin, output: process.stdout });
103
120
  const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
104
121
  const askHidden = (q) => new Promise((resolve) => {
@@ -112,14 +129,9 @@ export function registerLoginCommands(program) {
112
129
  process.stdin.removeListener('data', onData);
113
130
  process.stdout.write('\n');
114
131
  resolve(input);
115
- } else if (ch === '') {
116
- process.exit();
117
- } else if (ch === '') {
118
- input = input.slice(0, -1);
119
- } else {
120
- input += ch;
121
- process.stdout.write('*');
122
- }
132
+ } else if (ch === '') { process.exit(); }
133
+ else if (ch === '') { input = input.slice(0, -1); }
134
+ else { input += ch; process.stdout.write('*'); }
123
135
  };
124
136
  process.stdin.resume();
125
137
  process.stdin.setEncoding('utf8');
@@ -129,68 +141,69 @@ export function registerLoginCommands(program) {
129
141
  try {
130
142
  const cellphone = opts.phone || await ask('手机号: ');
131
143
 
132
- // 检查是否已注册
133
144
  process.stdout.write('正在检查账号...');
134
145
  const exists = await checkAccountExists(apiBase, cellphone);
146
+ console.log(exists ? ' 已注册' : ' 未注册');
135
147
  if (exists) {
136
- console.log('');
137
- console.error('✗ 该手机号已注册,请直接登录: quote login --sms');
138
- rl.close();
139
- process.exit(1);
148
+ console.error('✗ 该手机号已注册,请直接登录: quote login');
149
+ rl.close(); process.exit(1);
140
150
  }
141
- console.log(' 未注册');
142
151
 
143
- // 发送注册验证码
144
- process.stdout.write('正在发送验证码...');
145
- await sendRegisterCode(apiBase, cellphone);
146
- console.log(' 已发送');
152
+ if (!isNonInteractive) {
153
+ process.stdout.write('正在发送验证码...');
154
+ await sendRegisterCode(apiBase, cellphone);
155
+ console.log(' 已发送');
156
+ }
147
157
 
148
- const verifyCode = await ask('验证码: ');
149
- const username = opts.username || await ask('用户名: ');
150
- const password = opts.password || await askHidden('密码: ');
158
+ const verifyCode = opts.code || await ask('验证码: ');
159
+ const username = opts.username || await ask('用户名: ');
160
+ const password = opts.password || await askHidden('密码: ');
151
161
 
152
- rl.close();
162
+ if (!isNonInteractive) rl.close();
153
163
 
154
164
  await registerUser(apiBase, { cellphone, password, username, verificationCode: verifyCode });
155
-
156
165
  console.log('✓ 注册成功,正在自动登录...');
157
166
 
158
167
  store.setConfig({ apiBase, mode: 'api' });
159
168
  const tokenData = await loginWithPassword(apiBase, cellphone, password);
160
169
  console.log(`✓ 登录成功 用户: ${tokenData.userLoginId || username}`);
161
170
 
162
- // 引导完善公司信息
171
+ // 公司信息 —— 有 flag 则非交互
172
+ if (opts.company && opts.provinceId && opts.cityId && opts.countyId) {
173
+ await saveCompanyInfo(apiBase, {
174
+ companyName: opts.company,
175
+ provinceId: opts.provinceId, provinceName: opts.provinceName || '',
176
+ cityId: opts.cityId, cityName: opts.cityName || '',
177
+ countyId: opts.countyId, countyName: opts.countyName || '',
178
+ });
179
+ console.log('✓ 公司信息已保存,使用 quote inquiry create 发布询价');
180
+ rl.close();
181
+ return;
182
+ }
183
+
163
184
  console.log('\n完善公司/门店信息(发布询价必须):');
164
185
  const companyName = await ask('公司/门店名称: ');
165
186
 
166
- // 选省
167
187
  const provinces = await fetchAreas(apiBase, 'CHN');
168
188
  provinces.forEach((p, i) => process.stdout.write(` ${String(i + 1).padStart(2)}. ${p.geoName}\n`));
169
- const pIdx = parseInt(await ask(`选择省份 (1-${provinces.length}): `), 10) - 1;
170
- const province = provinces[pIdx];
189
+ const province = provinces[parseInt(await ask(`选择省份 (1-${provinces.length}): `), 10) - 1];
171
190
 
172
- // 选市
173
191
  const cities = await fetchAreas(apiBase, province.geoId);
174
192
  cities.forEach((c, i) => process.stdout.write(` ${String(i + 1).padStart(2)}. ${c.geoName}\n`));
175
- const cIdx = parseInt(await ask(`选择城市 (1-${cities.length}): `), 10) - 1;
176
- const city = cities[cIdx];
193
+ const city = cities[parseInt(await ask(`选择城市 (1-${cities.length}): `), 10) - 1];
177
194
 
178
- // 选区县
179
195
  const counties = await fetchAreas(apiBase, city.geoId);
180
196
  counties.forEach((d, i) => process.stdout.write(` ${String(i + 1).padStart(2)}. ${d.geoName}\n`));
181
- const dIdx = parseInt(await ask(`选择区县 (1-${counties.length}): `), 10) - 1;
182
- const county = counties[dIdx];
197
+ const county = counties[parseInt(await ask(`选择区县 (1-${counties.length}): `), 10) - 1];
183
198
 
184
199
  rl.close();
185
-
186
200
  await saveCompanyInfo(apiBase, {
187
201
  companyName,
188
- provinceId: province.geoId, provinceName: province.geoName,
189
- cityId: city.geoId, cityName: city.geoName,
190
- countyId: county.geoId, countyName: county.geoName,
202
+ provinceId: province.geoId, provinceName: province.geoName,
203
+ cityId: city.geoId, cityName: city.geoName,
204
+ countyId: county.geoId, countyName: county.geoName,
191
205
  });
192
- console.log('✓ 公司信息已保存');
193
- console.log('账号已准备就绪,使用 quote inquiry create 发布询价');
206
+ console.log('✓ 公司信息已保存,使用 quote inquiry create 发布询价');
194
207
  } catch (err) {
195
208
  rl.close();
196
209
  console.error(`✗ 注册失败: ${err.message}`);
@@ -202,10 +215,7 @@ export function registerLoginCommands(program) {
202
215
  .command('logout')
203
216
  .description('登出(清除本地令牌)')
204
217
  .action(() => {
205
- if (!isLoggedIn()) {
206
- console.log('当前未登录');
207
- return;
208
- }
218
+ if (!isLoggedIn()) { console.log('当前未登录'); return; }
209
219
  logout();
210
220
  console.log('✓ 已登出,令牌已清除');
211
221
  });