@readwise/cli 0.3.0 → 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 +12 -11
- package/dist/config.d.ts +1 -0
- package/dist/index.js +11 -54
- 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/README.md
CHANGED
|
@@ -7,10 +7,7 @@ Commands are auto-discovered from the Readwise API, so the CLI stays up to date
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
|
|
11
|
-
npm install
|
|
12
|
-
npm run build
|
|
13
|
-
npm link
|
|
10
|
+
npm install -g @readwise/cli
|
|
14
11
|
```
|
|
15
12
|
|
|
16
13
|
## Setup
|
|
@@ -67,6 +64,8 @@ readwise reader-get-document-details --document-id <document-id>
|
|
|
67
64
|
readwise reader-get-document-highlights --document-id <document-id>
|
|
68
65
|
```
|
|
69
66
|
|
|
67
|
+
> **Tip: seen vs unseen documents.** In the response, `firstOpenedAt: null` means the document is **unseen** (never opened). A non-null `firstOpenedAt` means it has been opened/seen. Use `reader-edit-document-metadata --seen true` to mark a document as seen.
|
|
68
|
+
|
|
70
69
|
### Save a document
|
|
71
70
|
|
|
72
71
|
```bash
|
|
@@ -91,6 +90,7 @@ readwise reader-move-document --document-id <id> --location archive
|
|
|
91
90
|
|
|
92
91
|
# Edit metadata
|
|
93
92
|
readwise reader-edit-document-metadata --document-id <id> --title "Better Title"
|
|
93
|
+
readwise reader-edit-document-metadata --document-id <id> --seen true # mark as seen/opened
|
|
94
94
|
readwise reader-set-document-notes --document-id <id> --notes "Updated notes"
|
|
95
95
|
```
|
|
96
96
|
|
|
@@ -125,16 +125,17 @@ Pipe results to `jq`:
|
|
|
125
125
|
readwise reader-list-documents --limit 3 --json | jq '.results[].title'
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
+
## How it works
|
|
129
|
+
|
|
130
|
+
The CLI connects to the [Readwise MCP server](https://mcp2.readwise.io) internally, auto-discovers available tools, and exposes each one as a CLI command. The tool list is cached locally for 24 hours.
|
|
131
|
+
|
|
128
132
|
## Development
|
|
129
133
|
|
|
130
134
|
```bash
|
|
135
|
+
git clone https://github.com/readwise/readwise-cli && cd readwise-cli
|
|
136
|
+
npm install
|
|
137
|
+
npm run build
|
|
138
|
+
|
|
131
139
|
# Run without building
|
|
132
140
|
npx tsx src/index.ts --help
|
|
133
|
-
|
|
134
|
-
# Build
|
|
135
|
-
npm run build
|
|
136
141
|
```
|
|
137
|
-
|
|
138
|
-
## How it works
|
|
139
|
-
|
|
140
|
-
The CLI connects to the [Readwise MCP server](https://mcp2.readwise.io) internally, auto-discovers available tools, and exposes each one as a CLI command. The tool list is cached locally for 24 hours.
|
package/dist/config.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { createInterface } from "node:readline";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { login, loginWithToken, ensureValidToken } from "./auth.js";
|
|
5
|
-
import { getTools
|
|
6
|
-
import { registerTools
|
|
5
|
+
import { getTools } from "./mcp.js";
|
|
6
|
+
import { registerTools } from "./commands.js";
|
|
7
7
|
import { loadConfig } from "./config.js";
|
|
8
8
|
import { VERSION } from "./version.js";
|
|
9
9
|
function readHiddenInput(prompt) {
|
|
@@ -85,62 +85,14 @@ program
|
|
|
85
85
|
process.exitCode = 1;
|
|
86
86
|
}
|
|
87
87
|
});
|
|
88
|
-
program
|
|
89
|
-
.command("search <query>")
|
|
90
|
-
.description("Search across Reader documents and Readwise highlights")
|
|
91
|
-
.option("--limit <n>", "Max results per source (default: 10)")
|
|
92
|
-
.action(async (query, options) => {
|
|
93
|
-
try {
|
|
94
|
-
const { token, authType } = await ensureValidToken();
|
|
95
|
-
const limit = options.limit ? Number(options.limit) : 10;
|
|
96
|
-
const json = program.opts().json || false;
|
|
97
|
-
const [readerResult, highlightsResult] = await Promise.all([
|
|
98
|
-
callTool(token, authType, "reader_search_documents", { query, limit }),
|
|
99
|
-
callTool(token, authType, "readwise_search_highlights", { vector_search_term: query, limit }),
|
|
100
|
-
]);
|
|
101
|
-
if (json) {
|
|
102
|
-
const combined = {};
|
|
103
|
-
for (const item of readerResult.content) {
|
|
104
|
-
if (item.type === "text" && item.text) {
|
|
105
|
-
try {
|
|
106
|
-
combined.reader = JSON.parse(item.text);
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
combined.reader = item.text;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
for (const item of highlightsResult.content) {
|
|
114
|
-
if (item.type === "text" && item.text) {
|
|
115
|
-
try {
|
|
116
|
-
combined.highlights = JSON.parse(item.text);
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
combined.highlights = item.text;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
process.stdout.write(JSON.stringify(combined) + "\n");
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
console.log("\x1b[1m\x1b[36m── Reader Documents ──\x1b[0m\n");
|
|
127
|
-
displayResult(readerResult, false);
|
|
128
|
-
console.log("\n\x1b[1m\x1b[36m── Readwise Highlights ──\x1b[0m\n");
|
|
129
|
-
displayResult(highlightsResult, false);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
catch (err) {
|
|
133
|
-
process.stderr.write(`\x1b[31m${err.message}\x1b[0m\n`);
|
|
134
|
-
process.exitCode = 1;
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
88
|
async function main() {
|
|
138
89
|
const config = await loadConfig();
|
|
139
90
|
const forceRefresh = process.argv.includes("--refresh");
|
|
140
91
|
const positionalArgs = process.argv.slice(2).filter((a) => !a.startsWith("--"));
|
|
141
92
|
const hasSubcommand = positionalArgs.length > 0;
|
|
142
|
-
|
|
143
|
-
|
|
93
|
+
const wantsHelp = process.argv.includes("--help") || process.argv.includes("-h");
|
|
94
|
+
// If no subcommand, TTY, and authenticated → launch TUI (unless --help)
|
|
95
|
+
if (!hasSubcommand && !wantsHelp && process.stdout.isTTY && config.access_token) {
|
|
144
96
|
try {
|
|
145
97
|
const { token, authType } = await ensureValidToken();
|
|
146
98
|
const tools = await getTools(token, authType, forceRefresh);
|
|
@@ -159,6 +111,12 @@ async function main() {
|
|
|
159
111
|
console.log("\nRun `readwise login` or `readwise login-with-token` to authenticate.");
|
|
160
112
|
return;
|
|
161
113
|
}
|
|
114
|
+
// If not authenticated and trying a non-login command, tell user to log in
|
|
115
|
+
if (!config.access_token && hasSubcommand && positionalArgs[0] !== "login" && positionalArgs[0] !== "login-with-token") {
|
|
116
|
+
process.stderr.write("\x1b[31mNot logged in.\x1b[0m Run `readwise login` or `readwise login-with-token` to authenticate.\n");
|
|
117
|
+
process.exitCode = 1;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
162
120
|
// Try to load tools if we have a token (for subcommand mode)
|
|
163
121
|
if (config.access_token) {
|
|
164
122
|
try {
|
|
@@ -168,7 +126,6 @@ async function main() {
|
|
|
168
126
|
}
|
|
169
127
|
catch (err) {
|
|
170
128
|
// Don't fail — login command should still work
|
|
171
|
-
// Only warn if user is trying to run a non-login command
|
|
172
129
|
if (hasSubcommand && positionalArgs[0] !== "login" && positionalArgs[0] !== "login-with-token") {
|
|
173
130
|
process.stderr.write(`\x1b[33mWarning: Could not fetch tools: ${err.message}\x1b[0m\n`);
|
|
174
131
|
}
|