pplx-npx-search 0.2.1 → 0.2.2

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/CHANGELOG.md CHANGED
@@ -5,6 +5,15 @@ All notable changes to pplx-cli will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.2] - 2026-05-21
9
+
10
+ ### Added
11
+ - **Configurable stream timeout.** `search`, `reason`, and `research` now accept `--timeout-ms <duration>`, with support for raw milliseconds plus `s` and `m` suffixes.
12
+
13
+ ### Changed
14
+ - `pplx research` now defaults to a 10-minute stream timeout so Deep Research can finish instead of hitting the old 2-minute ceiling.
15
+ - `pplx --version` now reads from `package.json`, keeping CLI output aligned with npm releases.
16
+
8
17
  ## [0.2.1] - 2026-05-18
9
18
 
10
19
  First public release worth telling people about. (v0.2.0 was unpublished before this release; do not use it.)
@@ -46,6 +55,7 @@ First public release worth telling people about. (v0.2.0 was unpublished before
46
55
  - SSE streaming for real-time answers
47
56
  - Optional Playwright and Chrome CDP transports
48
57
 
58
+ [0.2.2]: https://github.com/thatsrajan/pplx-cli/compare/v0.2.1...v0.2.2
49
59
  [0.2.1]: https://github.com/thatsrajan/pplx-cli/compare/v0.1.1...v0.2.1
50
60
  [0.1.1]: https://github.com/thatsrajan/pplx-cli/compare/v0.1.0...v0.1.1
51
61
  [0.1.0]: https://github.com/thatsrajan/pplx-cli/releases/tag/v0.1.0
package/README.md CHANGED
@@ -127,6 +127,8 @@ pplx search "query" | head -1
127
127
  pplx search "query" --json || echo "failed"
128
128
  ```
129
129
 
130
+ Deep Research is slower than normal search. `pplx research` defaults to a 10-minute stream timeout; override it per run with `--timeout-ms 600000`, `--timeout-ms 120s`, or `--timeout-ms 10m`.
131
+
130
132
  Recommended agent invocation:
131
133
 
132
134
  ```bash
@@ -160,6 +162,7 @@ pplx search "research this topic" --json --raw --mode pro
160
162
  | `--chrome` | Use Chrome CDP bridge instead of HTTP |
161
163
  | `--playwright` | Use Playwright headless Chromium |
162
164
  | `--no-playwright` | Force HTTP transport even if config enables Playwright |
165
+ | `--timeout-ms 120000\|120s\|10m` | Overall stream timeout |
163
166
  | `--curl` | Force curl-impersonate (auto-downloads if missing) |
164
167
  | `--allow-anonymous` | Allow anonymous Perplexity responses when cookies are expired |
165
168
  | `--incognito` | Do not save the query to Perplexity history |
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "pplx-npx-search",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "CLI for Perplexity AI with cookie-based auth. Headless, agent-friendly, no API key required.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "pplx": "./bin/pplx.js"
7
+ "pplx": "bin/pplx.js"
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node bin/pplx.js",
package/src/cli.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { readFileSync } from 'node:fs';
1
2
  import { program } from 'commander';
2
3
  import chalk from 'chalk';
3
4
  import ora from 'ora';
@@ -10,6 +11,9 @@ import { formatSources } from './format.js';
10
11
  import { LABS_MODELS, MODEL_MAP } from './constants.js';
11
12
  import { setUseCurl } from './http.js';
12
13
  import { loadConfig } from './config.js';
14
+ import { resolveTimeoutMs } from './timeout.js';
15
+
16
+ const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
13
17
 
14
18
  // --- Output state ---
15
19
  let rawMode = false;
@@ -84,7 +88,7 @@ async function extractAndValidateBrowser(browser, profile) {
84
88
  program
85
89
  .name('pplx')
86
90
  .description('CLI for Perplexity AI')
87
- .version('0.1.1');
91
+ .version(pkg.version);
88
92
 
89
93
  program.option('--verbose', 'Enable verbose logging');
90
94
  program.option('--proxy <url>', 'Set proxy URL (sets HTTPS_PROXY env var)');
@@ -274,6 +278,13 @@ async function doSearch(query, opts) {
274
278
  }
275
279
 
276
280
  const mode = opts.mode || 'pro';
281
+ let timeoutMs;
282
+ try {
283
+ timeoutMs = resolveTimeoutMs({ ...opts, mode });
284
+ } catch (e) {
285
+ console.error(chalk.red(e.message));
286
+ process.exit(1);
287
+ }
277
288
  const sources = opts.sources ? opts.sources.split(',') : ['web'];
278
289
  const lang = opts.lang || 'en-US';
279
290
 
@@ -291,6 +302,7 @@ async function doSearch(query, opts) {
291
302
  chrome: opts.chrome,
292
303
  playwright: opts.playwright,
293
304
  curl: opts.curl,
305
+ timeoutMs,
294
306
  })) {
295
307
  lastData = data;
296
308
 
@@ -361,6 +373,7 @@ program
361
373
  .option('--chrome', 'Use Chrome CDP bridge instead of HTTP')
362
374
  .option('--playwright', 'Use Playwright headless Chromium instead of HTTP')
363
375
  .option('--no-playwright', 'Disable Playwright even if config enables it')
376
+ .option('--timeout-ms <duration>', 'Overall stream timeout: milliseconds by default, or use 120s / 10m')
364
377
  .option('--allow-anonymous', 'Allow anonymous Perplexity responses when cookies are expired')
365
378
  .action(async (queryArg, opts) => {
366
379
  if (opts.raw) { rawMode = true; chalk.level = 0; }
@@ -378,6 +391,7 @@ program
378
391
  .option('--chrome', 'Use Chrome CDP bridge')
379
392
  .option('--playwright', 'Use Playwright headless Chromium')
380
393
  .option('--no-playwright', 'Disable Playwright even if config enables it')
394
+ .option('--timeout-ms <duration>', 'Overall stream timeout: milliseconds by default, or use 120s / 10m')
381
395
  .option('--allow-anonymous', 'Allow anonymous Perplexity responses when cookies are expired')
382
396
  .action(async (queryArg, opts) => {
383
397
  const query = await resolveQuery(queryArg);
@@ -393,6 +407,7 @@ program
393
407
  .option('--chrome', 'Use Chrome CDP bridge')
394
408
  .option('--playwright', 'Use Playwright headless Chromium')
395
409
  .option('--no-playwright', 'Disable Playwright even if config enables it')
410
+ .option('--timeout-ms <duration>', 'Overall stream timeout: milliseconds by default, or use 120s / 10m')
396
411
  .option('--allow-anonymous', 'Allow anonymous Perplexity responses when cookies are expired')
397
412
  .action(async (queryArg, opts) => {
398
413
  const query = await resolveQuery(queryArg);
package/src/search.js CHANGED
@@ -126,6 +126,7 @@ async function* searchWithChrome(query, cookies, opts) {
126
126
  method: 'POST',
127
127
  headers: { 'content-type': 'application/json' },
128
128
  body,
129
+ timeout: opts.timeoutMs,
129
130
  }
130
131
  )) {
131
132
  parser.feed(chunk);
@@ -177,6 +178,7 @@ async function* searchWithPlaywright(query, cookies, opts) {
177
178
  method: 'POST',
178
179
  headers: { 'content-type': 'application/json' },
179
180
  body,
181
+ timeout: opts.timeoutMs,
180
182
  }
181
183
  )) {
182
184
  parser.feed(chunk);
@@ -219,6 +221,7 @@ async function* searchWithHttp(query, cookies, opts) {
219
221
  'cookie': cookieHeader(sessionCookies),
220
222
  },
221
223
  body,
224
+ timeout: opts.timeoutMs,
222
225
  });
223
226
 
224
227
  if (!resp.ok) {
package/src/timeout.js ADDED
@@ -0,0 +1,40 @@
1
+ export const DEFAULT_SEARCH_TIMEOUT_MS = 120000;
2
+ export const DEFAULT_RESEARCH_TIMEOUT_MS = 600000;
3
+
4
+ export function parseTimeoutMs(value, label = 'timeout') {
5
+ if (value == null || value === '') return null;
6
+
7
+ if (typeof value === 'number') {
8
+ if (Number.isFinite(value) && value > 0) return Math.trunc(value);
9
+ throw new Error(`${label} must be a positive number of milliseconds`);
10
+ }
11
+
12
+ const text = String(value).trim().toLowerCase();
13
+ const match = text.match(/^(\d+(?:\.\d+)?)(ms|s|m)?$/);
14
+ if (!match) {
15
+ throw new Error(`${label} must be a positive duration like 120000, 120s, or 10m`);
16
+ }
17
+
18
+ const amount = Number(match[1]);
19
+ const unit = match[2] ?? 'ms';
20
+ const multipliers = { ms: 1, s: 1000, m: 60000 };
21
+ const timeoutMs = amount * multipliers[unit];
22
+
23
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
24
+ throw new Error(`${label} must be a positive duration`);
25
+ }
26
+
27
+ return Math.trunc(timeoutMs);
28
+ }
29
+
30
+ export function resolveTimeoutMs(opts = {}) {
31
+ const explicit = parseTimeoutMs(opts.timeoutMs, '--timeout-ms');
32
+ if (explicit != null) return explicit;
33
+
34
+ const configured = parseTimeoutMs(opts.timeout, 'config timeout');
35
+ if (configured != null) return configured;
36
+
37
+ return opts.mode === 'deep-research'
38
+ ? DEFAULT_RESEARCH_TIMEOUT_MS
39
+ : DEFAULT_SEARCH_TIMEOUT_MS;
40
+ }