iivo-sub 0.1.0 → 0.1.2
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 +7 -0
- package/bin/iivo-sub.js +271 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,8 +26,15 @@ The main menu includes:
|
|
|
26
26
|
- Quick configuration
|
|
27
27
|
- Backup configuration
|
|
28
28
|
- Restore backup
|
|
29
|
+
- Backup chat records
|
|
30
|
+
- Restore chat records
|
|
31
|
+
- Clear cache and backups
|
|
29
32
|
- Exit
|
|
30
33
|
|
|
31
34
|
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
35
|
|
|
36
|
+
Chat record backup lets you back up common local conversation paths for Codex, Claude Code, Hermes, and OpenClaw. Restore creates a safety backup before replacing existing chat record paths.
|
|
37
|
+
|
|
38
|
+
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.
|
|
39
|
+
|
|
33
40
|
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.
|
|
8
|
+
const VERSION = '0.1.2'
|
|
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,9 @@ const MAIN_MENU = [
|
|
|
39
40
|
{ label: '快速配置', value: 'quick' },
|
|
40
41
|
{ label: '备份配置', value: 'backup' },
|
|
41
42
|
{ label: '恢复备份', value: 'restore' },
|
|
43
|
+
{ label: '备份聊天记录', value: 'backup-chats' },
|
|
44
|
+
{ label: '还原聊天记录', value: 'restore-chats' },
|
|
45
|
+
{ label: '清空缓存及备份', value: 'clear-cache' },
|
|
42
46
|
{ label: '退出', value: 'exit' }
|
|
43
47
|
]
|
|
44
48
|
|
|
@@ -50,11 +54,24 @@ const BACKUP_TARGETS = [
|
|
|
50
54
|
{ label: '全部备份', value: 'all' }
|
|
51
55
|
]
|
|
52
56
|
|
|
57
|
+
const CHAT_BACKUP_TARGETS = [
|
|
58
|
+
{ label: 'Codex 聊天记录', value: 'codex' },
|
|
59
|
+
{ label: 'Claude Code 聊天记录', value: 'claude-code' },
|
|
60
|
+
{ label: 'Hermes 聊天记录', value: 'hermes' },
|
|
61
|
+
{ label: 'OpenClaw 聊天记录', value: 'openclaw' },
|
|
62
|
+
{ label: '全部聊天记录', value: 'all' }
|
|
63
|
+
]
|
|
64
|
+
|
|
53
65
|
const RESTORE_CONFIRM_OPTIONS = [
|
|
54
66
|
{ label: '确认恢复', value: 'yes' },
|
|
55
67
|
{ label: '取消', value: 'no' }
|
|
56
68
|
]
|
|
57
69
|
|
|
70
|
+
const CLEAR_CONFIRM_OPTIONS = [
|
|
71
|
+
{ label: '确认清空', value: 'yes' },
|
|
72
|
+
{ label: '取消', value: 'no' }
|
|
73
|
+
]
|
|
74
|
+
|
|
58
75
|
const colors = {
|
|
59
76
|
reset: '\x1b[0m',
|
|
60
77
|
dim: '\x1b[2m',
|
|
@@ -253,6 +270,10 @@ function fileExists(filePath) {
|
|
|
253
270
|
return fs.existsSync(filePath) && fs.statSync(filePath).isFile()
|
|
254
271
|
}
|
|
255
272
|
|
|
273
|
+
function pathExists(targetPath) {
|
|
274
|
+
return fs.existsSync(targetPath)
|
|
275
|
+
}
|
|
276
|
+
|
|
256
277
|
function configTargets() {
|
|
257
278
|
return {
|
|
258
279
|
codex: [
|
|
@@ -294,6 +315,38 @@ function filesForBackupTarget(target) {
|
|
|
294
315
|
return targets[target] ?? []
|
|
295
316
|
}
|
|
296
317
|
|
|
318
|
+
function chatTargets() {
|
|
319
|
+
return {
|
|
320
|
+
codex: [
|
|
321
|
+
path.join(os.homedir(), '.codex', 'sessions'),
|
|
322
|
+
path.join(os.homedir(), '.codex', 'history.json')
|
|
323
|
+
],
|
|
324
|
+
'claude-code': [
|
|
325
|
+
path.join(os.homedir(), '.claude', 'projects'),
|
|
326
|
+
path.join(os.homedir(), '.claude', 'todos'),
|
|
327
|
+
path.join(os.homedir(), '.claude', 'shell-snapshots')
|
|
328
|
+
],
|
|
329
|
+
hermes: [
|
|
330
|
+
path.join(os.homedir(), '.hermes', 'sessions'),
|
|
331
|
+
path.join(os.homedir(), '.hermes', 'chats'),
|
|
332
|
+
path.join(os.homedir(), '.hermes', 'history.json')
|
|
333
|
+
],
|
|
334
|
+
openclaw: [
|
|
335
|
+
path.join(os.homedir(), '.openclaw', 'sessions'),
|
|
336
|
+
path.join(os.homedir(), '.openclaw', 'chats'),
|
|
337
|
+
path.join(os.homedir(), '.openclaw', 'history.json')
|
|
338
|
+
]
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function pathsForChatBackupTarget(target) {
|
|
343
|
+
const targets = chatTargets()
|
|
344
|
+
if (target === 'all') {
|
|
345
|
+
return Object.values(targets).flat()
|
|
346
|
+
}
|
|
347
|
+
return targets[target] ?? []
|
|
348
|
+
}
|
|
349
|
+
|
|
297
350
|
function uniqueFiles(files) {
|
|
298
351
|
return [...new Set(files)]
|
|
299
352
|
}
|
|
@@ -302,6 +355,10 @@ function backupTargetLabel(value) {
|
|
|
302
355
|
return BACKUP_TARGETS.find((item) => item.value === value)?.label ?? value
|
|
303
356
|
}
|
|
304
357
|
|
|
358
|
+
function chatBackupTargetLabel(value) {
|
|
359
|
+
return CHAT_BACKUP_TARGETS.find((item) => item.value === value)?.label ?? value
|
|
360
|
+
}
|
|
361
|
+
|
|
305
362
|
function timestampForName() {
|
|
306
363
|
const now = new Date()
|
|
307
364
|
const pad = (value) => String(value).padStart(2, '0')
|
|
@@ -342,6 +399,19 @@ function copyConfigToBackup(filePath, backupRoot) {
|
|
|
342
399
|
}
|
|
343
400
|
}
|
|
344
401
|
|
|
402
|
+
function copyPathToBackup(sourcePath, backupRoot) {
|
|
403
|
+
const stat = fs.statSync(sourcePath)
|
|
404
|
+
const backupFile = toBackupFileName(sourcePath)
|
|
405
|
+
const backupPath = path.join(backupRoot, 'files', backupFile)
|
|
406
|
+
ensureDir(path.dirname(backupPath))
|
|
407
|
+
fs.cpSync(sourcePath, backupPath, { recursive: true, force: true })
|
|
408
|
+
return {
|
|
409
|
+
source: sourcePath,
|
|
410
|
+
backup: path.relative(backupRoot, backupPath),
|
|
411
|
+
type: stat.isDirectory() ? 'directory' : 'file'
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
345
415
|
function loadBackupManifest(dir) {
|
|
346
416
|
const manifestPath = path.join(dir, 'manifest.json')
|
|
347
417
|
if (!fileExists(manifestPath)) return null
|
|
@@ -352,6 +422,43 @@ function loadBackupManifest(dir) {
|
|
|
352
422
|
}
|
|
353
423
|
}
|
|
354
424
|
|
|
425
|
+
function readCache() {
|
|
426
|
+
if (!fileExists(CACHE_FILE)) return {}
|
|
427
|
+
try {
|
|
428
|
+
return JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'))
|
|
429
|
+
} catch {
|
|
430
|
+
return {}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function writeCache(cache) {
|
|
435
|
+
ensureDir(APP_DIR)
|
|
436
|
+
fs.writeFileSync(CACHE_FILE, json({
|
|
437
|
+
...cache,
|
|
438
|
+
updated_at: new Date().toISOString()
|
|
439
|
+
}), { encoding: 'utf8', mode: 0o600 })
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function getCachedApiKey() {
|
|
443
|
+
const cache = readCache()
|
|
444
|
+
return typeof cache.api_key === 'string' ? cache.api_key : ''
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function setCachedApiKey(apiKey) {
|
|
448
|
+
if (!apiKey) return
|
|
449
|
+
writeCache({
|
|
450
|
+
...readCache(),
|
|
451
|
+
api_key: apiKey,
|
|
452
|
+
api_key_hint: maskKey(apiKey)
|
|
453
|
+
})
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function deletePath(target) {
|
|
457
|
+
if (!fs.existsSync(target)) return false
|
|
458
|
+
fs.rmSync(target, { recursive: true, force: true })
|
|
459
|
+
return true
|
|
460
|
+
}
|
|
461
|
+
|
|
355
462
|
function writeFileWithBackup(filePath, content, backupRoot) {
|
|
356
463
|
ensureDir(path.dirname(filePath))
|
|
357
464
|
if (fs.existsSync(filePath)) {
|
|
@@ -564,7 +671,10 @@ async function quickConfig() {
|
|
|
564
671
|
if (step === 0) {
|
|
565
672
|
clear()
|
|
566
673
|
banner()
|
|
567
|
-
const
|
|
674
|
+
const cachedApiKey = getCachedApiKey()
|
|
675
|
+
const defaultHint = cachedApiKey ? maskKey(cachedApiKey) : ''
|
|
676
|
+
const apiKeyInput = await question('请输入 API Key [Esc 返回主菜单]', { mask: true, defaultValue: defaultHint })
|
|
677
|
+
const apiKey = apiKeyInput === defaultHint && cachedApiKey ? cachedApiKey : apiKeyInput
|
|
568
678
|
if (apiKey === '__exit__') return '__exit__'
|
|
569
679
|
if (apiKey === null) return
|
|
570
680
|
if (!apiKey) {
|
|
@@ -573,6 +683,7 @@ async function quickConfig() {
|
|
|
573
683
|
continue
|
|
574
684
|
}
|
|
575
685
|
state.apiKey = apiKey
|
|
686
|
+
setCachedApiKey(apiKey)
|
|
576
687
|
step = 1
|
|
577
688
|
continue
|
|
578
689
|
}
|
|
@@ -698,6 +809,7 @@ async function backupConfig() {
|
|
|
698
809
|
ensureDir(backupRoot)
|
|
699
810
|
const files = existing.map((filePath) => copyConfigToBackup(filePath, backupRoot))
|
|
700
811
|
const manifest = {
|
|
812
|
+
kind: 'config',
|
|
701
813
|
name,
|
|
702
814
|
target,
|
|
703
815
|
target_label: backupTargetLabel(target),
|
|
@@ -721,27 +833,105 @@ async function backupConfig() {
|
|
|
721
833
|
await pause()
|
|
722
834
|
}
|
|
723
835
|
|
|
836
|
+
async function backupChatRecords() {
|
|
837
|
+
clear()
|
|
838
|
+
banner()
|
|
839
|
+
const target = await select('请选择聊天记录备份目标:', CHAT_BACKUP_TARGETS)
|
|
840
|
+
if (target === '__exit__') return '__exit__'
|
|
841
|
+
if (!target) return
|
|
842
|
+
|
|
843
|
+
const defaultName = `${timestampForName()}_chats_${target}`
|
|
844
|
+
clear()
|
|
845
|
+
banner()
|
|
846
|
+
const rawName = await question('请输入聊天记录备份名称 [Esc 返回上一步]', { defaultValue: defaultName })
|
|
847
|
+
if (rawName === '__exit__') return '__exit__'
|
|
848
|
+
if (rawName === null) return backupChatRecords()
|
|
849
|
+
|
|
850
|
+
const name = safeBackupName(rawName) || defaultName
|
|
851
|
+
const backupRoot = path.join(APP_DIR, 'backups', name)
|
|
852
|
+
if (fs.existsSync(backupRoot)) {
|
|
853
|
+
console.log(c('red', `备份名称已存在: ${name}`))
|
|
854
|
+
await pause()
|
|
855
|
+
return backupChatRecords()
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
const candidates = uniqueFiles(pathsForChatBackupTarget(target))
|
|
859
|
+
const existing = candidates.filter(pathExists)
|
|
860
|
+
if (existing.length === 0) {
|
|
861
|
+
console.log(c('yellow', `没有找到可备份的 ${chatBackupTargetLabel(target)}。`))
|
|
862
|
+
console.log()
|
|
863
|
+
console.log(c('dim', '已检查常见目录:'))
|
|
864
|
+
for (const item of candidates) {
|
|
865
|
+
console.log(c('dim', `- ${item}`))
|
|
866
|
+
}
|
|
867
|
+
await pause()
|
|
868
|
+
return
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
ensureDir(backupRoot)
|
|
872
|
+
const files = existing.map((sourcePath) => copyPathToBackup(sourcePath, backupRoot))
|
|
873
|
+
const manifest = {
|
|
874
|
+
kind: 'chat',
|
|
875
|
+
name,
|
|
876
|
+
target,
|
|
877
|
+
target_label: chatBackupTargetLabel(target),
|
|
878
|
+
created_at: new Date().toISOString(),
|
|
879
|
+
files
|
|
880
|
+
}
|
|
881
|
+
fs.writeFileSync(path.join(backupRoot, 'manifest.json'), json(manifest), 'utf8')
|
|
882
|
+
|
|
883
|
+
clear()
|
|
884
|
+
banner()
|
|
885
|
+
console.log(c('green', '聊天记录备份完成。'))
|
|
886
|
+
console.log()
|
|
887
|
+
console.log(`备份名称: ${name}`)
|
|
888
|
+
console.log(`备份目标: ${chatBackupTargetLabel(target)}`)
|
|
889
|
+
console.log(`备份目录: ${backupRoot}`)
|
|
890
|
+
console.log()
|
|
891
|
+
console.log(c('cyan', '已备份路径:'))
|
|
892
|
+
for (const file of files) {
|
|
893
|
+
console.log(`- ${file.source}`)
|
|
894
|
+
}
|
|
895
|
+
await pause()
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
function backupEntryMatchesKind(entryName, backupDir, kind) {
|
|
899
|
+
const manifest = loadBackupManifest(path.join(backupDir, entryName))
|
|
900
|
+
if (!manifest) return kind === 'config'
|
|
901
|
+
if (kind === 'config') return !manifest.kind || manifest.kind === 'config'
|
|
902
|
+
return manifest.kind === kind
|
|
903
|
+
}
|
|
904
|
+
|
|
724
905
|
async function restoreBackup() {
|
|
906
|
+
return restoreBackupByKind('config', '请选择要查看的配置备份:', '暂无配置备份。')
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
async function restoreChatRecords() {
|
|
910
|
+
return restoreBackupByKind('chat', '请选择要还原的聊天记录备份:', '暂无聊天记录备份。')
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
async function restoreBackupByKind(kind, title, emptyMessage) {
|
|
725
914
|
clear()
|
|
726
915
|
banner()
|
|
727
916
|
const backupDir = path.join(APP_DIR, 'backups')
|
|
728
917
|
if (!fs.existsSync(backupDir)) {
|
|
729
|
-
console.log(c('yellow',
|
|
918
|
+
console.log(c('yellow', emptyMessage))
|
|
730
919
|
return pause()
|
|
731
920
|
}
|
|
732
921
|
|
|
733
922
|
const entries = fs.readdirSync(backupDir, { withFileTypes: true })
|
|
734
923
|
.filter((entry) => entry.isDirectory())
|
|
735
924
|
.map((entry) => entry.name)
|
|
925
|
+
.filter((name) => backupEntryMatchesKind(name, backupDir, kind))
|
|
736
926
|
.sort()
|
|
737
927
|
.reverse()
|
|
738
928
|
|
|
739
929
|
if (entries.length === 0) {
|
|
740
|
-
console.log(c('yellow',
|
|
930
|
+
console.log(c('yellow', emptyMessage))
|
|
741
931
|
return pause()
|
|
742
932
|
}
|
|
743
933
|
|
|
744
|
-
const selected = await select(
|
|
934
|
+
const selected = await select(title, [
|
|
745
935
|
...entries.map((name) => ({ label: name, value: name })),
|
|
746
936
|
{ label: '返回', value: null }
|
|
747
937
|
])
|
|
@@ -768,13 +958,16 @@ async function restoreBackup() {
|
|
|
768
958
|
console.log(`备份目标: ${manifest.target_label || manifest.target || '未知'}`)
|
|
769
959
|
console.log(`创建时间: ${manifest.created_at || '未知'}`)
|
|
770
960
|
console.log()
|
|
771
|
-
console.log(c('cyan', '将恢复以下文件:'))
|
|
961
|
+
console.log(c('cyan', kind === 'chat' ? '将还原以下聊天记录路径:' : '将恢复以下文件:'))
|
|
772
962
|
for (const file of manifest.files || []) {
|
|
773
963
|
console.log(`- ${file.source}`)
|
|
774
964
|
}
|
|
775
965
|
console.log()
|
|
776
966
|
|
|
777
|
-
const
|
|
967
|
+
const confirmText = kind === 'chat'
|
|
968
|
+
? '确认还原这个聊天记录备份吗?当前同名聊天记录会先自动备份。'
|
|
969
|
+
: '确认恢复这个备份吗?当前同名配置会先自动备份。'
|
|
970
|
+
const confirm = await select(confirmText, RESTORE_CONFIRM_OPTIONS)
|
|
778
971
|
if (confirm === '__exit__') return '__exit__'
|
|
779
972
|
if (confirm !== 'yes') return
|
|
780
973
|
|
|
@@ -784,18 +977,21 @@ async function restoreBackup() {
|
|
|
784
977
|
for (const file of manifest.files || []) {
|
|
785
978
|
const source = file.source
|
|
786
979
|
const backupPath = path.join(selectedDir, file.backup)
|
|
787
|
-
if (!source || !
|
|
788
|
-
if (
|
|
789
|
-
|
|
980
|
+
if (!source || !pathExists(backupPath)) continue
|
|
981
|
+
if (pathExists(source)) {
|
|
982
|
+
copyPathToBackup(source, safetyBackup)
|
|
790
983
|
}
|
|
791
984
|
ensureDir(path.dirname(source))
|
|
792
|
-
|
|
985
|
+
if (pathExists(source)) {
|
|
986
|
+
deletePath(source)
|
|
987
|
+
}
|
|
988
|
+
fs.cpSync(backupPath, source, { recursive: true, force: true })
|
|
793
989
|
restored.push(source)
|
|
794
990
|
}
|
|
795
991
|
|
|
796
992
|
clear()
|
|
797
993
|
banner()
|
|
798
|
-
console.log(c('green', '恢复完成。'))
|
|
994
|
+
console.log(c('green', kind === 'chat' ? '聊天记录还原完成。' : '恢复完成。'))
|
|
799
995
|
console.log()
|
|
800
996
|
for (const file of restored) {
|
|
801
997
|
console.log(`- ${file}`)
|
|
@@ -805,6 +1001,57 @@ async function restoreBackup() {
|
|
|
805
1001
|
await pause()
|
|
806
1002
|
}
|
|
807
1003
|
|
|
1004
|
+
async function clearCacheAndBackups() {
|
|
1005
|
+
clear()
|
|
1006
|
+
banner()
|
|
1007
|
+
console.log(c('yellow', '将删除以下 iivo-sub 数据:'))
|
|
1008
|
+
console.log(`- API Key 缓存: ${CACHE_FILE}`)
|
|
1009
|
+
console.log(`- 备份目录: ${path.join(APP_DIR, 'backups')} ${c('dim', '(包含配置备份和聊天记录备份)')}`)
|
|
1010
|
+
console.log(`- 临时配置脚本: ${path.join(APP_DIR, '*.env / *.ps1 / *.cmd')}`)
|
|
1011
|
+
console.log(`- 最近配置摘要: ${path.join(APP_DIR, 'config.json')}`)
|
|
1012
|
+
console.log()
|
|
1013
|
+
console.log('不会删除 Codex、Claude Code、Hermes、OpenClaw 的实际配置文件。')
|
|
1014
|
+
console.log()
|
|
1015
|
+
|
|
1016
|
+
const confirm = await select('确认清空缓存及备份吗?', CLEAR_CONFIRM_OPTIONS)
|
|
1017
|
+
if (confirm === '__exit__') return '__exit__'
|
|
1018
|
+
if (confirm !== 'yes') return
|
|
1019
|
+
|
|
1020
|
+
const removed = []
|
|
1021
|
+
for (const target of [
|
|
1022
|
+
CACHE_FILE,
|
|
1023
|
+
path.join(APP_DIR, 'backups'),
|
|
1024
|
+
path.join(APP_DIR, 'config.json'),
|
|
1025
|
+
path.join(APP_DIR, 'codex.env'),
|
|
1026
|
+
path.join(APP_DIR, 'codex.ps1'),
|
|
1027
|
+
path.join(APP_DIR, 'codex.cmd'),
|
|
1028
|
+
path.join(APP_DIR, 'claude-code.env'),
|
|
1029
|
+
path.join(APP_DIR, 'claude-code.ps1'),
|
|
1030
|
+
path.join(APP_DIR, 'claude-code.cmd'),
|
|
1031
|
+
path.join(APP_DIR, 'hermes.env'),
|
|
1032
|
+
path.join(APP_DIR, 'hermes.ps1'),
|
|
1033
|
+
path.join(APP_DIR, 'hermes.cmd'),
|
|
1034
|
+
path.join(APP_DIR, 'openclaw.env'),
|
|
1035
|
+
path.join(APP_DIR, 'openclaw.ps1'),
|
|
1036
|
+
path.join(APP_DIR, 'openclaw.cmd')
|
|
1037
|
+
]) {
|
|
1038
|
+
if (deletePath(target)) removed.push(target)
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
clear()
|
|
1042
|
+
banner()
|
|
1043
|
+
if (removed.length === 0) {
|
|
1044
|
+
console.log(c('yellow', '没有找到需要清空的数据。'))
|
|
1045
|
+
} else {
|
|
1046
|
+
console.log(c('green', '缓存及备份已清空。'))
|
|
1047
|
+
console.log()
|
|
1048
|
+
for (const target of removed) {
|
|
1049
|
+
console.log(`- ${target}`)
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
await pause()
|
|
1053
|
+
}
|
|
1054
|
+
|
|
808
1055
|
async function pause() {
|
|
809
1056
|
await question('按回车继续')
|
|
810
1057
|
}
|
|
@@ -826,6 +1073,18 @@ async function main() {
|
|
|
826
1073
|
const result = await restoreBackup()
|
|
827
1074
|
if (result === '__exit__') break
|
|
828
1075
|
}
|
|
1076
|
+
if (action === 'backup-chats') {
|
|
1077
|
+
const result = await backupChatRecords()
|
|
1078
|
+
if (result === '__exit__') break
|
|
1079
|
+
}
|
|
1080
|
+
if (action === 'restore-chats') {
|
|
1081
|
+
const result = await restoreChatRecords()
|
|
1082
|
+
if (result === '__exit__') break
|
|
1083
|
+
}
|
|
1084
|
+
if (action === 'clear-cache') {
|
|
1085
|
+
const result = await clearCacheAndBackups()
|
|
1086
|
+
if (result === '__exit__') break
|
|
1087
|
+
}
|
|
829
1088
|
}
|
|
830
1089
|
clear()
|
|
831
1090
|
console.log('已退出 iivo-sub。')
|