pplx-zero 2.1.0 → 2.2.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 CHANGED
@@ -1,133 +1,112 @@
1
- # pplx-zero
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/codewithkenzo/pplx-zero/main/logo.png" alt="pplx-zero" width="140" />
3
+ </p>
2
4
 
3
- [![npm version](https://img.shields.io/npm/v/pplx-zero.svg)](https://www.npmjs.com/package/pplx-zero)
4
- [![AUR version](https://img.shields.io/aur/version/pplx-zero)](https://aur.archlinux.org/packages/pplx-zero)
5
- ![Bun](https://img.shields.io/badge/runtime-bun-f9f1e1)
6
- ![License](https://img.shields.io/badge/license-MIT-blue)
5
+ <h1 align="center">pplx</h1>
7
6
 
8
- Search the web with AI from your terminal. Zero bloat, maximum speed.
7
+ <p align="center">
8
+ <strong>AI search from your terminal. Zero bloat.</strong>
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/pplx-zero"><img src="https://img.shields.io/npm/v/pplx-zero.svg?color=00d4ff" alt="npm"></a>
13
+ <a href="https://aur.archlinux.org/packages/pplx-zero"><img src="https://img.shields.io/aur/version/pplx-zero?color=00d4ff" alt="AUR"></a>
14
+ <img src="https://img.shields.io/badge/bun-runtime-f9f1e1" alt="Bun">
15
+ <img src="https://img.shields.io/badge/license-MIT-blue" alt="License">
16
+ </p>
17
+
18
+ ---
9
19
 
10
20
  ```bash
11
21
  pplx "what is bun"
12
22
  ```
13
23
 
14
- ## Why pplx-zero?
15
-
16
- - **Fast** — Bun-native, streams responses as they arrive
17
- - **Minimal** — ~400 lines of code, one dependency (zod)
18
- - **Powerful** — 5 models including deep research, file & image support
19
- - **Conversational** — Continue previous queries with `-c`
20
- - **Unix-friendly** — Pipes, JSON output, history, exit codes done right
24
+ Query [Perplexity AI](https://perplexity.ai) directly from your terminal. Responses stream in real-time with beautiful markdown formatting.
21
25
 
22
- ## Installation
26
+ ## Features
23
27
 
24
- ```bash
25
- # Bun (recommended)
26
- bun install -g pplx-zero
28
+ - **⚡ Streaming** — Answers appear as they're generated
29
+ - **💬 Conversations** — Continue with `-c` for multi-turn
30
+ - **📄 Documents** — Analyze PDFs, code, text files
31
+ - **🖼️ Images** — Describe screenshots and diagrams
32
+ - **📝 Export** — Save research to markdown
33
+ - **🎨 Pretty** — Rendered markdown by default
34
+ - **🕐 History** — Browse and search past queries
27
35
 
28
- # npm (requires bun installed)
29
- npm install -g pplx-zero
36
+ ## Install
30
37
 
31
- # Arch Linux
32
- yay -S pplx-zero
38
+ ```bash
39
+ bun install -g pplx-zero # recommended
40
+ npm install -g pplx-zero # requires bun
41
+ yay -S pplx-zero # arch linux
33
42
  ```
34
43
 
35
44
  ## Setup
36
45
 
37
- Get your API key from [Perplexity Settings](https://www.perplexity.ai/settings/api).
38
-
39
46
  ```bash
40
47
  export PERPLEXITY_API_KEY="pplx-..."
41
48
  ```
42
49
 
50
+ Get your key at [perplexity.ai/settings/api](https://www.perplexity.ai/settings/api)
51
+
43
52
  ## Usage
44
53
 
45
54
  ```bash
46
- # Quick search
47
- pplx "best practices for error handling in typescript"
55
+ # search
56
+ pplx "best typescript patterns 2025"
48
57
 
49
- # Use a more powerful model
50
- pplx -m sonar-pro "explain quantum entanglement simply"
58
+ # models
59
+ pplx -m sonar-pro "explain transformers"
60
+ pplx -m sonar-deep-research "AI regulation analysis"
51
61
 
52
- # Deep research mode (takes longer, more comprehensive)
53
- pplx -m sonar-deep-research "comprehensive analysis of AI regulation in 2024"
62
+ # conversation
63
+ pplx "what is rust"
64
+ pplx -c "compare to go"
54
65
 
55
- # Analyze a document
56
- pplx -f report.pdf "summarize the key findings"
66
+ # files
67
+ pplx -f paper.pdf "summarize"
68
+ pplx -i diagram.png "explain this"
57
69
 
58
- # Describe an image
59
- pplx -i screenshot.png "what's happening in this image"
70
+ # export
71
+ pplx "topic" -o research.md
60
72
 
61
- # Continue a conversation
62
- pplx "what is rust"
63
- pplx -c "how does it compare to go?"
64
- pplx -c "which should I learn first?"
73
+ # pretty markdown is default
74
+ pplx "explain monads"
65
75
 
66
- # Save research to markdown
67
- pplx -m sonar-deep-research "AI trends 2025" -o research.md
76
+ # raw output (no formatting)
77
+ pplx --raw "explain monads"
68
78
 
69
- # Get JSON output for scripting
70
- pplx --json "capital of france" | jq .answer
71
-
72
- # View query history
79
+ # history
73
80
  pplx --history
74
-
75
- # Search without saving to history
76
- pplx --no-history "sensitive query"
77
81
  ```
78
82
 
79
83
  ## Models
80
84
 
81
- | Model | Best For |
82
- |-------|----------|
83
- | `sonar` | Quick answers (default) |
85
+ | Model | Use |
86
+ |-------|-----|
87
+ | `sonar` | Quick answers |
84
88
  | `sonar-pro` | Complex questions |
85
- | `sonar-reasoning` | Step-by-step thinking |
89
+ | `sonar-reasoning` | Step-by-step |
86
90
  | `sonar-reasoning-pro` | Advanced reasoning |
87
- | `sonar-deep-research` | Comprehensive research |
91
+ | `sonar-deep-research` | Research reports |
88
92
 
89
93
  ## Options
90
94
 
91
95
  | Flag | Description |
92
96
  |------|-------------|
93
- | `-m, --model <name>` | Select model |
94
- | `-f, --file <path>` | Attach document (PDF, TXT, MD, etc.) |
95
- | `-i, --image <path>` | Attach image (PNG, JPG, WebP, etc.) |
96
- | `-o, --output <path>` | Save output to file (.md, .txt) |
97
- | `-c, --continue` | Continue from last query |
98
- | `--history` | Show query history |
99
- | `--no-history` | Don't save query to history |
100
- | `--json` | Output as JSON |
101
- | `-h, --help` | Show help |
102
-
103
- ## History & Sessions
104
-
105
- pplx-zero keeps a local history of your queries at `~/.pplx/history.jsonl`.
106
-
107
- ```bash
108
- # View recent queries
109
- pplx --history
110
-
111
- # Filter with grep
112
- pplx --history | grep "typescript"
113
-
114
- # Continue last conversation
115
- pplx -c "tell me more"
116
-
117
- # Skip history for sensitive queries
118
- pplx --no-history "private question"
119
- ```
120
-
121
- History auto-rotates at 1000 entries to keep the file small.
97
+ | `-m` | Model selection |
98
+ | `-f` | Attach file |
99
+ | `-i` | Attach image |
100
+ | `-o` | Output to file |
101
+ | `-c` | Continue conversation |
102
+ | `--raw` | Raw output (no markdown) |
103
+ | `--history` | View history |
104
+ | `--json` | JSON output |
122
105
 
123
- ## Exit Codes
106
+ ## Philosophy
124
107
 
125
- | Code | Meaning |
126
- |------|---------|
127
- | `0` | Success |
128
- | `1` | API error |
129
- | `2` | Configuration error |
108
+ ~400 lines. 1 dependency. No frameworks.
130
109
 
131
- ## License
110
+ ---
132
111
 
133
- MIT
112
+ <p align="center">MIT © <a href="https://github.com/codewithkenzo">kenzo</a></p>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pplx-zero",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Minimal Perplexity AI CLI - search from terminal",
5
5
  "author": "kenzo",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ import { encodeFile } from './files';
5
5
  import { getEnv } from './env';
6
6
  import { fmt, write, writeLn } from './output';
7
7
  import { appendHistory, readHistory, getLastEntry } from './history';
8
+ import { renderMarkdown, createMarkdownState } from './markdown';
8
9
 
9
10
  getEnv();
10
11
 
@@ -20,6 +21,7 @@ const { values, positionals } = parseArgs({
20
21
  'no-history': { type: 'boolean', default: false },
21
22
  continue: { type: 'boolean', short: 'c', default: false },
22
23
  output: { type: 'string', short: 'o' },
24
+ raw: { type: 'boolean', default: false },
23
25
  },
24
26
  allowPositionals: true,
25
27
  strict: true,
@@ -39,6 +41,7 @@ Options:
39
41
  -c, --continue Continue from last query (add context)
40
42
  --history Show query history
41
43
  --no-history Don't save this query to history
44
+ --raw Raw output (no markdown rendering)
42
45
  --json Output as JSON
43
46
  -h, --help Show this help
44
47
 
@@ -91,7 +94,7 @@ const file = filePath ? await encodeFile(filePath) : undefined;
91
94
 
92
95
  const startTime = Date.now();
93
96
  let fullContent = '';
94
- let outputBuffer = '';
97
+ const mdState = createMarkdownState();
95
98
 
96
99
  if (!values.json) {
97
100
  await write(fmt.model(model) + ' ');
@@ -102,7 +105,8 @@ await search(query, model, {
102
105
  onContent: async (text) => {
103
106
  fullContent += text;
104
107
  if (!values.json) {
105
- await write(text);
108
+ const out = values.raw ? text : renderMarkdown(text, mdState);
109
+ await write(out);
106
110
  }
107
111
  },
108
112
  onDone: async (citations, usage) => {
@@ -0,0 +1,64 @@
1
+ const c = {
2
+ reset: '\x1b[0m',
3
+ bold: '\x1b[1m',
4
+ dim: '\x1b[2m',
5
+ italic: '\x1b[3m',
6
+ cyan: '\x1b[36m',
7
+ yellow: '\x1b[33m',
8
+ magenta: '\x1b[35m',
9
+ gray: '\x1b[90m',
10
+ bgBlue: '\x1b[44m',
11
+ } as const;
12
+
13
+ export interface MarkdownState {
14
+ inCode: boolean;
15
+ codeLanguage: string;
16
+ }
17
+
18
+ export function createMarkdownState(): MarkdownState {
19
+ return { inCode: false, codeLanguage: '' };
20
+ }
21
+
22
+ export function renderMarkdown(chunk: string, state: MarkdownState): string {
23
+ let out = chunk;
24
+
25
+ const fenceMatch = out.match(/```(\w*)/);
26
+ if (fenceMatch) {
27
+ state.inCode = !state.inCode;
28
+ state.codeLanguage = fenceMatch[1] || '';
29
+ out = out.replace(/```\w*/g, state.inCode ? `${c.yellow}━━━ ${state.codeLanguage || 'code'} ━━━${c.reset}` : `${c.yellow}━━━━━━━━━━━${c.reset}`);
30
+ return out;
31
+ }
32
+
33
+ if (state.inCode) {
34
+ return `${c.dim}${out}${c.reset}`;
35
+ }
36
+
37
+ if (out.startsWith('### ')) {
38
+ return `${c.bold}${c.cyan}${out.slice(4)}${c.reset}`;
39
+ }
40
+ if (out.startsWith('## ')) {
41
+ return `${c.bold}${c.magenta}${out.slice(3)}${c.reset}`;
42
+ }
43
+ if (out.startsWith('# ')) {
44
+ return `${c.bold}${c.cyan}▸ ${out.slice(2)}${c.reset}`;
45
+ }
46
+
47
+ if (out.startsWith('> ')) {
48
+ return `${c.italic}${c.gray}│ ${out.slice(2)}${c.reset}`;
49
+ }
50
+
51
+ if (out.match(/^[\-\*] /)) {
52
+ out = out.replace(/^[\-\*] /, `${c.cyan}• ${c.reset}`);
53
+ }
54
+ if (out.match(/^\d+\. /)) {
55
+ out = out.replace(/^(\d+)\. /, `${c.cyan}$1.${c.reset} `);
56
+ }
57
+
58
+ out = out
59
+ .replace(/\*\*([^*]+)\*\*/g, `${c.bold}$1${c.reset}`)
60
+ .replace(/\*([^*]+)\*/g, `${c.italic}$1${c.reset}`)
61
+ .replace(/`([^`]+)`/g, `${c.bgBlue} $1 ${c.reset}`);
62
+
63
+ return out;
64
+ }