atm-droid 1.0.3 → 1.0.5

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/bin/atm.js CHANGED
@@ -53,10 +53,14 @@ program
53
53
  .description('检查并自动切换(余额不足时)')
54
54
  .action(check);
55
55
 
56
+ program
57
+ .command('menu')
58
+ .description('进入交互式菜单')
59
+ .action(require('../src/menu'));
60
+
56
61
  program.parse();
57
62
 
58
63
  if (!process.argv.slice(2).length) {
59
- console.log(chalk.cyan('\n ATM Token Manager v' + pkg.version));
60
- console.log(chalk.gray(' 跨平台 Factory Token 管理工具\n'));
61
- program.outputHelp();
64
+ // 没有参数时进入交互式菜单
65
+ require('../src/menu')();
62
66
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atm-droid",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "ATM Token Manager CLI - 跨平台 Factory Token 管理工具",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/commands.js CHANGED
@@ -173,7 +173,7 @@ async function switchToken(index) {
173
173
  if (activateResult.success) {
174
174
  // 写入 auth.json
175
175
  if (activateResult.access_token && activateResult.refresh_token) {
176
- writeFactoryAuth(activateResult.access_token, activateResult.refresh_token);
176
+ writeFactoryAuth(activateResult.access_token, activateResult.refresh_token, targetToken.id);
177
177
  config.set('currentTokenId', targetToken.id);
178
178
 
179
179
  switchSpinner.succeed(chalk.green(`已切换到 ${targetToken.email}`));
@@ -349,7 +349,7 @@ async function check() {
349
349
  const activateResult = await api.activateToken(target.id);
350
350
 
351
351
  if (activateResult.success && activateResult.access_token) {
352
- writeFactoryAuth(activateResult.access_token, activateResult.refresh_token);
352
+ writeFactoryAuth(activateResult.access_token, activateResult.refresh_token, target.id);
353
353
  config.set('currentTokenId', target.id);
354
354
  spinner.succeed(chalk.green(`已自动切换到 ${target.email}`));
355
355
  } else {
package/src/config.js CHANGED
@@ -42,14 +42,15 @@ function readFactoryAuth() {
42
42
  return null;
43
43
  }
44
44
 
45
- // 写入 Factory auth.json
46
- function writeFactoryAuth(accessToken, refreshToken) {
45
+ // 写入 Factory auth.json (格式与 Tauri 客户端一致)
46
+ function writeFactoryAuth(accessToken, refreshToken, tokenId) {
47
47
  ensureFactoryDir();
48
48
  const authPath = getFactoryAuthPath();
49
49
  const data = {
50
- accessToken,
51
- refreshToken,
52
- expiresAt: Date.now() + 3600000 // 1小时后过期
50
+ access_token: accessToken,
51
+ refresh_token: refreshToken,
52
+ token_id: tokenId || null,
53
+ updated_at: Math.floor(Date.now() / 1000)
53
54
  };
54
55
  fs.writeFileSync(authPath, JSON.stringify(data, null, 2));
55
56
  return true;
package/src/daemon.js CHANGED
@@ -86,7 +86,7 @@ async function refreshCurrentToken() {
86
86
 
87
87
  const result = await api.activateToken(currentTokenId);
88
88
  if (result.success && result.access_token && result.refresh_token) {
89
- writeFactoryAuth(result.access_token, result.refresh_token);
89
+ writeFactoryAuth(result.access_token, result.refresh_token, currentTokenId);
90
90
  log(`Token 已更新: ${currentTokenId}`);
91
91
  }
92
92
  } catch (e) {
@@ -121,7 +121,7 @@ async function autoSwitch() {
121
121
 
122
122
  if (activateResult.success) {
123
123
  config.set('currentTokenId', target.id);
124
- writeFactoryAuth(activateResult.access_token, activateResult.refresh_token);
124
+ writeFactoryAuth(activateResult.access_token, activateResult.refresh_token, target.id);
125
125
  log(`自动切换到: ${target.email}`);
126
126
  }
127
127
  } else {
package/src/menu.js ADDED
@@ -0,0 +1,442 @@
1
+ const chalk = require('chalk');
2
+ const inquirer = require('inquirer');
3
+ const { config, writeFactoryAuth, getFactoryAuthPath } = require('./config');
4
+ const api = require('./api');
5
+ const daemon = require('./daemon');
6
+
7
+ // 格式化数字
8
+ function formatNumber(num) {
9
+ if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M';
10
+ if (num >= 1000) return (num / 1000).toFixed(1) + 'K';
11
+ return num.toString();
12
+ }
13
+
14
+ // 清屏
15
+ function clearScreen() {
16
+ console.clear();
17
+ }
18
+
19
+ // 显示标题
20
+ function showHeader() {
21
+ console.log(chalk.cyan('╔════════════════════════════════════╗'));
22
+ console.log(chalk.cyan('║') + chalk.white.bold(' ATM Token Manager v1.0.3 ') + chalk.cyan('║'));
23
+ console.log(chalk.cyan('╚════════════════════════════════════╝'));
24
+ console.log();
25
+ }
26
+
27
+ // 显示当前状态
28
+ async function showStatus() {
29
+ const sessionToken = config.get('sessionToken');
30
+ const currentId = config.get('currentTokenId');
31
+ const autoSwitch = config.get('autoSwitch');
32
+ const daemonStatus = daemon.getDaemonStatus();
33
+
34
+ if (!sessionToken) {
35
+ console.log(chalk.yellow(' 状态: 未登录\n'));
36
+ return null;
37
+ }
38
+
39
+ console.log(chalk.green(' 状态: 已登录'));
40
+
41
+ try {
42
+ const result = await api.getTokens();
43
+ if (result.success && result.data) {
44
+ const tokens = result.data;
45
+ const current = tokens.find(t => t.id === currentId);
46
+
47
+ if (current) {
48
+ const remaining = (current.quota_total || 0) - (current.quota_used || 0);
49
+ console.log(chalk.white(` 当前: ${current.email}`));
50
+ console.log(chalk.white(` 余额: ${formatNumber(remaining)}`));
51
+ } else {
52
+ console.log(chalk.yellow(' 当前: 未选择账号'));
53
+ }
54
+
55
+ console.log(chalk.gray(` 账号: ${tokens.length} 个可用`));
56
+ console.log(chalk.gray(` 自动切换: ${autoSwitch ? '开启' : '关闭'}`));
57
+ console.log(chalk.gray(` 后台服务: ${daemonStatus.running ? '运行中' : '未运行'}`));
58
+ console.log();
59
+
60
+ return tokens;
61
+ }
62
+ } catch (e) {
63
+ console.log(chalk.red(` 错误: ${e.message}\n`));
64
+ }
65
+
66
+ return null;
67
+ }
68
+
69
+ // 主菜单
70
+ async function mainMenu() {
71
+ clearScreen();
72
+ showHeader();
73
+
74
+ const tokens = await showStatus();
75
+ const sessionToken = config.get('sessionToken');
76
+
77
+ const choices = [];
78
+
79
+ if (!sessionToken) {
80
+ choices.push({ name: '🔑 登录 (输入激活码)', value: 'login' });
81
+ } else {
82
+ choices.push({ name: '📋 查看账号列表', value: 'list' });
83
+ choices.push({ name: '🔄 切换账号', value: 'switch' });
84
+ choices.push({ name: '✅ 检查并自动切换', value: 'check' });
85
+ choices.push(new inquirer.Separator());
86
+
87
+ const daemonStatus = daemon.getDaemonStatus();
88
+ if (daemonStatus.running) {
89
+ choices.push({ name: '⏹️ 停止后台服务', value: 'stop' });
90
+ } else {
91
+ choices.push({ name: '▶️ 启动后台服务', value: 'start' });
92
+ }
93
+
94
+ choices.push(new inquirer.Separator());
95
+ choices.push({ name: '🚪 退出登录', value: 'logout' });
96
+ }
97
+
98
+ choices.push(new inquirer.Separator());
99
+ choices.push({ name: '❌ 退出程序', value: 'exit' });
100
+
101
+ const { action } = await inquirer.prompt([{
102
+ type: 'list',
103
+ name: 'action',
104
+ message: '请选择操作:',
105
+ choices,
106
+ pageSize: 15
107
+ }]);
108
+
109
+ return action;
110
+ }
111
+
112
+ // 登录
113
+ async function doLogin() {
114
+ clearScreen();
115
+ showHeader();
116
+
117
+ const { code } = await inquirer.prompt([{
118
+ type: 'input',
119
+ name: 'code',
120
+ message: '请输入激活码:',
121
+ validate: input => input.length > 0 || '激活码不能为空'
122
+ }]);
123
+
124
+ console.log(chalk.gray('\n 正在激活...\n'));
125
+
126
+ try {
127
+ const result = await api.activate(code);
128
+
129
+ if (result.success) {
130
+ config.set('sessionToken', result.session_token);
131
+ config.set('autoSwitch', result.auto_switch || false);
132
+
133
+ console.log(chalk.green(' ✓ 激活成功!'));
134
+ console.log(chalk.gray(` 可用账号数: ${result.quota || 0}`));
135
+
136
+ if (result.auto_switch) {
137
+ console.log(chalk.cyan(' 自动切换: 已启用'));
138
+ }
139
+ } else {
140
+ console.log(chalk.red(` ✗ 激活失败: ${result.error || '未知错误'}`));
141
+ }
142
+ } catch (e) {
143
+ console.log(chalk.red(` ✗ 激活失败: ${e.message}`));
144
+ }
145
+
146
+ await pause();
147
+ }
148
+
149
+ // 查看账号列表
150
+ async function doList() {
151
+ clearScreen();
152
+ showHeader();
153
+ console.log(chalk.white.bold(' 账号列表:\n'));
154
+
155
+ try {
156
+ const result = await api.getTokens();
157
+
158
+ if (!result.success) {
159
+ console.log(chalk.red(` 获取失败: ${result.error}`));
160
+ await pause();
161
+ return;
162
+ }
163
+
164
+ const tokens = result.data || [];
165
+ const currentId = config.get('currentTokenId');
166
+
167
+ if (tokens.length === 0) {
168
+ console.log(chalk.yellow(' 暂无可用账号'));
169
+ } else {
170
+ tokens.forEach((token, index) => {
171
+ const total = token.quota_total || 0;
172
+ const used = token.quota_used || 0;
173
+ const remaining = Math.max(0, total - used);
174
+ const isCurrent = token.id === currentId;
175
+
176
+ const prefix = isCurrent ? chalk.green(' ► ') : ' ';
177
+ const indexStr = chalk.gray(`${index + 1}.`);
178
+ const email = isCurrent ? chalk.green(token.email) : chalk.white(token.email);
179
+ const quota = remaining > 0
180
+ ? chalk.cyan(`(${formatNumber(remaining)})`)
181
+ : chalk.red('(已用完)');
182
+ const current = isCurrent ? chalk.green(' [当前]') : '';
183
+
184
+ console.log(`${prefix}${indexStr} ${email} ${quota}${current}`);
185
+ });
186
+ }
187
+
188
+ console.log();
189
+ } catch (e) {
190
+ console.log(chalk.red(` 获取失败: ${e.message}`));
191
+ }
192
+
193
+ await pause();
194
+ }
195
+
196
+ // 切换账号
197
+ async function doSwitch() {
198
+ clearScreen();
199
+ showHeader();
200
+ console.log(chalk.white.bold(' 切换账号:\n'));
201
+
202
+ try {
203
+ const result = await api.getTokens();
204
+
205
+ if (!result.success) {
206
+ console.log(chalk.red(` 获取失败: ${result.error}`));
207
+ await pause();
208
+ return;
209
+ }
210
+
211
+ const tokens = result.data || [];
212
+
213
+ if (tokens.length === 0) {
214
+ console.log(chalk.yellow(' 暂无可用账号'));
215
+ await pause();
216
+ return;
217
+ }
218
+
219
+ const currentId = config.get('currentTokenId');
220
+
221
+ const choices = tokens.map((t, i) => {
222
+ const remaining = (t.quota_total || 0) - (t.quota_used || 0);
223
+ const isCurrent = t.id === currentId;
224
+ return {
225
+ name: `${isCurrent ? '► ' : ' '}${t.email} (${formatNumber(remaining)})${isCurrent ? ' [当前]' : ''}`,
226
+ value: t,
227
+ short: t.email
228
+ };
229
+ });
230
+
231
+ choices.push(new inquirer.Separator());
232
+ choices.push({ name: '← 返回', value: null });
233
+
234
+ const { selected } = await inquirer.prompt([{
235
+ type: 'list',
236
+ name: 'selected',
237
+ message: '选择要切换的账号:',
238
+ choices,
239
+ pageSize: 15
240
+ }]);
241
+
242
+ if (!selected) return;
243
+
244
+ console.log(chalk.gray(`\n 正在切换到 ${selected.email}...\n`));
245
+
246
+ const activateResult = await api.activateToken(selected.id);
247
+
248
+ if (activateResult.success && activateResult.access_token) {
249
+ writeFactoryAuth(activateResult.access_token, activateResult.refresh_token, selected.id);
250
+ config.set('currentTokenId', selected.id);
251
+
252
+ console.log(chalk.green(` ✓ 已切换到 ${selected.email}`));
253
+ console.log(chalk.gray(` auth.json 已更新`));
254
+ } else {
255
+ console.log(chalk.red(` ✗ 切换失败: ${activateResult.error || '未知错误'}`));
256
+ }
257
+
258
+ } catch (e) {
259
+ console.log(chalk.red(` 切换失败: ${e.message}`));
260
+ }
261
+
262
+ await pause();
263
+ }
264
+
265
+ // 检查并自动切换
266
+ async function doCheck() {
267
+ clearScreen();
268
+ showHeader();
269
+ console.log(chalk.white.bold(' 检查账号状态...\n'));
270
+
271
+ try {
272
+ const result = await api.getTokens();
273
+
274
+ if (!result.success) {
275
+ console.log(chalk.red(` 检查失败: ${result.error}`));
276
+ await pause();
277
+ return;
278
+ }
279
+
280
+ const tokens = result.data || [];
281
+ const currentId = config.get('currentTokenId');
282
+ const current = tokens.find(t => t.id === currentId);
283
+
284
+ if (current) {
285
+ const remaining = (current.quota_total || 0) - (current.quota_used || 0);
286
+
287
+ if (remaining > 0) {
288
+ console.log(chalk.green(` ✓ 当前账号 ${current.email} 余额充足`));
289
+ console.log(chalk.gray(` 剩余: ${formatNumber(remaining)}`));
290
+ await pause();
291
+ return;
292
+ }
293
+
294
+ console.log(chalk.yellow(` 当前账号余额不足,正在寻找可用账号...\n`));
295
+ }
296
+
297
+ const available = tokens.filter(t => {
298
+ const r = (t.quota_total || 0) - (t.quota_used || 0);
299
+ return r > 0 && t.id !== currentId;
300
+ });
301
+
302
+ if (available.length === 0) {
303
+ console.log(chalk.red(' ✗ 所有账号余额已用完'));
304
+ await pause();
305
+ return;
306
+ }
307
+
308
+ // 切换到余额最少的
309
+ available.sort((a, b) => {
310
+ const ra = (a.quota_total || 0) - (a.quota_used || 0);
311
+ const rb = (b.quota_total || 0) - (b.quota_used || 0);
312
+ return ra - rb;
313
+ });
314
+
315
+ const target = available[0];
316
+ const activateResult = await api.activateToken(target.id);
317
+
318
+ if (activateResult.success && activateResult.access_token) {
319
+ writeFactoryAuth(activateResult.access_token, activateResult.refresh_token, target.id);
320
+ config.set('currentTokenId', target.id);
321
+ console.log(chalk.green(` ✓ 已自动切换到 ${target.email}`));
322
+ } else {
323
+ console.log(chalk.red(` ✗ 切换失败: ${activateResult.error || '未知错误'}`));
324
+ }
325
+
326
+ } catch (e) {
327
+ console.log(chalk.red(` 检查失败: ${e.message}`));
328
+ }
329
+
330
+ await pause();
331
+ }
332
+
333
+ // 启动后台服务
334
+ async function doStart() {
335
+ const { spawn } = require('child_process');
336
+ const path = require('path');
337
+
338
+ console.log(chalk.gray('\n 正在启动后台服务...\n'));
339
+
340
+ const child = spawn(process.execPath, [
341
+ path.join(__dirname, 'daemon-runner.js')
342
+ ], {
343
+ detached: true,
344
+ stdio: 'ignore'
345
+ });
346
+
347
+ child.unref();
348
+
349
+ await new Promise(resolve => setTimeout(resolve, 1000));
350
+
351
+ const status = daemon.getDaemonStatus();
352
+ if (status.running) {
353
+ console.log(chalk.green(` ✓ 后台服务已启动 (PID: ${status.pid})`));
354
+ } else {
355
+ console.log(chalk.red(' ✗ 启动失败'));
356
+ }
357
+
358
+ await pause();
359
+ }
360
+
361
+ // 停止后台服务
362
+ async function doStop() {
363
+ console.log(chalk.gray('\n 正在停止后台服务...\n'));
364
+
365
+ const result = daemon.stopDaemon();
366
+
367
+ if (result) {
368
+ console.log(chalk.green(' ✓ 后台服务已停止'));
369
+ } else {
370
+ console.log(chalk.yellow(' 后台服务未运行'));
371
+ }
372
+
373
+ await pause();
374
+ }
375
+
376
+ // 退出登录
377
+ async function doLogout() {
378
+ const { confirm } = await inquirer.prompt([{
379
+ type: 'confirm',
380
+ name: 'confirm',
381
+ message: '确定要退出登录吗?',
382
+ default: false
383
+ }]);
384
+
385
+ if (!confirm) return;
386
+
387
+ try {
388
+ await api.unbind();
389
+ } catch (e) {
390
+ // 忽略
391
+ }
392
+
393
+ config.clear();
394
+ console.log(chalk.green('\n ✓ 已退出登录\n'));
395
+ await pause();
396
+ }
397
+
398
+ // 暂停等待用户按键
399
+ async function pause() {
400
+ await inquirer.prompt([{
401
+ type: 'input',
402
+ name: 'continue',
403
+ message: '按回车键继续...'
404
+ }]);
405
+ }
406
+
407
+ // 主循环
408
+ async function menu() {
409
+ while (true) {
410
+ const action = await mainMenu();
411
+
412
+ switch (action) {
413
+ case 'login':
414
+ await doLogin();
415
+ break;
416
+ case 'list':
417
+ await doList();
418
+ break;
419
+ case 'switch':
420
+ await doSwitch();
421
+ break;
422
+ case 'check':
423
+ await doCheck();
424
+ break;
425
+ case 'start':
426
+ await doStart();
427
+ break;
428
+ case 'stop':
429
+ await doStop();
430
+ break;
431
+ case 'logout':
432
+ await doLogout();
433
+ break;
434
+ case 'exit':
435
+ clearScreen();
436
+ console.log(chalk.cyan('\n 再见!\n'));
437
+ process.exit(0);
438
+ }
439
+ }
440
+ }
441
+
442
+ module.exports = menu;