@readwise/cli 0.3.1 → 0.5.0
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 +3 -0
- package/dist/config.d.ts +1 -0
- package/dist/index.js +9 -3
- package/dist/tui/app.js +875 -232
- package/dist/tui/term.js +45 -6
- package/package.json +3 -3
- package/src/config.ts +1 -0
- package/src/index.ts +10 -3
- package/src/tui/app.ts +849 -224
- package/src/tui/term.ts +41 -6
package/dist/tui/term.js
CHANGED
|
@@ -17,11 +17,15 @@ export const style = {
|
|
|
17
17
|
// --- Screen control ---
|
|
18
18
|
export function enterFullScreen() {
|
|
19
19
|
process.stdout.write(`${ESC}[?1049h`); // alternate screen buffer
|
|
20
|
+
process.stdout.write(`${ESC}[?2004h`); // enable bracketed paste mode
|
|
21
|
+
process.stdout.write(`${ESC}[>1u`); // enable kitty keyboard protocol (disambiguate mode)
|
|
20
22
|
process.stdout.write(`${ESC}[?25l`); // hide cursor
|
|
21
23
|
process.stdout.write(`${ESC}[H`); // cursor home
|
|
22
24
|
}
|
|
23
25
|
export function exitFullScreen() {
|
|
24
26
|
process.stdout.write(`${ESC}[?25h`); // show cursor
|
|
27
|
+
process.stdout.write(`${ESC}[<u`); // disable kitty keyboard protocol
|
|
28
|
+
process.stdout.write(`${ESC}[?2004l`); // disable bracketed paste mode
|
|
25
29
|
process.stdout.write(`${ESC}[?1049l`); // restore screen buffer
|
|
26
30
|
}
|
|
27
31
|
/** Paint lines to terminal without flicker: cursor home → overwrite each line → clear remainder */
|
|
@@ -30,11 +34,13 @@ export function paint(lines) {
|
|
|
30
34
|
let out = `${ESC}[H`; // cursor home
|
|
31
35
|
const count = Math.min(lines.length, rows);
|
|
32
36
|
for (let i = 0; i < count; i++) {
|
|
33
|
-
out += lines[i] + `${ESC}[K
|
|
37
|
+
out += lines[i] + `${ESC}[K`; // line content + clear to end of line
|
|
38
|
+
if (i < count - 1)
|
|
39
|
+
out += "\n";
|
|
34
40
|
}
|
|
35
41
|
// Clear any remaining lines below content
|
|
36
42
|
if (count < rows) {
|
|
37
|
-
out +=
|
|
43
|
+
out += `\n${ESC}[J`; // clear from cursor to end of screen
|
|
38
44
|
}
|
|
39
45
|
process.stdout.write(out);
|
|
40
46
|
}
|
|
@@ -43,6 +49,24 @@ export function screenSize() {
|
|
|
43
49
|
}
|
|
44
50
|
export function parseKey(data) {
|
|
45
51
|
const s = data.toString("utf-8");
|
|
52
|
+
// Bracketed paste: \x1b[200~ ... \x1b[201~
|
|
53
|
+
if (s.startsWith(`${ESC}[200~`)) {
|
|
54
|
+
const content = s.replace(/\x1b\[200~/g, "").replace(/\x1b\[201~/g, "");
|
|
55
|
+
const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
56
|
+
return { raw: normalized, name: "paste", shift: false, ctrl: false };
|
|
57
|
+
}
|
|
58
|
+
// Alt+Enter (ESC + CR/LF) — reliable newline insertion across all terminals
|
|
59
|
+
if (s === `${ESC}\r` || s === `${ESC}\n`) {
|
|
60
|
+
return { raw: s, name: "return", shift: true, ctrl: false };
|
|
61
|
+
}
|
|
62
|
+
// Alt+Arrow (word navigation)
|
|
63
|
+
if (s === `${ESC}[1;3D` || s === `${ESC}b`)
|
|
64
|
+
return { raw: s, name: "wordLeft", shift: false, ctrl: false };
|
|
65
|
+
if (s === `${ESC}[1;3C` || s === `${ESC}f`)
|
|
66
|
+
return { raw: s, name: "wordRight", shift: false, ctrl: false };
|
|
67
|
+
// Alt+Backspace (word delete)
|
|
68
|
+
if (s === `${ESC}\x7f`)
|
|
69
|
+
return { raw: s, name: "wordBackspace", shift: false, ctrl: false };
|
|
46
70
|
const ctrl = s.length === 1 && s.charCodeAt(0) < 32;
|
|
47
71
|
// Escape sequences
|
|
48
72
|
if (s === `${ESC}[A`)
|
|
@@ -61,13 +85,23 @@ export function parseKey(data) {
|
|
|
61
85
|
return { raw: s, name: "tab", shift: true, ctrl: false };
|
|
62
86
|
if (s === ESC || s === `${ESC}${ESC}`)
|
|
63
87
|
return { raw: s, name: "escape", shift: false, ctrl: false };
|
|
64
|
-
//
|
|
88
|
+
// Kitty keyboard protocol CSI-u encodings (when disambiguate mode is active)
|
|
65
89
|
if (s === `${ESC}[13;2u`)
|
|
66
|
-
return { raw: s, name: "return", shift: true, ctrl: false }; //
|
|
90
|
+
return { raw: s, name: "return", shift: true, ctrl: false }; // Shift+Enter
|
|
67
91
|
if (s === `${ESC}[27;2;13~`)
|
|
68
|
-
return { raw: s, name: "return", shift: true, ctrl: false }; // xterm
|
|
92
|
+
return { raw: s, name: "return", shift: true, ctrl: false }; // Shift+Enter (xterm)
|
|
69
93
|
if (s === `${ESC}OM`)
|
|
70
|
-
return { raw: s, name: "return", shift: true, ctrl: false }; // misc
|
|
94
|
+
return { raw: s, name: "return", shift: true, ctrl: false }; // Shift+Enter (misc)
|
|
95
|
+
if (s === `${ESC}[27u`)
|
|
96
|
+
return { raw: s, name: "escape", shift: false, ctrl: false };
|
|
97
|
+
if (s === `${ESC}[13u`)
|
|
98
|
+
return { raw: s, name: "return", shift: false, ctrl: false };
|
|
99
|
+
if (s === `${ESC}[9u`)
|
|
100
|
+
return { raw: s, name: "tab", shift: false, ctrl: false };
|
|
101
|
+
if (s === `${ESC}[9;2u`)
|
|
102
|
+
return { raw: s, name: "tab", shift: true, ctrl: false };
|
|
103
|
+
if (s === `${ESC}[127u`)
|
|
104
|
+
return { raw: s, name: "backspace", shift: false, ctrl: false };
|
|
71
105
|
// Single characters
|
|
72
106
|
if (s === "\r" || s === "\n")
|
|
73
107
|
return { raw: s, name: "return", shift: false, ctrl: false };
|
|
@@ -82,6 +116,11 @@ export function parseKey(data) {
|
|
|
82
116
|
if (ctrl) {
|
|
83
117
|
return { raw: s, name: String.fromCharCode(s.charCodeAt(0) + 96), shift: false, ctrl: true };
|
|
84
118
|
}
|
|
119
|
+
// Multi-character non-escape input = paste (terminal without bracketed paste support)
|
|
120
|
+
if (s.length > 1 && !s.startsWith(ESC)) {
|
|
121
|
+
const normalized = s.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
122
|
+
return { raw: normalized, name: "paste", shift: false, ctrl: false };
|
|
123
|
+
}
|
|
85
124
|
return { raw: s, name: s, shift: false, ctrl: false };
|
|
86
125
|
}
|
|
87
126
|
/** Strip ANSI escape codes to get visible character count */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@readwise/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Command-line interface for Readwise and Reader",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"open": "^10"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"typescript": "^5",
|
|
27
26
|
"@types/node": "^22",
|
|
28
|
-
"tsx": "^4"
|
|
27
|
+
"tsx": "^4",
|
|
28
|
+
"typescript": "^5"
|
|
29
29
|
}
|
|
30
30
|
}
|
package/src/config.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -93,9 +93,10 @@ async function main() {
|
|
|
93
93
|
const forceRefresh = process.argv.includes("--refresh");
|
|
94
94
|
const positionalArgs = process.argv.slice(2).filter((a) => !a.startsWith("--"));
|
|
95
95
|
const hasSubcommand = positionalArgs.length > 0;
|
|
96
|
+
const wantsHelp = process.argv.includes("--help") || process.argv.includes("-h");
|
|
96
97
|
|
|
97
|
-
// If no subcommand, TTY, and authenticated → launch TUI
|
|
98
|
-
if (!hasSubcommand && process.stdout.isTTY && config.access_token) {
|
|
98
|
+
// If no subcommand, TTY, and authenticated → launch TUI (unless --help)
|
|
99
|
+
if (!hasSubcommand && !wantsHelp && process.stdout.isTTY && config.access_token) {
|
|
99
100
|
try {
|
|
100
101
|
const { token, authType } = await ensureValidToken();
|
|
101
102
|
const tools = await getTools(token, authType, forceRefresh);
|
|
@@ -115,6 +116,13 @@ async function main() {
|
|
|
115
116
|
return;
|
|
116
117
|
}
|
|
117
118
|
|
|
119
|
+
// If not authenticated and trying a non-login command, tell user to log in
|
|
120
|
+
if (!config.access_token && hasSubcommand && positionalArgs[0] !== "login" && positionalArgs[0] !== "login-with-token") {
|
|
121
|
+
process.stderr.write("\x1b[31mNot logged in.\x1b[0m Run `readwise login` or `readwise login-with-token` to authenticate.\n");
|
|
122
|
+
process.exitCode = 1;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
118
126
|
// Try to load tools if we have a token (for subcommand mode)
|
|
119
127
|
if (config.access_token) {
|
|
120
128
|
try {
|
|
@@ -123,7 +131,6 @@ async function main() {
|
|
|
123
131
|
registerTools(program, tools);
|
|
124
132
|
} catch (err) {
|
|
125
133
|
// Don't fail — login command should still work
|
|
126
|
-
// Only warn if user is trying to run a non-login command
|
|
127
134
|
if (hasSubcommand && positionalArgs[0] !== "login" && positionalArgs[0] !== "login-with-token") {
|
|
128
135
|
process.stderr.write(`\x1b[33mWarning: Could not fetch tools: ${(err as Error).message}\x1b[0m\n`);
|
|
129
136
|
}
|