apex-dev 2.0.0 → 2.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apex-dev",
3
- "version": "2.0.0",
3
+ "version": "2.1.3",
4
4
  "description": "Apex AI - a friendly agentic coding assistant for the terminal",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -2,6 +2,7 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
+ const https = require('https');
5
6
  const { execSync } = require('child_process');
6
7
  const {
7
8
  PROJECT_ROOT,
@@ -215,6 +216,64 @@ async function executeTool(name, args) {
215
216
  return truncateOutput(`Task: ${args.description}\n${'─'.repeat(40)}\n${results.join('\n\n')}`);
216
217
  }
217
218
 
219
+ case 'WebSearch': {
220
+ const apiKey = process.env.EXA_API_KEY;
221
+ if (!apiKey) return 'Error: EXA_API_KEY environment variable is not set. Get one at https://dashboard.exa.ai/api-keys';
222
+
223
+ const body = JSON.stringify({
224
+ query: args.query,
225
+ numResults: Math.min(args.num_results || 5, 10),
226
+ type: args.type || 'auto',
227
+ ...(args.include_domains && { includeDomains: args.include_domains }),
228
+ ...(args.category && { category: args.category }),
229
+ contents: { highlights: { maxCharacters: 300 }, text: { maxCharacters: 1000 } },
230
+ });
231
+
232
+ const result = await new Promise((resolve) => {
233
+ const req = https.request({
234
+ hostname: 'api.exa.ai',
235
+ path: '/search',
236
+ method: 'POST',
237
+ headers: {
238
+ 'Content-Type': 'application/json',
239
+ 'x-api-key': apiKey,
240
+ },
241
+ }, (res) => {
242
+ let data = '';
243
+ res.on('data', (chunk) => data += chunk);
244
+ res.on('end', () => {
245
+ if (res.statusCode !== 200) {
246
+ resolve(`Error: Exa API returned ${res.statusCode}: ${data}`);
247
+ return;
248
+ }
249
+ try {
250
+ const json = JSON.parse(data);
251
+ if (!json.results || json.results.length === 0) {
252
+ resolve('No results found.');
253
+ return;
254
+ }
255
+ const formatted = json.results.map((r, i) => {
256
+ let entry = `${i + 1}. **${r.title || 'Untitled'}**\n ${r.url}`;
257
+ if (r.publishedDate) entry += `\n Published: ${r.publishedDate.split('T')[0]}`;
258
+ if (r.author) entry += `\n Author: ${r.author}`;
259
+ if (r.text) entry += `\n ${r.text.trim().slice(0, 500)}`;
260
+ else if (r.highlights && r.highlights.length) entry += `\n ${r.highlights[0].trim().slice(0, 300)}`;
261
+ return entry;
262
+ }).join('\n\n');
263
+ resolve(truncateOutput(`Web Search Results (${json.results.length}):\n${'─'.repeat(40)}\n${formatted}`));
264
+ } catch (e) {
265
+ resolve(`Error: Failed to parse Exa response: ${e.message}`);
266
+ }
267
+ });
268
+ });
269
+ req.on('error', (e) => resolve(`Error: Exa request failed: ${e.message}`));
270
+ req.setTimeout(15000, () => { req.destroy(); resolve('Error: Exa search timed out.'); });
271
+ req.write(body);
272
+ req.end();
273
+ });
274
+ return result;
275
+ }
276
+
218
277
  case 'CodeReview': {
219
278
  // Auto-collect all modified files from this session
220
279
  const allFiles = new Set([...session.filesModified]);
package/src/tools.js CHANGED
@@ -169,6 +169,28 @@ const toolDefs = [
169
169
  },
170
170
  },
171
171
  },
172
+ {
173
+ type: 'function',
174
+ function: {
175
+ name: 'WebSearch',
176
+ description: 'Search the web using Exa AI. Returns relevant results with titles, URLs, and text snippets. Use this to find up-to-date information, documentation, or answers from the internet.',
177
+ parameters: {
178
+ type: 'object',
179
+ properties: {
180
+ query: { type: 'string', description: 'The search query to execute.' },
181
+ num_results: { type: 'number', description: 'Number of results to return (default: 5, max: 10).' },
182
+ type: { type: 'string', description: 'Search type: "auto" (default), "neural", or "keyword".' },
183
+ include_domains: {
184
+ type: 'array',
185
+ description: 'Only return results from these domains, e.g. ["github.com", "stackoverflow.com"].',
186
+ items: { type: 'string' },
187
+ },
188
+ category: { type: 'string', description: 'Filter by category: "news", "research paper", "tweet", "company", "personal site", etc.' },
189
+ },
190
+ required: ['query'],
191
+ },
192
+ },
193
+ },
172
194
  {
173
195
  type: 'function',
174
196
  function: {