diff --git a/package-lock.json b/package-lock.json index ffc7960..e99f1b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "@readwise/cli", "version": "0.5.9", "dependencies": { - "@modelcontextprotocol/sdk": "latest", + "@modelcontextprotocol/sdk": "^1.26.0", "commander": "^13", "open": "^10" }, diff --git a/package.json b/package.json index 121b0e1..e4f179b 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "test": "node --import tsx --test tests/*.test.ts" }, "dependencies": { - "@modelcontextprotocol/sdk": "latest", + "@modelcontextprotocol/sdk": "^1.26.0", "commander": "^13", "open": "^10" }, diff --git a/src/config.ts b/src/config.ts index 05c59f1..94160e6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,4 +1,4 @@ -import { readFile, writeFile } from "node:fs/promises"; +import { chmod, readFile, writeFile } from "node:fs/promises"; import { homedir } from "node:os"; import { join } from "node:path"; @@ -76,7 +76,11 @@ export async function loadConfig(): Promise { } export async function saveConfig(config: Config): Promise { - await writeFile(getConfigPath(), JSON.stringify(config, null, 2) + "\n", "utf-8"); + // The config holds OAuth tokens and the client secret, so keep it owner-only. + // `mode` only applies on creation; chmod tightens files written by older versions. + const path = getConfigPath(); + await writeFile(path, JSON.stringify(config, null, 2) + "\n", { encoding: "utf-8", mode: 0o600 }); + await chmod(path, 0o600); } export function isCacheValid(config: Config): boolean { diff --git a/src/tui/app.ts b/src/tui/app.ts index a3cc511..b59a369 100644 --- a/src/tui/app.ts +++ b/src/tui/app.ts @@ -1,4 +1,4 @@ -import { exec } from "node:child_process"; +import open from "open"; import type { ToolDef, SchemaProperty } from "../config.js"; import { loadConfig, saveConfig, getAllConfigEntries, setConfigValue, filterReadOnlyTools } from "../config.js"; import { resolveProperty } from "../commands.js"; @@ -551,6 +551,17 @@ function extractCardUrl(obj: Record): string { return ""; } +// Card URLs can come from user-saved content (e.g. source_url), so only hand +// web URLs to the OS opener — not file:// or arbitrary app schemes. +function isOpenableUrl(url: string): boolean { + try { + const { protocol } = new URL(url); + return protocol === "http:" || protocol === "https:"; + } catch { + return false; + } +} + // --- Word boundary helpers --- function prevWordBoundary(buf: string, pos: number): number { @@ -3102,9 +3113,9 @@ export async function runApp(tools: ToolDef[], allTools: ToolDef[]): Promise {}); } return; }