readshell 0.2.6 → 0.3.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 +9 -6
- package/dist/index.js +58 -30
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,6 +70,11 @@ novel resume
|
|
|
70
70
|
# Open a specific book
|
|
71
71
|
novel open <book-id>
|
|
72
72
|
|
|
73
|
+
# Configure application (language | line-spacing | reading-mode)
|
|
74
|
+
novel config language en
|
|
75
|
+
novel config line-spacing 1
|
|
76
|
+
novel config reading-mode scroll
|
|
77
|
+
|
|
73
78
|
# View book list
|
|
74
79
|
novel list
|
|
75
80
|
|
|
@@ -82,14 +87,12 @@ novel update
|
|
|
82
87
|
|
|
83
88
|
### Reader Shortcuts
|
|
84
89
|
|
|
85
|
-
|
|
|
86
|
-
|
|
87
|
-
| `
|
|
88
|
-
| `k` / `↑` | Previous Page |
|
|
89
|
-
| `c` | Open Chapters and Bookmarks list |
|
|
90
|
+
| `Space` / `j` / `↓` | Next Page (or Scroll half-page in scroll mode) |
|
|
91
|
+
| `k` / `↑` | Previous Page (or Scroll half-page in scroll mode) |
|
|
92
|
+
| `c` | **Open Chapters and Bookmarks list (Quick Navigation)** |
|
|
90
93
|
| `Tab` | Switch between Chapters/Bookmarks in menu |
|
|
91
94
|
| `m` / `M` | Add Bookmark at the current page |
|
|
92
|
-
| `b` / `Esc` | Boss Key (disguise terminal
|
|
95
|
+
| `b` / `Esc` | Boss Key (instantly disguise terminal and save) |
|
|
93
96
|
| `q` | Quit and save progress |
|
|
94
97
|
| `?` | Help |
|
|
95
98
|
|
package/dist/index.js
CHANGED
|
@@ -530,10 +530,12 @@ var zh_default = {
|
|
|
530
530
|
"cli.remove.not_found": "\u2717 \u672A\u627E\u5230\u5339\u914D\u4E66\u7C4D:",
|
|
531
531
|
"cli.remove.success": "\u2713 \u5DF2\u79FB\u9664\u4E66\u7C4D:",
|
|
532
532
|
"cli.remove.fail": "\u79FB\u9664\u4E66\u7C4D\u5931\u8D25:",
|
|
533
|
-
"cli.lang.desc": "\u5207\u6362\u8BED\u8A00",
|
|
534
|
-
"cli.lang.help": "\u8BED\u8A00\u4EE3\u7801: zh | en",
|
|
535
533
|
"cli.lang.success": "\u2713 \u8BED\u8A00\u5DF2\u5207\u6362\u4E3A: {0}",
|
|
536
534
|
"cli.lang.unsupported": "\u2717 \u4E0D\u652F\u6301\u7684\u8BED\u8A00: {0}",
|
|
535
|
+
"cli.config.desc": "\u4FEE\u6539\u5E94\u7528\u914D\u7F6E",
|
|
536
|
+
"cli.config.line_spacing.desc": "\u884C\u95F4\u8DDD: 0 (\u7D27\u51D1) | 1 (\u9002\u4E2D) | 2 (\u5BBD\u677E)",
|
|
537
|
+
"cli.config.line_spacing.success": "\u2713 \u884C\u95F4\u8DDD\u5DF2\u8BBE\u7F6E\u4E3A: {0}",
|
|
538
|
+
"cli.config.reading_mode.desc": "\u9605\u8BFB\u6A21\u5F0F: page (\u7FFB\u9875) | scroll (\u6EDA\u52A8)",
|
|
537
539
|
"cli.update.desc": "\u68C0\u67E5\u6700\u65B0\u7248\u672C\u5E76\u81EA\u52A8\u66F4\u65B0",
|
|
538
540
|
"cli.update.checking": "\u6B63\u5728\u68C0\u67E5\u66F4\u65B0...",
|
|
539
541
|
"cli.update.latest": "\u2713 \u5F53\u524D\u5DF2\u662F\u6700\u65B0\u7248\u672C (v{0})",
|
|
@@ -550,10 +552,10 @@ var zh_default = {
|
|
|
550
552
|
"tui.reader.loading": "\u8BFB\u53D6\u4E2D...",
|
|
551
553
|
"tui.reader.bookmark_add": "\u2713 \u589E\u52A0\u4E66\u7B7E: {0}",
|
|
552
554
|
"tui.reader.status.remaining": "\u9884\u8BA1\u5269\u4F59 {0}",
|
|
553
|
-
// TUI - ChapterNav
|
|
554
555
|
"tui.nav.tab.chapters": "[\u5168\u90E8\u7AE0\u8282]",
|
|
555
556
|
"tui.nav.tab.bookmarks": "[\u6211\u7684\u4E66\u7B7E]",
|
|
556
557
|
"tui.nav.tips": "Enter \u8DF3\u8F6C \xB7 Tab \u5207\u6362 \xB7 Esc/q \u5173\u95ED",
|
|
558
|
+
"tui.nav.hint": "\u6309 C \u952E\u5F39\u51FA\u6B64\u6E05\u5355\uFF0C\u6309 Tab \u5207\u6362\u4E66\u7B7E",
|
|
557
559
|
"tui.nav.empty": "\u6CA1\u6709\u8BB0\u5F55",
|
|
558
560
|
"tui.nav.page": "\u7B2C {0} / {1} \u9875"
|
|
559
561
|
};
|
|
@@ -592,10 +594,12 @@ var en = {
|
|
|
592
594
|
"cli.remove.not_found": "\u2717 Book not found:",
|
|
593
595
|
"cli.remove.success": "\u2713 Book removed:",
|
|
594
596
|
"cli.remove.fail": "Failed to remove book:",
|
|
595
|
-
"cli.lang.desc": "Switch language",
|
|
596
|
-
"cli.lang.help": "Language code: zh | en",
|
|
597
597
|
"cli.lang.success": "\u2713 Language switched to: {0}",
|
|
598
598
|
"cli.lang.unsupported": "\u2717 Unsupported language: {0}",
|
|
599
|
+
"cli.config.desc": "Modify application configuration",
|
|
600
|
+
"cli.config.line_spacing.desc": "Line spacing: 0 (tight) | 1 (medium) | 2 (loose)",
|
|
601
|
+
"cli.config.line_spacing.success": "\u2713 Line spacing set to: {0}",
|
|
602
|
+
"cli.config.reading_mode.desc": "Reading mode: page (paging) | scroll (scrolling)",
|
|
599
603
|
"cli.update.desc": "Check for the latest version and update automatically",
|
|
600
604
|
"cli.update.checking": "Checking for updates...",
|
|
601
605
|
"cli.update.latest": "\u2713 Already up to date (v{0})",
|
|
@@ -616,6 +620,7 @@ var en = {
|
|
|
616
620
|
"tui.nav.tab.chapters": "[All Chapters]",
|
|
617
621
|
"tui.nav.tab.bookmarks": "[My Bookmarks]",
|
|
618
622
|
"tui.nav.tips": "Enter jump \xB7 Tab switch \xB7 Esc/q close",
|
|
623
|
+
"tui.nav.hint": "Press C to open this list, press Tab to switch to bookmarks",
|
|
619
624
|
"tui.nav.empty": "No records",
|
|
620
625
|
"tui.nav.page": "Page {0} / {1}"
|
|
621
626
|
};
|
|
@@ -627,7 +632,8 @@ var defaults = {
|
|
|
627
632
|
linesPerPage: 0,
|
|
628
633
|
showStatusBar: true,
|
|
629
634
|
readingMode: "page",
|
|
630
|
-
language: "zh"
|
|
635
|
+
language: "zh",
|
|
636
|
+
lineSpacing: 0
|
|
631
637
|
};
|
|
632
638
|
var config = new Conf({
|
|
633
639
|
projectName: "readshell",
|
|
@@ -638,7 +644,8 @@ function getConfig() {
|
|
|
638
644
|
linesPerPage: config.get("linesPerPage"),
|
|
639
645
|
showStatusBar: config.get("showStatusBar"),
|
|
640
646
|
readingMode: config.get("readingMode"),
|
|
641
|
-
language: config.get("language")
|
|
647
|
+
language: config.get("language"),
|
|
648
|
+
lineSpacing: config.get("lineSpacing")
|
|
642
649
|
};
|
|
643
650
|
}
|
|
644
651
|
function setConfig(key, value) {
|
|
@@ -946,7 +953,7 @@ function renderLineWithHighlight(line) {
|
|
|
946
953
|
return /* @__PURE__ */ jsx3(Text3, { children: part }, index);
|
|
947
954
|
}) });
|
|
948
955
|
}
|
|
949
|
-
function TextRenderer({ lines, height }) {
|
|
956
|
+
function TextRenderer({ lines, height, lineSpacing = 0 }) {
|
|
950
957
|
const displayLines = [...lines];
|
|
951
958
|
if (height && displayLines.length < height) {
|
|
952
959
|
const padding = height - displayLines.length;
|
|
@@ -954,7 +961,7 @@ function TextRenderer({ lines, height }) {
|
|
|
954
961
|
displayLines.push("");
|
|
955
962
|
}
|
|
956
963
|
}
|
|
957
|
-
return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: displayLines.map((line, index) => /* @__PURE__ */ jsx3(Box3, { children: renderLineWithHighlight(line) }, index)) });
|
|
964
|
+
return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: displayLines.map((line, index) => /* @__PURE__ */ jsx3(Box3, { marginBottom: lineSpacing, children: renderLineWithHighlight(line) }, index)) });
|
|
958
965
|
}
|
|
959
966
|
|
|
960
967
|
// src/ui/components/StatusBar.tsx
|
|
@@ -1222,9 +1229,10 @@ function isFullWidth(char) {
|
|
|
1222
1229
|
}
|
|
1223
1230
|
|
|
1224
1231
|
// src/utils/paginate.ts
|
|
1225
|
-
function paginate(text, width, height) {
|
|
1232
|
+
function paginate(text, width, height, step) {
|
|
1226
1233
|
const pages = [];
|
|
1227
1234
|
const rawLines = text.split("\n");
|
|
1235
|
+
const actualStep = step || height;
|
|
1228
1236
|
const wrappedLines = [];
|
|
1229
1237
|
let currentOffset = 0;
|
|
1230
1238
|
for (const rawLine of rawLines) {
|
|
@@ -1234,12 +1242,14 @@ function paginate(text, width, height) {
|
|
|
1234
1242
|
}
|
|
1235
1243
|
currentOffset += Buffer.byteLength(rawLine + "\n", "utf-8");
|
|
1236
1244
|
}
|
|
1237
|
-
for (let i = 0; i < wrappedLines.length; i +=
|
|
1245
|
+
for (let i = 0; i < wrappedLines.length; i += actualStep) {
|
|
1238
1246
|
const pageLines = wrappedLines.slice(i, i + height);
|
|
1247
|
+
if (pageLines.length === 0) break;
|
|
1239
1248
|
pages.push({
|
|
1240
1249
|
lines: pageLines.map((l) => l.text),
|
|
1241
1250
|
byteOffset: pageLines[0]?.byteOffset ?? 0
|
|
1242
1251
|
});
|
|
1252
|
+
if (i + actualStep >= wrappedLines.length) break;
|
|
1243
1253
|
}
|
|
1244
1254
|
return pages;
|
|
1245
1255
|
}
|
|
@@ -1413,13 +1423,20 @@ var BookmarkService = class {
|
|
|
1413
1423
|
|
|
1414
1424
|
// src/utils/bossKey.ts
|
|
1415
1425
|
function triggerBossKey() {
|
|
1426
|
+
process.stdout.write("\x1B[3J\x1B[2J\x1B[1;1H");
|
|
1416
1427
|
console.clear();
|
|
1417
1428
|
const fakeLog = `
|
|
1418
|
-
|
|
1429
|
+
file:///Users/yindawei/project/node_modules/vite/dist/node/chunks/dep-BbV93i69.js:43916
|
|
1430
|
+
throw new Error(\`[vite] Failed to resolve module import "./App.vue". Check if the file exists.\`);
|
|
1431
|
+
^
|
|
1432
|
+
|
|
1433
|
+
Error: [vite] Failed to resolve module import "./App.vue". Check if the file exists.
|
|
1434
|
+
at Object.run (file:///Users/yindawei/project/node_modules/vite/dist/node/chunks/dep-BbV93i69.js:43916:13)
|
|
1435
|
+
at async file:///Users/yindawei/project/node_modules/vite/dist/node/cli.js:722:7
|
|
1436
|
+
at async startVite (file:///Users/yindawei/project/node_modules/vite/dist/node/cli.js:700:5)
|
|
1437
|
+
at async Object.handler (file:///Users/yindawei/project/node_modules/vite/dist/node/cli.js:650:1)
|
|
1419
1438
|
|
|
1420
|
-
|
|
1421
|
-
\u279C Network: use --host to expose
|
|
1422
|
-
\u279C press h + enter to show help
|
|
1439
|
+
Node.js v20.11.0
|
|
1423
1440
|
`;
|
|
1424
1441
|
console.log(fakeLog);
|
|
1425
1442
|
process.exit(0);
|
|
@@ -1447,7 +1464,8 @@ function ReaderContent({
|
|
|
1447
1464
|
pages,
|
|
1448
1465
|
initialByteOffset,
|
|
1449
1466
|
termHeight,
|
|
1450
|
-
contentHeight
|
|
1467
|
+
contentHeight,
|
|
1468
|
+
lineSpacing
|
|
1451
1469
|
}) {
|
|
1452
1470
|
const { exit } = useApp3();
|
|
1453
1471
|
const [chapterTitle, setChapterTitle] = useState5();
|
|
@@ -1460,6 +1478,14 @@ function ReaderContent({
|
|
|
1460
1478
|
const chapterServiceRef = useRef(new ChapterService());
|
|
1461
1479
|
const bookmarkServiceRef = useRef(new BookmarkService());
|
|
1462
1480
|
const reader = useReader(pages, initialByteOffset);
|
|
1481
|
+
const saveReadingProgress = () => {
|
|
1482
|
+
const offset = reader.getCurrentOffset();
|
|
1483
|
+
const percent = reader.getPercent();
|
|
1484
|
+
const chapter = chapterServiceRef.current.getChapterByOffset(bookId, offset);
|
|
1485
|
+
const chapterNo = chapter?.chapter_no ?? 0;
|
|
1486
|
+
progressServiceRef.current.saveProgress(bookId, chapterNo, offset, percent);
|
|
1487
|
+
logger.debug(`\u8FDB\u5EA6\u5DF2\u4FDD\u5B58: offset=${offset}, ${(percent * 100).toFixed(1)}%`);
|
|
1488
|
+
};
|
|
1463
1489
|
useEffect4(() => {
|
|
1464
1490
|
const currentOffset = reader.getCurrentOffset();
|
|
1465
1491
|
const chapter = chapterServiceRef.current.getChapterByOffset(bookId, currentOffset);
|
|
@@ -1491,12 +1517,7 @@ function ReaderContent({
|
|
|
1491
1517
|
};
|
|
1492
1518
|
useEffect4(() => {
|
|
1493
1519
|
return () => {
|
|
1494
|
-
|
|
1495
|
-
const percent = reader.getPercent();
|
|
1496
|
-
const chapter = chapterServiceRef.current.getChapterByOffset(bookId, offset);
|
|
1497
|
-
const chapterNo = chapter?.chapter_no ?? 0;
|
|
1498
|
-
progressServiceRef.current.saveProgress(bookId, chapterNo, offset, percent);
|
|
1499
|
-
logger.debug(`\u8FDB\u5EA6\u5DF2\u4FDD\u5B58: offset=${offset}, ${(percent * 100).toFixed(1)}%`);
|
|
1520
|
+
saveReadingProgress();
|
|
1500
1521
|
};
|
|
1501
1522
|
}, [bookId, reader]);
|
|
1502
1523
|
useKeyboard(
|
|
@@ -1505,7 +1526,10 @@ function ReaderContent({
|
|
|
1505
1526
|
onPrev: () => reader.prevPage(),
|
|
1506
1527
|
onQuit: () => exit(),
|
|
1507
1528
|
onChapterList: () => setShowChapterNav(true),
|
|
1508
|
-
onBossKey: () =>
|
|
1529
|
+
onBossKey: () => {
|
|
1530
|
+
saveReadingProgress();
|
|
1531
|
+
triggerBossKey();
|
|
1532
|
+
},
|
|
1509
1533
|
onBookmarkAdd: handleAddBookmark
|
|
1510
1534
|
},
|
|
1511
1535
|
!showChapterNav
|
|
@@ -1519,7 +1543,7 @@ function ReaderContent({
|
|
|
1519
1543
|
const remainingMinutes = estimateReadingTime(remainingChars, true);
|
|
1520
1544
|
const remainingTimeStr = formatReadingTime(remainingMinutes);
|
|
1521
1545
|
return /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", height: termHeight, children: !showChapterNav ? /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
1522
|
-
/* @__PURE__ */ jsx6(Box6, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: /* @__PURE__ */ jsx6(TextRenderer, { lines: currentLines, height: calculatedContentHeight }) }),
|
|
1546
|
+
/* @__PURE__ */ jsx6(Box6, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: /* @__PURE__ */ jsx6(TextRenderer, { lines: currentLines, height: calculatedContentHeight, lineSpacing }) }),
|
|
1523
1547
|
/* @__PURE__ */ jsx6(
|
|
1524
1548
|
StatusBar,
|
|
1525
1549
|
{
|
|
@@ -1555,7 +1579,9 @@ function ReaderPage({ bookId, initialByteOffset, onNavigate: _onNavigate }) {
|
|
|
1555
1579
|
const [error, setError] = useState5(null);
|
|
1556
1580
|
const termWidth = stdout?.columns ?? 80;
|
|
1557
1581
|
const termHeight = stdout?.rows ?? 24;
|
|
1558
|
-
const
|
|
1582
|
+
const appConfig = getConfig();
|
|
1583
|
+
const lineSpacing = appConfig.lineSpacing || 0;
|
|
1584
|
+
const contentHeight = Math.max(Math.floor((termHeight - 3) / (1 + lineSpacing)), 2);
|
|
1559
1585
|
useEffect4(() => {
|
|
1560
1586
|
try {
|
|
1561
1587
|
const bookModel = new BookModel();
|
|
@@ -1568,9 +1594,10 @@ function ReaderPage({ bookId, initialByteOffset, onNavigate: _onNavigate }) {
|
|
|
1568
1594
|
const recentService = new RecentService();
|
|
1569
1595
|
recentService.recordOpen(bookId);
|
|
1570
1596
|
parseFile(bookRecord.file_path, bookRecord.format).then((parsed) => {
|
|
1571
|
-
const
|
|
1597
|
+
const stepSize = appConfig.readingMode === "scroll" ? Math.max(1, Math.floor(contentHeight / 2)) : contentHeight;
|
|
1598
|
+
const paginatedPages = paginate(parsed.content, termWidth - 2, contentHeight, stepSize);
|
|
1572
1599
|
setPages(paginatedPages);
|
|
1573
|
-
logger.debug(`\u52A0\u8F7D\u5B8C\u6210: ${bookRecord.title}, ${paginatedPages.length} \u9875`);
|
|
1600
|
+
logger.debug(`\u52A0\u8F7D\u5B8C\u6210: ${bookRecord.title}, ${paginatedPages.length} \u9875, \u6A21\u5F0F: ${appConfig.readingMode}`);
|
|
1574
1601
|
}).catch((err) => {
|
|
1575
1602
|
setError(`\u5185\u5BB9\u89E3\u6790\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
1576
1603
|
});
|
|
@@ -1604,7 +1631,8 @@ function ReaderPage({ bookId, initialByteOffset, onNavigate: _onNavigate }) {
|
|
|
1604
1631
|
pages,
|
|
1605
1632
|
initialByteOffset,
|
|
1606
1633
|
termHeight,
|
|
1607
|
-
contentHeight
|
|
1634
|
+
contentHeight,
|
|
1635
|
+
lineSpacing
|
|
1608
1636
|
}
|
|
1609
1637
|
);
|
|
1610
1638
|
}
|
|
@@ -1812,7 +1840,7 @@ var updateCommand = {
|
|
|
1812
1840
|
handler: async () => {
|
|
1813
1841
|
try {
|
|
1814
1842
|
console.log(t("cli.update.checking"));
|
|
1815
|
-
const localVersion = true ? "0.
|
|
1843
|
+
const localVersion = true ? "0.3.1" : "0.2.2";
|
|
1816
1844
|
const npmOutput = execSync("npm view readshell version", { encoding: "utf-8" });
|
|
1817
1845
|
const latestVersion = npmOutput.trim();
|
|
1818
1846
|
if (!latestVersion) {
|
|
@@ -1836,7 +1864,7 @@ var updateCommand = {
|
|
|
1836
1864
|
|
|
1837
1865
|
// src/cli/parser.ts
|
|
1838
1866
|
function createParser() {
|
|
1839
|
-
const version = true ? "0.
|
|
1867
|
+
const version = true ? "0.3.1" : "dev";
|
|
1840
1868
|
return yargs(hideBin(process.argv)).scriptName("novel").usage("$0 <command> [options]").command(importCommand).command(resumeCommand).command(openCommand).command(libraryCommand).command(removeCommand).command(langCommand).command(updateCommand).demandCommand(1, "\u8BF7\u6307\u5B9A\u4E00\u4E2A\u547D\u4EE4\u3002\u4F7F\u7528 --help \u67E5\u770B\u53EF\u7528\u547D\u4EE4\u3002").strict().alias("h", "help").alias("v", "version").version(version).epilogue("ReadShell \u2014 \u7EC8\u7AEF\u5185\u4F4E\u6253\u65AD\u8F7B\u9605\u8BFB\u5DE5\u5177");
|
|
1841
1869
|
}
|
|
1842
1870
|
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/parser.ts","../src/services/BookService.ts","../src/db/client.ts","../src/config/paths.ts","../src/utils/logger.ts","../src/db/models/Book.ts","../src/db/models/Chapter.ts","../src/db/models/Recent.ts","../src/db/models/Progress.ts","../src/parsers/TxtParser.ts","../src/parsers/EpubParser.ts","../src/parsers/index.ts","../src/utils/hash.ts","../src/locales/zh.ts","../src/locales/en.ts","../src/config/AppConfig.ts","../src/locales/index.ts","../src/cli/commands/import.ts","../src/services/ProgressService.ts","../src/ui/renderApp.ts","../src/ui/App.tsx","../src/ui/pages/ResumePage.tsx","../src/ui/pages/LibraryPage.tsx","../src/ui/pages/ReaderPage.tsx","../src/ui/components/TextRenderer.tsx","../src/ui/components/StatusBar.tsx","../src/ui/components/ChapterNav.tsx","../src/ui/hooks/useReader.ts","../src/ui/hooks/useKeyboard.ts","../src/utils/stringWidth.ts","../src/utils/paginate.ts","../src/services/ChapterService.ts","../src/services/RecentService.ts","../src/db/models/Bookmark.ts","../src/services/BookmarkService.ts","../src/utils/bossKey.ts","../src/utils/time.ts","../src/cli/commands/resume.ts","../src/cli/commands/open.ts","../src/cli/commands/library.ts","../src/cli/commands/remove.ts","../src/cli/commands/lang.ts","../src/cli/commands/update.ts","../src/db/migrate.ts","../src/index.ts"],"sourcesContent":["/**\n * CLI 参数解析器\n * 使用 yargs 解析子命令\n */\n\nimport yargs from 'yargs';\nimport { hideBin } from 'yargs/helpers';\nimport { importCommand } from './commands/import.js';\nimport { resumeCommand } from './commands/resume.js';\nimport { openCommand } from './commands/open.js';\nimport { libraryCommand } from './commands/library.js';\nimport { removeCommand } from './commands/remove.js';\nimport { langCommand } from './commands/lang.js';\nimport { updateCommand } from './commands/update.js';\n\nexport function createParser() {\n const version = typeof APP_VERSION !== 'undefined' ? APP_VERSION : 'dev';\n\n return yargs(hideBin(process.argv))\n .scriptName('novel')\n .usage('$0 <command> [options]')\n .command(importCommand)\n .command(resumeCommand)\n .command(openCommand)\n .command(libraryCommand)\n .command(removeCommand)\n .command(langCommand)\n .command(updateCommand)\n .demandCommand(1, '请指定一个命令。使用 --help 查看可用命令。')\n .strict()\n .alias('h', 'help')\n .alias('v', 'version')\n .version(version)\n .epilogue('ReadShell — 终端内低打断轻阅读工具');\n}\n","/**\n * 书籍业务逻辑\n * 书籍 CRUD、导入、去重\n */\n\nimport { resolve } from 'path';\nimport { existsSync, statSync } from 'fs';\nimport { nanoid } from 'nanoid';\nimport { BookModel, type BookRecord } from '../db/models/Book.js';\nimport { ChapterModel } from '../db/models/Chapter.js';\nimport { RecentModel } from '../db/models/Recent.js';\nimport { ProgressModel } from '../db/models/Progress.js';\nimport { parseFile } from '../parsers/index.js';\nimport { computeFileHash } from '../utils/hash.js';\nimport { logger } from '../utils/logger.js';\n\nexport class BookService {\n private bookModel = new BookModel();\n private chapterModel = new ChapterModel();\n private recentModel = new RecentModel();\n private progressModel = new ProgressModel();\n\n /**\n * 导入书籍文件\n */\n async importBook(filePath: string): Promise<BookRecord> {\n const absPath = resolve(filePath);\n\n // 检查文件存在\n if (!existsSync(absPath)) {\n throw new Error(`文件不存在: ${absPath}`);\n }\n\n // 检测文件格式\n const format = this.detectFormat(absPath);\n if (!format) {\n throw new Error('不支持的文件格式。目前支持: .txt, .epub');\n }\n\n // 计算文件 hash 用于去重\n const fileHash = await computeFileHash(absPath);\n const existing = this.bookModel.findByHash(fileHash);\n if (existing) {\n logger.debug(`文件已存在: ${existing.title} (${existing.id})`);\n return existing;\n }\n\n // 解析文件\n const parsed = await parseFile(absPath, format);\n\n // 获取文件大小\n const stats = statSync(absPath);\n\n // 创建书籍记录\n const book: BookRecord = {\n id: nanoid(),\n title: parsed.title,\n author: parsed.author || null,\n file_path: absPath,\n format,\n file_hash: fileHash,\n file_size: stats.size,\n created_at: Date.now(),\n };\n\n this.bookModel.insert(book);\n\n // 保存章节索引\n if (parsed.chapters.length > 0) {\n this.chapterModel.insertMany(\n parsed.chapters.map((ch, idx) => ({\n book_id: book.id,\n chapter_no: idx,\n title: ch.title,\n byte_offset: ch.byteOffset,\n })),\n );\n }\n\n logger.debug(`导入成功: ${book.title}`);\n return book;\n }\n\n /**\n * 查找书籍(ID 或模糊匹配书名)\n */\n findBook(target: string): BookRecord | undefined {\n // 先尝试精确 ID 匹配\n const byId = this.bookModel.findById(target);\n if (byId) return byId;\n\n // 再尝试书名模糊匹配\n const results = this.bookModel.searchByTitle(target);\n return results[0];\n }\n\n /**\n * 搜索书籍\n */\n searchBooks(keyword: string): BookRecord[] {\n return this.bookModel.searchByTitle(keyword);\n }\n\n /**\n * 获取所有书籍\n */\n getAllBooks(): BookRecord[] {\n return this.bookModel.findAll();\n }\n\n /**\n * 删除书籍及相关数据\n */\n deleteBook(id: string): void {\n this.chapterModel.deleteByBookId(id);\n this.progressModel.delete(id);\n this.recentModel.delete(id);\n this.bookModel.delete(id);\n }\n\n /**\n * 检测文件格式\n */\n private detectFormat(filePath: string): 'txt' | 'epub' | null {\n const ext = filePath.toLowerCase().split('.').pop();\n if (ext === 'txt') return 'txt';\n if (ext === 'epub') return 'epub';\n return null;\n }\n}\n","/**\n * SQLite 数据库连接初始化\n * 使用 better-sqlite3 同步 API\n */\n\nimport Database from 'better-sqlite3';\nimport { getDbPath } from '../config/paths.js';\nimport { logger } from '../utils/logger.js';\n\nlet db: Database.Database | null = null;\n\n/**\n * 获取数据库连接实例(单例模式)\n */\nexport function getDb(): Database.Database {\n if (!db) {\n const dbPath = getDbPath();\n logger.debug(`数据库路径: ${dbPath}`);\n\n db = new Database(dbPath);\n\n // 启用 WAL 模式以提升写入性能\n db.pragma('journal_mode = WAL');\n // 启用外键约束\n db.pragma('foreign_keys = ON');\n }\n\n return db;\n}\n\n/**\n * 关闭数据库连接\n */\nexport function closeDb(): void {\n if (db) {\n db.close();\n db = null;\n logger.debug('数据库连接已关闭');\n }\n}\n\n// 进程退出时自动关闭数据库\nprocess.on('exit', () => closeDb());\nprocess.on('SIGINT', () => {\n closeDb();\n process.exit(0);\n});\nprocess.on('SIGTERM', () => {\n closeDb();\n process.exit(0);\n});\n","/**\n * 跨平台路径管理\n * 数据库、配置文件位置\n */\n\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { mkdirSync, existsSync } from 'fs';\n\n/**\n * 获取应用数据目录\n * ~/.config/readshell/ (macOS/Linux)\n */\nexport function getAppDataDir(): string {\n const platform = process.platform;\n let configDir: string;\n\n if (platform === 'darwin') {\n configDir = join(homedir(), 'Library', 'Application Support', 'readshell');\n } else if (platform === 'win32') {\n configDir = join(process.env['APPDATA'] || join(homedir(), 'AppData', 'Roaming'), 'readshell');\n } else {\n // Linux / 其他\n configDir = join(process.env['XDG_CONFIG_HOME'] || join(homedir(), '.config'), 'readshell');\n }\n\n // 确保目录存在\n if (!existsSync(configDir)) {\n mkdirSync(configDir, { recursive: true });\n }\n\n return configDir;\n}\n\n/**\n * 获取数据库文件路径\n */\nexport function getDbPath(): string {\n return join(getAppDataDir(), 'readshell.db');\n}\n\n/**\n * 获取配置文件路径\n */\nexport function getConfigPath(): string {\n return join(getAppDataDir(), 'config.json');\n}\n","/**\n * 调试日志\n * 仅 DEBUG=1 时输出\n */\n\nconst isDebug = process.env['DEBUG'] === '1' || process.env['DEBUG'] === 'true';\n\nexport const logger = {\n debug: (...args: unknown[]): void => {\n if (isDebug) {\n console.error('[DEBUG]', ...args);\n }\n },\n\n info: (...args: unknown[]): void => {\n console.error('[INFO]', ...args);\n },\n\n warn: (...args: unknown[]): void => {\n console.error('[WARN]', ...args);\n },\n\n error: (...args: unknown[]): void => {\n console.error('[ERROR]', ...args);\n },\n};\n","/**\n * books 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface BookRecord {\n id: string;\n title: string;\n author: string | null;\n file_path: string;\n format: 'txt' | 'epub';\n file_hash: string;\n file_size: number | null;\n created_at: number;\n}\n\nexport class BookModel {\n /**\n * 插入新书\n */\n insert(book: BookRecord): void {\n const db = getDb();\n db.prepare(`\n INSERT INTO books (id, title, author, file_path, format, file_hash, file_size, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `).run(book.id, book.title, book.author, book.file_path, book.format, book.file_hash, book.file_size, book.created_at);\n }\n\n /**\n * 通过 ID 获取书籍\n */\n findById(id: string): BookRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM books WHERE id = ?').get(id) as BookRecord | undefined;\n }\n\n /**\n * 通过文件 hash 查找(去重用)\n */\n findByHash(hash: string): BookRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM books WHERE file_hash = ?').get(hash) as BookRecord | undefined;\n }\n\n /**\n * 模糊搜索书名\n */\n searchByTitle(keyword: string): BookRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM books WHERE title LIKE ? ORDER BY created_at DESC').all(`%${keyword}%`) as BookRecord[];\n }\n\n /**\n * 获取所有书籍\n */\n findAll(): BookRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM books ORDER BY created_at DESC').all() as BookRecord[];\n }\n\n /**\n * 删除书籍\n */\n delete(id: string): void {\n const db = getDb();\n db.prepare('DELETE FROM books WHERE id = ?').run(id);\n }\n}\n","/**\n * chapter_index 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface ChapterRecord {\n id?: number;\n book_id: string;\n chapter_no: number;\n title: string | null;\n byte_offset: number;\n}\n\nexport class ChapterModel {\n /**\n * 批量插入章节索引\n */\n insertMany(chapters: Omit<ChapterRecord, 'id'>[]): void {\n const db = getDb();\n const stmt = db.prepare(`\n INSERT OR REPLACE INTO chapter_index (book_id, chapter_no, title, byte_offset)\n VALUES (?, ?, ?, ?)\n `);\n\n db.transaction(() => {\n for (const chapter of chapters) {\n stmt.run(chapter.book_id, chapter.chapter_no, chapter.title, chapter.byte_offset);\n }\n })();\n }\n\n /**\n * 获取指定书籍的所有章节\n */\n findByBookId(bookId: string): ChapterRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM chapter_index WHERE book_id = ? ORDER BY chapter_no').all(bookId) as ChapterRecord[];\n }\n\n /**\n * 获取指定章节\n */\n findChapter(bookId: string, chapterNo: number): ChapterRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM chapter_index WHERE book_id = ? AND chapter_no = ?').get(bookId, chapterNo) as ChapterRecord | undefined;\n }\n\n /**\n * 获取书籍章节总数\n */\n getChapterCount(bookId: string): number {\n const db = getDb();\n const result = db.prepare('SELECT COUNT(*) as count FROM chapter_index WHERE book_id = ?').get(bookId) as { count: number };\n return result.count;\n }\n\n /**\n * 删除指定书籍的章节索引\n */\n deleteByBookId(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM chapter_index WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * recent_reads 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface RecentRecord {\n book_id: string;\n opened_at: number;\n open_count: number;\n}\n\nexport class RecentModel {\n /**\n * 记录打开(插入或更新计数)\n */\n recordOpen(bookId: string): void {\n const db = getDb();\n const now = Date.now();\n db.prepare(`\n INSERT INTO recent_reads (book_id, opened_at, open_count)\n VALUES (?, ?, 1)\n ON CONFLICT(book_id) DO UPDATE SET\n opened_at = excluded.opened_at,\n open_count = open_count + 1\n `).run(bookId, now);\n }\n\n /**\n * 获取最近阅读列表(按时间倒序)\n */\n getRecent(limit: number = 20): RecentRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM recent_reads ORDER BY opened_at DESC LIMIT ?').all(limit) as RecentRecord[];\n }\n\n /**\n * 删除记录\n */\n delete(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM recent_reads WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * reading_progress 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface ProgressRecord {\n book_id: string;\n chapter_no: number;\n byte_offset: number;\n percent: number;\n updated_at: number;\n opened_at: number;\n}\n\nexport class ProgressModel {\n /**\n * 保存或更新阅读进度\n */\n upsert(progress: ProgressRecord): void {\n const db = getDb();\n db.prepare(`\n INSERT INTO reading_progress (book_id, chapter_no, byte_offset, percent, updated_at, opened_at)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(book_id) DO UPDATE SET\n chapter_no = excluded.chapter_no,\n byte_offset = excluded.byte_offset,\n percent = excluded.percent,\n updated_at = excluded.updated_at,\n opened_at = excluded.opened_at\n `).run(progress.book_id, progress.chapter_no, progress.byte_offset, progress.percent, progress.updated_at, progress.opened_at);\n }\n\n /**\n * 获取指定书籍的阅读进度\n */\n findByBookId(bookId: string): ProgressRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM reading_progress WHERE book_id = ?').get(bookId) as ProgressRecord | undefined;\n }\n\n /**\n * 获取最近打开的书籍进度(用于 resume)\n */\n getLastOpened(): ProgressRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM reading_progress ORDER BY opened_at DESC LIMIT 1').get() as ProgressRecord | undefined;\n }\n\n /**\n * 删除指定书籍的进度\n */\n delete(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM reading_progress WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * TXT 文件解析器\n * 编码检测、章节切割\n */\n\nimport { readFileSync } from 'fs';\nimport { detect } from 'chardet';\nimport iconv from 'iconv-lite';\nimport { logger } from '../utils/logger.js';\n\nexport interface ParsedChapter {\n title: string;\n byteOffset: number;\n}\n\nexport interface ParsedBook {\n title: string;\n author: string | null;\n content: string;\n chapters: ParsedChapter[];\n}\n\n// 常见的章节标题正则\nconst CHAPTER_PATTERNS = [\n /^第[零一二三四五六七八九十百千万\\d]+[章节回卷集部篇]/m,\n /^Chapter\\s+\\d+/im,\n /^CHAPTER\\s+\\d+/m,\n /^第\\s*\\d+\\s*[章节回]/m,\n];\n\n/**\n * 解析 TXT 文件\n */\nexport async function parseTxt(filePath: string): Promise<ParsedBook> {\n // 读取原始字节\n const buffer = readFileSync(filePath);\n\n // 自动检测编码\n const encoding = detect(buffer) || 'utf-8';\n logger.debug(`检测到编码: ${encoding}`);\n\n // 转换为 UTF-8 字符串\n const content = encoding.toLowerCase() === 'utf-8'\n ? buffer.toString('utf-8')\n : iconv.decode(buffer, encoding);\n\n // 从文件名提取标题\n const title = extractTitle(filePath);\n\n // 提取章节索引\n const chapters = extractChapters(content);\n\n return {\n title,\n author: null,\n content,\n chapters,\n };\n}\n\n/**\n * 从文件名提取书名\n */\nfunction extractTitle(filePath: string): string {\n const basename = filePath.split('/').pop() || filePath;\n // 去掉扩展名\n return basename.replace(/\\.txt$/i, '').trim() || '未命名';\n}\n\n/**\n * 提取章节索引\n */\nfunction extractChapters(content: string): ParsedChapter[] {\n const chapters: ParsedChapter[] = [];\n const lines = content.split('\\n');\n let byteOffset = 0;\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n for (const pattern of CHAPTER_PATTERNS) {\n if (pattern.test(trimmed)) {\n chapters.push({\n title: trimmed,\n byteOffset,\n });\n break;\n }\n }\n\n // 计算 byte offset(UTF-8 编码)\n byteOffset += Buffer.byteLength(line + '\\n', 'utf-8');\n }\n\n logger.debug(`检测到 ${chapters.length} 个章节`);\n return chapters;\n}\n","/**\n * EPUB 文件解析器\n * 元数据 + 章节提取,并转为纯文本阅读格式\n */\n\nimport { EPub } from 'epub2';\nimport { convert } from 'html-to-text';\nimport { detect } from 'chardet';\nimport iconv from 'iconv-lite';\nimport { logger } from '../utils/logger.js';\nimport type { ParsedBook, ParsedChapter } from './TxtParser.js';\n\n/**\n * 解析 EPUB 文件\n */\nexport async function parseEpub(filePath: string): Promise<ParsedBook> {\n logger.debug(`开始解析 EPUB: ${filePath}`);\n\n const epub = await openEpub(filePath);\n\n const title = epub.metadata.title || '未知书名';\n const author = epub.metadata.creator || null;\n\n const chapters: ParsedChapter[] = [];\n let fullContent = '';\n let currentByteOffset = 0;\n\n // epub.flow contains the reading order\n for (const chapterRef of epub.flow) {\n if (!chapterRef.id) continue;\n\n try {\n const htmlText = await getChapterHtml(epub, chapterRef.id);\n \n // 使用 html-to-text 转为纯文本,保持换行,去除无关标签\n const plainText = convert(htmlText, {\n wordwrap: false,\n selectors: [\n // 忽略图片和链接\n { selector: 'img', format: 'skip' },\n { selector: 'a', options: { ignoreHref: true } },\n ],\n });\n\n if (!plainText.trim()) continue; // 跳过空章节\n\n const chapterTitle = chapterRef.title || '无标题章节';\n\n chapters.push({\n title: chapterTitle,\n byteOffset: currentByteOffset,\n });\n\n // 加上章节标题作为正文本页头部\n const chapterContent = `\\n\\n${chapterTitle}\\n\\n${plainText}\\n`;\n fullContent += chapterContent;\n\n currentByteOffset += Buffer.byteLength(chapterContent, 'utf-8');\n } catch (err) {\n logger.debug(`警告: 读取章节 ${chapterRef.id} 失败`, err);\n }\n }\n\n return {\n title,\n author,\n content: fullContent,\n chapters,\n };\n}\n\n/**\n * 提取 EPUB 元数据\n */\nexport async function getEpubMetadata(filePath: string): Promise<{ title: string; author: string | null }> {\n const epub = await openEpub(filePath);\n return {\n title: epub.metadata.title || '未知',\n author: epub.metadata.creator || null,\n };\n}\n\n// ============== 辅助函数 ==============\n\n/**\n * 包装 epub2 基于回调的初始化过程为 Promise\n */\nfunction openEpub(filePath: string): Promise<EPub> {\n return new Promise((resolve, reject) => {\n const epub = new EPub(filePath);\n epub.on('error', (err) => reject(err));\n epub.on('end', () => resolve(epub));\n epub.parse();\n });\n}\n\n/**\n * 从 HTML/XML 原始字节中提取编码声明\n * 优先读取 XML 声明 (<?xml encoding=\"GBK\">) 和 meta charset\n */\nfunction detectHtmlEncoding(buffer: Buffer): string {\n // 用 latin1 无损读取前 1KB,仅为提取编码声明\n const head = buffer.slice(0, 1024).toString('latin1');\n\n // 1. 优先查找 XML 声明:<?xml version=\"1.0\" encoding=\"GBK\"?>\n const xmlDecl = head.match(/<\\?xml[^>]*encoding=[\"']([^\"']+)[\"']/i);\n if (xmlDecl?.[1]) return xmlDecl[1];\n\n // 2. 查找 HTML meta charset:<meta charset=\"GBK\"> 或 Content-Type: charset=GBK\n const metaCharset = head.match(/charset=[\"']?([A-Za-z0-9_-]+)[\"']?/i);\n if (metaCharset?.[1] && metaCharset[1].toLowerCase() !== 'utf-8') {\n return metaCharset[1];\n }\n\n // 3. 回退:使用 chardet 字节模式探测\n const chardetResult = detect(buffer);\n return chardetResult || 'utf-8';\n}\n\n/**\n * 获取单个章节的 HTML 原文并自动转码\n * 包装为 Promise\n */\nfunction getChapterHtml(epub: any, chapterId: string): Promise<string> {\n return new Promise((resolve, reject) => {\n // 使用 getFile 获取原始 Buffer,避免 epub2 内部 getChapterRaw 强制转 utf-8 导致的乱码\n epub.getFile(chapterId, (err: Error | null, data: Buffer) => {\n if (err) return reject(err);\n\n const buffer = data;\n const encoding = detectHtmlEncoding(buffer);\n\n const htmlText = encoding.toLowerCase().replace('-', '') === 'utf8' || encoding.toLowerCase() === 'utf-8'\n ? buffer.toString('utf-8')\n : iconv.decode(buffer, encoding);\n\n resolve(htmlText);\n });\n });\n}\n","/**\n * 解析器分发\n * 按文件格式分发到对应解析器\n */\n\nimport { parseTxt, type ParsedBook } from './TxtParser.js';\nimport { parseEpub } from './EpubParser.js';\n\nexport type { ParsedBook, ParsedChapter } from './TxtParser.js';\n\n/**\n * 根据格式解析文件\n */\nexport async function parseFile(filePath: string, format: 'txt' | 'epub'): Promise<ParsedBook> {\n switch (format) {\n case 'txt':\n return parseTxt(filePath);\n case 'epub':\n return parseEpub(filePath);\n default:\n throw new Error(`不支持的文件格式: ${format}`);\n }\n}\n","/**\n * 文件 hash 计算(去重用)\n */\n\nimport { createHash } from 'crypto';\nimport { readFileSync } from 'fs';\n\n/**\n * 计算文件的 SHA-256 hash\n */\nexport async function computeFileHash(filePath: string): Promise<string> {\n const buffer = readFileSync(filePath);\n const hash = createHash('sha256');\n hash.update(buffer);\n return hash.digest('hex');\n}\n","export default {\n // Common\n 'common.yes': '是',\n 'common.no': '否',\n 'common.confirm': '确认',\n 'common.cancel': '取消',\n 'common.quit': '按 q 退出',\n\n // CLI\n 'cli.import.desc': '导入本地文件或目录到书架',\n 'cli.import.help': '文件或目录路径(支持 .txt / .epub)',\n 'cli.import.success': '✓ 已导入:',\n 'cli.import.fail': '导入失败:',\n 'cli.import.not_found': '路径不存在:',\n 'cli.import.unsupported': '不支持的文件格式。目前支持: .txt, .epub',\n 'cli.import.scan_dir': '扫描目录',\n 'cli.import.found_files': '找到以下书籍:',\n 'cli.import.confirm_batch': '是否确认导入上述 {0} 本书?(y/N)',\n 'cli.import.canceled': '✓ 导入已取消',\n\n 'cli.resume.desc': '恢复上次阅读',\n 'cli.resume.none': '✗ 没有找到最近阅读记录,请先使用 novel import <file> 或 novel open <book-id> 打开一本书。',\n\n 'cli.open.desc': '打开指定书籍',\n 'cli.open.help': '书籍 ID 或书名(支持模糊匹配)',\n 'cli.open.not_found': '✗ 未找到匹配书籍:',\n\n 'cli.library.desc': '查看书架书籍列表',\n 'cli.library.help': '可选关键字搜索',\n 'cli.library.none': '✗ 书架为空。使用 novel import <file> 导入你的第一本书。',\n 'cli.library.search_none': '📚 未找到匹配「{0}」的书籍。',\n 'cli.library.search_result': '📚 搜索结果 ({0} 本):\\n',\n\n 'cli.remove.desc': '从书架移除书籍(仅删除记录,不删源文件)',\n 'cli.remove.help': '书籍 ID 或书名(支持模糊匹配)',\n 'cli.remove.not_found': '✗ 未找到匹配书籍:',\n 'cli.remove.success': '✓ 已移除书籍:',\n 'cli.remove.fail': '移除书籍失败:',\n\n 'cli.lang.desc': '切换语言',\n 'cli.lang.help': '语言代码: zh | en',\n 'cli.lang.success': '✓ 语言已切换为: {0}',\n 'cli.lang.unsupported': '✗ 不支持的语言: {0}',\n\n 'cli.update.desc': '检查最新版本并自动更新',\n 'cli.update.checking': '正在检查更新...',\n 'cli.update.latest': '✓ 当前已是最新版本 (v{0})',\n 'cli.update.updating': '发现新版本 v{0} (当前 v{1}),正在更新...',\n 'cli.update.success': '✓ 升级成功!请重新运行 novel 命令。',\n 'cli.update.fail': '✗ 升级失败: {0}',\n\n // TUI - Library\n 'tui.lib.loading': '📚 加载书架...',\n 'tui.lib.empty.title': '📚 书架',\n 'tui.lib.empty.desc': '书架为空。使用 novel import <file> 导入你的第一本书。',\n 'tui.lib.title': '📚 书架 ({0} 本)',\n 'tui.lib.tips': ' ↑↓/jk 选择 · Enter 打开 · d/x 删除 · q 退出',\n\n // TUI - Reader\n 'tui.reader.loading': '读取中...',\n 'tui.reader.bookmark_add': '✓ 增加书签: {0}',\n 'tui.reader.status.remaining': '预计剩余 {0}',\n\n // TUI - ChapterNav\n 'tui.nav.tab.chapters': '[全部章节]',\n 'tui.nav.tab.bookmarks': '[我的书签]',\n 'tui.nav.tips': 'Enter 跳转 · Tab 切换 · Esc/q 关闭',\n 'tui.nav.empty': '没有记录',\n 'tui.nav.page': '第 {0} / {1} 页',\n};\n","import type { LocaleDictionary } from './types.js';\n\nconst en: LocaleDictionary = {\n // Common\n 'common.yes': 'Yes',\n 'common.no': 'No',\n 'common.confirm': 'Confirm',\n 'common.cancel': 'Cancel',\n 'common.quit': 'Press q to quit',\n\n // CLI\n 'cli.import.desc': 'Import local file or directory to library',\n 'cli.import.help': 'File or directory path (supports .txt / .epub)',\n 'cli.import.success': '✓ Imported:',\n 'cli.import.fail': 'Import failed:',\n 'cli.import.not_found': 'Path not found:',\n 'cli.import.unsupported': 'Unsupported file format. Currently supports: .txt, .epub',\n 'cli.import.scan_dir': 'Scanning directory',\n 'cli.import.found_files': 'Found following books:',\n 'cli.import.confirm_batch': 'Confirm importing these {0} books? (y/N)',\n 'cli.import.canceled': '✓ Import canceled',\n\n 'cli.resume.desc': 'Resume last reading',\n 'cli.resume.none': '✗ No recent reading record found. Please use `novel import <file>` or `novel open <book-id>` first.',\n\n 'cli.open.desc': 'Open specific book',\n 'cli.open.help': 'Book ID or title (fuzzy match)',\n 'cli.open.not_found': '✗ Book not found:',\n\n 'cli.library.desc': 'View book list / library',\n 'cli.library.help': 'Optional keyword search',\n 'cli.library.none': '✗ Library is empty. Use `novel import <file>` to import your first book.',\n 'cli.library.search_none': '📚 No books found matching \"{0}\".',\n 'cli.library.search_result': '📚 Search results ({0} books):\\n',\n\n 'cli.remove.desc': 'Remove book from library (only deletes records, not source file)',\n 'cli.remove.help': 'Book ID or title (fuzzy match)',\n 'cli.remove.not_found': '✗ Book not found:',\n 'cli.remove.success': '✓ Book removed:',\n 'cli.remove.fail': 'Failed to remove book:',\n\n 'cli.lang.desc': 'Switch language',\n 'cli.lang.help': 'Language code: zh | en',\n 'cli.lang.success': '✓ Language switched to: {0}',\n 'cli.lang.unsupported': '✗ Unsupported language: {0}',\n\n 'cli.update.desc': 'Check for the latest version and update automatically',\n 'cli.update.checking': 'Checking for updates...',\n 'cli.update.latest': '✓ Already up to date (v{0})',\n 'cli.update.updating': 'New version v{0} found (current v{1}), updating...',\n 'cli.update.success': '✓ Successfully updated! Please restart novel command.',\n 'cli.update.fail': '✗ Update failed: {0}',\n\n // TUI - Library\n 'tui.lib.loading': '📚 Loading library...',\n 'tui.lib.empty.title': '📚 Library',\n 'tui.lib.empty.desc': 'Library is empty. Use `novel import <file>` to import your first book.',\n 'tui.lib.title': '📚 Library ({0} books)',\n 'tui.lib.tips': ' ↑↓/jk select · Enter open · d/x delete · q quit',\n\n // TUI - Reader\n 'tui.reader.loading': 'Loading...',\n 'tui.reader.bookmark_add': '✓ Bookmark added: {0}',\n 'tui.reader.status.remaining': 'Est. remaining {0}',\n\n // TUI - ChapterNav\n 'tui.nav.tab.chapters': '[All Chapters]',\n 'tui.nav.tab.bookmarks': '[My Bookmarks]',\n 'tui.nav.tips': 'Enter jump · Tab switch · Esc/q close',\n 'tui.nav.empty': 'No records',\n 'tui.nav.page': 'Page {0} / {1}',\n};\n\nexport default en;\n","/**\n * 应用配置管理\n * 读写 ~/.config/readshell/config.json\n */\n\nimport Conf from 'conf';\n\ninterface ReadShellConfig {\n /** 每页显示行数(0 = 自适应终端高度) */\n linesPerPage: number;\n /** 是否显示状态栏 */\n showStatusBar: boolean;\n /** 阅读模式: 'page' | 'scroll' */\n readingMode: 'page' | 'scroll';\n /** 界面语言 */\n language: 'zh' | 'en';\n}\n\nconst defaults: ReadShellConfig = {\n linesPerPage: 0,\n showStatusBar: true,\n readingMode: 'page',\n language: 'zh',\n};\n\nconst config = new Conf<ReadShellConfig>({\n projectName: 'readshell',\n defaults,\n});\n\nexport function getConfig(): ReadShellConfig {\n return {\n linesPerPage: config.get('linesPerPage'),\n showStatusBar: config.get('showStatusBar'),\n readingMode: config.get('readingMode'),\n language: config.get('language'),\n };\n}\n\nexport function setConfig<K extends keyof ReadShellConfig>(key: K, value: ReadShellConfig[K]): void {\n config.set(key, value);\n}\n\nexport { config };\n","import zh from './zh.js';\nimport en from './en.js';\nimport type { LocaleDictionary, LocaleKeys } from './types.js';\nimport { getConfig } from '../config/AppConfig.js';\n\nconst dictionaries: Record<'zh' | 'en', LocaleDictionary> = {\n zh,\n en,\n};\n\nlet currentLang: 'zh' | 'en' = 'zh';\nlet currentDict: LocaleDictionary = dictionaries.zh;\n\n/**\n * 初始化并加载当前配置的语言\n */\nexport function initI18n() {\n const config = getConfig();\n currentLang = config.language || 'zh';\n currentDict = dictionaries[currentLang] || dictionaries.zh;\n}\n\n/**\n * 切换语言字典(运行时热切换支持)\n */\nexport function setLanguage(lang: 'zh' | 'en') {\n currentLang = lang;\n currentDict = dictionaries[lang] || dictionaries.zh;\n}\n\n/**\n * 获取翻译词条\n * 支持占位符 {0}, {1} 替换\n */\nexport function t(key: LocaleKeys, ...args: (string | number)[]): string {\n let template = currentDict[key];\n if (!template) {\n return key;\n }\n \n if (args.length > 0) {\n args.forEach((arg, index) => {\n template = template.replace(`{${index}}`, String(arg));\n });\n }\n return template;\n}\n","/**\n * novel import <file> — 导入本地文件\n * 支持 txt,基础去重,导入后自动入书架\n */\n\nimport type { CommandModule } from 'yargs';\nimport { BookService } from '../../services/BookService.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\nimport { statSync, readdirSync } from 'node:fs';\nimport { resolve, join, extname } from 'node:path';\nimport * as readline from 'node:readline/promises';\n\nexport interface ImportArgs {\n file: string;\n}\n\n// 递归扫描目录文件\nfunction scanDirectory(dir: string): string[] {\n let results: string[] = [];\n try {\n const list = readdirSync(dir);\n for (const file of list) {\n const fullPath = join(dir, file);\n const stat = statSync(fullPath);\n if (stat.isDirectory()) {\n results = results.concat(scanDirectory(fullPath));\n } else {\n const ext = extname(fullPath).toLowerCase();\n if (ext === '.txt' || ext === '.epub') {\n results.push(fullPath);\n }\n }\n }\n } catch (err) {\n logger.error('Scan error:', err);\n }\n return results;\n}\n\nexport const importCommand: CommandModule<object, ImportArgs> = {\n command: 'import <file>',\n describe: t('cli.import.desc'),\n builder: (yargs) => {\n return yargs.positional('file', {\n describe: t('cli.import.help'),\n type: 'string',\n demandOption: true,\n });\n },\n handler: async (argv) => {\n try {\n const targetPath = resolve(argv.file);\n let stat;\n try {\n stat = statSync(targetPath);\n } catch (e) {\n console.log(`${t('cli.import.not_found')} ${targetPath}`);\n process.exit(1);\n }\n\n const bookService = new BookService();\n\n if (stat.isDirectory()) {\n console.log(`${t('cli.import.scan_dir')} ${targetPath}...`);\n const files = scanDirectory(targetPath);\n\n if (files.length === 0) {\n console.log(`✗ ` + t('cli.import.unsupported'));\n return;\n }\n\n console.log(t('cli.import.found_files'));\n files.forEach((f, i) => console.log(` ${i + 1}. ${f}`));\n\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const answer = await rl.question(t('cli.import.confirm_batch', files.length) + ' ');\n rl.close();\n\n if (answer.toLowerCase() === 'y') {\n for (const file of files) {\n try {\n const book = await bookService.importBook(file);\n console.log(`${t('cli.import.success')} ${book.title} (${book.id})`);\n } catch (err) {\n console.log(`${t('cli.import.fail')} ${file} - ${err}`);\n }\n }\n } else {\n console.log(t('cli.import.canceled'));\n }\n } else {\n // 单文件导入\n const book = await bookService.importBook(argv.file);\n console.log(`${t('cli.import.success')} ${book.title} (${book.id})`);\n }\n } catch (error) {\n console.log(`${t('cli.import.fail')} ${error}`);\n process.exit(1);\n }\n },\n};\n","/**\n * 阅读进度业务逻辑\n * 进度读写、resume 核心逻辑\n */\n\nimport { ProgressModel, type ProgressRecord } from '../db/models/Progress.js';\nimport { RecentModel } from '../db/models/Recent.js';\nimport { logger } from '../utils/logger.js';\n\nexport class ProgressService {\n private progressModel = new ProgressModel();\n private recentModel = new RecentModel();\n\n /**\n * 保存阅读进度(退出时调用)\n */\n saveProgress(bookId: string, chapterNo: number, byteOffset: number, percent: number): void {\n const now = Date.now();\n\n this.progressModel.upsert({\n book_id: bookId,\n chapter_no: chapterNo,\n byte_offset: byteOffset,\n percent,\n updated_at: now,\n opened_at: now,\n });\n\n // 同时更新最近阅读记录\n this.recentModel.recordOpen(bookId);\n\n logger.debug(`进度已保存: book=${bookId}, chapter=${chapterNo}, offset=${byteOffset}, ${(percent * 100).toFixed(1)}%`);\n }\n\n /**\n * 获取指定书籍的阅读进度(resume 时调用)\n */\n getProgress(bookId: string): ProgressRecord | undefined {\n return this.progressModel.findByBookId(bookId);\n }\n\n /**\n * 获取最近打开的书籍进度(启动时调用,决定 resume 哪本书)\n */\n getLastOpenedBook(): ProgressRecord | undefined {\n return this.progressModel.getLastOpened();\n }\n}\n","/**\n * Ink TUI 渲染启动函数\n * 封装 ink.render(<App />) 的统一入口\n */\n\nimport React from 'react';\nimport { render } from 'ink';\nimport { App, type PageRoute } from './App.js';\n\ninterface RenderOptions {\n initialPage?: PageRoute;\n bookId?: string;\n initialByteOffset?: number;\n}\n\n/**\n * 启动 Ink TUI 应用\n */\nexport function renderApp(options: RenderOptions = {}): void {\n const { initialPage = 'resume', bookId, initialByteOffset } = options;\n\n const { waitUntilExit } = render(\n React.createElement(App, {\n initialPage,\n bookId,\n initialByteOffset,\n }),\n );\n\n waitUntilExit().catch(() => {\n // Ink 退出时的清理\n process.exit(0);\n });\n}\n","/**\n * App 根组件\n * 管理页面路由状态\n */\n\nimport React, { useState } from 'react';\nimport { Box, Text } from 'ink';\nimport { ResumePage } from './pages/ResumePage.js';\nimport { LibraryPage } from './pages/LibraryPage.js';\nimport { ReaderPage } from './pages/ReaderPage.js';\n\nexport type PageRoute = 'resume' | 'library' | 'reader';\n\ninterface AppProps {\n initialPage?: PageRoute;\n bookId?: string;\n initialByteOffset?: number;\n}\n\nexport function App({ initialPage = 'resume', bookId, initialByteOffset }: AppProps) {\n const [currentPage, setCurrentPage] = useState<PageRoute>(initialPage);\n const [currentBookId, setCurrentBookId] = useState<string | undefined>(bookId);\n const [currentByteOffset, setCurrentByteOffset] = useState<number | undefined>(initialByteOffset);\n\n const navigateTo = (page: PageRoute, targetBookId?: string, byteOffset?: number) => {\n setCurrentPage(page);\n if (targetBookId) setCurrentBookId(targetBookId);\n if (byteOffset !== undefined) setCurrentByteOffset(byteOffset);\n };\n\n return (\n <Box flexDirection=\"column\" width=\"100%\">\n {currentPage === 'resume' && (\n <ResumePage onNavigate={navigateTo} />\n )}\n {currentPage === 'library' && (\n <LibraryPage onNavigate={navigateTo} />\n )}\n {currentPage === 'reader' && currentBookId && (\n <ReaderPage\n bookId={currentBookId}\n initialByteOffset={currentByteOffset}\n onNavigate={navigateTo}\n />\n )}\n {currentPage === 'reader' && !currentBookId && (\n <Box>\n <Text color=\"red\">错误: 未指定书籍</Text>\n </Box>\n )}\n </Box>\n );\n}\n","/**\n * 继续阅读页\n * 启动默认页,自动查询最近阅读并跳转到阅读器\n */\n\nimport React, { useEffect, useState } from 'react';\nimport { Box, Text, useApp } from 'ink';\nimport type { PageRoute } from '../App.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { BookModel, type BookRecord } from '../../db/models/Book.js';\n\ninterface ResumePageProps {\n onNavigate: (page: PageRoute, bookId?: string, byteOffset?: number) => void;\n}\n\nexport function ResumePage({ onNavigate }: ResumePageProps) {\n const { exit } = useApp();\n const [checking, setChecking] = useState(true);\n\n useEffect(() => {\n const progressService = new ProgressService();\n const lastProgress = progressService.getLastOpenedBook();\n\n if (!lastProgress) {\n setChecking(false);\n return;\n }\n\n // 验证书籍存在\n const bookModel = new BookModel();\n const book = bookModel.findById(lastProgress.book_id);\n\n if (!book) {\n setChecking(false);\n return;\n }\n\n // 自动跳转到阅读器\n onNavigate('reader', book.id, lastProgress.byte_offset);\n }, [onNavigate]);\n\n if (checking) {\n return (\n <Box padding={1}>\n <Text color=\"cyan\">📖 检查阅读记录...</Text>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Text bold color=\"cyan\">\n 📖 ReadShell — 终端内轻阅读\n </Text>\n <Box marginTop={1} flexDirection=\"column\">\n <Text dimColor>暂无阅读记录。</Text>\n <Text dimColor>使用 novel import <file> 导入一本书开始阅读。</Text>\n <Box marginTop={1}>\n <Text dimColor>按 q 退出</Text>\n </Box>\n </Box>\n </Box>\n );\n}\n","/**\n * 书架页\n * 交互式书架列表,上下键选择,Enter 打开阅读\n */\n\nimport React, { useState, useEffect } from 'react';\nimport { Box, Text, useApp, useInput } from 'ink';\nimport type { PageRoute } from '../App.js';\nimport { BookService } from '../../services/BookService.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { RecentService } from '../../services/RecentService.js';\nimport type { BookRecord } from '../../db/models/Book.js';\nimport { t } from '../../locales/index.js';\n\ninterface LibraryPageProps {\n onNavigate: (page: PageRoute, bookId?: string, byteOffset?: number) => void;\n}\n\nexport function LibraryPage({ onNavigate }: LibraryPageProps) {\n const { exit } = useApp();\n const [books, setBooks] = useState<BookRecord[]>([]);\n const [selectedIndex, setSelectedIndex] = useState(0);\n const [loading, setLoading] = useState(true);\n\n const isRawModeSupported = process.stdin.isTTY ?? false;\n\n useEffect(() => {\n const bookService = new BookService();\n setBooks(bookService.getAllBooks());\n setLoading(false);\n }, []);\n\n useInput((input, key) => {\n if (input === 'q') {\n exit();\n return;\n }\n\n if (books.length === 0) return;\n\n // 上下导航\n if (key.upArrow || input === 'k') {\n setSelectedIndex((prev) => Math.max(prev - 1, 0));\n }\n if (key.downArrow || input === 'j') {\n setSelectedIndex((prev) => Math.min(prev + 1, books.length - 1));\n }\n\n // 删除选中的书 (backspace 键或 d 键)\n if (key.backspace || key.delete || input === 'd' || input === 'x') {\n const selected = books[selectedIndex];\n if (selected) {\n const bookService = new BookService();\n bookService.deleteBook(selected.id);\n \n // 更新列表\n setBooks((prev) => {\n const next = prev.filter((b) => b.id !== selected.id);\n // 调整选中游标,防止越界\n if (selectedIndex >= next.length) {\n setSelectedIndex(Math.max(0, next.length - 1));\n }\n return next;\n });\n }\n return;\n }\n\n // Enter 打开选中的书\n if (key.return) {\n const selected = books[selectedIndex];\n if (selected) {\n const progressService = new ProgressService();\n const progress = progressService.getProgress(selected.id);\n onNavigate('reader', selected.id, progress?.byte_offset ?? 0);\n }\n }\n }, { isActive: isRawModeSupported });\n\n if (loading) {\n return (\n <Box padding={1}>\n <Text color=\"cyan\">{t('tui.lib.loading')}</Text>\n </Box>\n );\n }\n\n if (books.length === 0) {\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Text bold color=\"cyan\">{t('tui.lib.empty.title')}</Text>\n <Box marginTop={1} flexDirection=\"column\">\n <Text dimColor>{t('tui.lib.empty.desc')}</Text>\n <Box marginTop={1}>\n <Text dimColor>{t('common.quit')}</Text>\n </Box>\n </Box>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Text bold color=\"cyan\">{t('tui.lib.title', books.length)}</Text>\n <Text dimColor>{t('tui.lib.tips')}</Text>\n <Box flexDirection=\"column\" marginTop={1}>\n {books.map((book, index) => {\n const isSelected = index === selectedIndex;\n \n return (\n <Box key={book.id} paddingX={1} justifyContent=\"space-between\">\n <Box>\n <Text\n color={isSelected ? 'cyan' : undefined}\n bold={isSelected}\n >\n {isSelected ? '▸ ' : ' '}\n {book.title}\n </Text>\n <Text dimColor> ({book.format})</Text>\n </Box>\n </Box>\n );\n })}\n </Box>\n </Box>\n );\n}\n","/**\n * 阅读器页 — 核心阅读体验\n *\n * 功能:\n * - 加载书籍文件内容,按终端尺寸分页\n * - 键盘翻页(空格/j/↓ 下一页, k/↑ 上一页)\n * - 状态栏显示书名 + 进度百分比\n * - 退出时(q)自动保存进度\n * - resume 进入时根据 byte_offset 定位到对应页\n */\n\nimport React, { useState, useEffect, useRef, useMemo } from 'react';\nimport { Box, Text, useApp, useStdout } from 'ink';\nimport type { PageRoute } from '../App.js';\nimport { TextRenderer } from '../components/TextRenderer.js';\nimport { StatusBar } from '../components/StatusBar.js';\nimport { ChapterNav } from '../components/ChapterNav.js';\nimport { useReader } from '../hooks/useReader.js';\nimport { useKeyboard } from '../hooks/useKeyboard.js';\nimport { paginate, type Page } from '../../utils/paginate.js';\nimport { BookModel, type BookRecord } from '../../db/models/Book.js';\nimport { parseFile, type ParsedBook } from '../../parsers/index.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { ChapterService } from '../../services/ChapterService.js';\nimport { RecentService } from '../../services/RecentService.js';\nimport { BookmarkService } from '../../services/BookmarkService.js';\nimport type { ChapterRecord } from '../../db/models/Chapter.js';\nimport type { BookmarkRecord } from '../../db/models/Bookmark.js';\nimport { triggerBossKey } from '../../utils/bossKey.js';\nimport { estimateReadingTime, formatReadingTime } from '../../utils/time.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\ninterface ReaderPageProps {\n bookId: string;\n initialByteOffset?: number;\n onNavigate: (page: PageRoute, bookId?: string, byteOffset?: number) => void;\n}\n\n/**\n * 阅读器内部组件 — 在 pages 准备好后渲染\n */\nfunction ReaderContent({\n book,\n bookId,\n pages,\n initialByteOffset,\n termHeight,\n contentHeight,\n}: {\n book: BookRecord;\n bookId: string;\n pages: Page[];\n initialByteOffset?: number;\n termHeight: number;\n contentHeight: number;\n}) {\n const { exit } = useApp();\n const [chapterTitle, setChapterTitle] = useState<string | undefined>();\n const [currentChapter, setCurrentChapter] = useState<ChapterRecord | undefined>();\n const [showChapterNav, setShowChapterNav] = useState(false);\n const [allChapters, setAllChapters] = useState<ChapterRecord[]>([]);\n const [allBookmarks, setAllBookmarks] = useState<BookmarkRecord[]>([]);\n const [toastMessage, setToastMessage] = useState<string | null>(null);\n\n const progressServiceRef = useRef(new ProgressService());\n const chapterServiceRef = useRef(new ChapterService());\n const bookmarkServiceRef = useRef(new BookmarkService());\n\n const reader = useReader(pages, initialByteOffset);\n\n // 加载并更新当前章节信息\n useEffect(() => {\n const currentOffset = reader.getCurrentOffset();\n const chapter = chapterServiceRef.current.getChapterByOffset(bookId, currentOffset);\n setCurrentChapter(chapter ?? undefined);\n setChapterTitle(chapter?.title ?? undefined);\n }, [reader.currentPage, bookId]);\n\n // 获取该书全部章节及书签用于渲染导航\n useEffect(() => {\n const chaptersList = chapterServiceRef.current.getChaptersByBookId(bookId);\n setAllChapters(chaptersList);\n \n if (showChapterNav) {\n setAllBookmarks(bookmarkServiceRef.current.getBookmarksByBookId(bookId));\n }\n }, [bookId, showChapterNav]);\n\n /**\n * 取当前页首行生成书签名\n */\n const handleAddBookmark = () => {\n const currentPageInfo = reader.getCurrentPage();\n if (!currentPageInfo) return;\n\n let markTitle = '无标题书签';\n for (const line of currentPageInfo.lines) {\n const stripped = line.trim();\n if (stripped.length > 0) {\n markTitle = stripped.slice(0, 15) + (stripped.length > 15 ? '...' : '');\n break;\n }\n }\n\n const currentOffset = reader.getCurrentOffset();\n bookmarkServiceRef.current.addBookmark(bookId, markTitle, currentOffset);\n \n setToastMessage(t('tui.reader.bookmark_add', markTitle));\n setTimeout(() => setToastMessage(null), 2000);\n };\n\n /**\n * 自动在组件卸载时保存进度\n */\n useEffect(() => {\n return () => {\n const offset = reader.getCurrentOffset();\n const percent = reader.getPercent();\n const chapter = chapterServiceRef.current.getChapterByOffset(bookId, offset);\n const chapterNo = chapter?.chapter_no ?? 0;\n\n progressServiceRef.current.saveProgress(bookId, chapterNo, offset, percent);\n logger.debug(`进度已保存: offset=${offset}, ${(percent * 100).toFixed(1)}%`);\n };\n }, [bookId, reader]);\n\n // 拦截全局键盘事件\n useKeyboard(\n {\n onNext: () => reader.nextPage(),\n onPrev: () => reader.prevPage(),\n onQuit: () => exit(),\n onChapterList: () => setShowChapterNav(true),\n onBossKey: () => triggerBossKey(),\n onBookmarkAdd: handleAddBookmark,\n },\n !showChapterNav, // 如果浮层显示,则停止普通的阅读快捷键\n );\n\n const currentPage = reader.getCurrentPage();\n const currentLines = currentPage?.lines ?? [];\n // The contentHeight prop is already passed, but the instruction redefines it.\n // Assuming the user intends to use this new calculation for contentHeight within ReaderContent.\n const calculatedContentHeight = Math.max(1, termHeight - 2);\n\n // 计算剩余阅读时间:总字符近似于字节数的 1/3 (utf-8 场景下),中文字符占绝大部分\n const totalChars = (book.file_size ?? 0) / 3;\n const remainingChars = Math.max(0, totalChars * (1 - reader.getPercent()));\n const remainingMinutes = estimateReadingTime(remainingChars, true);\n const remainingTimeStr = formatReadingTime(remainingMinutes);\n\n return (\n <Box flexDirection=\"column\" height={termHeight}>\n {/* 相对定位于容器中,使用 flex 布局进行展现。章节模式下隐藏正文 */}\n {!showChapterNav ? (\n <>\n <Box flexDirection=\"column\" flexGrow={1} paddingX={1}>\n <TextRenderer lines={currentLines} height={calculatedContentHeight} />\n </Box>\n <StatusBar\n bookTitle={book.title}\n percent={reader.getPercent()}\n chapterTitle={chapterTitle}\n currentPage={reader.currentPage + 1}\n totalPages={reader.totalPages}\n remainingTime={remainingTimeStr}\n />\n {toastMessage && (\n <Box alignSelf=\"flex-end\" marginTop={-2} marginRight={1} borderStyle=\"round\" borderColor=\"green\" paddingX={1}>\n <Text color=\"green\">{toastMessage}</Text>\n </Box>\n )}\n </>\n ) : (\n <ChapterNav\n chapters={allChapters}\n bookmarks={allBookmarks}\n currentChapterId={currentChapter?.id}\n termHeight={termHeight}\n onSelect={(offset) => {\n reader.goToOffset(offset);\n setShowChapterNav(false);\n }}\n onClose={() => setShowChapterNav(false)}\n />\n )}\n </Box>\n );\n}\n\nexport function ReaderPage({ bookId, initialByteOffset, onNavigate: _onNavigate }: ReaderPageProps) {\n const { exit } = useApp();\n const { stdout } = useStdout();\n\n const [book, setBook] = useState<BookRecord | null>(null);\n const [pages, setPages] = useState<Page[] | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const termWidth = stdout?.columns ?? 80;\n const termHeight = stdout?.rows ?? 24;\n const contentHeight = Math.max(termHeight - 3, 5);\n\n // 加载书籍内容\n useEffect(() => {\n try {\n const bookModel = new BookModel();\n const bookRecord = bookModel.findById(bookId);\n\n if (!bookRecord) {\n setError(`书籍不存在: ${bookId}`);\n return;\n }\n\n setBook(bookRecord);\n\n // 记录打开事件\n const recentService = new RecentService();\n recentService.recordOpen(bookId);\n\n // 异步读取并解析文件内容 (支持 TXT 和 EPUB)\n parseFile(bookRecord.file_path, bookRecord.format as 'txt' | 'epub')\n .then((parsed: ParsedBook) => {\n // 分页\n const paginatedPages = paginate(parsed.content, termWidth - 2, contentHeight);\n setPages(paginatedPages);\n logger.debug(`加载完成: ${bookRecord.title}, ${paginatedPages.length} 页`);\n })\n .catch((err: Error) => {\n setError(`内容解析失败: ${err instanceof Error ? err.message : String(err)}`);\n });\n } catch (err) {\n setError(`加载过程出错: ${err instanceof Error ? err.message : String(err)}`);\n }\n }, [bookId, termWidth, contentHeight]);\n\n // 非 TTY 下支持 q 退出\n useKeyboard({\n onQuit: () => exit(),\n });\n\n // 错误\n if (error) {\n return (\n <Box padding={1} flexDirection=\"column\">\n <Text color=\"red\">✗ {error}</Text>\n <Text dimColor>{t('common.quit')}</Text>\n </Box>\n );\n }\n\n // 加载中\n if (!book || !pages) {\n return (\n <Box padding={1}>\n <Text color=\"cyan\">📖 {t('tui.reader.loading')}</Text>\n </Box>\n );\n }\n\n // pages 准备好后渲染阅读器内容\n return (\n <ReaderContent\n book={book}\n bookId={bookId}\n pages={pages}\n initialByteOffset={initialByteOffset}\n termHeight={termHeight}\n contentHeight={contentHeight}\n />\n );\n}\n","/**\n * 正文渲染组件\n * 显示分页后的文本行,并支持智能高亮\n */\n\nimport React from 'react';\nimport { Box, Text } from 'ink';\n\ninterface TextRendererProps {\n lines: string[];\n height?: number;\n}\n\n/**\n * 处理单行内的智能高亮匹配\n * 例如将 「XXX」或 “XXX” 进行暗化或着色,提升大段文字沉浸感\n */\nfunction renderLineWithHighlight(line: string) {\n if (!line) return <Text> </Text>; // 保留空行\n\n // 匹配对话大纲的正则 (包括中英文常见方括号/双引号)\n // 此处拆分为:对话段落与非对话段落\n // (「.*?」|“.*?”|『.*?』|《.*?》)\n const regex = /(「.*?」|“.*?”|『.*?』|《.*?》)/g;\n const parts = line.split(regex);\n\n return (\n <Text>\n {parts.map((part, index) => {\n // 如果是正则匹配出的组(即被高亮的部分)\n if (regex.test(part)) {\n // 由于 Regex 的全局状态,test 之后要注意\n // 但其实 split 后,奇数项是捕获组的内容\n }\n \n // 简单判定:如果是奇数项,就是捕获组\n const isHighlight = index % 2 === 1;\n\n if (isHighlight) {\n // 对话颜色应用 dimColor,让环境和描述语句突出,或者是相反。\n // 这里将对话变暗 (dimColor),减轻视觉疲劳\n return <Text key={index} dimColor>{part}</Text>;\n }\n\n // 常规文本\n return <Text key={index}>{part}</Text>;\n })}\n </Text>\n );\n}\n\nexport function TextRenderer({ lines, height }: TextRendererProps) {\n // 补齐空行以占满屏幕高度(避免内容跳动)\n const displayLines = [...lines];\n if (height && displayLines.length < height) {\n const padding = height - displayLines.length;\n for (let i = 0; i < padding; i++) {\n displayLines.push('');\n }\n }\n\n return (\n <Box flexDirection=\"column\">\n {displayLines.map((line, index) => (\n <Box key={index}>\n {renderLineWithHighlight(line)}\n </Box>\n ))}\n </Box>\n );\n}\n","/**\n * 底部状态栏组件\n * 显示书名、章节名、页码和进度百分比\n */\n\nimport React from 'react';\nimport { Box, Text } from 'ink';\nimport { t } from '../../locales/index.js';\n\ninterface StatusBarProps {\n bookTitle: string;\n chapterTitle?: string;\n percent: number;\n currentPage: number;\n totalPages: number;\n remainingTime?: string;\n}\n\nexport function StatusBar({\n bookTitle,\n chapterTitle,\n percent,\n currentPage,\n totalPages,\n remainingTime,\n}: StatusBarProps) {\n const displayPercent = (percent * 100).toFixed(1);\n const titleDisplay = chapterTitle ? `${bookTitle} · ${chapterTitle}` : bookTitle;\n\n return (\n <Box flexDirection=\"row\" justifyContent=\"space-between\" borderStyle=\"single\" borderTop={false} borderLeft={false} borderRight={false} paddingX={1}>\n <Box>\n <Text color=\"gray\">📖 {titleDisplay}</Text>\n </Box>\n\n <Box>\n {remainingTime && (\n <Text dimColor>{t('tui.reader.status.remaining', remainingTime)} </Text>\n )}\n <Text color=\"gray\">\n {currentPage}/{totalPages} \n </Text>\n <Text color=\"gray\">\n {displayPercent}% \n </Text>\n <Text dimColor>{t('common.quit')}</Text>\n </Box>\n </Box>\n );\n}\n","/**\n * 章节导航浮层组件\n * 允许在阅读器内唤起章节列表,并支持翻页与选择跳转\n */\n\nimport React, { useState, useEffect } from 'react';\nimport { Box, Text, useInput } from 'ink';\nimport type { ChapterRecord } from '../../db/models/Chapter.js';\nimport type { BookmarkRecord } from '../../db/models/Bookmark.js';\nimport { t } from '../../locales/index.js';\n\ninterface ChapterNavProps {\n chapters: ChapterRecord[];\n bookmarks: BookmarkRecord[];\n currentChapterId?: number;\n termHeight: number;\n onSelect: (byteOffset: number) => void;\n onClose: () => void;\n}\n\nexport function ChapterNav({\n chapters,\n bookmarks,\n currentChapterId,\n termHeight,\n onSelect,\n onClose,\n}: ChapterNavProps) {\n const [activeTab, setActiveTab] = useState<'chapters' | 'bookmarks'>('chapters');\n const isBookmarks = activeTab === 'bookmarks';\n const currentList = isBookmarks ? bookmarks : chapters;\n\n // 找到当前章节的初始索引\n const initialIndex = currentChapterId && !isBookmarks\n ? Math.max(\n 0,\n chapters.findIndex((c) => c.id === currentChapterId),\n )\n : 0;\n\n const [selectedIndex, setSelectedIndex] = useState(initialIndex);\n\n // 切换 tab 时重置索引\n useEffect(() => {\n setSelectedIndex(isBookmarks ? 0 : initialIndex);\n }, [activeTab, initialIndex, isBookmarks]);\n\n // 一页展示多少个项(留出上下边距)\n const pageSize = Math.max(5, termHeight - 6);\n\n // 计算当前可视窗口的起始和结束索引\n const windowStart = Math.max(0, Math.floor(selectedIndex / pageSize) * pageSize);\n const visibleItems = currentList.slice(windowStart, windowStart + pageSize);\n\n const isRawModeSupported = process.stdin.isTTY ?? false;\n\n useInput(\n (input, key) => {\n // 退出\n if (key.escape || input === 'q') {\n onClose();\n return;\n }\n \n // 切换视图\n if (key.tab) {\n setActiveTab(prev => prev === 'chapters' ? 'bookmarks' : 'chapters');\n return;\n }\n\n // 确认\n if (key.return) {\n if (currentList[selectedIndex]) {\n onSelect(currentList[selectedIndex].byte_offset);\n }\n return;\n }\n\n // 上移\n if (key.upArrow || input === 'k') {\n setSelectedIndex((prev) => Math.max(0, prev - 1));\n }\n\n // 下移\n if (key.downArrow || input === 'j') {\n setSelectedIndex((prev) => Math.min(currentList.length - 1, prev + 1));\n }\n },\n { isActive: isRawModeSupported },\n );\n\n return (\n <Box\n flexDirection=\"column\"\n borderStyle=\"round\"\n borderColor=\"green\"\n paddingX={2}\n paddingY={1}\n width=\"80%\"\n alignSelf=\"center\"\n marginTop={2}\n >\n <Box justifyContent=\"space-between\" marginBottom={1}>\n <Box>\n <Text bold color={activeTab === 'chapters' ? 'green' : 'gray'}>{t('tui.nav.tab.chapters')} </Text>\n <Text bold color={activeTab === 'bookmarks' ? 'green' : 'gray'}>{t('tui.nav.tab.bookmarks')}</Text>\n </Box>\n <Text dimColor>{t('tui.nav.tips')}</Text>\n </Box>\n\n {visibleItems.length === 0 ? (\n <Text dimColor>{t('tui.nav.empty')}</Text>\n ) : (\n visibleItems.map((item, idx) => {\n const actualIndex = windowStart + idx;\n const isSelected = actualIndex === selectedIndex;\n \n return (\n <Text\n key={item.id}\n color={isSelected ? 'green' : undefined}\n bold={isSelected}\n >\n {isSelected ? '▶ ' : ' '}\n {item.title}\n </Text>\n );\n })\n )}\n\n <Box marginTop={1} justifyContent=\"flex-end\">\n <Text dimColor>\n {t('tui.nav.page', Math.floor(selectedIndex / pageSize) + 1, Math.ceil(currentList.length / pageSize) || 1)}\n </Text>\n </Box>\n </Box>\n );\n}\n","/**\n * 阅读器核心状态 Hook\n * 管理当前页面、offset、分页等状态\n */\n\nimport { useState, useCallback, useMemo } from 'react';\nimport type { Page } from '../../utils/paginate.js';\n\ninterface ReaderState {\n currentPage: number;\n totalPages: number;\n}\n\nexport function useReader(pages: Page[], initialByteOffset?: number) {\n // 根据 initialByteOffset 找到初始页\n const initialPage = useMemo(() => {\n if (!initialByteOffset || pages.length === 0) return 0;\n\n // 找到 byte_offset <= initialByteOffset 的最后一页\n let targetPage = 0;\n for (let i = 0; i < pages.length; i++) {\n if (pages[i].byteOffset <= initialByteOffset) {\n targetPage = i;\n } else {\n break;\n }\n }\n return targetPage;\n }, [pages, initialByteOffset]);\n\n const [state, setState] = useState<ReaderState>({\n currentPage: initialPage,\n totalPages: pages.length,\n });\n\n const nextPage = useCallback(() => {\n setState((prev) => ({\n ...prev,\n currentPage: Math.min(prev.currentPage + 1, prev.totalPages - 1),\n }));\n }, []);\n\n const prevPage = useCallback(() => {\n setState((prev) => ({\n ...prev,\n currentPage: Math.max(prev.currentPage - 1, 0),\n }));\n }, []);\n\n const goToPage = useCallback((pageNum: number) => {\n setState((prev) => ({\n ...prev,\n currentPage: Math.max(0, Math.min(pageNum, prev.totalPages - 1)),\n }));\n }, []);\n\n /**\n * 根据 byte_offset 跳转到对应页\n */\n const goToOffset = useCallback((byteOffset: number) => {\n let targetPage = 0;\n for (let i = 0; i < pages.length; i++) {\n if (pages[i].byteOffset <= byteOffset) {\n targetPage = i;\n } else {\n break;\n }\n }\n setState((prev) => ({\n ...prev,\n currentPage: targetPage,\n }));\n }, [pages]);\n\n const getCurrentPage = useCallback((): Page | undefined => {\n return pages[state.currentPage];\n }, [state.currentPage, pages]);\n\n /**\n * 获取当前页的 byte_offset(用于保存进度)\n */\n const getCurrentOffset = useCallback((): number => {\n return pages[state.currentPage]?.byteOffset ?? 0;\n }, [state.currentPage, pages]);\n\n const getPercent = useCallback((): number => {\n if (state.totalPages === 0) return 0;\n return (state.currentPage + 1) / state.totalPages;\n }, [state.currentPage, state.totalPages]);\n\n const isFirstPage = state.currentPage === 0;\n const isLastPage = state.currentPage === state.totalPages - 1;\n\n return {\n ...state,\n nextPage,\n prevPage,\n goToPage,\n goToOffset,\n getCurrentPage,\n getCurrentOffset,\n getPercent,\n isFirstPage,\n isLastPage,\n };\n}\n","/**\n * 键盘事件统一管理 Hook\n * 在非 TTY 环境下安全降级\n */\n\nimport { useInput } from 'ink';\n\ninterface KeyboardHandlers {\n onNext?: () => void;\n onPrev?: () => void;\n onQuit?: () => void;\n onChapterList?: () => void;\n onHelp?: () => void;\n onBossKey?: () => void;\n onBookmarkAdd?: () => void;\n}\n\nexport function useKeyboard(handlers: KeyboardHandlers, isActive: boolean = true) {\n const isRawModeSupported = process.stdin.isTTY ?? false;\n const shouldListen = isRawModeSupported && isActive;\n\n useInput((input, key) => {\n // 翻页:空格 / j / 下箭头 / f → 下一页\n if (input === ' ' || input === 'j' || key.downArrow || input === 'f') {\n handlers.onNext?.();\n }\n\n // 上一页:k / 上箭头 / b\n if (input === 'k' || key.upArrow || input === 'b') {\n handlers.onPrev?.();\n }\n\n // 退出:q\n if (input === 'q') {\n handlers.onQuit?.();\n }\n\n // 章节列表:c\n if (input === 'c') {\n handlers.onChapterList?.();\n }\n\n // 帮助:?\n if (input === '?') {\n handlers.onHelp?.();\n }\n \n // 老板键\n if (handlers.onBossKey && (key.escape || input === 'esc' || input === 'b' || input === 'B')) {\n handlers.onBossKey?.();\n }\n \n // 按 m 或 M 加书签\n if (handlers.onBookmarkAdd && (input === 'm' || input === 'M')) {\n handlers.onBookmarkAdd?.();\n }\n }, { isActive: shouldListen });\n}\n","/**\n * 字符串宽度计算\n * 处理中文字符宽度为 2\n */\n\n/**\n * 获取字符串在终端中的显示宽度\n */\nexport function getStringWidth(str: string): number {\n let width = 0;\n for (const char of str) {\n width += isFullWidth(char) ? 2 : 1;\n }\n return width;\n}\n\n/**\n * 判断字符是否为全角字符\n * CJK 统一表意字符 + 全角标点\n */\nfunction isFullWidth(char: string): boolean {\n const code = char.codePointAt(0);\n if (code === undefined) return false;\n\n return (\n // CJK 统一表意字符\n (code >= 0x4e00 && code <= 0x9fff) ||\n // CJK 统一表意字符扩展 A\n (code >= 0x3400 && code <= 0x4dbf) ||\n // CJK 统一表意字符扩展 B\n (code >= 0x20000 && code <= 0x2a6df) ||\n // CJK 兼容表意字符\n (code >= 0xf900 && code <= 0xfaff) ||\n // 全角 ASCII、全角标点\n (code >= 0xff01 && code <= 0xff60) ||\n (code >= 0xffe0 && code <= 0xffe6) ||\n // CJK 标点符号\n (code >= 0x3000 && code <= 0x303f) ||\n // 日文平假名/片假名\n (code >= 0x3040 && code <= 0x30ff) ||\n // 韩文音节\n (code >= 0xac00 && code <= 0xd7af)\n );\n}\n","/**\n * 文本分页算法\n * 按终端宽高切割文本为页面\n */\n\nimport { getStringWidth } from './stringWidth.js';\n\nexport interface Page {\n /** 页面内容行 */\n lines: string[];\n /** 此页在原始文本中的 byte offset 起始位置 */\n byteOffset: number;\n}\n\n/**\n * 将文本按终端尺寸分页\n * @param text 原始文本\n * @param width 终端宽度(列数)\n * @param height 可用行数(扣除状态栏后)\n * @returns 分页结果\n */\nexport function paginate(text: string, width: number, height: number): Page[] {\n const pages: Page[] = [];\n const rawLines = text.split('\\n');\n\n // 将原始文本行按终端宽度折行\n const wrappedLines: { text: string; byteOffset: number }[] = [];\n let currentOffset = 0;\n\n for (const rawLine of rawLines) {\n const wrapped = wrapLine(rawLine, width);\n for (const line of wrapped) {\n wrappedLines.push({ text: line, byteOffset: currentOffset });\n }\n currentOffset += Buffer.byteLength(rawLine + '\\n', 'utf-8');\n }\n\n // 按高度切割为页\n for (let i = 0; i < wrappedLines.length; i += height) {\n const pageLines = wrappedLines.slice(i, i + height);\n pages.push({\n lines: pageLines.map((l) => l.text),\n byteOffset: pageLines[0]?.byteOffset ?? 0,\n });\n }\n\n return pages;\n}\n\n/**\n * 将一行文本按终端宽度折行\n * 处理中英文混排(中文字符宽度为 2)\n */\nexport function wrapLine(line: string, width: number): string[] {\n if (line.length === 0) return [''];\n\n const result: string[] = [];\n let currentLine = '';\n let currentWidth = 0;\n\n for (const char of line) {\n const charWidth = getStringWidth(char);\n\n if (currentWidth + charWidth > width) {\n result.push(currentLine);\n currentLine = char;\n currentWidth = charWidth;\n } else {\n currentLine += char;\n currentWidth += charWidth;\n }\n }\n\n if (currentLine.length > 0) {\n result.push(currentLine);\n }\n\n return result.length > 0 ? result : [''];\n}\n","/**\n * 章节索引业务逻辑\n * 章节索引构建与查询\n */\n\nimport { ChapterModel, type ChapterRecord } from '../db/models/Chapter.js';\n\nexport class ChapterService {\n private chapterModel = new ChapterModel();\n\n /**\n * 获取指定书籍的所有章节\n */\n getChapters(bookId: string): ChapterRecord[] {\n return this.chapterModel.findByBookId(bookId);\n }\n\n /**\n * 获取指定章节信息\n */\n getChapter(bookId: string, chapterNo: number): ChapterRecord | undefined {\n return this.chapterModel.findChapter(bookId, chapterNo);\n }\n\n /**\n * 获取章节总数\n */\n getChapterCount(bookId: string): number {\n return this.chapterModel.getChapterCount(bookId);\n }\n\n /**\n * 获取指定书籍下的所有章节\n */\n getChaptersByBookId(bookId: string): ChapterRecord[] {\n return this.chapterModel.findByBookId(bookId);\n }\n\n /**\n * 根据 offset 查询当前所属章节(用于高亮当前所在章)\n */\n getChapterByOffset(bookId: string, byteOffset: number): ChapterRecord | undefined {\n const chapters = this.chapterModel.findByBookId(bookId);\n if (chapters.length === 0) return undefined;\n\n // 找到 offset 所在的章节(最后一个 byte_offset <= 给定 offset 的章节)\n let current: ChapterRecord | undefined;\n for (const chapter of chapters) {\n if (chapter.byte_offset <= byteOffset) {\n current = chapter;\n } else {\n break;\n }\n }\n return current;\n }\n}\n","/**\n * 最近阅读业务逻辑\n */\n\nimport { RecentModel } from '../db/models/Recent.js';\nimport { BookModel, type BookRecord } from '../db/models/Book.js';\n\nexport class RecentService {\n private recentModel = new RecentModel();\n private bookModel = new BookModel();\n\n /**\n * 获取最近阅读的书籍列表(包含书籍详情)\n */\n getRecentBooks(limit: number = 20): BookRecord[] {\n const recentRecords = this.recentModel.getRecent(limit);\n\n return recentRecords\n .map((record) => this.bookModel.findById(record.book_id))\n .filter((book): book is BookRecord => book !== undefined);\n }\n\n /**\n * 记录打开事件\n */\n recordOpen(bookId: string): void {\n this.recentModel.recordOpen(bookId);\n }\n}\n","/**\n * bookmarks 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface BookmarkRecord {\n id?: number;\n book_id: string;\n title: string;\n byte_offset: number;\n created_at: number;\n}\n\nexport class BookmarkModel {\n /**\n * 插入书签\n */\n insert(bookmark: Omit<BookmarkRecord, 'id'>): void {\n const db = getDb();\n db.prepare(`\n INSERT INTO bookmarks (book_id, title, byte_offset, created_at)\n VALUES (?, ?, ?, ?)\n `).run(bookmark.book_id, bookmark.title, bookmark.byte_offset, bookmark.created_at);\n }\n\n /**\n * 获取指定书籍的所有书签\n */\n findByBookId(bookId: string): BookmarkRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM bookmarks WHERE book_id = ? ORDER BY created_at DESC').all(bookId) as BookmarkRecord[];\n }\n\n /**\n * 获取指定书签\n */\n findById(id: number): BookmarkRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM bookmarks WHERE id = ?').get(id) as BookmarkRecord | undefined;\n }\n\n /**\n * 获取书籍书签总数\n */\n getCount(bookId: string): number {\n const db = getDb();\n const result = db.prepare('SELECT COUNT(*) as count FROM bookmarks WHERE book_id = ?').get(bookId) as { count: number };\n return result.count;\n }\n\n /**\n * 删除书签\n */\n delete(id: number): void {\n const db = getDb();\n db.prepare('DELETE FROM bookmarks WHERE id = ?').run(id);\n }\n\n /**\n * 移除整本书的书签 (配合彻底清理书籍使用)\n */\n deleteByBookId(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM bookmarks WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * 书签管理服务\n * 处理针对某一位置打标记录以及后续的跳转\n */\n\nimport { BookmarkModel, type BookmarkRecord } from '../db/models/Bookmark.js';\n\nexport class BookmarkService {\n private bookmarkModel: BookmarkModel;\n\n constructor() {\n this.bookmarkModel = new BookmarkModel();\n }\n\n /**\n * 增加一条书签\n * @param title 该书签展现给用户的文案(一句话大纲)\n */\n addBookmark(bookId: string, title: string, byteOffset: number): void {\n this.bookmarkModel.insert({\n book_id: bookId,\n title,\n byte_offset: byteOffset,\n created_at: Date.now(),\n });\n }\n\n /**\n * 罗列该书全部的书签\n */\n getBookmarksByBookId(bookId: string): BookmarkRecord[] {\n return this.bookmarkModel.findByBookId(bookId);\n }\n\n /**\n * 删掉对应书签\n */\n removeBookmark(id: number): void {\n this.bookmarkModel.delete(id);\n }\n}\n","/**\n * 防打断老板键 (Boss Key)\n * 瞬间退出并用伪装日志覆盖屏幕\n */\n\nexport function triggerBossKey(): void {\n // 1. 清空屏幕\n console.clear();\n\n // 2. 打印极度逼真的伪装日志(例如 Vite 启动成功日志)\n const fakeLog = `\n VITE v5.2.8 ready in 213 ms\n\n ➜ Local: http://localhost:5173/\n ➜ Network: use --host to expose\n ➜ press h + enter to show help\n`;\n\n console.log(fakeLog);\n\n // 3. 强制安静终止应用,不留痕迹\n process.exit(0);\n}\n","/**\n * 阅读时间估算\n */\n\n/**\n * 估算阅读时间(分钟)\n * @param charCount 字符数\n * @param isChinese 是否中文内容\n * @returns 估计阅读分钟数\n */\nexport function estimateReadingTime(charCount: number, isChinese: boolean = true): number {\n // 中文平均阅读速度约 500 字/分钟\n // 英文平均阅读速度约 250 词/分钟(约 1250 字符/分钟)\n const charsPerMinute = isChinese ? 500 : 1250;\n return Math.ceil(charCount / charsPerMinute);\n}\n\n/**\n * 格式化时间显示\n */\nexport function formatReadingTime(minutes: number): string {\n if (minutes < 60) {\n return `${minutes} 分钟`;\n }\n const hours = Math.floor(minutes / 60);\n const mins = minutes % 60;\n return mins > 0 ? `${hours} 小时 ${mins} 分钟` : `${hours} 小时`;\n}\n","/**\n * novel resume — 恢复上次阅读\n * 启动后默认入口,精确恢复到上次 offset,零摩擦\n */\n\nimport type { CommandModule } from 'yargs';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { BookService } from '../../services/BookService.js';\nimport { renderApp } from '../../ui/renderApp.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\nexport const resumeCommand: CommandModule = {\n command: 'resume',\n describe: t('cli.resume.desc'),\n handler: async () => {\n try {\n const progressService = new ProgressService();\n const lastProgress = progressService.getLastOpenedBook();\n\n if (!lastProgress) {\n console.log(t('cli.resume.none'));\n return;\n }\n\n // 验证书籍仍然存在\n const bookService = new BookService();\n const book = bookService.findBook(lastProgress.book_id);\n if (!book) {\n console.log(t('cli.resume.none'));\n return;\n }\n\n logger.debug(`恢复阅读: ${book.title}, offset=${lastProgress.byte_offset}`);\n\n // 启动 Ink TUI 阅读器,恢复到上次 offset\n renderApp({\n initialPage: 'reader',\n bookId: lastProgress.book_id,\n initialByteOffset: lastProgress.byte_offset,\n });\n } catch (error) {\n logger.error('恢复阅读失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel open <id|name> — 打开指定书\n * 支持 book-id 或模糊匹配书名\n */\n\nimport type { CommandModule } from 'yargs';\nimport { BookService } from '../../services/BookService.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { renderApp } from '../../ui/renderApp.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\nexport interface OpenArgs {\n target: string;\n}\n\nexport const openCommand: CommandModule<object, OpenArgs> = {\n command: 'open <target>',\n describe: t('cli.open.desc'),\n builder: (yargs) => {\n return yargs.positional('target', {\n describe: t('cli.open.help'),\n type: 'string',\n demandOption: true,\n });\n },\n handler: async (argv) => {\n try {\n const bookService = new BookService();\n const book = bookService.findBook(argv.target);\n\n if (!book) {\n console.log(`${t('cli.open.not_found')} ${argv.target}`);\n process.exit(1);\n }\n\n // 检查是否有之前的阅读进度\n const progressService = new ProgressService();\n const progress = progressService.getProgress(book.id);\n const byteOffset = progress?.byte_offset ?? 0;\n\n logger.debug(`打开: ${book.title}, offset=${byteOffset}`);\n\n // 启动 Ink TUI 阅读器\n renderApp({\n initialPage: 'reader',\n bookId: book.id,\n initialByteOffset: byteOffset,\n });\n } catch (error) {\n logger.error('打开失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel library — 书架列表\n * 含最近阅读排序 + 搜索\n */\n\nimport type { CommandModule } from 'yargs';\nimport { renderApp } from '../../ui/renderApp.js';\nimport { BookService } from '../../services/BookService.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\nexport interface LibraryArgs {\n search?: string;\n}\n\nexport const libraryCommand: CommandModule<object, LibraryArgs> = {\n command: 'list',\n aliases: ['library'],\n describe: t('cli.library.desc'),\n builder: (yargs) => {\n return yargs.option('search', {\n alias: 's',\n describe: t('cli.library.help'),\n type: 'string',\n });\n },\n handler: async (argv) => {\n try {\n // 如果有搜索参数,以非交互模式输出\n if (argv.search) {\n const bookService = new BookService();\n const books = bookService.searchBooks(argv.search);\n\n if (books.length === 0) {\n console.log(t('cli.library.search_none', argv.search));\n return;\n }\n\n console.log(t('cli.library.search_result', books.length));\n books.forEach((book, index) => {\n console.log(` ${index + 1}. ${book.title} [${book.id}] (${book.format})`);\n });\n return;\n }\n\n // 无搜索参数,启动交互式书架\n renderApp({ initialPage: 'library' });\n } catch (error) {\n logger.error('获取书架失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel remove <id|name> — 移除书籍\n * 删除相关所有数据,包括记录和本地进度的 DB 项\n */\n\nimport type { CommandModule } from 'yargs';\nimport { BookService } from '../../services/BookService.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\nexport interface RemoveArgs {\n target: string;\n}\n\nexport const removeCommand: CommandModule<object, RemoveArgs> = {\n command: 'remove <target>',\n describe: t('cli.remove.desc'),\n builder: (yargs) => {\n return yargs.positional('target', {\n describe: t('cli.remove.help'),\n type: 'string',\n demandOption: true,\n });\n },\n handler: async (argv) => {\n try {\n const bookService = new BookService();\n const book = bookService.findBook(argv.target);\n\n if (!book) {\n console.log(`${t('cli.remove.not_found')} ${argv.target}`);\n process.exit(1);\n }\n\n bookService.deleteBook(book.id);\n console.log(`${t('cli.remove.success')} ${book.title}`);\n } catch (error) {\n logger.error('移除书籍失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel lang <zh|en> — 切换语言\n */\n\nimport type { CommandModule } from 'yargs';\nimport { setConfig } from '../../config/AppConfig.js';\nimport { t, setLanguage } from '../../locales/index.js';\n\nexport interface LangArgs {\n target: 'zh' | 'en';\n}\n\nexport const langCommand: CommandModule<object, LangArgs> = {\n command: 'lang <target>',\n describe: t('cli.lang.desc'),\n builder: (yargs) => {\n return yargs.positional('target', {\n describe: t('cli.lang.help'),\n type: 'string',\n choices: ['zh', 'en'] as const,\n demandOption: true,\n });\n },\n handler: (argv) => {\n const lang = argv.target;\n if (lang === 'zh' || lang === 'en') {\n setConfig('language', lang);\n setLanguage(lang);\n console.log(t('cli.lang.success', lang));\n } else {\n console.log(t('cli.lang.unsupported', lang));\n process.exit(1);\n }\n },\n};\n","import type { CommandModule } from 'yargs';\nimport { execSync } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { t } from '../../locales/index.js';\nimport { logger } from '../../utils/logger.js';\n\nexport const updateCommand: CommandModule = {\n command: 'update',\n describe: t('cli.update.desc'),\n handler: async () => {\n try {\n console.log(t('cli.update.checking'));\n\n // 获取本地版本号 (由 tsup 在构建时注入)\n const localVersion = typeof APP_VERSION !== 'undefined' ? APP_VERSION : '0.2.2';\n\n // 获取 NPM 最新版本\n const npmOutput = execSync('npm view readshell version', { encoding: 'utf-8' });\n const latestVersion = npmOutput.trim();\n\n if (!latestVersion) {\n throw new Error('Could not fetch npm version');\n }\n\n if (latestVersion === localVersion) {\n console.log(t('cli.update.latest', localVersion));\n return;\n }\n\n console.log(t('cli.update.updating', latestVersion, localVersion));\n \n // 执行升级指令\n execSync('npm install -g readshell@latest', { stdio: 'inherit' });\n \n console.log(t('cli.update.success'));\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n console.log(t('cli.update.fail', msg));\n logger.error('更新失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * 数据库迁移逻辑\n * 程序启动时检查并执行建表/迁移\n */\n\nimport { getDb } from './client.js';\nimport { logger } from '../utils/logger.js';\n\nconst SCHEMA_VERSION = 2;\n\n/**\n * 初始化数据库(建表 + 版本管理)\n */\nexport function initDatabase(): void {\n const db = getDb();\n\n // 创建版本管理表\n db.exec(`\n CREATE TABLE IF NOT EXISTS schema_version (\n version INTEGER PRIMARY KEY\n );\n `);\n\n const row = db.prepare('SELECT version FROM schema_version LIMIT 1').get() as\n | { version: number }\n | undefined;\n const currentVersion = row?.version ?? 0;\n\n if (currentVersion < SCHEMA_VERSION) {\n logger.debug(`数据库迁移: v${currentVersion} → v${SCHEMA_VERSION}`);\n migrate(db, currentVersion);\n }\n}\n\nfunction migrate(db: ReturnType<typeof getDb>, fromVersion: number): void {\n const migrations: Record<number, string> = {\n 1: `\n -- 书籍元数据\n CREATE TABLE IF NOT EXISTS books (\n id TEXT PRIMARY KEY,\n title TEXT NOT NULL,\n author TEXT,\n file_path TEXT NOT NULL,\n format TEXT NOT NULL,\n file_hash TEXT NOT NULL,\n file_size INTEGER,\n created_at INTEGER NOT NULL\n );\n\n -- 核心状态表\n CREATE TABLE IF NOT EXISTS reading_progress (\n book_id TEXT PRIMARY KEY REFERENCES books(id),\n chapter_no INTEGER NOT NULL DEFAULT 0,\n byte_offset INTEGER NOT NULL DEFAULT 0,\n percent REAL NOT NULL DEFAULT 0,\n updated_at INTEGER NOT NULL,\n opened_at INTEGER NOT NULL\n );\n\n -- 最近阅读排序\n CREATE TABLE IF NOT EXISTS recent_reads (\n book_id TEXT PRIMARY KEY REFERENCES books(id),\n opened_at INTEGER NOT NULL,\n open_count INTEGER NOT NULL DEFAULT 1\n );\n\n -- 章节索引\n CREATE TABLE IF NOT EXISTS chapter_index (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n book_id TEXT NOT NULL REFERENCES books(id),\n chapter_no INTEGER NOT NULL,\n title TEXT,\n byte_offset INTEGER NOT NULL,\n UNIQUE(book_id, chapter_no)\n );\n\n -- 索引\n CREATE INDEX IF NOT EXISTS idx_chapter_book ON chapter_index(book_id);\n CREATE INDEX IF NOT EXISTS idx_recent_opened ON recent_reads(opened_at DESC);\n `,\n 2: `\n -- 书签管理\n CREATE TABLE IF NOT EXISTS bookmarks (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n book_id TEXT NOT NULL REFERENCES books(id),\n title TEXT NOT NULL,\n byte_offset INTEGER NOT NULL,\n created_at INTEGER NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_bookmarks_book ON bookmarks(book_id);\n `,\n };\n\n db.transaction(() => {\n for (let v = fromVersion + 1; v <= SCHEMA_VERSION; v++) {\n const sql = migrations[v];\n if (sql) {\n db.exec(sql);\n logger.debug(`已执行迁移 v${v}`);\n }\n }\n\n // 更新版本号\n db.prepare('DELETE FROM schema_version').run();\n db.prepare('INSERT INTO schema_version (version) VALUES (?)').run(SCHEMA_VERSION);\n })();\n}\n","#!/usr/bin/env node\n\n/**\n * ReadShell — 终端内低打断轻阅读工具\n * 程序主入口,解析 argv,路由到子命令\n */\n\nimport { createParser } from './cli/parser.js';\nimport { initDatabase } from './db/migrate.js';\nimport { initI18n } from './locales/index.js';\nimport { logger } from './utils/logger.js';\n\nasync function main() {\n try {\n // 初始化数据库(自动建表/迁移)\n initDatabase();\n \n // 初始化多语言本地化模块\n initI18n();\n\n // 解析命令行参数并执行对应命令\n const parser = createParser();\n await parser.parse();\n } catch (error) {\n logger.error('程序启动失败:', error);\n process.exit(1);\n }\n}\n\nmain();\n"],"mappings":"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","names":["existsSync","db","db","db","db","detect","iconv","resolve","detect","iconv","readFileSync","existsSync","config","statSync","resolve","join","yargs","React","useState","Box","Text","useState","useEffect","Box","Text","useApp","jsx","jsxs","useApp","useState","useEffect","Box","Text","useState","useEffect","Box","Text","useApp","Box","Text","jsx","Box","Text","jsx","jsxs","Box","Text","useState","useEffect","Box","Text","useInput","jsx","jsxs","useState","useEffect","useInput","Box","Text","useState","useInput","db","jsx","jsxs","useApp","useState","useEffect","Box","Text","jsx","jsxs","useState","Box","Text","React","yargs","yargs","yargs","yargs","db"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli/parser.ts","../src/services/BookService.ts","../src/db/client.ts","../src/config/paths.ts","../src/utils/logger.ts","../src/db/models/Book.ts","../src/db/models/Chapter.ts","../src/db/models/Recent.ts","../src/db/models/Progress.ts","../src/parsers/TxtParser.ts","../src/parsers/EpubParser.ts","../src/parsers/index.ts","../src/utils/hash.ts","../src/locales/zh.ts","../src/locales/en.ts","../src/config/AppConfig.ts","../src/locales/index.ts","../src/cli/commands/import.ts","../src/services/ProgressService.ts","../src/ui/renderApp.ts","../src/ui/App.tsx","../src/ui/pages/ResumePage.tsx","../src/ui/pages/LibraryPage.tsx","../src/ui/pages/ReaderPage.tsx","../src/ui/components/TextRenderer.tsx","../src/ui/components/StatusBar.tsx","../src/ui/components/ChapterNav.tsx","../src/ui/hooks/useReader.ts","../src/ui/hooks/useKeyboard.ts","../src/utils/stringWidth.ts","../src/utils/paginate.ts","../src/services/ChapterService.ts","../src/services/RecentService.ts","../src/db/models/Bookmark.ts","../src/services/BookmarkService.ts","../src/utils/bossKey.ts","../src/utils/time.ts","../src/cli/commands/resume.ts","../src/cli/commands/open.ts","../src/cli/commands/library.ts","../src/cli/commands/remove.ts","../src/cli/commands/lang.ts","../src/cli/commands/update.ts","../src/db/migrate.ts","../src/index.ts"],"sourcesContent":["/**\n * CLI 参数解析器\n * 使用 yargs 解析子命令\n */\n\nimport yargs from 'yargs';\nimport { hideBin } from 'yargs/helpers';\nimport { importCommand } from './commands/import.js';\nimport { resumeCommand } from './commands/resume.js';\nimport { openCommand } from './commands/open.js';\nimport { libraryCommand } from './commands/library.js';\nimport { removeCommand } from './commands/remove.js';\nimport { langCommand } from './commands/lang.js';\nimport { updateCommand } from './commands/update.js';\n\nexport function createParser() {\n const version = typeof APP_VERSION !== 'undefined' ? APP_VERSION : 'dev';\n\n return yargs(hideBin(process.argv))\n .scriptName('novel')\n .usage('$0 <command> [options]')\n .command(importCommand)\n .command(resumeCommand)\n .command(openCommand)\n .command(libraryCommand)\n .command(removeCommand)\n .command(langCommand)\n .command(updateCommand)\n .demandCommand(1, '请指定一个命令。使用 --help 查看可用命令。')\n .strict()\n .alias('h', 'help')\n .alias('v', 'version')\n .version(version)\n .epilogue('ReadShell — 终端内低打断轻阅读工具');\n}\n","/**\n * 书籍业务逻辑\n * 书籍 CRUD、导入、去重\n */\n\nimport { resolve } from 'path';\nimport { existsSync, statSync } from 'fs';\nimport { nanoid } from 'nanoid';\nimport { BookModel, type BookRecord } from '../db/models/Book.js';\nimport { ChapterModel } from '../db/models/Chapter.js';\nimport { RecentModel } from '../db/models/Recent.js';\nimport { ProgressModel } from '../db/models/Progress.js';\nimport { parseFile } from '../parsers/index.js';\nimport { computeFileHash } from '../utils/hash.js';\nimport { logger } from '../utils/logger.js';\n\nexport class BookService {\n private bookModel = new BookModel();\n private chapterModel = new ChapterModel();\n private recentModel = new RecentModel();\n private progressModel = new ProgressModel();\n\n /**\n * 导入书籍文件\n */\n async importBook(filePath: string): Promise<BookRecord> {\n const absPath = resolve(filePath);\n\n // 检查文件存在\n if (!existsSync(absPath)) {\n throw new Error(`文件不存在: ${absPath}`);\n }\n\n // 检测文件格式\n const format = this.detectFormat(absPath);\n if (!format) {\n throw new Error('不支持的文件格式。目前支持: .txt, .epub');\n }\n\n // 计算文件 hash 用于去重\n const fileHash = await computeFileHash(absPath);\n const existing = this.bookModel.findByHash(fileHash);\n if (existing) {\n logger.debug(`文件已存在: ${existing.title} (${existing.id})`);\n return existing;\n }\n\n // 解析文件\n const parsed = await parseFile(absPath, format);\n\n // 获取文件大小\n const stats = statSync(absPath);\n\n // 创建书籍记录\n const book: BookRecord = {\n id: nanoid(),\n title: parsed.title,\n author: parsed.author || null,\n file_path: absPath,\n format,\n file_hash: fileHash,\n file_size: stats.size,\n created_at: Date.now(),\n };\n\n this.bookModel.insert(book);\n\n // 保存章节索引\n if (parsed.chapters.length > 0) {\n this.chapterModel.insertMany(\n parsed.chapters.map((ch, idx) => ({\n book_id: book.id,\n chapter_no: idx,\n title: ch.title,\n byte_offset: ch.byteOffset,\n })),\n );\n }\n\n logger.debug(`导入成功: ${book.title}`);\n return book;\n }\n\n /**\n * 查找书籍(ID 或模糊匹配书名)\n */\n findBook(target: string): BookRecord | undefined {\n // 先尝试精确 ID 匹配\n const byId = this.bookModel.findById(target);\n if (byId) return byId;\n\n // 再尝试书名模糊匹配\n const results = this.bookModel.searchByTitle(target);\n return results[0];\n }\n\n /**\n * 搜索书籍\n */\n searchBooks(keyword: string): BookRecord[] {\n return this.bookModel.searchByTitle(keyword);\n }\n\n /**\n * 获取所有书籍\n */\n getAllBooks(): BookRecord[] {\n return this.bookModel.findAll();\n }\n\n /**\n * 删除书籍及相关数据\n */\n deleteBook(id: string): void {\n this.chapterModel.deleteByBookId(id);\n this.progressModel.delete(id);\n this.recentModel.delete(id);\n this.bookModel.delete(id);\n }\n\n /**\n * 检测文件格式\n */\n private detectFormat(filePath: string): 'txt' | 'epub' | null {\n const ext = filePath.toLowerCase().split('.').pop();\n if (ext === 'txt') return 'txt';\n if (ext === 'epub') return 'epub';\n return null;\n }\n}\n","/**\n * SQLite 数据库连接初始化\n * 使用 better-sqlite3 同步 API\n */\n\nimport Database from 'better-sqlite3';\nimport { getDbPath } from '../config/paths.js';\nimport { logger } from '../utils/logger.js';\n\nlet db: Database.Database | null = null;\n\n/**\n * 获取数据库连接实例(单例模式)\n */\nexport function getDb(): Database.Database {\n if (!db) {\n const dbPath = getDbPath();\n logger.debug(`数据库路径: ${dbPath}`);\n\n db = new Database(dbPath);\n\n // 启用 WAL 模式以提升写入性能\n db.pragma('journal_mode = WAL');\n // 启用外键约束\n db.pragma('foreign_keys = ON');\n }\n\n return db;\n}\n\n/**\n * 关闭数据库连接\n */\nexport function closeDb(): void {\n if (db) {\n db.close();\n db = null;\n logger.debug('数据库连接已关闭');\n }\n}\n\n// 进程退出时自动关闭数据库\nprocess.on('exit', () => closeDb());\nprocess.on('SIGINT', () => {\n closeDb();\n process.exit(0);\n});\nprocess.on('SIGTERM', () => {\n closeDb();\n process.exit(0);\n});\n","/**\n * 跨平台路径管理\n * 数据库、配置文件位置\n */\n\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { mkdirSync, existsSync } from 'fs';\n\n/**\n * 获取应用数据目录\n * ~/.config/readshell/ (macOS/Linux)\n */\nexport function getAppDataDir(): string {\n const platform = process.platform;\n let configDir: string;\n\n if (platform === 'darwin') {\n configDir = join(homedir(), 'Library', 'Application Support', 'readshell');\n } else if (platform === 'win32') {\n configDir = join(process.env['APPDATA'] || join(homedir(), 'AppData', 'Roaming'), 'readshell');\n } else {\n // Linux / 其他\n configDir = join(process.env['XDG_CONFIG_HOME'] || join(homedir(), '.config'), 'readshell');\n }\n\n // 确保目录存在\n if (!existsSync(configDir)) {\n mkdirSync(configDir, { recursive: true });\n }\n\n return configDir;\n}\n\n/**\n * 获取数据库文件路径\n */\nexport function getDbPath(): string {\n return join(getAppDataDir(), 'readshell.db');\n}\n\n/**\n * 获取配置文件路径\n */\nexport function getConfigPath(): string {\n return join(getAppDataDir(), 'config.json');\n}\n","/**\n * 调试日志\n * 仅 DEBUG=1 时输出\n */\n\nconst isDebug = process.env['DEBUG'] === '1' || process.env['DEBUG'] === 'true';\n\nexport const logger = {\n debug: (...args: unknown[]): void => {\n if (isDebug) {\n console.error('[DEBUG]', ...args);\n }\n },\n\n info: (...args: unknown[]): void => {\n console.error('[INFO]', ...args);\n },\n\n warn: (...args: unknown[]): void => {\n console.error('[WARN]', ...args);\n },\n\n error: (...args: unknown[]): void => {\n console.error('[ERROR]', ...args);\n },\n};\n","/**\n * books 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface BookRecord {\n id: string;\n title: string;\n author: string | null;\n file_path: string;\n format: 'txt' | 'epub';\n file_hash: string;\n file_size: number | null;\n created_at: number;\n}\n\nexport class BookModel {\n /**\n * 插入新书\n */\n insert(book: BookRecord): void {\n const db = getDb();\n db.prepare(`\n INSERT INTO books (id, title, author, file_path, format, file_hash, file_size, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `).run(book.id, book.title, book.author, book.file_path, book.format, book.file_hash, book.file_size, book.created_at);\n }\n\n /**\n * 通过 ID 获取书籍\n */\n findById(id: string): BookRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM books WHERE id = ?').get(id) as BookRecord | undefined;\n }\n\n /**\n * 通过文件 hash 查找(去重用)\n */\n findByHash(hash: string): BookRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM books WHERE file_hash = ?').get(hash) as BookRecord | undefined;\n }\n\n /**\n * 模糊搜索书名\n */\n searchByTitle(keyword: string): BookRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM books WHERE title LIKE ? ORDER BY created_at DESC').all(`%${keyword}%`) as BookRecord[];\n }\n\n /**\n * 获取所有书籍\n */\n findAll(): BookRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM books ORDER BY created_at DESC').all() as BookRecord[];\n }\n\n /**\n * 删除书籍\n */\n delete(id: string): void {\n const db = getDb();\n db.prepare('DELETE FROM books WHERE id = ?').run(id);\n }\n}\n","/**\n * chapter_index 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface ChapterRecord {\n id?: number;\n book_id: string;\n chapter_no: number;\n title: string | null;\n byte_offset: number;\n}\n\nexport class ChapterModel {\n /**\n * 批量插入章节索引\n */\n insertMany(chapters: Omit<ChapterRecord, 'id'>[]): void {\n const db = getDb();\n const stmt = db.prepare(`\n INSERT OR REPLACE INTO chapter_index (book_id, chapter_no, title, byte_offset)\n VALUES (?, ?, ?, ?)\n `);\n\n db.transaction(() => {\n for (const chapter of chapters) {\n stmt.run(chapter.book_id, chapter.chapter_no, chapter.title, chapter.byte_offset);\n }\n })();\n }\n\n /**\n * 获取指定书籍的所有章节\n */\n findByBookId(bookId: string): ChapterRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM chapter_index WHERE book_id = ? ORDER BY chapter_no').all(bookId) as ChapterRecord[];\n }\n\n /**\n * 获取指定章节\n */\n findChapter(bookId: string, chapterNo: number): ChapterRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM chapter_index WHERE book_id = ? AND chapter_no = ?').get(bookId, chapterNo) as ChapterRecord | undefined;\n }\n\n /**\n * 获取书籍章节总数\n */\n getChapterCount(bookId: string): number {\n const db = getDb();\n const result = db.prepare('SELECT COUNT(*) as count FROM chapter_index WHERE book_id = ?').get(bookId) as { count: number };\n return result.count;\n }\n\n /**\n * 删除指定书籍的章节索引\n */\n deleteByBookId(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM chapter_index WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * recent_reads 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface RecentRecord {\n book_id: string;\n opened_at: number;\n open_count: number;\n}\n\nexport class RecentModel {\n /**\n * 记录打开(插入或更新计数)\n */\n recordOpen(bookId: string): void {\n const db = getDb();\n const now = Date.now();\n db.prepare(`\n INSERT INTO recent_reads (book_id, opened_at, open_count)\n VALUES (?, ?, 1)\n ON CONFLICT(book_id) DO UPDATE SET\n opened_at = excluded.opened_at,\n open_count = open_count + 1\n `).run(bookId, now);\n }\n\n /**\n * 获取最近阅读列表(按时间倒序)\n */\n getRecent(limit: number = 20): RecentRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM recent_reads ORDER BY opened_at DESC LIMIT ?').all(limit) as RecentRecord[];\n }\n\n /**\n * 删除记录\n */\n delete(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM recent_reads WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * reading_progress 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface ProgressRecord {\n book_id: string;\n chapter_no: number;\n byte_offset: number;\n percent: number;\n updated_at: number;\n opened_at: number;\n}\n\nexport class ProgressModel {\n /**\n * 保存或更新阅读进度\n */\n upsert(progress: ProgressRecord): void {\n const db = getDb();\n db.prepare(`\n INSERT INTO reading_progress (book_id, chapter_no, byte_offset, percent, updated_at, opened_at)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(book_id) DO UPDATE SET\n chapter_no = excluded.chapter_no,\n byte_offset = excluded.byte_offset,\n percent = excluded.percent,\n updated_at = excluded.updated_at,\n opened_at = excluded.opened_at\n `).run(progress.book_id, progress.chapter_no, progress.byte_offset, progress.percent, progress.updated_at, progress.opened_at);\n }\n\n /**\n * 获取指定书籍的阅读进度\n */\n findByBookId(bookId: string): ProgressRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM reading_progress WHERE book_id = ?').get(bookId) as ProgressRecord | undefined;\n }\n\n /**\n * 获取最近打开的书籍进度(用于 resume)\n */\n getLastOpened(): ProgressRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM reading_progress ORDER BY opened_at DESC LIMIT 1').get() as ProgressRecord | undefined;\n }\n\n /**\n * 删除指定书籍的进度\n */\n delete(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM reading_progress WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * TXT 文件解析器\n * 编码检测、章节切割\n */\n\nimport { readFileSync } from 'fs';\nimport { detect } from 'chardet';\nimport iconv from 'iconv-lite';\nimport { logger } from '../utils/logger.js';\n\nexport interface ParsedChapter {\n title: string;\n byteOffset: number;\n}\n\nexport interface ParsedBook {\n title: string;\n author: string | null;\n content: string;\n chapters: ParsedChapter[];\n}\n\n// 常见的章节标题正则\nconst CHAPTER_PATTERNS = [\n /^第[零一二三四五六七八九十百千万\\d]+[章节回卷集部篇]/m,\n /^Chapter\\s+\\d+/im,\n /^CHAPTER\\s+\\d+/m,\n /^第\\s*\\d+\\s*[章节回]/m,\n];\n\n/**\n * 解析 TXT 文件\n */\nexport async function parseTxt(filePath: string): Promise<ParsedBook> {\n // 读取原始字节\n const buffer = readFileSync(filePath);\n\n // 自动检测编码\n const encoding = detect(buffer) || 'utf-8';\n logger.debug(`检测到编码: ${encoding}`);\n\n // 转换为 UTF-8 字符串\n const content = encoding.toLowerCase() === 'utf-8'\n ? buffer.toString('utf-8')\n : iconv.decode(buffer, encoding);\n\n // 从文件名提取标题\n const title = extractTitle(filePath);\n\n // 提取章节索引\n const chapters = extractChapters(content);\n\n return {\n title,\n author: null,\n content,\n chapters,\n };\n}\n\n/**\n * 从文件名提取书名\n */\nfunction extractTitle(filePath: string): string {\n const basename = filePath.split('/').pop() || filePath;\n // 去掉扩展名\n return basename.replace(/\\.txt$/i, '').trim() || '未命名';\n}\n\n/**\n * 提取章节索引\n */\nfunction extractChapters(content: string): ParsedChapter[] {\n const chapters: ParsedChapter[] = [];\n const lines = content.split('\\n');\n let byteOffset = 0;\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n for (const pattern of CHAPTER_PATTERNS) {\n if (pattern.test(trimmed)) {\n chapters.push({\n title: trimmed,\n byteOffset,\n });\n break;\n }\n }\n\n // 计算 byte offset(UTF-8 编码)\n byteOffset += Buffer.byteLength(line + '\\n', 'utf-8');\n }\n\n logger.debug(`检测到 ${chapters.length} 个章节`);\n return chapters;\n}\n","/**\n * EPUB 文件解析器\n * 元数据 + 章节提取,并转为纯文本阅读格式\n */\n\nimport { EPub } from 'epub2';\nimport { convert } from 'html-to-text';\nimport { detect } from 'chardet';\nimport iconv from 'iconv-lite';\nimport { logger } from '../utils/logger.js';\nimport type { ParsedBook, ParsedChapter } from './TxtParser.js';\n\n/**\n * 解析 EPUB 文件\n */\nexport async function parseEpub(filePath: string): Promise<ParsedBook> {\n logger.debug(`开始解析 EPUB: ${filePath}`);\n\n const epub = await openEpub(filePath);\n\n const title = epub.metadata.title || '未知书名';\n const author = epub.metadata.creator || null;\n\n const chapters: ParsedChapter[] = [];\n let fullContent = '';\n let currentByteOffset = 0;\n\n // epub.flow contains the reading order\n for (const chapterRef of epub.flow) {\n if (!chapterRef.id) continue;\n\n try {\n const htmlText = await getChapterHtml(epub, chapterRef.id);\n \n // 使用 html-to-text 转为纯文本,保持换行,去除无关标签\n const plainText = convert(htmlText, {\n wordwrap: false,\n selectors: [\n // 忽略图片和链接\n { selector: 'img', format: 'skip' },\n { selector: 'a', options: { ignoreHref: true } },\n ],\n });\n\n if (!plainText.trim()) continue; // 跳过空章节\n\n const chapterTitle = chapterRef.title || '无标题章节';\n\n chapters.push({\n title: chapterTitle,\n byteOffset: currentByteOffset,\n });\n\n // 加上章节标题作为正文本页头部\n const chapterContent = `\\n\\n${chapterTitle}\\n\\n${plainText}\\n`;\n fullContent += chapterContent;\n\n currentByteOffset += Buffer.byteLength(chapterContent, 'utf-8');\n } catch (err) {\n logger.debug(`警告: 读取章节 ${chapterRef.id} 失败`, err);\n }\n }\n\n return {\n title,\n author,\n content: fullContent,\n chapters,\n };\n}\n\n/**\n * 提取 EPUB 元数据\n */\nexport async function getEpubMetadata(filePath: string): Promise<{ title: string; author: string | null }> {\n const epub = await openEpub(filePath);\n return {\n title: epub.metadata.title || '未知',\n author: epub.metadata.creator || null,\n };\n}\n\n// ============== 辅助函数 ==============\n\n/**\n * 包装 epub2 基于回调的初始化过程为 Promise\n */\nfunction openEpub(filePath: string): Promise<EPub> {\n return new Promise((resolve, reject) => {\n const epub = new EPub(filePath);\n epub.on('error', (err) => reject(err));\n epub.on('end', () => resolve(epub));\n epub.parse();\n });\n}\n\n/**\n * 从 HTML/XML 原始字节中提取编码声明\n * 优先读取 XML 声明 (<?xml encoding=\"GBK\">) 和 meta charset\n */\nfunction detectHtmlEncoding(buffer: Buffer): string {\n // 用 latin1 无损读取前 1KB,仅为提取编码声明\n const head = buffer.slice(0, 1024).toString('latin1');\n\n // 1. 优先查找 XML 声明:<?xml version=\"1.0\" encoding=\"GBK\"?>\n const xmlDecl = head.match(/<\\?xml[^>]*encoding=[\"']([^\"']+)[\"']/i);\n if (xmlDecl?.[1]) return xmlDecl[1];\n\n // 2. 查找 HTML meta charset:<meta charset=\"GBK\"> 或 Content-Type: charset=GBK\n const metaCharset = head.match(/charset=[\"']?([A-Za-z0-9_-]+)[\"']?/i);\n if (metaCharset?.[1] && metaCharset[1].toLowerCase() !== 'utf-8') {\n return metaCharset[1];\n }\n\n // 3. 回退:使用 chardet 字节模式探测\n const chardetResult = detect(buffer);\n return chardetResult || 'utf-8';\n}\n\n/**\n * 获取单个章节的 HTML 原文并自动转码\n * 包装为 Promise\n */\nfunction getChapterHtml(epub: any, chapterId: string): Promise<string> {\n return new Promise((resolve, reject) => {\n // 使用 getFile 获取原始 Buffer,避免 epub2 内部 getChapterRaw 强制转 utf-8 导致的乱码\n epub.getFile(chapterId, (err: Error | null, data: Buffer) => {\n if (err) return reject(err);\n\n const buffer = data;\n const encoding = detectHtmlEncoding(buffer);\n\n const htmlText = encoding.toLowerCase().replace('-', '') === 'utf8' || encoding.toLowerCase() === 'utf-8'\n ? buffer.toString('utf-8')\n : iconv.decode(buffer, encoding);\n\n resolve(htmlText);\n });\n });\n}\n","/**\n * 解析器分发\n * 按文件格式分发到对应解析器\n */\n\nimport { parseTxt, type ParsedBook } from './TxtParser.js';\nimport { parseEpub } from './EpubParser.js';\n\nexport type { ParsedBook, ParsedChapter } from './TxtParser.js';\n\n/**\n * 根据格式解析文件\n */\nexport async function parseFile(filePath: string, format: 'txt' | 'epub'): Promise<ParsedBook> {\n switch (format) {\n case 'txt':\n return parseTxt(filePath);\n case 'epub':\n return parseEpub(filePath);\n default:\n throw new Error(`不支持的文件格式: ${format}`);\n }\n}\n","/**\n * 文件 hash 计算(去重用)\n */\n\nimport { createHash } from 'crypto';\nimport { readFileSync } from 'fs';\n\n/**\n * 计算文件的 SHA-256 hash\n */\nexport async function computeFileHash(filePath: string): Promise<string> {\n const buffer = readFileSync(filePath);\n const hash = createHash('sha256');\n hash.update(buffer);\n return hash.digest('hex');\n}\n","export default {\n // Common\n 'common.yes': '是',\n 'common.no': '否',\n 'common.confirm': '确认',\n 'common.cancel': '取消',\n 'common.quit': '按 q 退出',\n\n // CLI\n 'cli.import.desc': '导入本地文件或目录到书架',\n 'cli.import.help': '文件或目录路径(支持 .txt / .epub)',\n 'cli.import.success': '✓ 已导入:',\n 'cli.import.fail': '导入失败:',\n 'cli.import.not_found': '路径不存在:',\n 'cli.import.unsupported': '不支持的文件格式。目前支持: .txt, .epub',\n 'cli.import.scan_dir': '扫描目录',\n 'cli.import.found_files': '找到以下书籍:',\n 'cli.import.confirm_batch': '是否确认导入上述 {0} 本书?(y/N)',\n 'cli.import.canceled': '✓ 导入已取消',\n\n 'cli.resume.desc': '恢复上次阅读',\n 'cli.resume.none': '✗ 没有找到最近阅读记录,请先使用 novel import <file> 或 novel open <book-id> 打开一本书。',\n\n 'cli.open.desc': '打开指定书籍',\n 'cli.open.help': '书籍 ID 或书名(支持模糊匹配)',\n 'cli.open.not_found': '✗ 未找到匹配书籍:',\n\n 'cli.library.desc': '查看书架书籍列表',\n 'cli.library.help': '可选关键字搜索',\n 'cli.library.none': '✗ 书架为空。使用 novel import <file> 导入你的第一本书。',\n 'cli.library.search_none': '📚 未找到匹配「{0}」的书籍。',\n 'cli.library.search_result': '📚 搜索结果 ({0} 本):\\n',\n\n 'cli.remove.desc': '从书架移除书籍(仅删除记录,不删源文件)',\n 'cli.remove.help': '书籍 ID 或书名(支持模糊匹配)',\n 'cli.remove.not_found': '✗ 未找到匹配书籍:',\n 'cli.remove.success': '✓ 已移除书籍:',\n 'cli.remove.fail': '移除书籍失败:',\n\n 'cli.lang.success': '✓ 语言已切换为: {0}',\n 'cli.lang.unsupported': '✗ 不支持的语言: {0}',\n\n 'cli.config.desc': '修改应用配置',\n 'cli.config.line_spacing.desc': '行间距: 0 (紧凑) | 1 (适中) | 2 (宽松)',\n 'cli.config.line_spacing.success': '✓ 行间距已设置为: {0}',\n 'cli.config.reading_mode.desc': '阅读模式: page (翻页) | scroll (滚动)',\n\n 'cli.update.desc': '检查最新版本并自动更新',\n 'cli.update.checking': '正在检查更新...',\n 'cli.update.latest': '✓ 当前已是最新版本 (v{0})',\n 'cli.update.updating': '发现新版本 v{0} (当前 v{1}),正在更新...',\n 'cli.update.success': '✓ 升级成功!请重新运行 novel 命令。',\n 'cli.update.fail': '✗ 升级失败: {0}',\n\n // TUI - Library\n 'tui.lib.loading': '📚 加载书架...',\n 'tui.lib.empty.title': '📚 书架',\n 'tui.lib.empty.desc': '书架为空。使用 novel import <file> 导入你的第一本书。',\n 'tui.lib.title': '📚 书架 ({0} 本)',\n 'tui.lib.tips': ' ↑↓/jk 选择 · Enter 打开 · d/x 删除 · q 退出',\n\n // TUI - Reader\n 'tui.reader.loading': '读取中...',\n 'tui.reader.bookmark_add': '✓ 增加书签: {0}',\n 'tui.reader.status.remaining': '预计剩余 {0}',\n\n 'tui.nav.tab.chapters': '[全部章节]',\n 'tui.nav.tab.bookmarks': '[我的书签]',\n 'tui.nav.tips': 'Enter 跳转 · Tab 切换 · Esc/q 关闭',\n 'tui.nav.hint': '按 C 键弹出此清单,按 Tab 切换书签',\n 'tui.nav.empty': '没有记录',\n 'tui.nav.page': '第 {0} / {1} 页',\n};\n","import type { LocaleDictionary } from './types.js';\n\nconst en: LocaleDictionary = {\n // Common\n 'common.yes': 'Yes',\n 'common.no': 'No',\n 'common.confirm': 'Confirm',\n 'common.cancel': 'Cancel',\n 'common.quit': 'Press q to quit',\n\n // CLI\n 'cli.import.desc': 'Import local file or directory to library',\n 'cli.import.help': 'File or directory path (supports .txt / .epub)',\n 'cli.import.success': '✓ Imported:',\n 'cli.import.fail': 'Import failed:',\n 'cli.import.not_found': 'Path not found:',\n 'cli.import.unsupported': 'Unsupported file format. Currently supports: .txt, .epub',\n 'cli.import.scan_dir': 'Scanning directory',\n 'cli.import.found_files': 'Found following books:',\n 'cli.import.confirm_batch': 'Confirm importing these {0} books? (y/N)',\n 'cli.import.canceled': '✓ Import canceled',\n\n 'cli.resume.desc': 'Resume last reading',\n 'cli.resume.none': '✗ No recent reading record found. Please use `novel import <file>` or `novel open <book-id>` first.',\n\n 'cli.open.desc': 'Open specific book',\n 'cli.open.help': 'Book ID or title (fuzzy match)',\n 'cli.open.not_found': '✗ Book not found:',\n\n 'cli.library.desc': 'View book list / library',\n 'cli.library.help': 'Optional keyword search',\n 'cli.library.none': '✗ Library is empty. Use `novel import <file>` to import your first book.',\n 'cli.library.search_none': '📚 No books found matching \"{0}\".',\n 'cli.library.search_result': '📚 Search results ({0} books):\\n',\n\n 'cli.remove.desc': 'Remove book from library (only deletes records, not source file)',\n 'cli.remove.help': 'Book ID or title (fuzzy match)',\n 'cli.remove.not_found': '✗ Book not found:',\n 'cli.remove.success': '✓ Book removed:',\n 'cli.remove.fail': 'Failed to remove book:',\n\n 'cli.lang.success': '✓ Language switched to: {0}',\n 'cli.lang.unsupported': '✗ Unsupported language: {0}',\n\n 'cli.config.desc': 'Modify application configuration',\n 'cli.config.line_spacing.desc': 'Line spacing: 0 (tight) | 1 (medium) | 2 (loose)',\n 'cli.config.line_spacing.success': '✓ Line spacing set to: {0}',\n 'cli.config.reading_mode.desc': 'Reading mode: page (paging) | scroll (scrolling)',\n\n 'cli.update.desc': 'Check for the latest version and update automatically',\n 'cli.update.checking': 'Checking for updates...',\n 'cli.update.latest': '✓ Already up to date (v{0})',\n 'cli.update.updating': 'New version v{0} found (current v{1}), updating...',\n 'cli.update.success': '✓ Successfully updated! Please restart novel command.',\n 'cli.update.fail': '✗ Update failed: {0}',\n\n // TUI - Library\n 'tui.lib.loading': '📚 Loading library...',\n 'tui.lib.empty.title': '📚 Library',\n 'tui.lib.empty.desc': 'Library is empty. Use `novel import <file>` to import your first book.',\n 'tui.lib.title': '📚 Library ({0} books)',\n 'tui.lib.tips': ' ↑↓/jk select · Enter open · d/x delete · q quit',\n\n // TUI - Reader\n 'tui.reader.loading': 'Loading...',\n 'tui.reader.bookmark_add': '✓ Bookmark added: {0}',\n 'tui.reader.status.remaining': 'Est. remaining {0}',\n\n // TUI - ChapterNav\n 'tui.nav.tab.chapters': '[All Chapters]',\n 'tui.nav.tab.bookmarks': '[My Bookmarks]',\n 'tui.nav.tips': 'Enter jump · Tab switch · Esc/q close',\n 'tui.nav.hint': 'Press C to open this list, press Tab to switch to bookmarks',\n 'tui.nav.empty': 'No records',\n 'tui.nav.page': 'Page {0} / {1}',\n};\n\nexport default en;\n","/**\n * 应用配置管理\n * 读写 ~/.config/readshell/config.json\n */\n\nimport Conf from 'conf';\n\ninterface ReadShellConfig {\n /** 每页显示行数(0 = 自适应终端高度) */\n linesPerPage: number;\n /** 是否显示状态栏 */\n showStatusBar: boolean;\n /** 阅读模式: 'page' | 'scroll' */\n readingMode: 'page' | 'scroll';\n /** 界面语言 */\n language: 'zh' | 'en';\n /** 行间距 (0-2) */\n lineSpacing: number;\n}\n\nconst defaults: ReadShellConfig = {\n linesPerPage: 0,\n showStatusBar: true,\n readingMode: 'page',\n language: 'zh',\n lineSpacing: 0,\n};\n\nconst config = new Conf<ReadShellConfig>({\n projectName: 'readshell',\n defaults,\n});\n\nexport function getConfig(): ReadShellConfig {\n return {\n linesPerPage: config.get('linesPerPage'),\n showStatusBar: config.get('showStatusBar'),\n readingMode: config.get('readingMode'),\n language: config.get('language'),\n lineSpacing: config.get('lineSpacing'),\n };\n}\n\nexport function setConfig<K extends keyof ReadShellConfig>(key: K, value: ReadShellConfig[K]): void {\n config.set(key, value);\n}\n\nexport { config };\n","import zh from './zh.js';\nimport en from './en.js';\nimport type { LocaleDictionary, LocaleKeys } from './types.js';\nimport { getConfig } from '../config/AppConfig.js';\n\nconst dictionaries: Record<'zh' | 'en', LocaleDictionary> = {\n zh,\n en,\n};\n\nlet currentLang: 'zh' | 'en' = 'zh';\nlet currentDict: LocaleDictionary = dictionaries.zh;\n\n/**\n * 初始化并加载当前配置的语言\n */\nexport function initI18n() {\n const config = getConfig();\n currentLang = config.language || 'zh';\n currentDict = dictionaries[currentLang] || dictionaries.zh;\n}\n\n/**\n * 切换语言字典(运行时热切换支持)\n */\nexport function setLanguage(lang: 'zh' | 'en') {\n currentLang = lang;\n currentDict = dictionaries[lang] || dictionaries.zh;\n}\n\n/**\n * 获取翻译词条\n * 支持占位符 {0}, {1} 替换\n */\nexport function t(key: LocaleKeys, ...args: (string | number)[]): string {\n let template = currentDict[key];\n if (!template) {\n return key;\n }\n \n if (args.length > 0) {\n args.forEach((arg, index) => {\n template = template.replace(`{${index}}`, String(arg));\n });\n }\n return template;\n}\n","/**\n * novel import <file> — 导入本地文件\n * 支持 txt,基础去重,导入后自动入书架\n */\n\nimport type { CommandModule } from 'yargs';\nimport { BookService } from '../../services/BookService.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\nimport { statSync, readdirSync } from 'node:fs';\nimport { resolve, join, extname } from 'node:path';\nimport * as readline from 'node:readline/promises';\n\nexport interface ImportArgs {\n file: string;\n}\n\n// 递归扫描目录文件\nfunction scanDirectory(dir: string): string[] {\n let results: string[] = [];\n try {\n const list = readdirSync(dir);\n for (const file of list) {\n const fullPath = join(dir, file);\n const stat = statSync(fullPath);\n if (stat.isDirectory()) {\n results = results.concat(scanDirectory(fullPath));\n } else {\n const ext = extname(fullPath).toLowerCase();\n if (ext === '.txt' || ext === '.epub') {\n results.push(fullPath);\n }\n }\n }\n } catch (err) {\n logger.error('Scan error:', err);\n }\n return results;\n}\n\nexport const importCommand: CommandModule<object, ImportArgs> = {\n command: 'import <file>',\n describe: t('cli.import.desc'),\n builder: (yargs) => {\n return yargs.positional('file', {\n describe: t('cli.import.help'),\n type: 'string',\n demandOption: true,\n });\n },\n handler: async (argv) => {\n try {\n const targetPath = resolve(argv.file);\n let stat;\n try {\n stat = statSync(targetPath);\n } catch (e) {\n console.log(`${t('cli.import.not_found')} ${targetPath}`);\n process.exit(1);\n }\n\n const bookService = new BookService();\n\n if (stat.isDirectory()) {\n console.log(`${t('cli.import.scan_dir')} ${targetPath}...`);\n const files = scanDirectory(targetPath);\n\n if (files.length === 0) {\n console.log(`✗ ` + t('cli.import.unsupported'));\n return;\n }\n\n console.log(t('cli.import.found_files'));\n files.forEach((f, i) => console.log(` ${i + 1}. ${f}`));\n\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const answer = await rl.question(t('cli.import.confirm_batch', files.length) + ' ');\n rl.close();\n\n if (answer.toLowerCase() === 'y') {\n for (const file of files) {\n try {\n const book = await bookService.importBook(file);\n console.log(`${t('cli.import.success')} ${book.title} (${book.id})`);\n } catch (err) {\n console.log(`${t('cli.import.fail')} ${file} - ${err}`);\n }\n }\n } else {\n console.log(t('cli.import.canceled'));\n }\n } else {\n // 单文件导入\n const book = await bookService.importBook(argv.file);\n console.log(`${t('cli.import.success')} ${book.title} (${book.id})`);\n }\n } catch (error) {\n console.log(`${t('cli.import.fail')} ${error}`);\n process.exit(1);\n }\n },\n};\n","/**\n * 阅读进度业务逻辑\n * 进度读写、resume 核心逻辑\n */\n\nimport { ProgressModel, type ProgressRecord } from '../db/models/Progress.js';\nimport { RecentModel } from '../db/models/Recent.js';\nimport { logger } from '../utils/logger.js';\n\nexport class ProgressService {\n private progressModel = new ProgressModel();\n private recentModel = new RecentModel();\n\n /**\n * 保存阅读进度(退出时调用)\n */\n saveProgress(bookId: string, chapterNo: number, byteOffset: number, percent: number): void {\n const now = Date.now();\n\n this.progressModel.upsert({\n book_id: bookId,\n chapter_no: chapterNo,\n byte_offset: byteOffset,\n percent,\n updated_at: now,\n opened_at: now,\n });\n\n // 同时更新最近阅读记录\n this.recentModel.recordOpen(bookId);\n\n logger.debug(`进度已保存: book=${bookId}, chapter=${chapterNo}, offset=${byteOffset}, ${(percent * 100).toFixed(1)}%`);\n }\n\n /**\n * 获取指定书籍的阅读进度(resume 时调用)\n */\n getProgress(bookId: string): ProgressRecord | undefined {\n return this.progressModel.findByBookId(bookId);\n }\n\n /**\n * 获取最近打开的书籍进度(启动时调用,决定 resume 哪本书)\n */\n getLastOpenedBook(): ProgressRecord | undefined {\n return this.progressModel.getLastOpened();\n }\n}\n","/**\n * Ink TUI 渲染启动函数\n * 封装 ink.render(<App />) 的统一入口\n */\n\nimport React from 'react';\nimport { render } from 'ink';\nimport { App, type PageRoute } from './App.js';\n\ninterface RenderOptions {\n initialPage?: PageRoute;\n bookId?: string;\n initialByteOffset?: number;\n}\n\n/**\n * 启动 Ink TUI 应用\n */\nexport function renderApp(options: RenderOptions = {}): void {\n const { initialPage = 'resume', bookId, initialByteOffset } = options;\n\n const { waitUntilExit } = render(\n React.createElement(App, {\n initialPage,\n bookId,\n initialByteOffset,\n }),\n );\n\n waitUntilExit().catch(() => {\n // Ink 退出时的清理\n process.exit(0);\n });\n}\n","/**\n * App 根组件\n * 管理页面路由状态\n */\n\nimport React, { useState } from 'react';\nimport { Box, Text } from 'ink';\nimport { ResumePage } from './pages/ResumePage.js';\nimport { LibraryPage } from './pages/LibraryPage.js';\nimport { ReaderPage } from './pages/ReaderPage.js';\n\nexport type PageRoute = 'resume' | 'library' | 'reader';\n\ninterface AppProps {\n initialPage?: PageRoute;\n bookId?: string;\n initialByteOffset?: number;\n}\n\nexport function App({ initialPage = 'resume', bookId, initialByteOffset }: AppProps) {\n const [currentPage, setCurrentPage] = useState<PageRoute>(initialPage);\n const [currentBookId, setCurrentBookId] = useState<string | undefined>(bookId);\n const [currentByteOffset, setCurrentByteOffset] = useState<number | undefined>(initialByteOffset);\n\n const navigateTo = (page: PageRoute, targetBookId?: string, byteOffset?: number) => {\n setCurrentPage(page);\n if (targetBookId) setCurrentBookId(targetBookId);\n if (byteOffset !== undefined) setCurrentByteOffset(byteOffset);\n };\n\n return (\n <Box flexDirection=\"column\" width=\"100%\">\n {currentPage === 'resume' && (\n <ResumePage onNavigate={navigateTo} />\n )}\n {currentPage === 'library' && (\n <LibraryPage onNavigate={navigateTo} />\n )}\n {currentPage === 'reader' && currentBookId && (\n <ReaderPage\n bookId={currentBookId}\n initialByteOffset={currentByteOffset}\n onNavigate={navigateTo}\n />\n )}\n {currentPage === 'reader' && !currentBookId && (\n <Box>\n <Text color=\"red\">错误: 未指定书籍</Text>\n </Box>\n )}\n </Box>\n );\n}\n","/**\n * 继续阅读页\n * 启动默认页,自动查询最近阅读并跳转到阅读器\n */\n\nimport React, { useEffect, useState } from 'react';\nimport { Box, Text, useApp } from 'ink';\nimport type { PageRoute } from '../App.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { BookModel, type BookRecord } from '../../db/models/Book.js';\n\ninterface ResumePageProps {\n onNavigate: (page: PageRoute, bookId?: string, byteOffset?: number) => void;\n}\n\nexport function ResumePage({ onNavigate }: ResumePageProps) {\n const { exit } = useApp();\n const [checking, setChecking] = useState(true);\n\n useEffect(() => {\n const progressService = new ProgressService();\n const lastProgress = progressService.getLastOpenedBook();\n\n if (!lastProgress) {\n setChecking(false);\n return;\n }\n\n // 验证书籍存在\n const bookModel = new BookModel();\n const book = bookModel.findById(lastProgress.book_id);\n\n if (!book) {\n setChecking(false);\n return;\n }\n\n // 自动跳转到阅读器\n onNavigate('reader', book.id, lastProgress.byte_offset);\n }, [onNavigate]);\n\n if (checking) {\n return (\n <Box padding={1}>\n <Text color=\"cyan\">📖 检查阅读记录...</Text>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Text bold color=\"cyan\">\n 📖 ReadShell — 终端内轻阅读\n </Text>\n <Box marginTop={1} flexDirection=\"column\">\n <Text dimColor>暂无阅读记录。</Text>\n <Text dimColor>使用 novel import <file> 导入一本书开始阅读。</Text>\n <Box marginTop={1}>\n <Text dimColor>按 q 退出</Text>\n </Box>\n </Box>\n </Box>\n );\n}\n","/**\n * 书架页\n * 交互式书架列表,上下键选择,Enter 打开阅读\n */\n\nimport React, { useState, useEffect } from 'react';\nimport { Box, Text, useApp, useInput } from 'ink';\nimport type { PageRoute } from '../App.js';\nimport { BookService } from '../../services/BookService.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { RecentService } from '../../services/RecentService.js';\nimport type { BookRecord } from '../../db/models/Book.js';\nimport { t } from '../../locales/index.js';\n\ninterface LibraryPageProps {\n onNavigate: (page: PageRoute, bookId?: string, byteOffset?: number) => void;\n}\n\nexport function LibraryPage({ onNavigate }: LibraryPageProps) {\n const { exit } = useApp();\n const [books, setBooks] = useState<BookRecord[]>([]);\n const [selectedIndex, setSelectedIndex] = useState(0);\n const [loading, setLoading] = useState(true);\n\n const isRawModeSupported = process.stdin.isTTY ?? false;\n\n useEffect(() => {\n const bookService = new BookService();\n setBooks(bookService.getAllBooks());\n setLoading(false);\n }, []);\n\n useInput((input, key) => {\n if (input === 'q') {\n exit();\n return;\n }\n\n if (books.length === 0) return;\n\n // 上下导航\n if (key.upArrow || input === 'k') {\n setSelectedIndex((prev) => Math.max(prev - 1, 0));\n }\n if (key.downArrow || input === 'j') {\n setSelectedIndex((prev) => Math.min(prev + 1, books.length - 1));\n }\n\n // 删除选中的书 (backspace 键或 d 键)\n if (key.backspace || key.delete || input === 'd' || input === 'x') {\n const selected = books[selectedIndex];\n if (selected) {\n const bookService = new BookService();\n bookService.deleteBook(selected.id);\n \n // 更新列表\n setBooks((prev) => {\n const next = prev.filter((b) => b.id !== selected.id);\n // 调整选中游标,防止越界\n if (selectedIndex >= next.length) {\n setSelectedIndex(Math.max(0, next.length - 1));\n }\n return next;\n });\n }\n return;\n }\n\n // Enter 打开选中的书\n if (key.return) {\n const selected = books[selectedIndex];\n if (selected) {\n const progressService = new ProgressService();\n const progress = progressService.getProgress(selected.id);\n onNavigate('reader', selected.id, progress?.byte_offset ?? 0);\n }\n }\n }, { isActive: isRawModeSupported });\n\n if (loading) {\n return (\n <Box padding={1}>\n <Text color=\"cyan\">{t('tui.lib.loading')}</Text>\n </Box>\n );\n }\n\n if (books.length === 0) {\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Text bold color=\"cyan\">{t('tui.lib.empty.title')}</Text>\n <Box marginTop={1} flexDirection=\"column\">\n <Text dimColor>{t('tui.lib.empty.desc')}</Text>\n <Box marginTop={1}>\n <Text dimColor>{t('common.quit')}</Text>\n </Box>\n </Box>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Text bold color=\"cyan\">{t('tui.lib.title', books.length)}</Text>\n <Text dimColor>{t('tui.lib.tips')}</Text>\n <Box flexDirection=\"column\" marginTop={1}>\n {books.map((book, index) => {\n const isSelected = index === selectedIndex;\n \n return (\n <Box key={book.id} paddingX={1} justifyContent=\"space-between\">\n <Box>\n <Text\n color={isSelected ? 'cyan' : undefined}\n bold={isSelected}\n >\n {isSelected ? '▸ ' : ' '}\n {book.title}\n </Text>\n <Text dimColor> ({book.format})</Text>\n </Box>\n </Box>\n );\n })}\n </Box>\n </Box>\n );\n}\n","/**\n * 阅读器页 — 核心阅读体验\n *\n * 功能:\n * - 加载书籍文件内容,按终端尺寸分页\n * - 键盘翻页(空格/j/↓ 下一页, k/↑ 上一页)\n * - 状态栏显示书名 + 进度百分比\n * - 退出时(q)自动保存进度\n * - resume 进入时根据 byte_offset 定位到对应页\n */\n\nimport React, { useState, useEffect, useRef, useMemo } from 'react';\nimport { Box, Text, useApp, useStdout } from 'ink';\nimport type { PageRoute } from '../App.js';\nimport { TextRenderer } from '../components/TextRenderer.js';\nimport { StatusBar } from '../components/StatusBar.js';\nimport { ChapterNav } from '../components/ChapterNav.js';\nimport { useReader } from '../hooks/useReader.js';\nimport { useKeyboard } from '../hooks/useKeyboard.js';\nimport { paginate, type Page } from '../../utils/paginate.js';\nimport { BookModel, type BookRecord } from '../../db/models/Book.js';\nimport { parseFile, type ParsedBook } from '../../parsers/index.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { ChapterService } from '../../services/ChapterService.js';\nimport { RecentService } from '../../services/RecentService.js';\nimport { BookmarkService } from '../../services/BookmarkService.js';\nimport type { ChapterRecord } from '../../db/models/Chapter.js';\nimport type { BookmarkRecord } from '../../db/models/Bookmark.js';\nimport { triggerBossKey } from '../../utils/bossKey.js';\nimport { estimateReadingTime, formatReadingTime } from '../../utils/time.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\nimport { getConfig } from '../../config/AppConfig.js';\n\ninterface ReaderPageProps {\n bookId: string;\n initialByteOffset?: number;\n onNavigate: (page: PageRoute, bookId?: string, byteOffset?: number) => void;\n}\n\n/**\n * 阅读器内部组件 — 在 pages 准备好后渲染\n */\nfunction ReaderContent({\n book,\n bookId,\n pages,\n initialByteOffset,\n termHeight,\n contentHeight,\n lineSpacing,\n}: {\n book: BookRecord;\n bookId: string;\n pages: Page[];\n initialByteOffset?: number;\n termHeight: number;\n contentHeight: number;\n lineSpacing: number;\n}) {\n const { exit } = useApp();\n const [chapterTitle, setChapterTitle] = useState<string | undefined>();\n const [currentChapter, setCurrentChapter] = useState<ChapterRecord | undefined>();\n const [showChapterNav, setShowChapterNav] = useState(false);\n const [allChapters, setAllChapters] = useState<ChapterRecord[]>([]);\n const [allBookmarks, setAllBookmarks] = useState<BookmarkRecord[]>([]);\n const [toastMessage, setToastMessage] = useState<string | null>(null);\n\n const progressServiceRef = useRef(new ProgressService());\n const chapterServiceRef = useRef(new ChapterService());\n const bookmarkServiceRef = useRef(new BookmarkService());\n\n const reader = useReader(pages, initialByteOffset);\n\n // 保存进度的通用方法\n const saveReadingProgress = () => {\n const offset = reader.getCurrentOffset();\n const percent = reader.getPercent();\n const chapter = chapterServiceRef.current.getChapterByOffset(bookId, offset);\n const chapterNo = chapter?.chapter_no ?? 0;\n\n progressServiceRef.current.saveProgress(bookId, chapterNo, offset, percent);\n logger.debug(`进度已保存: offset=${offset}, ${(percent * 100).toFixed(1)}%`);\n };\n\n // 加载并更新当前章节信息\n useEffect(() => {\n const currentOffset = reader.getCurrentOffset();\n const chapter = chapterServiceRef.current.getChapterByOffset(bookId, currentOffset);\n setCurrentChapter(chapter ?? undefined);\n setChapterTitle(chapter?.title ?? undefined);\n }, [reader.currentPage, bookId]);\n\n // 获取该书全部章节及书签用于渲染导航\n useEffect(() => {\n const chaptersList = chapterServiceRef.current.getChaptersByBookId(bookId);\n setAllChapters(chaptersList);\n \n if (showChapterNav) {\n setAllBookmarks(bookmarkServiceRef.current.getBookmarksByBookId(bookId));\n }\n }, [bookId, showChapterNav]);\n\n /**\n * 取当前页首行生成书签名\n */\n const handleAddBookmark = () => {\n const currentPageInfo = reader.getCurrentPage();\n if (!currentPageInfo) return;\n\n let markTitle = '无标题书签';\n for (const line of currentPageInfo.lines) {\n const stripped = line.trim();\n if (stripped.length > 0) {\n markTitle = stripped.slice(0, 15) + (stripped.length > 15 ? '...' : '');\n break;\n }\n }\n\n const currentOffset = reader.getCurrentOffset();\n bookmarkServiceRef.current.addBookmark(bookId, markTitle, currentOffset);\n \n setToastMessage(t('tui.reader.bookmark_add', markTitle));\n setTimeout(() => setToastMessage(null), 2000);\n };\n\n /**\n * 自动在组件卸载时保存进度\n */\n useEffect(() => {\n return () => {\n saveReadingProgress();\n };\n }, [bookId, reader]);\n\n // 拦截全局键盘事件\n useKeyboard(\n {\n onNext: () => reader.nextPage(),\n onPrev: () => reader.prevPage(),\n onQuit: () => exit(),\n onChapterList: () => setShowChapterNav(true),\n onBossKey: () => {\n saveReadingProgress(); // 老板来了!先存盘\n triggerBossKey(); // 再跑路\n },\n onBookmarkAdd: handleAddBookmark,\n },\n !showChapterNav, // 如果浮层显示,则停止普通的阅读快捷键\n );\n\n const currentPage = reader.getCurrentPage();\n const currentLines = currentPage?.lines ?? [];\n // The contentHeight prop is already passed, but the instruction redefines it.\n // Assuming the user intends to use this new calculation for contentHeight within ReaderContent.\n const calculatedContentHeight = Math.max(1, termHeight - 2);\n\n // 计算剩余阅读时间:总字符近似于字节数的 1/3 (utf-8 场景下),中文字符占绝大部分\n const totalChars = (book.file_size ?? 0) / 3;\n const remainingChars = Math.max(0, totalChars * (1 - reader.getPercent()));\n const remainingMinutes = estimateReadingTime(remainingChars, true);\n const remainingTimeStr = formatReadingTime(remainingMinutes);\n\n return (\n <Box flexDirection=\"column\" height={termHeight}>\n {/* 相对定位于容器中,使用 flex 布局进行展现。章节模式下隐藏正文 */}\n {!showChapterNav ? (\n <>\n <Box flexDirection=\"column\" flexGrow={1} paddingX={1}>\n <TextRenderer lines={currentLines} height={calculatedContentHeight} lineSpacing={lineSpacing} />\n </Box>\n <StatusBar\n bookTitle={book.title}\n percent={reader.getPercent()}\n chapterTitle={chapterTitle}\n currentPage={reader.currentPage + 1}\n totalPages={reader.totalPages}\n remainingTime={remainingTimeStr}\n />\n {toastMessage && (\n <Box alignSelf=\"flex-end\" marginTop={-2} marginRight={1} borderStyle=\"round\" borderColor=\"green\" paddingX={1}>\n <Text color=\"green\">{toastMessage}</Text>\n </Box>\n )}\n </>\n ) : (\n <ChapterNav\n chapters={allChapters}\n bookmarks={allBookmarks}\n currentChapterId={currentChapter?.id}\n termHeight={termHeight}\n onSelect={(offset) => {\n reader.goToOffset(offset);\n setShowChapterNav(false);\n }}\n onClose={() => setShowChapterNav(false)}\n />\n )}\n </Box>\n );\n}\n\nexport function ReaderPage({ bookId, initialByteOffset, onNavigate: _onNavigate }: ReaderPageProps) {\n const { exit } = useApp();\n const { stdout } = useStdout();\n\n const [book, setBook] = useState<BookRecord | null>(null);\n const [pages, setPages] = useState<Page[] | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const termWidth = stdout?.columns ?? 80;\n const termHeight = stdout?.rows ?? 24;\n\n const appConfig = getConfig();\n const lineSpacing = appConfig.lineSpacing || 0;\n \n // 核心优化:如果行间距 > 0,则物理行数会翻倍。为了保证不超出屏幕,分页时的内容高度需要对应缩小。\n const contentHeight = Math.max(Math.floor((termHeight - 3) / (1 + lineSpacing)), 2);\n\n // 加载书籍内容\n useEffect(() => {\n try {\n const bookModel = new BookModel();\n const bookRecord = bookModel.findById(bookId);\n\n if (!bookRecord) {\n setError(`书籍不存在: ${bookId}`);\n return;\n }\n\n setBook(bookRecord);\n\n // 记录打开事件\n const recentService = new RecentService();\n recentService.recordOpen(bookId);\n\n // 异步读取并解析文件内容 (支持 TXT 和 EPUB)\n parseFile(bookRecord.file_path, bookRecord.format as 'txt' | 'epub')\n .then((parsed: ParsedBook) => {\n // 核心优化:如果处于滚动模式,步进减半以实现平滑过渡\n const stepSize = appConfig.readingMode === 'scroll' \n ? Math.max(1, Math.floor(contentHeight / 2)) \n : contentHeight;\n\n // 分页\n const paginatedPages = paginate(parsed.content, termWidth - 2, contentHeight, stepSize);\n setPages(paginatedPages);\n logger.debug(`加载完成: ${bookRecord.title}, ${paginatedPages.length} 页, 模式: ${appConfig.readingMode}`);\n })\n .catch((err: Error) => {\n setError(`内容解析失败: ${err instanceof Error ? err.message : String(err)}`);\n });\n } catch (err) {\n setError(`加载过程出错: ${err instanceof Error ? err.message : String(err)}`);\n }\n }, [bookId, termWidth, contentHeight]);\n\n // 非 TTY 下支持 q 退出\n useKeyboard({\n onQuit: () => exit(),\n });\n\n // 错误\n if (error) {\n return (\n <Box padding={1} flexDirection=\"column\">\n <Text color=\"red\">✗ {error}</Text>\n <Text dimColor>{t('common.quit')}</Text>\n </Box>\n );\n }\n\n // 加载中\n if (!book || !pages) {\n return (\n <Box padding={1}>\n <Text color=\"cyan\">📖 {t('tui.reader.loading')}</Text>\n </Box>\n );\n }\n\n // pages 准备好后渲染阅读器内容\n return (\n <ReaderContent\n book={book}\n bookId={bookId}\n pages={pages}\n initialByteOffset={initialByteOffset}\n termHeight={termHeight}\n contentHeight={contentHeight}\n lineSpacing={lineSpacing}\n />\n );\n}\n","/**\n * 正文渲染组件\n * 显示分页后的文本行,并支持智能高亮\n */\n\nimport React from 'react';\nimport { Box, Text } from 'ink';\n\ninterface TextRendererProps {\n lines: string[];\n height?: number;\n lineSpacing?: number;\n}\n\n/**\n * 处理单行内的智能高亮匹配\n * 例如将 「XXX」或 “XXX” 进行暗化或着色,提升大段文字沉浸感\n */\nfunction renderLineWithHighlight(line: string) {\n if (!line) return <Text> </Text>; // 保留空行\n\n // 匹配对话大纲的正则 (包括中英文常见方括号/双引号)\n // 此处拆分为:对话段落与非对话段落\n // (「.*?」|“.*?”|『.*?』|《.*?》)\n const regex = /(「.*?」|“.*?”|『.*?』|《.*?》)/g;\n const parts = line.split(regex);\n\n return (\n <Text>\n {parts.map((part, index) => {\n // 如果是正则匹配出的组(即被高亮的部分)\n if (regex.test(part)) {\n // 由于 Regex 的全局状态,test 之后要注意\n // 但其实 split 后,奇数项是捕获组的内容\n }\n \n // 简单判定:如果是奇数项,就是捕获组\n const isHighlight = index % 2 === 1;\n\n if (isHighlight) {\n // 对话颜色应用 dimColor,让环境和描述语句突出,或者是相反。\n // 这里将对话变暗 (dimColor),减轻视觉疲劳\n return <Text key={index} dimColor>{part}</Text>;\n }\n\n // 常规文本\n return <Text key={index}>{part}</Text>;\n })}\n </Text>\n );\n}\n\nexport function TextRenderer({ lines, height, lineSpacing = 0 }: TextRendererProps) {\n // 补齐空行以占满屏幕高度(避免内容跳动)\n const displayLines = [...lines];\n if (height && displayLines.length < height) {\n const padding = height - displayLines.length;\n for (let i = 0; i < padding; i++) {\n displayLines.push('');\n }\n }\n\n return (\n <Box flexDirection=\"column\">\n {displayLines.map((line, index) => (\n <Box key={index} marginBottom={lineSpacing}>\n {renderLineWithHighlight(line)}\n </Box>\n ))}\n </Box>\n );\n}\n","/**\n * 底部状态栏组件\n * 显示书名、章节名、页码和进度百分比\n */\n\nimport React from 'react';\nimport { Box, Text } from 'ink';\nimport { t } from '../../locales/index.js';\n\ninterface StatusBarProps {\n bookTitle: string;\n chapterTitle?: string;\n percent: number;\n currentPage: number;\n totalPages: number;\n remainingTime?: string;\n}\n\nexport function StatusBar({\n bookTitle,\n chapterTitle,\n percent,\n currentPage,\n totalPages,\n remainingTime,\n}: StatusBarProps) {\n const displayPercent = (percent * 100).toFixed(1);\n const titleDisplay = chapterTitle ? `${bookTitle} · ${chapterTitle}` : bookTitle;\n\n return (\n <Box flexDirection=\"row\" justifyContent=\"space-between\" borderStyle=\"single\" borderTop={false} borderLeft={false} borderRight={false} paddingX={1}>\n <Box>\n <Text color=\"gray\">📖 {titleDisplay}</Text>\n </Box>\n\n <Box>\n {remainingTime && (\n <Text dimColor>{t('tui.reader.status.remaining', remainingTime)} </Text>\n )}\n <Text color=\"gray\">\n {currentPage}/{totalPages} \n </Text>\n <Text color=\"gray\">\n {displayPercent}% \n </Text>\n <Text dimColor>{t('common.quit')}</Text>\n </Box>\n </Box>\n );\n}\n","/**\n * 章节导航浮层组件\n * 允许在阅读器内唤起章节列表,并支持翻页与选择跳转\n */\n\nimport React, { useState, useEffect } from 'react';\nimport { Box, Text, useInput } from 'ink';\nimport type { ChapterRecord } from '../../db/models/Chapter.js';\nimport type { BookmarkRecord } from '../../db/models/Bookmark.js';\nimport { t } from '../../locales/index.js';\n\ninterface ChapterNavProps {\n chapters: ChapterRecord[];\n bookmarks: BookmarkRecord[];\n currentChapterId?: number;\n termHeight: number;\n onSelect: (byteOffset: number) => void;\n onClose: () => void;\n}\n\nexport function ChapterNav({\n chapters,\n bookmarks,\n currentChapterId,\n termHeight,\n onSelect,\n onClose,\n}: ChapterNavProps) {\n const [activeTab, setActiveTab] = useState<'chapters' | 'bookmarks'>('chapters');\n const isBookmarks = activeTab === 'bookmarks';\n const currentList = isBookmarks ? bookmarks : chapters;\n\n // 找到当前章节的初始索引\n const initialIndex = currentChapterId && !isBookmarks\n ? Math.max(\n 0,\n chapters.findIndex((c) => c.id === currentChapterId),\n )\n : 0;\n\n const [selectedIndex, setSelectedIndex] = useState(initialIndex);\n\n // 切换 tab 时重置索引\n useEffect(() => {\n setSelectedIndex(isBookmarks ? 0 : initialIndex);\n }, [activeTab, initialIndex, isBookmarks]);\n\n // 一页展示多少个项(留出上下边距)\n const pageSize = Math.max(5, termHeight - 6);\n\n // 计算当前可视窗口的起始和结束索引\n const windowStart = Math.max(0, Math.floor(selectedIndex / pageSize) * pageSize);\n const visibleItems = currentList.slice(windowStart, windowStart + pageSize);\n\n const isRawModeSupported = process.stdin.isTTY ?? false;\n\n useInput(\n (input, key) => {\n // 退出\n if (key.escape || input === 'q') {\n onClose();\n return;\n }\n \n // 切换视图\n if (key.tab) {\n setActiveTab(prev => prev === 'chapters' ? 'bookmarks' : 'chapters');\n return;\n }\n\n // 确认\n if (key.return) {\n if (currentList[selectedIndex]) {\n onSelect(currentList[selectedIndex].byte_offset);\n }\n return;\n }\n\n // 上移\n if (key.upArrow || input === 'k') {\n setSelectedIndex((prev) => Math.max(0, prev - 1));\n }\n\n // 下移\n if (key.downArrow || input === 'j') {\n setSelectedIndex((prev) => Math.min(currentList.length - 1, prev + 1));\n }\n },\n { isActive: isRawModeSupported },\n );\n\n return (\n <Box\n flexDirection=\"column\"\n borderStyle=\"round\"\n borderColor=\"green\"\n paddingX={2}\n paddingY={1}\n width=\"80%\"\n alignSelf=\"center\"\n marginTop={2}\n >\n <Box justifyContent=\"space-between\" marginBottom={1}>\n <Box>\n <Text bold color={activeTab === 'chapters' ? 'green' : 'gray'}>{t('tui.nav.tab.chapters')} </Text>\n <Text bold color={activeTab === 'bookmarks' ? 'green' : 'gray'}>{t('tui.nav.tab.bookmarks')}</Text>\n </Box>\n <Text dimColor>{t('tui.nav.tips')}</Text>\n </Box>\n\n {visibleItems.length === 0 ? (\n <Text dimColor>{t('tui.nav.empty')}</Text>\n ) : (\n visibleItems.map((item, idx) => {\n const actualIndex = windowStart + idx;\n const isSelected = actualIndex === selectedIndex;\n \n return (\n <Text\n key={item.id}\n color={isSelected ? 'green' : undefined}\n bold={isSelected}\n >\n {isSelected ? '▶ ' : ' '}\n {item.title}\n </Text>\n );\n })\n )}\n\n <Box marginTop={1} justifyContent=\"flex-end\">\n <Text dimColor>\n {t('tui.nav.page', Math.floor(selectedIndex / pageSize) + 1, Math.ceil(currentList.length / pageSize) || 1)}\n </Text>\n </Box>\n </Box>\n );\n}\n","/**\n * 阅读器核心状态 Hook\n * 管理当前页面、offset、分页等状态\n */\n\nimport { useState, useCallback, useMemo } from 'react';\nimport type { Page } from '../../utils/paginate.js';\n\ninterface ReaderState {\n currentPage: number;\n totalPages: number;\n}\n\nexport function useReader(pages: Page[], initialByteOffset?: number) {\n // 根据 initialByteOffset 找到初始页\n const initialPage = useMemo(() => {\n if (!initialByteOffset || pages.length === 0) return 0;\n\n // 找到 byte_offset <= initialByteOffset 的最后一页\n let targetPage = 0;\n for (let i = 0; i < pages.length; i++) {\n if (pages[i].byteOffset <= initialByteOffset) {\n targetPage = i;\n } else {\n break;\n }\n }\n return targetPage;\n }, [pages, initialByteOffset]);\n\n const [state, setState] = useState<ReaderState>({\n currentPage: initialPage,\n totalPages: pages.length,\n });\n\n const nextPage = useCallback(() => {\n setState((prev) => ({\n ...prev,\n currentPage: Math.min(prev.currentPage + 1, prev.totalPages - 1),\n }));\n }, []);\n\n const prevPage = useCallback(() => {\n setState((prev) => ({\n ...prev,\n currentPage: Math.max(prev.currentPage - 1, 0),\n }));\n }, []);\n\n const goToPage = useCallback((pageNum: number) => {\n setState((prev) => ({\n ...prev,\n currentPage: Math.max(0, Math.min(pageNum, prev.totalPages - 1)),\n }));\n }, []);\n\n /**\n * 根据 byte_offset 跳转到对应页\n */\n const goToOffset = useCallback((byteOffset: number) => {\n let targetPage = 0;\n for (let i = 0; i < pages.length; i++) {\n if (pages[i].byteOffset <= byteOffset) {\n targetPage = i;\n } else {\n break;\n }\n }\n setState((prev) => ({\n ...prev,\n currentPage: targetPage,\n }));\n }, [pages]);\n\n const getCurrentPage = useCallback((): Page | undefined => {\n return pages[state.currentPage];\n }, [state.currentPage, pages]);\n\n /**\n * 获取当前页的 byte_offset(用于保存进度)\n */\n const getCurrentOffset = useCallback((): number => {\n return pages[state.currentPage]?.byteOffset ?? 0;\n }, [state.currentPage, pages]);\n\n const getPercent = useCallback((): number => {\n if (state.totalPages === 0) return 0;\n return (state.currentPage + 1) / state.totalPages;\n }, [state.currentPage, state.totalPages]);\n\n const isFirstPage = state.currentPage === 0;\n const isLastPage = state.currentPage === state.totalPages - 1;\n\n return {\n ...state,\n nextPage,\n prevPage,\n goToPage,\n goToOffset,\n getCurrentPage,\n getCurrentOffset,\n getPercent,\n isFirstPage,\n isLastPage,\n };\n}\n","/**\n * 键盘事件统一管理 Hook\n * 在非 TTY 环境下安全降级\n */\n\nimport { useInput } from 'ink';\n\ninterface KeyboardHandlers {\n onNext?: () => void;\n onPrev?: () => void;\n onQuit?: () => void;\n onChapterList?: () => void;\n onHelp?: () => void;\n onBossKey?: () => void;\n onBookmarkAdd?: () => void;\n}\n\nexport function useKeyboard(handlers: KeyboardHandlers, isActive: boolean = true) {\n const isRawModeSupported = process.stdin.isTTY ?? false;\n const shouldListen = isRawModeSupported && isActive;\n\n useInput((input, key) => {\n // 翻页:空格 / j / 下箭头 / f → 下一页\n if (input === ' ' || input === 'j' || key.downArrow || input === 'f') {\n handlers.onNext?.();\n }\n\n // 上一页:k / 上箭头 / b\n if (input === 'k' || key.upArrow || input === 'b') {\n handlers.onPrev?.();\n }\n\n // 退出:q\n if (input === 'q') {\n handlers.onQuit?.();\n }\n\n // 章节列表:c\n if (input === 'c') {\n handlers.onChapterList?.();\n }\n\n // 帮助:?\n if (input === '?') {\n handlers.onHelp?.();\n }\n \n // 老板键\n if (handlers.onBossKey && (key.escape || input === 'esc' || input === 'b' || input === 'B')) {\n handlers.onBossKey?.();\n }\n \n // 按 m 或 M 加书签\n if (handlers.onBookmarkAdd && (input === 'm' || input === 'M')) {\n handlers.onBookmarkAdd?.();\n }\n }, { isActive: shouldListen });\n}\n","/**\n * 字符串宽度计算\n * 处理中文字符宽度为 2\n */\n\n/**\n * 获取字符串在终端中的显示宽度\n */\nexport function getStringWidth(str: string): number {\n let width = 0;\n for (const char of str) {\n width += isFullWidth(char) ? 2 : 1;\n }\n return width;\n}\n\n/**\n * 判断字符是否为全角字符\n * CJK 统一表意字符 + 全角标点\n */\nfunction isFullWidth(char: string): boolean {\n const code = char.codePointAt(0);\n if (code === undefined) return false;\n\n return (\n // CJK 统一表意字符\n (code >= 0x4e00 && code <= 0x9fff) ||\n // CJK 统一表意字符扩展 A\n (code >= 0x3400 && code <= 0x4dbf) ||\n // CJK 统一表意字符扩展 B\n (code >= 0x20000 && code <= 0x2a6df) ||\n // CJK 兼容表意字符\n (code >= 0xf900 && code <= 0xfaff) ||\n // 全角 ASCII、全角标点\n (code >= 0xff01 && code <= 0xff60) ||\n (code >= 0xffe0 && code <= 0xffe6) ||\n // CJK 标点符号\n (code >= 0x3000 && code <= 0x303f) ||\n // 日文平假名/片假名\n (code >= 0x3040 && code <= 0x30ff) ||\n // 韩文音节\n (code >= 0xac00 && code <= 0xd7af)\n );\n}\n","/**\n * 文本分页算法\n * 按终端宽高切割文本为页面\n */\n\nimport { getStringWidth } from './stringWidth.js';\n\nexport interface Page {\n /** 页面内容行 */\n lines: string[];\n /** 此页在原始文本中的 byte offset 起始位置 */\n byteOffset: number;\n}\n\n/**\n * 将文本按终端尺寸分页\n * @param text 原始文本\n * @param width 终端宽度(列数)\n * @param height 可用行数(扣除状态栏后)\n * @param step 步进行数(用于平滑滚动,不传则默认为 height)\n * @returns 分页结果\n */\nexport function paginate(text: string, width: number, height: number, step?: number): Page[] {\n const pages: Page[] = [];\n const rawLines = text.split('\\n');\n const actualStep = step || height;\n\n // 将原始文本行按终端宽度折行\n const wrappedLines: { text: string; byteOffset: number }[] = [];\n let currentOffset = 0;\n\n for (const rawLine of rawLines) {\n const wrapped = wrapLine(rawLine, width);\n for (const line of wrapped) {\n wrappedLines.push({ text: line, byteOffset: currentOffset });\n }\n currentOffset += Buffer.byteLength(rawLine + '\\n', 'utf-8');\n }\n\n // 按高度切割为页\n for (let i = 0; i < wrappedLines.length; i += actualStep) {\n const pageLines = wrappedLines.slice(i, i + height);\n if (pageLines.length === 0) break;\n\n pages.push({\n lines: pageLines.map((l) => l.text),\n byteOffset: pageLines[0]?.byteOffset ?? 0,\n });\n\n // 已经处理完所有行\n if (i + actualStep >= wrappedLines.length) break;\n }\n\n return pages;\n}\n\n/**\n * 将一行文本按终端宽度折行\n * 处理中英文混排(中文字符宽度为 2)\n */\nexport function wrapLine(line: string, width: number): string[] {\n if (line.length === 0) return [''];\n\n const result: string[] = [];\n let currentLine = '';\n let currentWidth = 0;\n\n for (const char of line) {\n const charWidth = getStringWidth(char);\n\n if (currentWidth + charWidth > width) {\n result.push(currentLine);\n currentLine = char;\n currentWidth = charWidth;\n } else {\n currentLine += char;\n currentWidth += charWidth;\n }\n }\n\n if (currentLine.length > 0) {\n result.push(currentLine);\n }\n\n return result.length > 0 ? result : [''];\n}\n","/**\n * 章节索引业务逻辑\n * 章节索引构建与查询\n */\n\nimport { ChapterModel, type ChapterRecord } from '../db/models/Chapter.js';\n\nexport class ChapterService {\n private chapterModel = new ChapterModel();\n\n /**\n * 获取指定书籍的所有章节\n */\n getChapters(bookId: string): ChapterRecord[] {\n return this.chapterModel.findByBookId(bookId);\n }\n\n /**\n * 获取指定章节信息\n */\n getChapter(bookId: string, chapterNo: number): ChapterRecord | undefined {\n return this.chapterModel.findChapter(bookId, chapterNo);\n }\n\n /**\n * 获取章节总数\n */\n getChapterCount(bookId: string): number {\n return this.chapterModel.getChapterCount(bookId);\n }\n\n /**\n * 获取指定书籍下的所有章节\n */\n getChaptersByBookId(bookId: string): ChapterRecord[] {\n return this.chapterModel.findByBookId(bookId);\n }\n\n /**\n * 根据 offset 查询当前所属章节(用于高亮当前所在章)\n */\n getChapterByOffset(bookId: string, byteOffset: number): ChapterRecord | undefined {\n const chapters = this.chapterModel.findByBookId(bookId);\n if (chapters.length === 0) return undefined;\n\n // 找到 offset 所在的章节(最后一个 byte_offset <= 给定 offset 的章节)\n let current: ChapterRecord | undefined;\n for (const chapter of chapters) {\n if (chapter.byte_offset <= byteOffset) {\n current = chapter;\n } else {\n break;\n }\n }\n return current;\n }\n}\n","/**\n * 最近阅读业务逻辑\n */\n\nimport { RecentModel } from '../db/models/Recent.js';\nimport { BookModel, type BookRecord } from '../db/models/Book.js';\n\nexport class RecentService {\n private recentModel = new RecentModel();\n private bookModel = new BookModel();\n\n /**\n * 获取最近阅读的书籍列表(包含书籍详情)\n */\n getRecentBooks(limit: number = 20): BookRecord[] {\n const recentRecords = this.recentModel.getRecent(limit);\n\n return recentRecords\n .map((record) => this.bookModel.findById(record.book_id))\n .filter((book): book is BookRecord => book !== undefined);\n }\n\n /**\n * 记录打开事件\n */\n recordOpen(bookId: string): void {\n this.recentModel.recordOpen(bookId);\n }\n}\n","/**\n * bookmarks 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface BookmarkRecord {\n id?: number;\n book_id: string;\n title: string;\n byte_offset: number;\n created_at: number;\n}\n\nexport class BookmarkModel {\n /**\n * 插入书签\n */\n insert(bookmark: Omit<BookmarkRecord, 'id'>): void {\n const db = getDb();\n db.prepare(`\n INSERT INTO bookmarks (book_id, title, byte_offset, created_at)\n VALUES (?, ?, ?, ?)\n `).run(bookmark.book_id, bookmark.title, bookmark.byte_offset, bookmark.created_at);\n }\n\n /**\n * 获取指定书籍的所有书签\n */\n findByBookId(bookId: string): BookmarkRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM bookmarks WHERE book_id = ? ORDER BY created_at DESC').all(bookId) as BookmarkRecord[];\n }\n\n /**\n * 获取指定书签\n */\n findById(id: number): BookmarkRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM bookmarks WHERE id = ?').get(id) as BookmarkRecord | undefined;\n }\n\n /**\n * 获取书籍书签总数\n */\n getCount(bookId: string): number {\n const db = getDb();\n const result = db.prepare('SELECT COUNT(*) as count FROM bookmarks WHERE book_id = ?').get(bookId) as { count: number };\n return result.count;\n }\n\n /**\n * 删除书签\n */\n delete(id: number): void {\n const db = getDb();\n db.prepare('DELETE FROM bookmarks WHERE id = ?').run(id);\n }\n\n /**\n * 移除整本书的书签 (配合彻底清理书籍使用)\n */\n deleteByBookId(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM bookmarks WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * 书签管理服务\n * 处理针对某一位置打标记录以及后续的跳转\n */\n\nimport { BookmarkModel, type BookmarkRecord } from '../db/models/Bookmark.js';\n\nexport class BookmarkService {\n private bookmarkModel: BookmarkModel;\n\n constructor() {\n this.bookmarkModel = new BookmarkModel();\n }\n\n /**\n * 增加一条书签\n * @param title 该书签展现给用户的文案(一句话大纲)\n */\n addBookmark(bookId: string, title: string, byteOffset: number): void {\n this.bookmarkModel.insert({\n book_id: bookId,\n title,\n byte_offset: byteOffset,\n created_at: Date.now(),\n });\n }\n\n /**\n * 罗列该书全部的书签\n */\n getBookmarksByBookId(bookId: string): BookmarkRecord[] {\n return this.bookmarkModel.findByBookId(bookId);\n }\n\n /**\n * 删掉对应书签\n */\n removeBookmark(id: number): void {\n this.bookmarkModel.delete(id);\n }\n}\n","/**\n * 防打断老板键 (Boss Key)\n * 瞬间退出并用伪装日志覆盖屏幕\n */\n\nexport function triggerBossKey(): void {\n // 1. 彻底清空当前屏幕并清除滚动回放缓冲区 (Scrollback Buffer)\n // \\u001b[3J: 清除滚动条历史\n // \\u001b[2J: 清除当前可视区域\n // \\u001b[1;1H: 移动光标到左上角\n process.stdout.write('\\u001b[3J\\u001b[2J\\u001b[1;1H');\n console.clear();\n\n // 2. 打印极度逼真的伪装日志(终端报错风格)\n const fakeLog = `\nfile:///Users/yindawei/project/node_modules/vite/dist/node/chunks/dep-BbV93i69.js:43916\n throw new Error(\\`[vite] Failed to resolve module import \"./App.vue\". Check if the file exists.\\`);\n ^\n\nError: [vite] Failed to resolve module import \"./App.vue\". Check if the file exists.\n at Object.run (file:///Users/yindawei/project/node_modules/vite/dist/node/chunks/dep-BbV93i69.js:43916:13)\n at async file:///Users/yindawei/project/node_modules/vite/dist/node/cli.js:722:7\n at async startVite (file:///Users/yindawei/project/node_modules/vite/dist/node/cli.js:700:5)\n at async Object.handler (file:///Users/yindawei/project/node_modules/vite/dist/node/cli.js:650:1)\n\nNode.js v20.11.0\n`;\n\n console.log(fakeLog);\n\n // 3. 强制安静终止应用,不留痕迹\n process.exit(0);\n}\n","/**\n * 阅读时间估算\n */\n\n/**\n * 估算阅读时间(分钟)\n * @param charCount 字符数\n * @param isChinese 是否中文内容\n * @returns 估计阅读分钟数\n */\nexport function estimateReadingTime(charCount: number, isChinese: boolean = true): number {\n // 中文平均阅读速度约 500 字/分钟\n // 英文平均阅读速度约 250 词/分钟(约 1250 字符/分钟)\n const charsPerMinute = isChinese ? 500 : 1250;\n return Math.ceil(charCount / charsPerMinute);\n}\n\n/**\n * 格式化时间显示\n */\nexport function formatReadingTime(minutes: number): string {\n if (minutes < 60) {\n return `${minutes} 分钟`;\n }\n const hours = Math.floor(minutes / 60);\n const mins = minutes % 60;\n return mins > 0 ? `${hours} 小时 ${mins} 分钟` : `${hours} 小时`;\n}\n","/**\n * novel resume — 恢复上次阅读\n * 启动后默认入口,精确恢复到上次 offset,零摩擦\n */\n\nimport type { CommandModule } from 'yargs';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { BookService } from '../../services/BookService.js';\nimport { renderApp } from '../../ui/renderApp.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\nexport const resumeCommand: CommandModule = {\n command: 'resume',\n describe: t('cli.resume.desc'),\n handler: async () => {\n try {\n const progressService = new ProgressService();\n const lastProgress = progressService.getLastOpenedBook();\n\n if (!lastProgress) {\n console.log(t('cli.resume.none'));\n return;\n }\n\n // 验证书籍仍然存在\n const bookService = new BookService();\n const book = bookService.findBook(lastProgress.book_id);\n if (!book) {\n console.log(t('cli.resume.none'));\n return;\n }\n\n logger.debug(`恢复阅读: ${book.title}, offset=${lastProgress.byte_offset}`);\n\n // 启动 Ink TUI 阅读器,恢复到上次 offset\n renderApp({\n initialPage: 'reader',\n bookId: lastProgress.book_id,\n initialByteOffset: lastProgress.byte_offset,\n });\n } catch (error) {\n logger.error('恢复阅读失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel open <id|name> — 打开指定书\n * 支持 book-id 或模糊匹配书名\n */\n\nimport type { CommandModule } from 'yargs';\nimport { BookService } from '../../services/BookService.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { renderApp } from '../../ui/renderApp.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\nexport interface OpenArgs {\n target: string;\n}\n\nexport const openCommand: CommandModule<object, OpenArgs> = {\n command: 'open <target>',\n describe: t('cli.open.desc'),\n builder: (yargs) => {\n return yargs.positional('target', {\n describe: t('cli.open.help'),\n type: 'string',\n demandOption: true,\n });\n },\n handler: async (argv) => {\n try {\n const bookService = new BookService();\n const book = bookService.findBook(argv.target);\n\n if (!book) {\n console.log(`${t('cli.open.not_found')} ${argv.target}`);\n process.exit(1);\n }\n\n // 检查是否有之前的阅读进度\n const progressService = new ProgressService();\n const progress = progressService.getProgress(book.id);\n const byteOffset = progress?.byte_offset ?? 0;\n\n logger.debug(`打开: ${book.title}, offset=${byteOffset}`);\n\n // 启动 Ink TUI 阅读器\n renderApp({\n initialPage: 'reader',\n bookId: book.id,\n initialByteOffset: byteOffset,\n });\n } catch (error) {\n logger.error('打开失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel library — 书架列表\n * 含最近阅读排序 + 搜索\n */\n\nimport type { CommandModule } from 'yargs';\nimport { renderApp } from '../../ui/renderApp.js';\nimport { BookService } from '../../services/BookService.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\nexport interface LibraryArgs {\n search?: string;\n}\n\nexport const libraryCommand: CommandModule<object, LibraryArgs> = {\n command: 'list',\n aliases: ['library'],\n describe: t('cli.library.desc'),\n builder: (yargs) => {\n return yargs.option('search', {\n alias: 's',\n describe: t('cli.library.help'),\n type: 'string',\n });\n },\n handler: async (argv) => {\n try {\n // 如果有搜索参数,以非交互模式输出\n if (argv.search) {\n const bookService = new BookService();\n const books = bookService.searchBooks(argv.search);\n\n if (books.length === 0) {\n console.log(t('cli.library.search_none', argv.search));\n return;\n }\n\n console.log(t('cli.library.search_result', books.length));\n books.forEach((book, index) => {\n console.log(` ${index + 1}. ${book.title} [${book.id}] (${book.format})`);\n });\n return;\n }\n\n // 无搜索参数,启动交互式书架\n renderApp({ initialPage: 'library' });\n } catch (error) {\n logger.error('获取书架失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel remove <id|name> — 移除书籍\n * 删除相关所有数据,包括记录和本地进度的 DB 项\n */\n\nimport type { CommandModule } from 'yargs';\nimport { BookService } from '../../services/BookService.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\nexport interface RemoveArgs {\n target: string;\n}\n\nexport const removeCommand: CommandModule<object, RemoveArgs> = {\n command: 'remove <target>',\n describe: t('cli.remove.desc'),\n builder: (yargs) => {\n return yargs.positional('target', {\n describe: t('cli.remove.help'),\n type: 'string',\n demandOption: true,\n });\n },\n handler: async (argv) => {\n try {\n const bookService = new BookService();\n const book = bookService.findBook(argv.target);\n\n if (!book) {\n console.log(`${t('cli.remove.not_found')} ${argv.target}`);\n process.exit(1);\n }\n\n bookService.deleteBook(book.id);\n console.log(`${t('cli.remove.success')} ${book.title}`);\n } catch (error) {\n logger.error('移除书籍失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel lang <zh|en> — 切换语言\n */\n\nimport type { CommandModule } from 'yargs';\nimport { setConfig } from '../../config/AppConfig.js';\nimport { t, setLanguage } from '../../locales/index.js';\n\nexport interface LangArgs {\n target: 'zh' | 'en';\n}\n\nexport const langCommand: CommandModule<object, LangArgs> = {\n command: 'lang <target>',\n describe: t('cli.lang.desc'),\n builder: (yargs) => {\n return yargs.positional('target', {\n describe: t('cli.lang.help'),\n type: 'string',\n choices: ['zh', 'en'] as const,\n demandOption: true,\n });\n },\n handler: (argv) => {\n const lang = argv.target;\n if (lang === 'zh' || lang === 'en') {\n setConfig('language', lang);\n setLanguage(lang);\n console.log(t('cli.lang.success', lang));\n } else {\n console.log(t('cli.lang.unsupported', lang));\n process.exit(1);\n }\n },\n};\n","import type { CommandModule } from 'yargs';\nimport { execSync } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { t } from '../../locales/index.js';\nimport { logger } from '../../utils/logger.js';\n\nexport const updateCommand: CommandModule = {\n command: 'update',\n describe: t('cli.update.desc'),\n handler: async () => {\n try {\n console.log(t('cli.update.checking'));\n\n // 获取本地版本号 (由 tsup 在构建时注入)\n const localVersion = typeof APP_VERSION !== 'undefined' ? APP_VERSION : '0.2.2';\n\n // 获取 NPM 最新版本\n const npmOutput = execSync('npm view readshell version', { encoding: 'utf-8' });\n const latestVersion = npmOutput.trim();\n\n if (!latestVersion) {\n throw new Error('Could not fetch npm version');\n }\n\n if (latestVersion === localVersion) {\n console.log(t('cli.update.latest', localVersion));\n return;\n }\n\n console.log(t('cli.update.updating', latestVersion, localVersion));\n \n // 执行升级指令\n execSync('npm install -g readshell@latest', { stdio: 'inherit' });\n \n console.log(t('cli.update.success'));\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n console.log(t('cli.update.fail', msg));\n logger.error('更新失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * 数据库迁移逻辑\n * 程序启动时检查并执行建表/迁移\n */\n\nimport { getDb } from './client.js';\nimport { logger } from '../utils/logger.js';\n\nconst SCHEMA_VERSION = 2;\n\n/**\n * 初始化数据库(建表 + 版本管理)\n */\nexport function initDatabase(): void {\n const db = getDb();\n\n // 创建版本管理表\n db.exec(`\n CREATE TABLE IF NOT EXISTS schema_version (\n version INTEGER PRIMARY KEY\n );\n `);\n\n const row = db.prepare('SELECT version FROM schema_version LIMIT 1').get() as\n | { version: number }\n | undefined;\n const currentVersion = row?.version ?? 0;\n\n if (currentVersion < SCHEMA_VERSION) {\n logger.debug(`数据库迁移: v${currentVersion} → v${SCHEMA_VERSION}`);\n migrate(db, currentVersion);\n }\n}\n\nfunction migrate(db: ReturnType<typeof getDb>, fromVersion: number): void {\n const migrations: Record<number, string> = {\n 1: `\n -- 书籍元数据\n CREATE TABLE IF NOT EXISTS books (\n id TEXT PRIMARY KEY,\n title TEXT NOT NULL,\n author TEXT,\n file_path TEXT NOT NULL,\n format TEXT NOT NULL,\n file_hash TEXT NOT NULL,\n file_size INTEGER,\n created_at INTEGER NOT NULL\n );\n\n -- 核心状态表\n CREATE TABLE IF NOT EXISTS reading_progress (\n book_id TEXT PRIMARY KEY REFERENCES books(id),\n chapter_no INTEGER NOT NULL DEFAULT 0,\n byte_offset INTEGER NOT NULL DEFAULT 0,\n percent REAL NOT NULL DEFAULT 0,\n updated_at INTEGER NOT NULL,\n opened_at INTEGER NOT NULL\n );\n\n -- 最近阅读排序\n CREATE TABLE IF NOT EXISTS recent_reads (\n book_id TEXT PRIMARY KEY REFERENCES books(id),\n opened_at INTEGER NOT NULL,\n open_count INTEGER NOT NULL DEFAULT 1\n );\n\n -- 章节索引\n CREATE TABLE IF NOT EXISTS chapter_index (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n book_id TEXT NOT NULL REFERENCES books(id),\n chapter_no INTEGER NOT NULL,\n title TEXT,\n byte_offset INTEGER NOT NULL,\n UNIQUE(book_id, chapter_no)\n );\n\n -- 索引\n CREATE INDEX IF NOT EXISTS idx_chapter_book ON chapter_index(book_id);\n CREATE INDEX IF NOT EXISTS idx_recent_opened ON recent_reads(opened_at DESC);\n `,\n 2: `\n -- 书签管理\n CREATE TABLE IF NOT EXISTS bookmarks (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n book_id TEXT NOT NULL REFERENCES books(id),\n title TEXT NOT NULL,\n byte_offset INTEGER NOT NULL,\n created_at INTEGER NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_bookmarks_book ON bookmarks(book_id);\n `,\n };\n\n db.transaction(() => {\n for (let v = fromVersion + 1; v <= SCHEMA_VERSION; v++) {\n const sql = migrations[v];\n if (sql) {\n db.exec(sql);\n logger.debug(`已执行迁移 v${v}`);\n }\n }\n\n // 更新版本号\n db.prepare('DELETE FROM schema_version').run();\n db.prepare('INSERT INTO schema_version (version) VALUES (?)').run(SCHEMA_VERSION);\n })();\n}\n","#!/usr/bin/env node\n\n/**\n * ReadShell — 终端内低打断轻阅读工具\n * 程序主入口,解析 argv,路由到子命令\n */\n\nimport { createParser } from './cli/parser.js';\nimport { initDatabase } from './db/migrate.js';\nimport { initI18n } from './locales/index.js';\nimport { logger } from './utils/logger.js';\n\nasync function main() {\n try {\n // 初始化数据库(自动建表/迁移)\n initDatabase();\n \n // 初始化多语言本地化模块\n initI18n();\n\n // 解析命令行参数并执行对应命令\n const parser = createParser();\n await parser.parse();\n } catch (error) {\n logger.error('程序启动失败:', error);\n process.exit(1);\n }\n}\n\nmain();\n"],"mappings":"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","names":["existsSync","db","db","db","db","detect","iconv","resolve","detect","iconv","readFileSync","existsSync","config","statSync","resolve","join","yargs","React","useState","Box","Text","useState","useEffect","Box","Text","useApp","jsx","jsxs","useApp","useState","useEffect","Box","Text","useState","useEffect","Box","Text","useApp","Box","Text","jsx","Box","Text","jsx","jsxs","Box","Text","useState","useEffect","Box","Text","useInput","jsx","jsxs","useState","useEffect","useInput","Box","Text","useState","useInput","db","jsx","jsxs","useApp","useState","useEffect","Box","Text","jsx","jsxs","useState","Box","Text","React","yargs","yargs","yargs","yargs","db"]}
|