iivo-sub 0.1.0 → 0.1.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/README.md CHANGED
@@ -26,8 +26,11 @@ The main menu includes:
26
26
  - Quick configuration
27
27
  - Backup configuration
28
28
  - Restore backup
29
+ - Clear cache and backups
29
30
  - Exit
30
31
 
31
32
  Backup configuration lets you choose Codex, Claude Code, Hermes, OpenClaw, or all targets. You can name the backup yourself; the default name is `<date-time>_<target>`.
32
33
 
34
+ After you enter an API key once, it is cached in `~/.iivo-sub/cache.json`. Next time the prompt shows a masked key such as `sk-x...abcd`; press Enter to reuse it or type a new key to replace it.
35
+
33
36
  Existing config files are backed up under `~/.iivo-sub/backups/` before being replaced.
package/bin/iivo-sub.js CHANGED
@@ -5,8 +5,9 @@ import os from 'node:os'
5
5
  import path from 'node:path'
6
6
  import readline from 'node:readline'
7
7
 
8
- const VERSION = '0.1.0'
8
+ const VERSION = '0.1.1'
9
9
  const APP_DIR = path.join(os.homedir(), '.iivo-sub')
10
+ const CACHE_FILE = path.join(APP_DIR, 'cache.json')
10
11
  const SCRIPTED_INPUT = !process.stdin.isTTY ? fs.readFileSync(0, 'utf8').split(/\r?\n/) : null
11
12
  let scriptedInputIndex = 0
12
13
  const DEFAULT_HOST = 'https://sub.iivo.net'
@@ -39,6 +40,7 @@ const MAIN_MENU = [
39
40
  { label: '快速配置', value: 'quick' },
40
41
  { label: '备份配置', value: 'backup' },
41
42
  { label: '恢复备份', value: 'restore' },
43
+ { label: '清空缓存及备份', value: 'clear-cache' },
42
44
  { label: '退出', value: 'exit' }
43
45
  ]
44
46
 
@@ -55,6 +57,11 @@ const RESTORE_CONFIRM_OPTIONS = [
55
57
  { label: '取消', value: 'no' }
56
58
  ]
57
59
 
60
+ const CLEAR_CONFIRM_OPTIONS = [
61
+ { label: '确认清空', value: 'yes' },
62
+ { label: '取消', value: 'no' }
63
+ ]
64
+
58
65
  const colors = {
59
66
  reset: '\x1b[0m',
60
67
  dim: '\x1b[2m',
@@ -352,6 +359,43 @@ function loadBackupManifest(dir) {
352
359
  }
353
360
  }
354
361
 
362
+ function readCache() {
363
+ if (!fileExists(CACHE_FILE)) return {}
364
+ try {
365
+ return JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'))
366
+ } catch {
367
+ return {}
368
+ }
369
+ }
370
+
371
+ function writeCache(cache) {
372
+ ensureDir(APP_DIR)
373
+ fs.writeFileSync(CACHE_FILE, json({
374
+ ...cache,
375
+ updated_at: new Date().toISOString()
376
+ }), { encoding: 'utf8', mode: 0o600 })
377
+ }
378
+
379
+ function getCachedApiKey() {
380
+ const cache = readCache()
381
+ return typeof cache.api_key === 'string' ? cache.api_key : ''
382
+ }
383
+
384
+ function setCachedApiKey(apiKey) {
385
+ if (!apiKey) return
386
+ writeCache({
387
+ ...readCache(),
388
+ api_key: apiKey,
389
+ api_key_hint: maskKey(apiKey)
390
+ })
391
+ }
392
+
393
+ function deletePath(target) {
394
+ if (!fs.existsSync(target)) return false
395
+ fs.rmSync(target, { recursive: true, force: true })
396
+ return true
397
+ }
398
+
355
399
  function writeFileWithBackup(filePath, content, backupRoot) {
356
400
  ensureDir(path.dirname(filePath))
357
401
  if (fs.existsSync(filePath)) {
@@ -564,7 +608,10 @@ async function quickConfig() {
564
608
  if (step === 0) {
565
609
  clear()
566
610
  banner()
567
- const apiKey = await question('请输入 API Key [Esc 返回主菜单]', { mask: true })
611
+ const cachedApiKey = getCachedApiKey()
612
+ const defaultHint = cachedApiKey ? maskKey(cachedApiKey) : ''
613
+ const apiKeyInput = await question('请输入 API Key [Esc 返回主菜单]', { mask: true, defaultValue: defaultHint })
614
+ const apiKey = apiKeyInput === defaultHint && cachedApiKey ? cachedApiKey : apiKeyInput
568
615
  if (apiKey === '__exit__') return '__exit__'
569
616
  if (apiKey === null) return
570
617
  if (!apiKey) {
@@ -573,6 +620,7 @@ async function quickConfig() {
573
620
  continue
574
621
  }
575
622
  state.apiKey = apiKey
623
+ setCachedApiKey(apiKey)
576
624
  step = 1
577
625
  continue
578
626
  }
@@ -805,6 +853,57 @@ async function restoreBackup() {
805
853
  await pause()
806
854
  }
807
855
 
856
+ async function clearCacheAndBackups() {
857
+ clear()
858
+ banner()
859
+ console.log(c('yellow', '将删除以下 iivo-sub 数据:'))
860
+ console.log(`- API Key 缓存: ${CACHE_FILE}`)
861
+ console.log(`- 备份目录: ${path.join(APP_DIR, 'backups')}`)
862
+ console.log(`- 临时配置脚本: ${path.join(APP_DIR, '*.env / *.ps1 / *.cmd')}`)
863
+ console.log(`- 最近配置摘要: ${path.join(APP_DIR, 'config.json')}`)
864
+ console.log()
865
+ console.log('不会删除 Codex、Claude Code、Hermes、OpenClaw 的实际配置文件。')
866
+ console.log()
867
+
868
+ const confirm = await select('确认清空缓存及备份吗?', CLEAR_CONFIRM_OPTIONS)
869
+ if (confirm === '__exit__') return '__exit__'
870
+ if (confirm !== 'yes') return
871
+
872
+ const removed = []
873
+ for (const target of [
874
+ CACHE_FILE,
875
+ path.join(APP_DIR, 'backups'),
876
+ path.join(APP_DIR, 'config.json'),
877
+ path.join(APP_DIR, 'codex.env'),
878
+ path.join(APP_DIR, 'codex.ps1'),
879
+ path.join(APP_DIR, 'codex.cmd'),
880
+ path.join(APP_DIR, 'claude-code.env'),
881
+ path.join(APP_DIR, 'claude-code.ps1'),
882
+ path.join(APP_DIR, 'claude-code.cmd'),
883
+ path.join(APP_DIR, 'hermes.env'),
884
+ path.join(APP_DIR, 'hermes.ps1'),
885
+ path.join(APP_DIR, 'hermes.cmd'),
886
+ path.join(APP_DIR, 'openclaw.env'),
887
+ path.join(APP_DIR, 'openclaw.ps1'),
888
+ path.join(APP_DIR, 'openclaw.cmd')
889
+ ]) {
890
+ if (deletePath(target)) removed.push(target)
891
+ }
892
+
893
+ clear()
894
+ banner()
895
+ if (removed.length === 0) {
896
+ console.log(c('yellow', '没有找到需要清空的数据。'))
897
+ } else {
898
+ console.log(c('green', '缓存及备份已清空。'))
899
+ console.log()
900
+ for (const target of removed) {
901
+ console.log(`- ${target}`)
902
+ }
903
+ }
904
+ await pause()
905
+ }
906
+
808
907
  async function pause() {
809
908
  await question('按回车继续')
810
909
  }
@@ -826,6 +925,10 @@ async function main() {
826
925
  const result = await restoreBackup()
827
926
  if (result === '__exit__') break
828
927
  }
928
+ if (action === 'clear-cache') {
929
+ const result = await clearCacheAndBackups()
930
+ if (result === '__exit__') break
931
+ }
829
932
  }
830
933
  clear()
831
934
  console.log('已退出 iivo-sub。')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iivo-sub",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "IIVO AI gateway quick configuration CLI",
5
5
  "type": "module",
6
6
  "bin": {