pplx-zero 1.0.0 → 1.0.1
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 +99 -42
- package/dist/cli.js +378 -61
- package/package.json +15 -3
package/README.md
CHANGED
|
@@ -1,23 +1,29 @@
|
|
|
1
|
-
#
|
|
1
|
+
# PPLX-Zero
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Fast Perplexity AI search CLI - minimal setup, maximal results
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://badge.fury.io/js/pplx-zero"><img src="https://badge.fury.io/js/pplx-zero.svg" alt="npm version"></a>
|
|
7
|
+
<a href="https://aur.archlinux.org/packages/pplx-zero"><img src="https://img.shields.io/aur/version/pplx-zero?style=flat-square" alt="AUR package"></a>
|
|
8
|
+
<a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-007ACC?logo=typescript&logoColor=white" alt="TypeScript"></a>
|
|
9
|
+
<a href="https://bun.sh"><img src="https://img.shields.io/badge/Bun-black?logo=bun&logoColor=white" alt="Bun"></a>
|
|
10
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
|
11
|
+
</p>
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
A fast TypeScript CLI for Perplexity AI search with multimodal support. Built with Bun runtime for performance and reliability.
|
|
10
14
|
|
|
11
|
-
##
|
|
15
|
+
## Features
|
|
12
16
|
|
|
13
|
-
- **⚡
|
|
14
|
-
- **🎯
|
|
15
|
-
- **📦 Batch Processing** - Handle multiple
|
|
16
|
-
- **🔄 Real-time
|
|
17
|
+
- **⚡ Fast Search** - Concurrent queries with intelligent rate limiting
|
|
18
|
+
- **🎯 Simple Setup** - Works with just an API key, no configuration required
|
|
19
|
+
- **📦 Batch Processing** - Handle multiple searches simultaneously
|
|
20
|
+
- **🔄 Real-time Updates** - JSONL streaming progress events
|
|
21
|
+
- **🖼️ File Analysis** - Process documents and images with AI models
|
|
22
|
+
- **🤖 AI Models** - Sonar, Sonar Pro, Sonar Deep Research, Sonar Reasoning
|
|
17
23
|
- **🛡️ Type Safe** - Full Zod validation and TypeScript support
|
|
18
|
-
- **🌍 Cross-Platform** - Native Bun runtime
|
|
24
|
+
- **🌍 Cross-Platform** - Native Bun runtime support
|
|
19
25
|
|
|
20
|
-
##
|
|
26
|
+
## Quick Start
|
|
21
27
|
|
|
22
28
|
### 1️⃣ Install
|
|
23
29
|
|
|
@@ -29,8 +35,6 @@ npm install -g pplx-zero
|
|
|
29
35
|
|
|
30
36
|
# AUR (Arch Linux)
|
|
31
37
|
yay -S pplx-zero
|
|
32
|
-
# or
|
|
33
|
-
pikaur -S pplx-zero
|
|
34
38
|
# or manual AUR
|
|
35
39
|
git clone https://aur.archlinux.org/pplx-zero.git
|
|
36
40
|
cd pplx-zero
|
|
@@ -51,38 +55,58 @@ sudo ln -s "$(pwd)/dist/cli.js" /usr/local/bin/pplx
|
|
|
51
55
|
|
|
52
56
|
### 2️⃣ Setup API Key
|
|
53
57
|
|
|
58
|
+
**Linux/macOS:**
|
|
54
59
|
```bash
|
|
55
|
-
export PERPLEXITY_API_KEY="your-
|
|
56
|
-
# Or use the fallback
|
|
57
|
-
export PERPLEXITY_AI_API_KEY="your-alternative-api-key"
|
|
60
|
+
export PERPLEXITY_API_KEY="your-api-key"
|
|
58
61
|
```
|
|
59
62
|
|
|
63
|
+
**Windows:**
|
|
64
|
+
```cmd
|
|
65
|
+
setx PERPLEXITY_API_KEY "your-api-key"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Get your API key:** https://www.perplexity.ai/account/api/keys
|
|
69
|
+
|
|
60
70
|
### 3️⃣ Start Searching
|
|
61
71
|
|
|
62
72
|
```bash
|
|
63
|
-
#
|
|
73
|
+
# Basic search
|
|
64
74
|
pplx "latest AI developments"
|
|
65
75
|
|
|
66
|
-
#
|
|
76
|
+
# Choose model
|
|
77
|
+
pplx --model sonar-pro "Detailed analysis"
|
|
78
|
+
|
|
79
|
+
# Document analysis
|
|
80
|
+
pplx --attach report.pdf "Summarize this document"
|
|
81
|
+
|
|
82
|
+
# Image analysis
|
|
83
|
+
pplx --attach-image screenshot.png "Analyze this interface"
|
|
84
|
+
|
|
85
|
+
# Batch processing
|
|
67
86
|
pplx --input queries.json
|
|
68
87
|
|
|
69
|
-
# Stream
|
|
88
|
+
# Stream processing
|
|
70
89
|
cat queries.jsonl | pplx --stdin
|
|
71
90
|
```
|
|
72
91
|
|
|
73
|
-
##
|
|
92
|
+
## Usage Guide
|
|
74
93
|
|
|
75
|
-
### Command Line
|
|
94
|
+
### Command Line Options
|
|
76
95
|
|
|
77
96
|
```bash
|
|
78
|
-
#
|
|
79
|
-
pplx "your search query"
|
|
80
|
-
|
|
81
|
-
# Advanced options
|
|
97
|
+
# Search with custom settings
|
|
82
98
|
pplx --concurrency 10 --timeout 60000 --format jsonl "machine learning trends"
|
|
83
99
|
|
|
84
|
-
#
|
|
85
|
-
pplx --
|
|
100
|
+
# Model selection
|
|
101
|
+
pplx --model sonar-pro "Detailed analysis"
|
|
102
|
+
pplx --model sonar-reasoning "Complex problem solving"
|
|
103
|
+
|
|
104
|
+
# File attachments
|
|
105
|
+
pplx --attach document.pdf "Summarize this report"
|
|
106
|
+
pplx --attach-image chart.png "Analyze this chart"
|
|
107
|
+
|
|
108
|
+
# Async processing
|
|
109
|
+
pplx --async --webhook https://api.example.com/callback "Research task"
|
|
86
110
|
```
|
|
87
111
|
|
|
88
112
|
### Batch Processing
|
|
@@ -110,6 +134,28 @@ Process with:
|
|
|
110
134
|
pplx --input queries.json --format jsonl
|
|
111
135
|
```
|
|
112
136
|
|
|
137
|
+
### File Attachments
|
|
138
|
+
|
|
139
|
+
Supported file formats for analysis:
|
|
140
|
+
|
|
141
|
+
**Documents (max 50MB):**
|
|
142
|
+
- PDF, DOC, DOCX, TXT, RTF
|
|
143
|
+
|
|
144
|
+
**Images (max 50MB):**
|
|
145
|
+
- PNG, JPEG, WebP, HEIF, HEIC, GIF
|
|
146
|
+
|
|
147
|
+
**Examples:**
|
|
148
|
+
```bash
|
|
149
|
+
# Document analysis
|
|
150
|
+
pplx --attach report.pdf "Summarize this document"
|
|
151
|
+
|
|
152
|
+
# Image analysis
|
|
153
|
+
pplx --attach-image screenshot.png "Analyze this interface"
|
|
154
|
+
|
|
155
|
+
# Multiple files
|
|
156
|
+
pplx --attach document.txt --attach-image chart.png "Analyze this data"
|
|
157
|
+
```
|
|
158
|
+
|
|
113
159
|
### Programmatic Usage
|
|
114
160
|
|
|
115
161
|
```typescript
|
|
@@ -128,7 +174,7 @@ const result = await tool.runBatch({
|
|
|
128
174
|
console.log(result);
|
|
129
175
|
```
|
|
130
176
|
|
|
131
|
-
##
|
|
177
|
+
## Configuration
|
|
132
178
|
|
|
133
179
|
| Option | Short | Type | Default | Description |
|
|
134
180
|
|--------|-------|------|---------|-------------|
|
|
@@ -137,11 +183,22 @@ console.log(result);
|
|
|
137
183
|
| `--concurrency` | `-c` | number | 5 | Max concurrent requests (1-20) |
|
|
138
184
|
| `--timeout` | `-t` | number | 30000 | Request timeout in ms (1000-300000) |
|
|
139
185
|
| `--format` | `-f` | string | json | Output format: json|jsonl |
|
|
140
|
-
| `--
|
|
186
|
+
| `--model` | `-m` | string | sonar | AI model to use |
|
|
187
|
+
| `--attach` | - | string[] | - | Attach document files |
|
|
188
|
+
| `--attach-image` | - | string[] | - | Attach image files |
|
|
189
|
+
| `--async` | - | boolean | false | Process requests asynchronously |
|
|
190
|
+
| `--webhook` | - | string | - | Webhook URL for async notifications |
|
|
141
191
|
| `--version` | `-v` | boolean | - | Show version |
|
|
142
192
|
| `--help` | `-h` | boolean | - | Show help |
|
|
143
193
|
|
|
144
|
-
|
|
194
|
+
### AI Models
|
|
195
|
+
|
|
196
|
+
- `sonar` - Fast, concise responses (default)
|
|
197
|
+
- `sonar-pro` - Detailed, comprehensive responses
|
|
198
|
+
- `sonar-deep-research` - In-depth research with web search
|
|
199
|
+
- `sonar-reasoning` - Step-by-step logical reasoning
|
|
200
|
+
|
|
201
|
+
## Output Formats
|
|
145
202
|
|
|
146
203
|
### JSON (Default)
|
|
147
204
|
```json
|
|
@@ -164,7 +221,7 @@ pplx --format jsonl "AI trends"
|
|
|
164
221
|
```
|
|
165
222
|
Each result printed as a separate JSON line for real-time processing.
|
|
166
223
|
|
|
167
|
-
##
|
|
224
|
+
## Development
|
|
168
225
|
|
|
169
226
|
```bash
|
|
170
227
|
# Development mode
|
|
@@ -183,15 +240,15 @@ bun run build
|
|
|
183
240
|
bun run build:binary
|
|
184
241
|
```
|
|
185
242
|
|
|
186
|
-
##
|
|
243
|
+
## Architecture
|
|
187
244
|
|
|
188
|
-
- **Bun Runtime** -
|
|
245
|
+
- **Bun Runtime** - Fast JavaScript runtime
|
|
189
246
|
- **Zod Validation** - Type-safe schema validation
|
|
190
|
-
- **
|
|
191
|
-
- **
|
|
247
|
+
- **Error Handling** - Resilient error recovery
|
|
248
|
+
- **Concurrency Control** - Semaphore pattern for rate limiting
|
|
192
249
|
- **Streaming Events** - Real-time progress updates
|
|
193
250
|
|
|
194
|
-
##
|
|
251
|
+
## Security Features
|
|
195
252
|
|
|
196
253
|
- Environment variable API key management
|
|
197
254
|
- Input validation and sanitization
|
|
@@ -199,7 +256,7 @@ bun run build:binary
|
|
|
199
256
|
- Error information filtering
|
|
200
257
|
- No external dependencies beyond core runtime
|
|
201
258
|
|
|
202
|
-
##
|
|
259
|
+
## Error Handling
|
|
203
260
|
|
|
204
261
|
PPLX-Zero provides comprehensive error classification:
|
|
205
262
|
|
|
@@ -215,14 +272,14 @@ enum ErrorCode {
|
|
|
215
272
|
}
|
|
216
273
|
```
|
|
217
274
|
|
|
218
|
-
##
|
|
275
|
+
## Contributing
|
|
219
276
|
|
|
220
277
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
221
278
|
|
|
222
|
-
##
|
|
279
|
+
## License
|
|
223
280
|
|
|
224
281
|
MIT License - see [LICENSE](LICENSE) file for details.
|
|
225
282
|
|
|
226
283
|
---
|
|
227
284
|
|
|
228
|
-
**Built with
|
|
285
|
+
**Built with [Bun](https://bun.sh) and [Perplexity AI](https://www.perplexity.ai)**
|
package/dist/cli.js
CHANGED
|
@@ -31,8 +31,8 @@ var __export = (target, all) => {
|
|
|
31
31
|
var require_package = __commonJS((exports, module) => {
|
|
32
32
|
module.exports = {
|
|
33
33
|
name: "pplx-zero",
|
|
34
|
-
version: "1.0.
|
|
35
|
-
description: "
|
|
34
|
+
version: "1.0.1",
|
|
35
|
+
description: "Fast Perplexity AI search CLI with multimodal support - minimal setup, maximal results",
|
|
36
36
|
type: "module",
|
|
37
37
|
main: "dist/index.js",
|
|
38
38
|
bin: {
|
|
@@ -80,10 +80,22 @@ var require_package = __commonJS((exports, module) => {
|
|
|
80
80
|
"minimal",
|
|
81
81
|
"fast",
|
|
82
82
|
"productivity",
|
|
83
|
-
"zero-config"
|
|
83
|
+
"zero-config",
|
|
84
|
+
"multimodal",
|
|
85
|
+
"attachments",
|
|
86
|
+
"images",
|
|
87
|
+
"documents",
|
|
88
|
+
"sonar",
|
|
89
|
+
"reasoning",
|
|
90
|
+
"research",
|
|
91
|
+
"async"
|
|
84
92
|
],
|
|
85
93
|
author: "Kenzo",
|
|
86
94
|
license: "MIT",
|
|
95
|
+
repository: {
|
|
96
|
+
type: "git",
|
|
97
|
+
url: "https://github.com/codewithkenzo/pplx-zero.git"
|
|
98
|
+
},
|
|
87
99
|
files: [
|
|
88
100
|
"dist",
|
|
89
101
|
"README.md",
|
|
@@ -5602,10 +5614,26 @@ Perplexity.Async = Async;
|
|
|
5602
5614
|
Perplexity.Search = Search;
|
|
5603
5615
|
// src/schema.ts
|
|
5604
5616
|
var SCHEMA_VERSION = "1.0.0";
|
|
5617
|
+
var SonarModelSchema = exports_external.enum(["sonar", "sonar-pro", "sonar-deep-research", "sonar-reasoning"]);
|
|
5618
|
+
var AttachmentSchema = exports_external.object({
|
|
5619
|
+
name: exports_external.string().min(1, "Attachment name is required"),
|
|
5620
|
+
extension: exports_external.string().min(1, "File extension is required"),
|
|
5621
|
+
mimeType: exports_external.string().min(1, "MIME type is required"),
|
|
5622
|
+
url: exports_external.string().min(1, "Attachment URL is required"),
|
|
5623
|
+
size: exports_external.number().int().min(0, "File size must be non-negative").optional()
|
|
5624
|
+
});
|
|
5625
|
+
var AttachmentInputSchema = exports_external.object({
|
|
5626
|
+
path: exports_external.string().min(1, "File path is required"),
|
|
5627
|
+
name: exports_external.string().optional(),
|
|
5628
|
+
type: exports_external.enum(["image", "document"]).optional()
|
|
5629
|
+
});
|
|
5605
5630
|
var SearchQuerySchema = exports_external.object({
|
|
5606
5631
|
query: exports_external.string().min(1, "Query cannot be empty"),
|
|
5607
5632
|
maxResults: exports_external.number().int().min(1).max(50).optional().default(5),
|
|
5608
|
-
country: exports_external.string().length(2).regex(/^[A-Z]{2}$/, "Invalid country code").optional()
|
|
5633
|
+
country: exports_external.string().length(2).regex(/^[A-Z]{2}$/, "Invalid country code").optional(),
|
|
5634
|
+
model: SonarModelSchema.optional().default("sonar"),
|
|
5635
|
+
attachments: exports_external.array(AttachmentSchema).optional(),
|
|
5636
|
+
attachmentInputs: exports_external.array(AttachmentInputSchema).optional()
|
|
5609
5637
|
});
|
|
5610
5638
|
var SearchInputV1Schema = exports_external.object({
|
|
5611
5639
|
id: exports_external.string().uuid().optional(),
|
|
@@ -5613,9 +5641,22 @@ var SearchInputV1Schema = exports_external.object({
|
|
|
5613
5641
|
args: SearchQuerySchema,
|
|
5614
5642
|
options: exports_external.object({
|
|
5615
5643
|
timeoutMs: exports_external.number().int().min(1000).max(300000).optional().default(30000),
|
|
5616
|
-
workspace: exports_external.string().optional()
|
|
5644
|
+
workspace: exports_external.string().optional(),
|
|
5645
|
+
async: exports_external.boolean().optional().default(false),
|
|
5646
|
+
webhook: exports_external.string().url().optional()
|
|
5617
5647
|
}).optional()
|
|
5618
5648
|
});
|
|
5649
|
+
var AsyncJobSchema = exports_external.object({
|
|
5650
|
+
id: exports_external.string(),
|
|
5651
|
+
model: SonarModelSchema,
|
|
5652
|
+
status: exports_external.enum(["CREATED", "IN_PROGRESS", "COMPLETED", "FAILED"]),
|
|
5653
|
+
createdAt: exports_external.number(),
|
|
5654
|
+
startedAt: exports_external.number().optional(),
|
|
5655
|
+
completedAt: exports_external.number().optional(),
|
|
5656
|
+
failedAt: exports_external.number().optional(),
|
|
5657
|
+
errorMessage: exports_external.string().optional(),
|
|
5658
|
+
response: exports_external.any().optional()
|
|
5659
|
+
});
|
|
5619
5660
|
var BatchSearchInputV1Schema = exports_external.object({
|
|
5620
5661
|
version: exports_external.string().regex(/^\d+\.\d+\.\d+$/).default(SCHEMA_VERSION),
|
|
5621
5662
|
requests: exports_external.array(SearchInputV1Schema).min(1).max(100),
|
|
@@ -6438,6 +6479,142 @@ function createLogger(context = {}) {
|
|
|
6438
6479
|
return new Logger(1 /* INFO */, context);
|
|
6439
6480
|
}
|
|
6440
6481
|
|
|
6482
|
+
// src/util/attachments.ts
|
|
6483
|
+
import { readFile, stat } from "node:fs/promises";
|
|
6484
|
+
import { extname, basename } from "node:path";
|
|
6485
|
+
var IMAGE_MIME_TYPES = {
|
|
6486
|
+
".png": "image/png",
|
|
6487
|
+
".jpg": "image/jpeg",
|
|
6488
|
+
".jpeg": "image/jpeg",
|
|
6489
|
+
".webp": "image/webp",
|
|
6490
|
+
".heif": "image/heif",
|
|
6491
|
+
".heic": "image/heic",
|
|
6492
|
+
".gif": "image/gif"
|
|
6493
|
+
};
|
|
6494
|
+
var DOCUMENT_MIME_TYPES = {
|
|
6495
|
+
".pdf": "application/pdf",
|
|
6496
|
+
".doc": "application/msword",
|
|
6497
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
6498
|
+
".txt": "text/plain",
|
|
6499
|
+
".rtf": "application/rtf"
|
|
6500
|
+
};
|
|
6501
|
+
var MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
6502
|
+
var MAX_ATTACHMENTS = 10;
|
|
6503
|
+
function isSupportedFileType(extension) {
|
|
6504
|
+
return extension.toLowerCase() in IMAGE_MIME_TYPES || extension.toLowerCase() in DOCUMENT_MIME_TYPES;
|
|
6505
|
+
}
|
|
6506
|
+
function isImageFile(extension) {
|
|
6507
|
+
return extension.toLowerCase() in IMAGE_MIME_TYPES;
|
|
6508
|
+
}
|
|
6509
|
+
function getMimeType(extension) {
|
|
6510
|
+
const ext = extension.toLowerCase();
|
|
6511
|
+
return IMAGE_MIME_TYPES[ext] || DOCUMENT_MIME_TYPES[ext] || null;
|
|
6512
|
+
}
|
|
6513
|
+
function calculateImageTokens(width, height) {
|
|
6514
|
+
return Math.ceil(width * height / 750);
|
|
6515
|
+
}
|
|
6516
|
+
async function getImageDimensions(filePath) {
|
|
6517
|
+
try {
|
|
6518
|
+
const buffer = await readFile(filePath);
|
|
6519
|
+
if (buffer.length < 24)
|
|
6520
|
+
return null;
|
|
6521
|
+
return { width: 1920, height: 1080 };
|
|
6522
|
+
} catch {
|
|
6523
|
+
return null;
|
|
6524
|
+
}
|
|
6525
|
+
}
|
|
6526
|
+
async function validateFile(filePath) {
|
|
6527
|
+
try {
|
|
6528
|
+
const stats = await stat(filePath);
|
|
6529
|
+
if (!stats.isFile()) {
|
|
6530
|
+
return { valid: false, error: "Path is not a file" };
|
|
6531
|
+
}
|
|
6532
|
+
if (stats.size > MAX_FILE_SIZE) {
|
|
6533
|
+
return {
|
|
6534
|
+
valid: false,
|
|
6535
|
+
error: `File size ${Math.round(stats.size / 1024 / 1024)}MB exceeds maximum ${MAX_FILE_SIZE / 1024 / 1024}MB`,
|
|
6536
|
+
size: stats.size
|
|
6537
|
+
};
|
|
6538
|
+
}
|
|
6539
|
+
return { valid: true, size: stats.size };
|
|
6540
|
+
} catch (error) {
|
|
6541
|
+
return {
|
|
6542
|
+
valid: false,
|
|
6543
|
+
error: `Cannot access file: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
6544
|
+
};
|
|
6545
|
+
}
|
|
6546
|
+
}
|
|
6547
|
+
async function processAttachment(input) {
|
|
6548
|
+
const filePath = input.path;
|
|
6549
|
+
const fileName = input.name || basename(filePath);
|
|
6550
|
+
const extension = extname(fileName);
|
|
6551
|
+
const mimeType = getMimeType(extension);
|
|
6552
|
+
if (!mimeType) {
|
|
6553
|
+
throw new Error(`Unsupported file type: ${extension}`);
|
|
6554
|
+
}
|
|
6555
|
+
const validation = await validateFile(filePath);
|
|
6556
|
+
if (!validation.valid) {
|
|
6557
|
+
throw new Error(validation.error || "File validation failed");
|
|
6558
|
+
}
|
|
6559
|
+
const fileContent = await readFile(filePath);
|
|
6560
|
+
const base64Content = fileContent.toString("base64");
|
|
6561
|
+
let url;
|
|
6562
|
+
if (isImageFile(extension)) {
|
|
6563
|
+
url = `data:${mimeType};base64,${base64Content}`;
|
|
6564
|
+
} else {
|
|
6565
|
+
url = base64Content;
|
|
6566
|
+
}
|
|
6567
|
+
let tokens;
|
|
6568
|
+
if (isImageFile(extension)) {
|
|
6569
|
+
const dimensions = await getImageDimensions(filePath);
|
|
6570
|
+
if (dimensions) {
|
|
6571
|
+
tokens = calculateImageTokens(dimensions.width, dimensions.height);
|
|
6572
|
+
}
|
|
6573
|
+
}
|
|
6574
|
+
return {
|
|
6575
|
+
name: fileName,
|
|
6576
|
+
extension,
|
|
6577
|
+
mimeType,
|
|
6578
|
+
url,
|
|
6579
|
+
size: validation.size
|
|
6580
|
+
};
|
|
6581
|
+
}
|
|
6582
|
+
async function processAttachments(inputs) {
|
|
6583
|
+
if (inputs.length > MAX_ATTACHMENTS) {
|
|
6584
|
+
throw new Error(`Maximum ${MAX_ATTACHMENTS} attachments allowed per request`);
|
|
6585
|
+
}
|
|
6586
|
+
const attachments = [];
|
|
6587
|
+
for (const input of inputs) {
|
|
6588
|
+
try {
|
|
6589
|
+
const attachment = await processAttachment(input);
|
|
6590
|
+
attachments.push(attachment);
|
|
6591
|
+
} catch (error) {
|
|
6592
|
+
throw new Error(`Failed to process attachment ${input.path}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
6593
|
+
}
|
|
6594
|
+
}
|
|
6595
|
+
return attachments;
|
|
6596
|
+
}
|
|
6597
|
+
function validateAttachmentInputs(inputs) {
|
|
6598
|
+
const errors2 = [];
|
|
6599
|
+
if (inputs.length > MAX_ATTACHMENTS) {
|
|
6600
|
+
errors2.push(`Maximum ${MAX_ATTACHMENTS} attachments allowed per request`);
|
|
6601
|
+
}
|
|
6602
|
+
for (const input of inputs) {
|
|
6603
|
+
if (!input.path) {
|
|
6604
|
+
errors2.push("Attachment path is required");
|
|
6605
|
+
continue;
|
|
6606
|
+
}
|
|
6607
|
+
const extension = extname(input.path).toLowerCase();
|
|
6608
|
+
if (!isSupportedFileType(extension)) {
|
|
6609
|
+
errors2.push(`Unsupported file type: ${extension} for ${input.path}`);
|
|
6610
|
+
}
|
|
6611
|
+
}
|
|
6612
|
+
return {
|
|
6613
|
+
valid: errors2.length === 0,
|
|
6614
|
+
errors: errors2
|
|
6615
|
+
};
|
|
6616
|
+
}
|
|
6617
|
+
|
|
6441
6618
|
// src/index.ts
|
|
6442
6619
|
class PerplexitySearchTool {
|
|
6443
6620
|
client;
|
|
@@ -6445,6 +6622,7 @@ class PerplexitySearchTool {
|
|
|
6445
6622
|
resilience;
|
|
6446
6623
|
logger;
|
|
6447
6624
|
metrics;
|
|
6625
|
+
defaultModel;
|
|
6448
6626
|
constructor(workspacePath, options = {}) {
|
|
6449
6627
|
const apiKey = process.env.PERPLEXITY_API_KEY || process.env.PERPLEXITY_AI_API_KEY;
|
|
6450
6628
|
if (!apiKey) {
|
|
@@ -6452,6 +6630,7 @@ class PerplexitySearchTool {
|
|
|
6452
6630
|
}
|
|
6453
6631
|
this.client = new Perplexity({ apiKey });
|
|
6454
6632
|
this.workspace = new WorkspaceSandbox(workspacePath);
|
|
6633
|
+
this.defaultModel = options.defaultModel || "sonar";
|
|
6455
6634
|
const resilienceConfig = options.resilienceProfile && options.resilienceProfile !== "custom" ? DEFAULT_CONFIGS[options.resilienceProfile] : options.resilienceConfig || DEFAULT_CONFIGS.balanced;
|
|
6456
6635
|
this.resilience = new ResilienceManager(resilienceConfig);
|
|
6457
6636
|
this.logger = createLogger({
|
|
@@ -6462,7 +6641,8 @@ class PerplexitySearchTool {
|
|
|
6462
6641
|
this.metrics = new MetricsCollector;
|
|
6463
6642
|
this.logger.info("PerplexitySearchTool initialized", {
|
|
6464
6643
|
workspace: workspacePath,
|
|
6465
|
-
resilienceProfile: options.resilienceProfile || "balanced"
|
|
6644
|
+
resilienceProfile: options.resilienceProfile || "balanced",
|
|
6645
|
+
defaultModel: this.defaultModel
|
|
6466
6646
|
});
|
|
6467
6647
|
}
|
|
6468
6648
|
async runTask(input, signal) {
|
|
@@ -6473,9 +6653,27 @@ class PerplexitySearchTool {
|
|
|
6473
6653
|
const validatedInput = SearchInputV1Schema.parse(input);
|
|
6474
6654
|
traceLogger.info("Starting search task", {
|
|
6475
6655
|
query: validatedInput.args.query,
|
|
6656
|
+
model: validatedInput.args.model || this.defaultModel,
|
|
6476
6657
|
maxResults: validatedInput.args.maxResults,
|
|
6477
|
-
country: validatedInput.args.country
|
|
6658
|
+
country: validatedInput.args.country,
|
|
6659
|
+
hasAttachments: validatedInput.args.attachments && validatedInput.args.attachments.length > 0,
|
|
6660
|
+
hasAttachmentInputs: validatedInput.args.attachmentInputs && validatedInput.args.attachmentInputs.length > 0,
|
|
6661
|
+
async: validatedInput.options?.async,
|
|
6662
|
+
webhook: validatedInput.options?.webhook
|
|
6478
6663
|
});
|
|
6664
|
+
let attachments = [];
|
|
6665
|
+
if (validatedInput.args.attachmentInputs && validatedInput.args.attachmentInputs.length > 0) {
|
|
6666
|
+
const validation = validateAttachmentInputs(validatedInput.args.attachmentInputs);
|
|
6667
|
+
if (!validation.valid) {
|
|
6668
|
+
throw new Error(`Attachment validation failed: ${validation.errors.join(", ")}`);
|
|
6669
|
+
}
|
|
6670
|
+
attachments = await processAttachments(validatedInput.args.attachmentInputs);
|
|
6671
|
+
} else if (validatedInput.args.attachments) {
|
|
6672
|
+
attachments = validatedInput.args.attachments;
|
|
6673
|
+
}
|
|
6674
|
+
if (validatedInput.args.attachments) {
|
|
6675
|
+
attachments = [...attachments, ...validatedInput.args.attachments];
|
|
6676
|
+
}
|
|
6479
6677
|
const timeoutMs = validatedInput.options?.timeoutMs || 30000;
|
|
6480
6678
|
const controller = new AbortController;
|
|
6481
6679
|
if (signal) {
|
|
@@ -6483,15 +6681,18 @@ class PerplexitySearchTool {
|
|
|
6483
6681
|
}
|
|
6484
6682
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
6485
6683
|
try {
|
|
6486
|
-
const { result, metrics: metrics2 } = await traceLogger.measure("search_operation", () => this.resilience.execute(() => this.
|
|
6684
|
+
const { result, metrics: metrics2 } = await traceLogger.measure("search_operation", () => this.resilience.execute(() => this.performChatCompletion(validatedInput.args, attachments, controller.signal)), {
|
|
6487
6685
|
query: validatedInput.args.query,
|
|
6686
|
+
model: validatedInput.args.model || this.defaultModel,
|
|
6488
6687
|
maxResults: validatedInput.args.maxResults,
|
|
6489
|
-
country: validatedInput.args.country
|
|
6688
|
+
country: validatedInput.args.country,
|
|
6689
|
+
attachmentCount: attachments.length
|
|
6490
6690
|
});
|
|
6491
6691
|
clearTimeout(timeoutId);
|
|
6492
6692
|
const duration = Date.now() - startTime;
|
|
6493
6693
|
this.metrics.recordMetric("search_duration", duration, "ms", {
|
|
6494
6694
|
query: validatedInput.args.query,
|
|
6695
|
+
model: validatedInput.args.model || this.defaultModel,
|
|
6495
6696
|
success: "true"
|
|
6496
6697
|
});
|
|
6497
6698
|
this.metrics.incrementCounter("search_requests_total", 1, { status: "success" });
|
|
@@ -6500,7 +6701,8 @@ class PerplexitySearchTool {
|
|
|
6500
6701
|
});
|
|
6501
6702
|
traceLogger.info("Search task completed successfully", {
|
|
6502
6703
|
resultCount: result.length,
|
|
6503
|
-
duration
|
|
6704
|
+
duration,
|
|
6705
|
+
model: validatedInput.args.model || this.defaultModel
|
|
6504
6706
|
});
|
|
6505
6707
|
return {
|
|
6506
6708
|
id,
|
|
@@ -6614,33 +6816,98 @@ class PerplexitySearchTool {
|
|
|
6614
6816
|
};
|
|
6615
6817
|
}
|
|
6616
6818
|
}
|
|
6617
|
-
async
|
|
6819
|
+
async performChatCompletion(query, attachments, signal) {
|
|
6618
6820
|
try {
|
|
6619
|
-
const
|
|
6620
|
-
|
|
6621
|
-
|
|
6821
|
+
const messages = [
|
|
6822
|
+
{
|
|
6823
|
+
role: "system",
|
|
6824
|
+
content: "You are a helpful AI assistant. Provide accurate, concise responses with citations when possible."
|
|
6825
|
+
}
|
|
6826
|
+
];
|
|
6827
|
+
const userMessage = {
|
|
6828
|
+
role: "user",
|
|
6829
|
+
content: [
|
|
6830
|
+
{ type: "text", text: query.query }
|
|
6831
|
+
]
|
|
6622
6832
|
};
|
|
6623
|
-
if (
|
|
6624
|
-
|
|
6833
|
+
if (attachments.length > 0) {
|
|
6834
|
+
for (const attachment of attachments) {
|
|
6835
|
+
if (attachment.mimeType.startsWith("image/")) {
|
|
6836
|
+
userMessage.content.push({
|
|
6837
|
+
type: "image_url",
|
|
6838
|
+
image_url: {
|
|
6839
|
+
url: attachment.url
|
|
6840
|
+
}
|
|
6841
|
+
});
|
|
6842
|
+
} else {
|
|
6843
|
+
userMessage.content.push({
|
|
6844
|
+
type: "file_url",
|
|
6845
|
+
file_url: {
|
|
6846
|
+
url: attachment.url
|
|
6847
|
+
},
|
|
6848
|
+
file_name: attachment.name
|
|
6849
|
+
});
|
|
6850
|
+
}
|
|
6851
|
+
}
|
|
6625
6852
|
}
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
}
|
|
6853
|
+
messages.push(userMessage);
|
|
6854
|
+
const chatParams = {
|
|
6855
|
+
model: query.model || this.defaultModel,
|
|
6856
|
+
messages,
|
|
6857
|
+
max_tokens: 4000,
|
|
6858
|
+
temperature: 0.1,
|
|
6859
|
+
top_p: 0.9,
|
|
6860
|
+
search_domain_filter: query.country ? [`.${query.country.toLowerCase()}`] : undefined,
|
|
6861
|
+
return_images: false,
|
|
6862
|
+
return_related_questions: false,
|
|
6863
|
+
search_recency_filter: undefined,
|
|
6864
|
+
search_after_date_filter: undefined,
|
|
6865
|
+
search_before_date_filter: undefined,
|
|
6866
|
+
last_updated_after_filter: undefined,
|
|
6867
|
+
last_updated_before_filter: undefined,
|
|
6868
|
+
top_k: 0,
|
|
6869
|
+
stream: false,
|
|
6870
|
+
presence_penalty: 0,
|
|
6871
|
+
frequency_penalty: 0,
|
|
6872
|
+
disable_search: false,
|
|
6873
|
+
enable_search_classifier: false,
|
|
6874
|
+
web_search_options: {
|
|
6875
|
+
search_context_size: "high"
|
|
6876
|
+
}
|
|
6877
|
+
};
|
|
6878
|
+
const response = await this.client.chat.completions.create(chatParams);
|
|
6879
|
+
if (response.choices && response.choices.length > 0) {
|
|
6880
|
+
const choice = response.choices[0];
|
|
6881
|
+
const content = choice.message?.content || "";
|
|
6882
|
+
const results = [];
|
|
6883
|
+
results.push({
|
|
6884
|
+
title: "AI Response",
|
|
6885
|
+
url: "https://www.perplexity.ai/",
|
|
6886
|
+
snippet: content,
|
|
6887
|
+
date: new Date().toISOString().split("T")[0]
|
|
6888
|
+
});
|
|
6889
|
+
if (response.search_results && Array.isArray(response.search_results)) {
|
|
6890
|
+
response.search_results.forEach((result) => {
|
|
6891
|
+
results.push({
|
|
6892
|
+
title: result.title || "Search Result",
|
|
6893
|
+
url: result.url || "",
|
|
6894
|
+
snippet: result.snippet || "",
|
|
6895
|
+
date: result.date || undefined
|
|
6896
|
+
});
|
|
6897
|
+
});
|
|
6898
|
+
}
|
|
6899
|
+
const maxResults = query.maxResults || 5;
|
|
6900
|
+
return results.slice(0, maxResults);
|
|
6634
6901
|
}
|
|
6635
6902
|
return [{
|
|
6636
|
-
title: "
|
|
6903
|
+
title: "No Response",
|
|
6637
6904
|
url: "https://www.perplexity.ai/",
|
|
6638
|
-
snippet: "No
|
|
6905
|
+
snippet: "No response received from the model",
|
|
6639
6906
|
date: undefined
|
|
6640
6907
|
}];
|
|
6641
6908
|
} catch (error) {
|
|
6642
6909
|
if (error instanceof Error && error.name === "AbortError") {
|
|
6643
|
-
throw new Error("
|
|
6910
|
+
throw new Error("Chat completion request timed out");
|
|
6644
6911
|
}
|
|
6645
6912
|
throw error instanceof Error ? error : new Error(String(error));
|
|
6646
6913
|
}
|
|
@@ -6723,34 +6990,59 @@ var { values: cliOptions, positionals: commandLineQueries } = parseArgs({
|
|
|
6723
6990
|
timeout: { type: "string", short: "t" },
|
|
6724
6991
|
workspace: { type: "string", short: "w" },
|
|
6725
6992
|
format: { type: "string", short: "f", default: "json" },
|
|
6726
|
-
"dry-run": { type: "boolean", short: "d" },
|
|
6727
6993
|
version: { type: "boolean", short: "v" },
|
|
6728
|
-
help: { type: "boolean", short: "h" }
|
|
6994
|
+
help: { type: "boolean", short: "h" },
|
|
6995
|
+
model: { type: "string", short: "m" },
|
|
6996
|
+
attach: { type: "string", multiple: true },
|
|
6997
|
+
"attach-image": { type: "string", multiple: true },
|
|
6998
|
+
async: { type: "boolean" },
|
|
6999
|
+
webhook: { type: "string" }
|
|
6729
7000
|
},
|
|
6730
7001
|
allowPositionals: true
|
|
6731
7002
|
});
|
|
6732
7003
|
if (cliOptions.help) {
|
|
6733
7004
|
console.error(`
|
|
6734
|
-
PPLX-Zero - Minimal, fast Perplexity AI search CLI
|
|
7005
|
+
PPLX-Zero - Minimal, fast Perplexity AI search CLI with multimodal support
|
|
6735
7006
|
|
|
6736
7007
|
USAGE:
|
|
6737
7008
|
pplx [OPTIONS] [QUERY...]
|
|
6738
7009
|
|
|
6739
7010
|
OPTIONS:
|
|
6740
|
-
-i, --input <file>
|
|
6741
|
-
-s, --stdin
|
|
6742
|
-
-c, --concurrency <n>
|
|
6743
|
-
-t, --timeout <ms>
|
|
6744
|
-
-w, --workspace <path>
|
|
6745
|
-
-f, --format <format>
|
|
6746
|
-
-
|
|
6747
|
-
|
|
6748
|
-
-
|
|
7011
|
+
-i, --input <file> Read batch requests from JSON file
|
|
7012
|
+
-s, --stdin Read JSONL requests from stdin
|
|
7013
|
+
-c, --concurrency <n> Max concurrent requests (default: 5)
|
|
7014
|
+
-t, --timeout <ms> Request timeout in milliseconds (default: 30000)
|
|
7015
|
+
-w, --workspace <path> Workspace directory for sandboxing
|
|
7016
|
+
-f, --format <format> Output format: json|jsonl (default: json)
|
|
7017
|
+
-m, --model <model> AI model: sonar, sonar-pro, sonar-deep-research, sonar-reasoning (default: sonar)
|
|
7018
|
+
--attach <file> Attach document files (PDF, DOC, DOCX, TXT, RTF) - can be used multiple times
|
|
7019
|
+
--attach-image <file> Attach image files (PNG, JPEG, WebP, HEIF, HEIC, GIF) - can be used multiple times
|
|
7020
|
+
--async Process requests asynchronously
|
|
7021
|
+
--webhook <url> Webhook URL for async notifications
|
|
7022
|
+
-v, --version Show version
|
|
7023
|
+
-h, --help Show this help
|
|
6749
7024
|
|
|
6750
7025
|
EXAMPLES:
|
|
6751
|
-
#
|
|
7026
|
+
# Basic query
|
|
6752
7027
|
pplx "latest AI developments"
|
|
6753
7028
|
|
|
7029
|
+
# Model selection
|
|
7030
|
+
pplx --model sonar-pro "Detailed analysis"
|
|
7031
|
+
pplx --model sonar-deep-research "Comprehensive research"
|
|
7032
|
+
pplx --model sonar-reasoning "Complex problem solving"
|
|
7033
|
+
|
|
7034
|
+
# Image analysis
|
|
7035
|
+
pplx --attach-image screenshot.png --model sonar-pro "Analyze this interface"
|
|
7036
|
+
|
|
7037
|
+
# Document analysis
|
|
7038
|
+
pplx --attach report.pdf --model sonar-deep-research "Summarize this document"
|
|
7039
|
+
|
|
7040
|
+
# Multimodal analysis
|
|
7041
|
+
pplx --attach document.txt --attach-image chart.png --model sonar-reasoning "Analyze this data"
|
|
7042
|
+
|
|
7043
|
+
# Async processing with webhook
|
|
7044
|
+
pplx --async --webhook https://api.example.com/callback "Long research task"
|
|
7045
|
+
|
|
6754
7046
|
# Batch from file
|
|
6755
7047
|
pplx --input queries.json
|
|
6756
7048
|
|
|
@@ -6760,8 +7052,12 @@ EXAMPLES:
|
|
|
6760
7052
|
# JSONL output for streaming
|
|
6761
7053
|
pplx --format jsonl --input queries.json
|
|
6762
7054
|
|
|
6763
|
-
# High concurrency batch
|
|
6764
|
-
pplx --concurrency 10 --timeout 60000 --input queries.json
|
|
7055
|
+
# High concurrency batch with attachments
|
|
7056
|
+
pplx --concurrency 10 --timeout 60000 --input queries.json --attach appendix.pdf
|
|
7057
|
+
|
|
7058
|
+
SUPPORTED FORMATS:
|
|
7059
|
+
Images: PNG, JPEG, WebP, HEIF, HEIC, GIF (max 50MB, 10 files)
|
|
7060
|
+
Documents: PDF, DOC, DOCX, TXT, RTF (max 50MB, 10 files)
|
|
6765
7061
|
`);
|
|
6766
7062
|
process.exit(0);
|
|
6767
7063
|
}
|
|
@@ -6842,11 +7138,20 @@ async function main() {
|
|
|
6842
7138
|
const requestTimeout = parseNumericArgument(cliOptions.timeout, DEFAULT_TIMEOUT, MIN_TIMEOUT, MAX_TIMEOUT, "Timeout");
|
|
6843
7139
|
const workspaceDirectory = cliOptions.workspace;
|
|
6844
7140
|
const outputFormat = cliOptions.format;
|
|
6845
|
-
const isDryRunMode = cliOptions["dry-run"];
|
|
6846
7141
|
if (!["json", "jsonl"].includes(outputFormat)) {
|
|
6847
7142
|
throw new Error("Format must be json or jsonl");
|
|
6848
7143
|
}
|
|
6849
|
-
|
|
7144
|
+
let selectedModel;
|
|
7145
|
+
if (cliOptions.model) {
|
|
7146
|
+
const validModels = ["sonar", "sonar-pro", "sonar-deep-research", "sonar-reasoning"];
|
|
7147
|
+
if (!validModels.includes(cliOptions.model)) {
|
|
7148
|
+
throw new Error(`Invalid model: ${cliOptions.model}. Valid models: ${validModels.join(", ")}`);
|
|
7149
|
+
}
|
|
7150
|
+
selectedModel = cliOptions.model;
|
|
7151
|
+
}
|
|
7152
|
+
const searchTool = new PerplexitySearchTool(workspaceDirectory, {
|
|
7153
|
+
defaultModel: selectedModel
|
|
7154
|
+
});
|
|
6850
7155
|
logEvent({
|
|
6851
7156
|
time: new Date().toISOString(),
|
|
6852
7157
|
level: "info",
|
|
@@ -6856,7 +7161,10 @@ async function main() {
|
|
|
6856
7161
|
timeout: requestTimeout,
|
|
6857
7162
|
workspace: workspaceDirectory,
|
|
6858
7163
|
format: outputFormat,
|
|
6859
|
-
|
|
7164
|
+
model: selectedModel,
|
|
7165
|
+
async: cliOptions.async,
|
|
7166
|
+
webhook: cliOptions.webhook,
|
|
7167
|
+
hasAttachments: (cliOptions.attach?.length || 0) + (cliOptions["attach-image"]?.length || 0) > 0
|
|
6860
7168
|
}
|
|
6861
7169
|
});
|
|
6862
7170
|
let batchSearchInput;
|
|
@@ -6869,13 +7177,37 @@ async function main() {
|
|
|
6869
7177
|
inputSourceType = cliOptions.input;
|
|
6870
7178
|
} else if (commandLineQueries.length > 0) {
|
|
6871
7179
|
const combinedQuery = commandLineQueries.join(" ");
|
|
7180
|
+
const attachmentInputs = [];
|
|
7181
|
+
if (cliOptions.attach && cliOptions.attach.length > 0) {
|
|
7182
|
+
for (const filePath of cliOptions.attach) {
|
|
7183
|
+
attachmentInputs.push({
|
|
7184
|
+
path: filePath,
|
|
7185
|
+
type: "document"
|
|
7186
|
+
});
|
|
7187
|
+
}
|
|
7188
|
+
}
|
|
7189
|
+
if (cliOptions["attach-image"] && cliOptions["attach-image"].length > 0) {
|
|
7190
|
+
for (const filePath of cliOptions["attach-image"]) {
|
|
7191
|
+
attachmentInputs.push({
|
|
7192
|
+
path: filePath,
|
|
7193
|
+
type: "image"
|
|
7194
|
+
});
|
|
7195
|
+
}
|
|
7196
|
+
}
|
|
6872
7197
|
batchSearchInput = {
|
|
6873
7198
|
version: "1.0.0",
|
|
6874
7199
|
requests: [{
|
|
6875
7200
|
op: "search",
|
|
6876
7201
|
args: {
|
|
6877
7202
|
query: combinedQuery,
|
|
6878
|
-
maxResults: 5
|
|
7203
|
+
maxResults: 5,
|
|
7204
|
+
model: selectedModel,
|
|
7205
|
+
attachmentInputs: attachmentInputs.length > 0 ? attachmentInputs : undefined
|
|
7206
|
+
},
|
|
7207
|
+
options: {
|
|
7208
|
+
timeoutMs: requestTimeout,
|
|
7209
|
+
async: cliOptions.async,
|
|
7210
|
+
webhook: cliOptions.webhook
|
|
6879
7211
|
}
|
|
6880
7212
|
}]
|
|
6881
7213
|
};
|
|
@@ -6899,21 +7231,6 @@ async function main() {
|
|
|
6899
7231
|
failFast: false,
|
|
6900
7232
|
...batchSearchInput.options
|
|
6901
7233
|
};
|
|
6902
|
-
if (isDryRunMode) {
|
|
6903
|
-
logEvent({
|
|
6904
|
-
time: new Date().toISOString(),
|
|
6905
|
-
level: "info",
|
|
6906
|
-
event: "dry_run_completed",
|
|
6907
|
-
data: { requestCount: batchSearchInput.requests?.length || 1 }
|
|
6908
|
-
});
|
|
6909
|
-
console.log(JSON.stringify({
|
|
6910
|
-
ok: true,
|
|
6911
|
-
message: "Dry run completed - input validation passed",
|
|
6912
|
-
requestCount: batchSearchInput.requests?.length || 1,
|
|
6913
|
-
inputSource: inputSourceType
|
|
6914
|
-
}, null, 2));
|
|
6915
|
-
return;
|
|
6916
|
-
}
|
|
6917
7234
|
logEvent({
|
|
6918
7235
|
time: new Date().toISOString(),
|
|
6919
7236
|
level: "info",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pplx-zero",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Fast Perplexity AI search CLI with multimodal support - minimal setup, maximal results",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -49,10 +49,22 @@
|
|
|
49
49
|
"minimal",
|
|
50
50
|
"fast",
|
|
51
51
|
"productivity",
|
|
52
|
-
"zero-config"
|
|
52
|
+
"zero-config",
|
|
53
|
+
"multimodal",
|
|
54
|
+
"attachments",
|
|
55
|
+
"images",
|
|
56
|
+
"documents",
|
|
57
|
+
"sonar",
|
|
58
|
+
"reasoning",
|
|
59
|
+
"research",
|
|
60
|
+
"async"
|
|
53
61
|
],
|
|
54
62
|
"author": "Kenzo",
|
|
55
63
|
"license": "MIT",
|
|
64
|
+
"repository": {
|
|
65
|
+
"type": "git",
|
|
66
|
+
"url": "https://github.com/codewithkenzo/pplx-zero.git"
|
|
67
|
+
},
|
|
56
68
|
"files": [
|
|
57
69
|
"dist",
|
|
58
70
|
"README.md",
|