🚀 Socket Launch Week Day 4:Socket MCP Adds Org Alerts, Threat Feed Review, and Package Inspection.Learn more
Sign In

@paperscope/cli

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@paperscope/cli - npm Package Compare versions

Comparing version
1.1.0
to
1.2.0
+1
-1
package.json
{
"name": "@paperscope/cli",
"version": "1.1.0",
"version": "1.2.0",
"description": "PaperScope CLI - Search and explore academic papers from your terminal",

@@ -5,0 +5,0 @@ "type": "module",

@@ -56,2 +56,4 @@ # PaperScope CLI

> **Security note:** The API key is stored in plaintext at `~/.paperscope/config.json` with `0600` permissions (owner-only read/write). On shared or multi-user machines, prefer the `PAPERSCOPE_API_KEY` environment variable instead.
## Usage

@@ -58,0 +60,0 @@

@@ -19,2 +19,10 @@ import { getApiKey } from "./utils/config.js";

const REQUEST_TIMEOUT_MS = 15_000;
const MAX_RETRIES = 1;
const RETRY_DELAY_MS = 1_000;
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {

@@ -40,23 +48,46 @@ const url = new URL(`${BASE_URL}${path}`);

const response = await fetch(url.toString(), { headers });
let lastError: Error | undefined;
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
try {
const response = await fetch(url.toString(), {
headers,
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
});
if (!response.ok) {
const body = await response.text();
let message = `HTTP ${response.status}`;
try {
const json = JSON.parse(body);
if (typeof json.error === "object" && json.error?.message) {
message = json.error.message;
} else if (typeof json.error === "string") {
message = json.error;
} else if (json.message) {
message = json.message;
if (!response.ok) {
const body = await response.text();
let message = `HTTP ${response.status}`;
try {
const json = JSON.parse(body);
if (typeof json.error === "object" && json.error?.message) {
message = json.error.message;
} else if (typeof json.error === "string") {
message = json.error;
} else if (json.message) {
message = json.message;
}
} catch {
if (body) message = body;
}
throw new ApiError(response.status, message);
}
} catch {
if (body) message = body;
return response.json() as Promise<T>;
} catch (err) {
if (err instanceof ApiError) {
// Do not retry client errors (4xx)
throw err;
}
lastError = err as Error;
if (attempt < MAX_RETRIES) {
await sleep(RETRY_DELAY_MS);
}
}
throw new ApiError(response.status, message);
}
return response.json() as Promise<T>;
// All retries exhausted
if (lastError?.name === "TimeoutError" || lastError?.name === "AbortError") {
throw new Error("Request timed out. Check your network.");
}
throw new Error(`Network error: ${lastError?.message || "unknown"}. Check your network.`);
}

@@ -63,0 +94,0 @@

@@ -12,2 +12,6 @@ import chalk from "chalk";

const limit = options.limit ? parseInt(options.limit, 10) : 10;
if (isNaN(limit) || limit <= 0 || limit > 100) {
console.error(chalk.red("\n Error: --limit must be a positive integer between 1 and 100.\n"));
process.exit(1);
}

@@ -14,0 +18,0 @@ const spinner = ora({

import chalk from "chalk";
import { setApiKey, getApiKey } from "../utils/config.js";
import { setApiKey, getApiKey, clearApiKey } from "../utils/config.js";
import { createInterface } from "readline";

@@ -93,5 +93,4 @@

}
const { clearApiKey } = require("../utils/config.js");
clearApiKey();
console.log(chalk.green("\n ✓ Logged out. API key removed.\n"));
}

@@ -13,2 +13,8 @@ import chalk from "chalk";

export async function readCommand(paperId: string, options: ReadOptions): Promise<void> {
const flagCount = [options.blog, options.summary, options.tags].filter(Boolean).length;
if (flagCount > 1) {
console.error(chalk.red("\n Error: --blog, --summary, and --tags are mutually exclusive. Please specify only one.\n"));
process.exit(1);
}
const spinner = ora({

@@ -15,0 +21,0 @@ text: `Fetching ${paperId}...`,

@@ -13,3 +13,12 @@ import chalk from "chalk";

export async function searchCommand(query: string, options: SearchOptions): Promise<void> {
if (!query.trim()) {
console.error(chalk.red("\n Error: Search query cannot be empty.\n"));
process.exit(1);
}
const limit = options.limit ? parseInt(options.limit, 10) : 10;
if (isNaN(limit) || limit <= 0 || limit > 100) {
console.error(chalk.red("\n Error: --limit must be a positive integer between 1 and 100.\n"));
process.exit(1);
}

@@ -16,0 +25,0 @@ const spinner = ora({

@@ -13,2 +13,6 @@ import chalk from "chalk";

const limit = options.limit ? parseInt(options.limit, 10) : 10;
if (isNaN(limit) || limit <= 0 || limit > 100) {
console.error(chalk.red("\n Error: --limit must be a positive integer between 1 and 100.\n"));
process.exit(1);
}

@@ -15,0 +19,0 @@ const spinner = ora({

@@ -16,3 +16,3 @@ #!/usr/bin/env bun

.description("PaperScope CLI - Search and explore academic papers from your terminal")
.version("1.0.0");
.version("1.2.0");

@@ -89,7 +89,8 @@ // ── search ───────────────────────────────────────────────

// Default: show help
// Default: show help once and exit cleanly
if (process.argv.length <= 2) {
program.outputHelp();
process.exit(0);
}
program.parse(process.argv);

@@ -1,2 +0,2 @@

import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from "fs";
import { homedir } from "os";

@@ -32,2 +32,3 @@ import { join } from "path";

writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
chmodSync(CONFIG_FILE, 0o600);
}

@@ -34,0 +35,0 @@