@readwise/cli 0.3.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 +140 -0
- package/dist/auth.d.ts +6 -0
- package/dist/auth.js +194 -0
- package/dist/commands.d.ts +14 -0
- package/dist/commands.js +152 -0
- package/dist/config.d.ts +38 -0
- package/dist/config.js +24 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +179 -0
- package/dist/mcp.d.ts +10 -0
- package/dist/mcp.js +53 -0
- package/dist/tui/app.d.ts +2 -0
- package/dist/tui/app.js +1806 -0
- package/dist/tui/index.d.ts +2 -0
- package/dist/tui/index.js +11 -0
- package/dist/tui/logo.d.ts +1 -0
- package/dist/tui/logo.js +16 -0
- package/dist/tui/term.d.ts +32 -0
- package/dist/tui/term.js +147 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +4 -0
- package/package.json +30 -0
- package/src/auth.ts +248 -0
- package/src/commands.ts +158 -0
- package/src/config.ts +64 -0
- package/src/index.ts +136 -0
- package/src/mcp.ts +66 -0
- package/src/tui/app.ts +1917 -0
- package/src/tui/index.ts +12 -0
- package/src/tui/logo.ts +16 -0
- package/src/tui/term.ts +151 -0
- package/src/version.ts +4 -0
- package/tsconfig.json +14 -0
package/src/tui/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ToolDef } from "../config.js";
|
|
2
|
+
import { enterFullScreen, exitFullScreen } from "./term.js";
|
|
3
|
+
import { runApp } from "./app.js";
|
|
4
|
+
|
|
5
|
+
export async function startTui(tools: ToolDef[], token: string, authType: "oauth" | "token"): Promise<void> {
|
|
6
|
+
enterFullScreen();
|
|
7
|
+
try {
|
|
8
|
+
await runApp(tools);
|
|
9
|
+
} finally {
|
|
10
|
+
exitFullScreen();
|
|
11
|
+
}
|
|
12
|
+
}
|
package/src/tui/logo.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const LOGO = [
|
|
2
|
+
"╔════════════════════════╗",
|
|
3
|
+
"║░░░░░░░░░░░░░░░░░░░░░░░░║",
|
|
4
|
+
"║░░░░░░░░░░░░░░░░░░░░░░░░║",
|
|
5
|
+
"║░░░░░▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░║",
|
|
6
|
+
"║░░░░░░▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░║",
|
|
7
|
+
"║░░░░░░▓▓▓▓▓▓░░▓▓▓▓▓░░░░░║",
|
|
8
|
+
"║░░░░░░▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░║",
|
|
9
|
+
"║░░░░░░▓▓▓▓▓▓▓▓▓▓░░░░░░░░║",
|
|
10
|
+
"║░░░░░░▓▓▓▓▓░▓▓▓▓░░░░░░░░║",
|
|
11
|
+
"║░░░░░░▓▓▓▓▓░░▓▓▓▓▓░░░░░░║",
|
|
12
|
+
"║░░░░░▓▓▓▓▓▓▓░░▓▓▓▓▓▓░░░░║",
|
|
13
|
+
"║░░░░░░░░░░░░░░░░░░░░░░░░║",
|
|
14
|
+
"║░░░░░░░░░░░░░░░░░░░░░░░░║",
|
|
15
|
+
"╚════════════════════════╝",
|
|
16
|
+
];
|
package/src/tui/term.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// Low-level terminal utilities for flicker-free full-screen rendering.
|
|
2
|
+
// Instead of clearing and rewriting (which blinks), we position the cursor
|
|
3
|
+
// at home and overwrite in-place — content is never erased before being replaced.
|
|
4
|
+
|
|
5
|
+
const ESC = "\x1b";
|
|
6
|
+
|
|
7
|
+
// --- ANSI helpers ---
|
|
8
|
+
|
|
9
|
+
export const style = {
|
|
10
|
+
bold: (s: string) => `${ESC}[1m${s}${ESC}[22m`,
|
|
11
|
+
dim: (s: string) => `${ESC}[2m${s}${ESC}[22m`,
|
|
12
|
+
inverse: (s: string) => `${ESC}[7m${s}${ESC}[27m`,
|
|
13
|
+
yellow: (s: string) => `${ESC}[33m${s}${ESC}[39m`,
|
|
14
|
+
red: (s: string) => `${ESC}[31m${s}${ESC}[39m`,
|
|
15
|
+
green: (s: string) => `${ESC}[32m${s}${ESC}[39m`,
|
|
16
|
+
cyan: (s: string) => `${ESC}[36m${s}${ESC}[39m`,
|
|
17
|
+
boldYellow: (s: string) => `${ESC}[1;33m${s}${ESC}[22;39m`,
|
|
18
|
+
blue: (s: string) => `${ESC}[38;2;60;110;253m${s}${ESC}[39m`,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// --- Screen control ---
|
|
22
|
+
|
|
23
|
+
export function enterFullScreen(): void {
|
|
24
|
+
process.stdout.write(`${ESC}[?1049h`); // alternate screen buffer
|
|
25
|
+
process.stdout.write(`${ESC}[?25l`); // hide cursor
|
|
26
|
+
process.stdout.write(`${ESC}[H`); // cursor home
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function exitFullScreen(): void {
|
|
30
|
+
process.stdout.write(`${ESC}[?25h`); // show cursor
|
|
31
|
+
process.stdout.write(`${ESC}[?1049l`); // restore screen buffer
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Paint lines to terminal without flicker: cursor home → overwrite each line → clear remainder */
|
|
35
|
+
export function paint(lines: string[]): void {
|
|
36
|
+
const rows = process.stdout.rows ?? 24;
|
|
37
|
+
let out = `${ESC}[H`; // cursor home
|
|
38
|
+
const count = Math.min(lines.length, rows);
|
|
39
|
+
for (let i = 0; i < count; i++) {
|
|
40
|
+
out += lines[i] + `${ESC}[K\n`; // line content + clear to end of line
|
|
41
|
+
}
|
|
42
|
+
// Clear any remaining lines below content
|
|
43
|
+
if (count < rows) {
|
|
44
|
+
out += `${ESC}[J`; // clear from cursor to end of screen
|
|
45
|
+
}
|
|
46
|
+
process.stdout.write(out);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function screenSize(): { cols: number; rows: number } {
|
|
50
|
+
return { cols: process.stdout.columns ?? 80, rows: process.stdout.rows ?? 24 };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// --- Keyboard input ---
|
|
54
|
+
|
|
55
|
+
export interface KeyEvent {
|
|
56
|
+
raw: string;
|
|
57
|
+
name: string; // 'up', 'down', 'return', 'escape', 'tab', 'backspace', 'pageup', 'pagedown', or the character
|
|
58
|
+
shift: boolean;
|
|
59
|
+
ctrl: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function parseKey(data: Buffer): KeyEvent {
|
|
63
|
+
const s = data.toString("utf-8");
|
|
64
|
+
const ctrl = s.length === 1 && s.charCodeAt(0) < 32;
|
|
65
|
+
|
|
66
|
+
// Escape sequences
|
|
67
|
+
if (s === `${ESC}[A`) return { raw: s, name: "up", shift: false, ctrl: false };
|
|
68
|
+
if (s === `${ESC}[B`) return { raw: s, name: "down", shift: false, ctrl: false };
|
|
69
|
+
if (s === `${ESC}[C`) return { raw: s, name: "right", shift: false, ctrl: false };
|
|
70
|
+
if (s === `${ESC}[D`) return { raw: s, name: "left", shift: false, ctrl: false };
|
|
71
|
+
if (s === `${ESC}[5~`) return { raw: s, name: "pageup", shift: false, ctrl: false };
|
|
72
|
+
if (s === `${ESC}[6~`) return { raw: s, name: "pagedown", shift: false, ctrl: false };
|
|
73
|
+
if (s === `${ESC}[Z`) return { raw: s, name: "tab", shift: true, ctrl: false };
|
|
74
|
+
if (s === ESC || s === `${ESC}${ESC}`) return { raw: s, name: "escape", shift: false, ctrl: false };
|
|
75
|
+
|
|
76
|
+
// Shift+Enter sequences
|
|
77
|
+
if (s === `${ESC}[13;2u`) return { raw: s, name: "return", shift: true, ctrl: false }; // CSI u / kitty
|
|
78
|
+
if (s === `${ESC}[27;2;13~`) return { raw: s, name: "return", shift: true, ctrl: false }; // xterm
|
|
79
|
+
if (s === `${ESC}OM`) return { raw: s, name: "return", shift: true, ctrl: false }; // misc terminals
|
|
80
|
+
|
|
81
|
+
// Single characters
|
|
82
|
+
if (s === "\r" || s === "\n") return { raw: s, name: "return", shift: false, ctrl: false };
|
|
83
|
+
if (s === "\t") return { raw: s, name: "tab", shift: false, ctrl: false };
|
|
84
|
+
if (s === "\x7f" || s === "\b") return { raw: s, name: "backspace", shift: false, ctrl: false };
|
|
85
|
+
if (s === "\x03") return { raw: s, name: "c", shift: false, ctrl: true }; // Ctrl+C
|
|
86
|
+
if (s === "\x04") return { raw: s, name: "d", shift: false, ctrl: true }; // Ctrl+D
|
|
87
|
+
|
|
88
|
+
if (ctrl) {
|
|
89
|
+
return { raw: s, name: String.fromCharCode(s.charCodeAt(0) + 96), shift: false, ctrl: true };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { raw: s, name: s, shift: false, ctrl: false };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Strip ANSI escape codes to get visible character count */
|
|
96
|
+
export function stripAnsi(s: string): string {
|
|
97
|
+
return s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Skip `offset` visible characters, preserving ANSI state, then return the rest */
|
|
101
|
+
export function ansiSlice(s: string, offset: number): string {
|
|
102
|
+
if (offset <= 0) return s;
|
|
103
|
+
// Collect ANSI sequences encountered while skipping so we can replay them
|
|
104
|
+
let activeAnsi = "";
|
|
105
|
+
let count = 0;
|
|
106
|
+
let i = 0;
|
|
107
|
+
while (i < s.length && count < offset) {
|
|
108
|
+
if (s[i] === "\x1b") {
|
|
109
|
+
const end = s.indexOf("m", i);
|
|
110
|
+
if (end >= 0) {
|
|
111
|
+
activeAnsi += s.slice(i, end + 1);
|
|
112
|
+
i = end + 1;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
count++;
|
|
117
|
+
i++;
|
|
118
|
+
}
|
|
119
|
+
// Consume any ANSI codes right at the boundary
|
|
120
|
+
while (i < s.length && s[i] === "\x1b") {
|
|
121
|
+
const end = s.indexOf("m", i);
|
|
122
|
+
if (end >= 0) {
|
|
123
|
+
activeAnsi += s.slice(i, end + 1);
|
|
124
|
+
i = end + 1;
|
|
125
|
+
} else break;
|
|
126
|
+
}
|
|
127
|
+
return activeAnsi + s.slice(i);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Pad/truncate a string to a visible width (ANSI-aware) */
|
|
131
|
+
export function fitWidth(s: string, width: number): string {
|
|
132
|
+
const visible = stripAnsi(s);
|
|
133
|
+
if (visible.length >= width) {
|
|
134
|
+
// Truncate — need to be careful with ANSI codes
|
|
135
|
+
let count = 0;
|
|
136
|
+
let i = 0;
|
|
137
|
+
while (i < s.length && count < width) {
|
|
138
|
+
if (s[i] === "\x1b") {
|
|
139
|
+
const end = s.indexOf("m", i);
|
|
140
|
+
if (end >= 0) { i = end + 1; continue; }
|
|
141
|
+
}
|
|
142
|
+
count++;
|
|
143
|
+
i++;
|
|
144
|
+
}
|
|
145
|
+
// Include any trailing ANSI reset codes
|
|
146
|
+
const rest = s.slice(i);
|
|
147
|
+
const resets = rest.match(/^(\x1b\[[0-9;]*m)*/)?.[0] || "";
|
|
148
|
+
return s.slice(0, i) + resets;
|
|
149
|
+
}
|
|
150
|
+
return s + " ".repeat(width - visible.length);
|
|
151
|
+
}
|
package/src/version.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"declaration": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"]
|
|
14
|
+
}
|