@phuetz/code-buddy 0.1.0 → 0.1.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/.codebuddy/skills/bundled/brave-search/SKILL.md +490 -0
- package/.codebuddy/skills/bundled/exa-search/SKILL.md +1122 -0
- package/.codebuddy/skills/bundled/perplexity/SKILL.md +748 -0
- package/.codebuddy/skills/bundled/playwright/SKILL.md +520 -0
- package/.codebuddy/skills/bundled/puppeteer/SKILL.md +708 -0
- package/.codebuddy/skills/bundled/web-fetch/SKILL.md +1003 -0
- package/README.md +56 -0
- package/dist/agent/agent-state.d.ts +3 -3
- package/dist/agent/agent-state.js +6 -6
- package/dist/agent/agent-state.js.map +1 -1
- package/dist/agent/base-agent.d.ts +4 -4
- package/dist/agent/base-agent.js +22 -9
- package/dist/agent/base-agent.js.map +1 -1
- package/dist/agent/cache-trace.d.ts +56 -0
- package/dist/agent/cache-trace.js +98 -0
- package/dist/agent/cache-trace.js.map +1 -0
- package/dist/agent/codebuddy-agent.js +4 -2
- package/dist/agent/codebuddy-agent.js.map +1 -1
- package/dist/agent/execution/agent-executor.d.ts +4 -4
- package/dist/agent/execution/agent-executor.js +46 -14
- package/dist/agent/execution/agent-executor.js.map +1 -1
- package/dist/agent/facades/agent-context-facade.js +1 -3
- package/dist/agent/facades/agent-context-facade.js.map +1 -1
- package/dist/agent/facades/message-history-manager.js +14 -12
- package/dist/agent/facades/message-history-manager.js.map +1 -1
- package/dist/agent/facades/session-facade.d.ts +3 -3
- package/dist/agent/facades/session-facade.js +6 -6
- package/dist/agent/facades/session-facade.js.map +1 -1
- package/dist/agent/history-repair.d.ts +37 -0
- package/dist/agent/history-repair.js +124 -0
- package/dist/agent/history-repair.js.map +1 -0
- package/dist/agent/index.d.ts +3 -3
- package/dist/agent/index.js +3 -3
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/isolation/agent-workspace.d.ts +1 -0
- package/dist/agent/isolation/agent-workspace.js +10 -0
- package/dist/agent/isolation/agent-workspace.js.map +1 -1
- package/dist/agent/specialized/archive-agent.d.ts +3 -0
- package/dist/agent/specialized/archive-agent.js +71 -31
- package/dist/agent/specialized/archive-agent.js.map +1 -1
- package/dist/agent/specialized/index.d.ts +9 -8
- package/dist/agent/specialized/index.js +16 -8
- package/dist/agent/specialized/index.js.map +1 -1
- package/dist/agent/specialized/security-review/agent.js +19 -8
- package/dist/agent/specialized/security-review/agent.js.map +1 -1
- package/dist/agent/tool-executor.js +5 -0
- package/dist/agent/tool-executor.js.map +1 -1
- package/dist/agent/turn-diff-tracker.d.ts +79 -0
- package/dist/agent/turn-diff-tracker.js +195 -0
- package/dist/agent/turn-diff-tracker.js.map +1 -0
- package/dist/browser/controller.js +8 -4
- package/dist/browser/controller.js.map +1 -1
- package/dist/browser-automation/browser-manager.js +8 -1
- package/dist/browser-automation/browser-manager.js.map +1 -1
- package/dist/checkpoints/checkpoint-versioning.js +78 -20
- package/dist/checkpoints/checkpoint-versioning.js.map +1 -1
- package/dist/cli/config-loader.js +2 -4
- package/dist/cli/config-loader.js.map +1 -1
- package/dist/codebuddy/client.js +70 -11
- package/dist/codebuddy/client.js.map +1 -1
- package/dist/codebuddy/tools.d.ts +1 -7
- package/dist/codebuddy/tools.js +2 -30
- package/dist/codebuddy/tools.js.map +1 -1
- package/dist/commands/cli/daemon-commands.d.ts +14 -0
- package/dist/commands/cli/daemon-commands.js +166 -0
- package/dist/commands/cli/daemon-commands.js.map +1 -0
- package/dist/commands/cli/speak-command.d.ts +10 -0
- package/dist/commands/cli/speak-command.js +97 -0
- package/dist/commands/cli/speak-command.js.map +1 -0
- package/dist/commands/cli/utility-commands.d.ts +10 -0
- package/dist/commands/cli/utility-commands.js +88 -0
- package/dist/commands/cli/utility-commands.js.map +1 -0
- package/dist/commands/handlers/fcs-handlers.js +1 -1
- package/dist/commands/handlers/fcs-handlers.js.map +1 -1
- package/dist/commands/handlers/memory-handlers.js +2 -1
- package/dist/commands/handlers/memory-handlers.js.map +1 -1
- package/dist/commands/handlers/vibe-handlers.js +0 -1
- package/dist/commands/handlers/vibe-handlers.js.map +1 -1
- package/dist/commands/handlers/worktree-handlers.js +11 -0
- package/dist/commands/handlers/worktree-handlers.js.map +1 -1
- package/dist/commands/index.d.ts +8 -7
- package/dist/commands/index.js +10 -8
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/mcp.d.ts +1 -0
- package/dist/commands/mcp.js +66 -7
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/pipeline.js +25 -13
- package/dist/commands/pipeline.js.map +1 -1
- package/dist/config/hot-reload/watcher.js +4 -4
- package/dist/config/hot-reload/watcher.js.map +1 -1
- package/dist/config/model-tools.d.ts +41 -0
- package/dist/config/model-tools.js +194 -0
- package/dist/config/model-tools.js.map +1 -0
- package/dist/context/context-manager-v2.d.ts +2 -1
- package/dist/context/context-manager-v2.js +34 -5
- package/dist/context/context-manager-v2.js.map +1 -1
- package/dist/context/index.d.ts +12 -12
- package/dist/context/index.js +25 -12
- package/dist/context/index.js.map +1 -1
- package/dist/daemon/daemon-manager.js +23 -19
- package/dist/daemon/daemon-manager.js.map +1 -1
- package/dist/database/database-manager.d.ts +4 -0
- package/dist/database/database-manager.js +16 -7
- package/dist/database/database-manager.js.map +1 -1
- package/dist/desktop-automation/nutjs-provider.js +89 -0
- package/dist/desktop-automation/nutjs-provider.js.map +1 -1
- package/dist/errors/index.d.ts +4 -4
- package/dist/errors/index.js +8 -4
- package/dist/errors/index.js.map +1 -1
- package/dist/fcs/builtins.d.ts +2 -6
- package/dist/fcs/builtins.js +2 -568
- package/dist/fcs/builtins.js.map +1 -1
- package/dist/fcs/codebuddy-bindings.d.ts +3 -43
- package/dist/fcs/codebuddy-bindings.js +2 -606
- package/dist/fcs/codebuddy-bindings.js.map +1 -1
- package/dist/fcs/index.d.ts +2 -27
- package/dist/fcs/index.js +2 -53
- package/dist/fcs/index.js.map +1 -1
- package/dist/fcs/lexer.d.ts +2 -37
- package/dist/fcs/lexer.js +2 -459
- package/dist/fcs/lexer.js.map +1 -1
- package/dist/fcs/parser.d.ts +2 -68
- package/dist/fcs/parser.js +2 -893
- package/dist/fcs/parser.js.map +1 -1
- package/dist/fcs/runtime.d.ts +2 -59
- package/dist/fcs/runtime.js +2 -623
- package/dist/fcs/runtime.js.map +1 -1
- package/dist/fcs/script-registry.d.ts +3 -69
- package/dist/fcs/script-registry.js +2 -219
- package/dist/fcs/script-registry.js.map +1 -1
- package/dist/fcs/sync-bindings.d.ts +3 -101
- package/dist/fcs/sync-bindings.js +2 -410
- package/dist/fcs/sync-bindings.js.map +1 -1
- package/dist/fcs/types.d.ts +2 -285
- package/dist/fcs/types.js +2 -103
- package/dist/fcs/types.js.map +1 -1
- package/dist/hooks/index.d.ts +4 -4
- package/dist/hooks/index.js +4 -4
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/use-input-handler.d.ts +1 -1
- package/dist/index.js +20 -330
- package/dist/index.js.map +1 -1
- package/dist/input/voice-control.js +11 -5
- package/dist/input/voice-control.js.map +1 -1
- package/dist/integrations/json-rpc/server.d.ts +9 -0
- package/dist/integrations/json-rpc/server.js +43 -13
- package/dist/integrations/json-rpc/server.js.map +1 -1
- package/dist/integrations/mcp/mcp-server.js +1 -1
- package/dist/integrations/mcp/mcp-server.js.map +1 -1
- package/dist/integrations/notification-integrations.d.ts +1 -0
- package/dist/integrations/notification-integrations.js +6 -1
- package/dist/integrations/notification-integrations.js.map +1 -1
- package/dist/mcp/client.js +2 -1
- package/dist/mcp/client.js.map +1 -1
- package/dist/mcp/config.js +89 -5
- package/dist/mcp/config.js.map +1 -1
- package/dist/mcp/mcp-client.js +65 -14
- package/dist/mcp/mcp-client.js.map +1 -1
- package/dist/mcp/transports.d.ts +0 -1
- package/dist/mcp/transports.js +1 -5
- package/dist/mcp/transports.js.map +1 -1
- package/dist/mcp/types.d.ts +2 -0
- package/dist/memory/index.d.ts +2 -2
- package/dist/memory/index.js +2 -2
- package/dist/memory/index.js.map +1 -1
- package/dist/persistence/session-lock.d.ts +42 -0
- package/dist/persistence/session-lock.js +165 -0
- package/dist/persistence/session-lock.js.map +1 -0
- package/dist/persistence/session-store.d.ts +18 -3
- package/dist/persistence/session-store.js +90 -21
- package/dist/persistence/session-store.js.map +1 -1
- package/dist/plugins/conflict-detection.js +2 -1
- package/dist/plugins/conflict-detection.js.map +1 -1
- package/dist/plugins/index.d.ts +3 -3
- package/dist/plugins/index.js +3 -3
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/isolated-plugin-runner.d.ts +6 -0
- package/dist/plugins/isolated-plugin-runner.js +19 -1
- package/dist/plugins/isolated-plugin-runner.js.map +1 -1
- package/dist/providers/local-llm-provider.js +28 -8
- package/dist/providers/local-llm-provider.js.map +1 -1
- package/dist/sandbox/docker-sandbox.js +7 -4
- package/dist/sandbox/docker-sandbox.js.map +1 -1
- package/dist/scripting/builtins.d.ts +8 -3
- package/dist/scripting/builtins.js +506 -355
- package/dist/scripting/builtins.js.map +1 -1
- package/dist/scripting/codebuddy-bindings.d.ts +47 -0
- package/dist/scripting/codebuddy-bindings.js +488 -0
- package/dist/scripting/codebuddy-bindings.js.map +1 -0
- package/dist/scripting/index.d.ts +33 -30
- package/dist/scripting/index.js +41 -36
- package/dist/scripting/index.js.map +1 -1
- package/dist/scripting/lexer.d.ts +31 -13
- package/dist/scripting/lexer.js +379 -292
- package/dist/scripting/lexer.js.map +1 -1
- package/dist/scripting/parser.d.ts +63 -44
- package/dist/scripting/parser.js +700 -473
- package/dist/scripting/parser.js.map +1 -1
- package/dist/scripting/runtime.d.ts +55 -24
- package/dist/scripting/runtime.js +600 -288
- package/dist/scripting/runtime.js.map +1 -1
- package/dist/scripting/script-registry.d.ts +54 -0
- package/dist/scripting/script-registry.js +202 -0
- package/dist/scripting/script-registry.js.map +1 -0
- package/dist/scripting/sync-bindings.d.ts +105 -0
- package/dist/scripting/sync-bindings.js +353 -0
- package/dist/scripting/sync-bindings.js.map +1 -0
- package/dist/scripting/types.d.ts +297 -199
- package/dist/scripting/types.js +86 -60
- package/dist/scripting/types.js.map +1 -1
- package/dist/search/usearch-index.js +42 -7
- package/dist/search/usearch-index.js.map +1 -1
- package/dist/security/bash-parser.d.ts +51 -0
- package/dist/security/bash-parser.js +327 -0
- package/dist/security/bash-parser.js.map +1 -0
- package/dist/security/index.d.ts +7 -5
- package/dist/security/index.js +8 -7
- package/dist/security/index.js.map +1 -1
- package/dist/security/skill-scanner.d.ts +36 -0
- package/dist/security/skill-scanner.js +149 -0
- package/dist/security/skill-scanner.js.map +1 -0
- package/dist/security/trust-folders.d.ts +1 -0
- package/dist/security/trust-folders.js +19 -1
- package/dist/security/trust-folders.js.map +1 -1
- package/dist/server/auth/index.d.ts +2 -2
- package/dist/server/auth/index.js +2 -2
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/middleware/index.d.ts +5 -5
- package/dist/server/middleware/index.js +5 -5
- package/dist/server/middleware/index.js.map +1 -1
- package/dist/server/middleware/rate-limit.js +15 -3
- package/dist/server/middleware/rate-limit.js.map +1 -1
- package/dist/server/websocket/handler.js +54 -6
- package/dist/server/websocket/handler.js.map +1 -1
- package/dist/skills/eligibility.js +26 -4
- package/dist/skills/eligibility.js.map +1 -1
- package/dist/tasks/background-tasks.js +5 -1
- package/dist/tasks/background-tasks.js.map +1 -1
- package/dist/tools/apply-patch.d.ts +55 -0
- package/dist/tools/apply-patch.js +273 -0
- package/dist/tools/apply-patch.js.map +1 -0
- package/dist/tools/hooks/default-hooks.d.ts +1 -1
- package/dist/tools/hooks/default-hooks.js +2 -1
- package/dist/tools/hooks/default-hooks.js.map +1 -1
- package/dist/tools/index.d.ts +10 -10
- package/dist/tools/index.js +11 -11
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/registry/bash-tools.js +6 -3
- package/dist/tools/registry/bash-tools.js.map +1 -1
- package/dist/tools/registry/misc-tools.js +1 -2
- package/dist/tools/registry/misc-tools.js.map +1 -1
- package/dist/tools/registry/search-tools.js +1 -1
- package/dist/tools/registry/search-tools.js.map +1 -1
- package/dist/tools/registry/text-editor-tools.js +1 -1
- package/dist/tools/registry/text-editor-tools.js.map +1 -1
- package/dist/tools/registry/todo-tools.js +37 -5
- package/dist/tools/registry/todo-tools.js.map +1 -1
- package/dist/tools/registry/tool-registry.js +5 -4
- package/dist/tools/registry/tool-registry.js.map +1 -1
- package/dist/tools/registry/web-tools.d.ts +1 -1
- package/dist/tools/registry/web-tools.js +28 -8
- package/dist/tools/registry/web-tools.js.map +1 -1
- package/dist/tools/text-editor.d.ts +1 -1
- package/dist/tools/text-editor.js +23 -5
- package/dist/tools/text-editor.js.map +1 -1
- package/dist/tools/web-search.d.ts +52 -37
- package/dist/tools/web-search.js +368 -163
- package/dist/tools/web-search.js.map +1 -1
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/errors.js +2 -8
- package/dist/types/errors.js.map +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.js +1 -2
- package/dist/types/index.js.map +1 -1
- package/dist/ui/components/ChatInterface.d.ts +1 -1
- package/dist/ui/index.d.ts +17 -21
- package/dist/ui/index.js +25 -22
- package/dist/ui/index.js.map +1 -1
- package/dist/utils/config-validation/schema.d.ts +15 -15
- package/dist/utils/head-tail-truncation.d.ts +34 -0
- package/dist/utils/head-tail-truncation.js +98 -0
- package/dist/utils/head-tail-truncation.js.map +1 -0
- package/dist/utils/logger.js +3 -9
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/sanitize.d.ts +5 -0
- package/dist/utils/sanitize.js +19 -0
- package/dist/utils/sanitize.js.map +1 -1
- package/dist/utils/settings-manager.js +4 -4
- package/dist/utils/settings-manager.js.map +1 -1
- package/dist/workflows/index.d.ts +4 -279
- package/dist/workflows/index.js +8 -822
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/state-manager.d.ts +77 -0
- package/dist/workflows/state-manager.js +198 -0
- package/dist/workflows/state-manager.js.map +1 -0
- package/dist/workflows/step-manager.d.ts +39 -0
- package/dist/workflows/step-manager.js +196 -0
- package/dist/workflows/step-manager.js.map +1 -0
- package/dist/workflows/types.d.ts +87 -0
- package/dist/workflows/types.js +5 -0
- package/dist/workflows/types.js.map +1 -0
- package/dist/workflows/workflow-engine.d.ts +34 -0
- package/dist/workflows/workflow-engine.js +354 -0
- package/dist/workflows/workflow-engine.js.map +1 -0
- package/package.json +5 -1
|
@@ -0,0 +1,1122 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: exa-search
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: Exa neural search API for semantic search, content extraction, and similarity finding
|
|
5
|
+
author: Code Buddy
|
|
6
|
+
tags: search, semantic, neural, ai, similarity, content-extraction, exa
|
|
7
|
+
env:
|
|
8
|
+
EXA_API_KEY: ""
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Exa Search
|
|
12
|
+
|
|
13
|
+
Exa (formerly Metaphor) is a neural search engine that understands meaning and context rather than just keywords. It excels at finding similar content, discovering high-quality sources, and extracting structured information from web pages. Perfect for research, content discovery, and building knowledge bases.
|
|
14
|
+
|
|
15
|
+
## Direct Control (CLI / API / Scripting)
|
|
16
|
+
|
|
17
|
+
### API Authentication
|
|
18
|
+
|
|
19
|
+
All requests require an API key via the `x-api-key` header. Get your key at https://dashboard.exa.ai/
|
|
20
|
+
|
|
21
|
+
### Basic Search
|
|
22
|
+
|
|
23
|
+
**cURL Example:**
|
|
24
|
+
```bash
|
|
25
|
+
curl -s https://api.exa.ai/search \
|
|
26
|
+
-H "x-api-key: ${EXA_API_KEY}" \
|
|
27
|
+
-H "Content-Type: application/json" \
|
|
28
|
+
-d '{
|
|
29
|
+
"query": "groundbreaking machine learning papers",
|
|
30
|
+
"num_results": 10,
|
|
31
|
+
"type": "neural",
|
|
32
|
+
"use_autoprompt": true
|
|
33
|
+
}' | jq .
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Node.js Example:**
|
|
37
|
+
```javascript
|
|
38
|
+
import fetch from 'node-fetch';
|
|
39
|
+
|
|
40
|
+
async function exaSearch(query, options = {}) {
|
|
41
|
+
const response = await fetch('https://api.exa.ai/search', {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: {
|
|
44
|
+
'x-api-key': process.env.EXA_API_KEY,
|
|
45
|
+
'Content-Type': 'application/json'
|
|
46
|
+
},
|
|
47
|
+
body: JSON.stringify({
|
|
48
|
+
query,
|
|
49
|
+
num_results: options.numResults || 10,
|
|
50
|
+
type: options.type || 'neural', // 'neural' or 'keyword'
|
|
51
|
+
use_autoprompt: options.useAutoprompt !== false,
|
|
52
|
+
category: options.category, // 'company', 'research paper', 'news', 'github', 'tweet', 'movie', 'song', 'personal site', 'pdf'
|
|
53
|
+
start_published_date: options.startDate, // ISO 8601 format
|
|
54
|
+
end_published_date: options.endDate,
|
|
55
|
+
include_domains: options.includeDomains, // ['example.com']
|
|
56
|
+
exclude_domains: options.excludeDomains,
|
|
57
|
+
start_crawl_date: options.startCrawlDate,
|
|
58
|
+
end_crawl_date: options.endCrawlDate
|
|
59
|
+
})
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
throw new Error(`Exa API error: ${response.status}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return await response.json();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Usage
|
|
70
|
+
const results = await exaSearch('innovative approaches to zero-knowledge proofs', {
|
|
71
|
+
numResults: 20,
|
|
72
|
+
type: 'neural',
|
|
73
|
+
category: 'research paper',
|
|
74
|
+
startDate: '2024-01-01'
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
results.results.forEach(result => {
|
|
78
|
+
console.log(`${result.title}`);
|
|
79
|
+
console.log(`${result.url}`);
|
|
80
|
+
console.log(`Score: ${result.score} | Published: ${result.published_date || 'N/A'}\n`);
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Python Example:**
|
|
85
|
+
```python
|
|
86
|
+
import requests
|
|
87
|
+
import os
|
|
88
|
+
from datetime import datetime, timedelta
|
|
89
|
+
|
|
90
|
+
def exa_search(query, num_results=10, search_type='neural', **kwargs):
|
|
91
|
+
"""
|
|
92
|
+
Neural search with Exa
|
|
93
|
+
search_type: 'neural' (semantic) or 'keyword' (traditional)
|
|
94
|
+
"""
|
|
95
|
+
payload = {
|
|
96
|
+
'query': query,
|
|
97
|
+
'num_results': num_results,
|
|
98
|
+
'type': search_type,
|
|
99
|
+
'use_autoprompt': kwargs.get('use_autoprompt', True)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# Optional filters
|
|
103
|
+
if 'category' in kwargs:
|
|
104
|
+
payload['category'] = kwargs['category']
|
|
105
|
+
if 'start_date' in kwargs:
|
|
106
|
+
payload['start_published_date'] = kwargs['start_date']
|
|
107
|
+
if 'end_date' in kwargs:
|
|
108
|
+
payload['end_published_date'] = kwargs['end_date']
|
|
109
|
+
if 'include_domains' in kwargs:
|
|
110
|
+
payload['include_domains'] = kwargs['include_domains']
|
|
111
|
+
if 'exclude_domains' in kwargs:
|
|
112
|
+
payload['exclude_domains'] = kwargs['exclude_domains']
|
|
113
|
+
|
|
114
|
+
response = requests.post(
|
|
115
|
+
'https://api.exa.ai/search',
|
|
116
|
+
headers={
|
|
117
|
+
'x-api-key': os.environ['EXA_API_KEY'],
|
|
118
|
+
'Content-Type': 'application/json'
|
|
119
|
+
},
|
|
120
|
+
json=payload
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
response.raise_for_status()
|
|
124
|
+
return response.json()
|
|
125
|
+
|
|
126
|
+
# Usage - Find recent research papers
|
|
127
|
+
results = exa_search(
|
|
128
|
+
'novel transformer architectures for vision tasks',
|
|
129
|
+
num_results=15,
|
|
130
|
+
search_type='neural',
|
|
131
|
+
category='research paper',
|
|
132
|
+
start_date='2025-01-01'
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
for result in results['results']:
|
|
136
|
+
print(f"{result['title']}")
|
|
137
|
+
print(f"{result['url']} (score: {result['score']:.3f})")
|
|
138
|
+
print()
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Find Similar Content
|
|
142
|
+
|
|
143
|
+
**Node.js Example:**
|
|
144
|
+
```javascript
|
|
145
|
+
async function exaFindSimilar(url, options = {}) {
|
|
146
|
+
const response = await fetch('https://api.exa.ai/findSimilar', {
|
|
147
|
+
method: 'POST',
|
|
148
|
+
headers: {
|
|
149
|
+
'x-api-key': process.env.EXA_API_KEY,
|
|
150
|
+
'Content-Type': 'application/json'
|
|
151
|
+
},
|
|
152
|
+
body: JSON.stringify({
|
|
153
|
+
url,
|
|
154
|
+
num_results: options.numResults || 10,
|
|
155
|
+
exclude_source_domain: options.excludeSourceDomain !== false,
|
|
156
|
+
category: options.category,
|
|
157
|
+
start_published_date: options.startDate,
|
|
158
|
+
end_published_date: options.endDate
|
|
159
|
+
})
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
return await response.json();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Find similar articles
|
|
166
|
+
const similar = await exaFindSimilar('https://arxiv.org/abs/2103.14030', {
|
|
167
|
+
numResults: 15,
|
|
168
|
+
category: 'research paper',
|
|
169
|
+
excludeSourceDomain: true
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
console.log('Similar papers:');
|
|
173
|
+
similar.results.forEach(result => {
|
|
174
|
+
console.log(`- ${result.title} (${result.url})`);
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Python Example:**
|
|
179
|
+
```python
|
|
180
|
+
def exa_find_similar(url, num_results=10, category=None, exclude_source=True):
|
|
181
|
+
"""
|
|
182
|
+
Find content similar to a given URL
|
|
183
|
+
"""
|
|
184
|
+
payload = {
|
|
185
|
+
'url': url,
|
|
186
|
+
'num_results': num_results,
|
|
187
|
+
'exclude_source_domain': exclude_source
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if category:
|
|
191
|
+
payload['category'] = category
|
|
192
|
+
|
|
193
|
+
response = requests.post(
|
|
194
|
+
'https://api.exa.ai/findSimilar',
|
|
195
|
+
headers={
|
|
196
|
+
'x-api-key': os.environ['EXA_API_KEY'],
|
|
197
|
+
'Content-Type': 'application/json'
|
|
198
|
+
},
|
|
199
|
+
json=payload
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
response.raise_for_status()
|
|
203
|
+
return response.json()
|
|
204
|
+
|
|
205
|
+
# Find similar GitHub repos
|
|
206
|
+
similar_repos = exa_find_similar(
|
|
207
|
+
'https://github.com/microsoft/TypeScript',
|
|
208
|
+
num_results=20,
|
|
209
|
+
category='github'
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
for repo in similar_repos['results']:
|
|
213
|
+
print(f"{repo['title']}: {repo['url']}")
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Get Contents (Extract Full Text)
|
|
217
|
+
|
|
218
|
+
**Node.js Example:**
|
|
219
|
+
```javascript
|
|
220
|
+
async function exaGetContents(ids, options = {}) {
|
|
221
|
+
const response = await fetch('https://api.exa.ai/contents', {
|
|
222
|
+
method: 'POST',
|
|
223
|
+
headers: {
|
|
224
|
+
'x-api-key': process.env.EXA_API_KEY,
|
|
225
|
+
'Content-Type': 'application/json'
|
|
226
|
+
},
|
|
227
|
+
body: JSON.stringify({
|
|
228
|
+
ids,
|
|
229
|
+
text: options.text !== false, // Extract text content
|
|
230
|
+
highlights: options.highlights, // Extract key highlights
|
|
231
|
+
summary: options.summary // Generate summary
|
|
232
|
+
})
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
return await response.json();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Search and extract content
|
|
239
|
+
const searchResults = await exaSearch('best practices for Rust async programming', {
|
|
240
|
+
numResults: 5
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const ids = searchResults.results.map(r => r.id);
|
|
244
|
+
const contents = await exaGetContents(ids, {
|
|
245
|
+
text: true,
|
|
246
|
+
highlights: {
|
|
247
|
+
query: 'async await patterns',
|
|
248
|
+
num_sentences: 3,
|
|
249
|
+
highlights_per_url: 2
|
|
250
|
+
},
|
|
251
|
+
summary: {
|
|
252
|
+
query: 'What are the main recommendations?'
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
contents.results.forEach(content => {
|
|
257
|
+
console.log(`\n=== ${content.title} ===`);
|
|
258
|
+
console.log(`URL: ${content.url}`);
|
|
259
|
+
if (content.summary) {
|
|
260
|
+
console.log(`\nSummary: ${content.summary}`);
|
|
261
|
+
}
|
|
262
|
+
if (content.highlights) {
|
|
263
|
+
console.log('\nKey Points:');
|
|
264
|
+
content.highlights.forEach(h => console.log(`- ${h}`));
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Python Example:**
|
|
270
|
+
```python
|
|
271
|
+
def exa_get_contents(ids, text=True, highlights=None, summary=None):
|
|
272
|
+
"""
|
|
273
|
+
Extract full content from search results
|
|
274
|
+
highlights: dict with 'query', 'num_sentences', 'highlights_per_url'
|
|
275
|
+
summary: dict with 'query'
|
|
276
|
+
"""
|
|
277
|
+
payload = {
|
|
278
|
+
'ids': ids,
|
|
279
|
+
'text': text
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if highlights:
|
|
283
|
+
payload['highlights'] = highlights
|
|
284
|
+
if summary:
|
|
285
|
+
payload['summary'] = summary
|
|
286
|
+
|
|
287
|
+
response = requests.post(
|
|
288
|
+
'https://api.exa.ai/contents',
|
|
289
|
+
headers={
|
|
290
|
+
'x-api-key': os.environ['EXA_API_KEY'],
|
|
291
|
+
'Content-Type': 'application/json'
|
|
292
|
+
},
|
|
293
|
+
json=payload
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
response.raise_for_status()
|
|
297
|
+
return response.json()
|
|
298
|
+
|
|
299
|
+
# Search and extract
|
|
300
|
+
search_results = exa_search(
|
|
301
|
+
'comprehensive guide to database indexing',
|
|
302
|
+
num_results=3,
|
|
303
|
+
category='pdf'
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
ids = [r['id'] for r in search_results['results']]
|
|
307
|
+
contents = exa_get_contents(
|
|
308
|
+
ids,
|
|
309
|
+
text=True,
|
|
310
|
+
highlights={
|
|
311
|
+
'query': 'B-tree index performance',
|
|
312
|
+
'num_sentences': 5,
|
|
313
|
+
'highlights_per_url': 3
|
|
314
|
+
},
|
|
315
|
+
summary={'query': 'Summarize the key indexing strategies'}
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
for content in contents['results']:
|
|
319
|
+
print(f"\n{content['title']}")
|
|
320
|
+
print(f"Summary: {content.get('summary', 'N/A')}")
|
|
321
|
+
print("Highlights:")
|
|
322
|
+
for highlight in content.get('highlights', []):
|
|
323
|
+
print(f" - {highlight}")
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Combined Search and Content Extraction
|
|
327
|
+
|
|
328
|
+
**Node.js Example:**
|
|
329
|
+
```javascript
|
|
330
|
+
async function exaSearchAndExtract(query, options = {}) {
|
|
331
|
+
// Step 1: Search
|
|
332
|
+
const searchResults = await exaSearch(query, {
|
|
333
|
+
numResults: options.numResults || 10,
|
|
334
|
+
type: options.type || 'neural',
|
|
335
|
+
category: options.category,
|
|
336
|
+
includeDomains: options.includeDomains,
|
|
337
|
+
excludeDomains: options.excludeDomains,
|
|
338
|
+
startDate: options.startDate
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// Step 2: Extract content from top results
|
|
342
|
+
const ids = searchResults.results.slice(0, options.extractTop || 5).map(r => r.id);
|
|
343
|
+
|
|
344
|
+
const contents = await exaGetContents(ids, {
|
|
345
|
+
text: true,
|
|
346
|
+
highlights: options.highlights,
|
|
347
|
+
summary: options.summary
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Step 3: Combine search metadata with content
|
|
351
|
+
return contents.results.map((content, i) => ({
|
|
352
|
+
...searchResults.results[i],
|
|
353
|
+
fullText: content.text,
|
|
354
|
+
highlights: content.highlights,
|
|
355
|
+
summary: content.summary
|
|
356
|
+
}));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Usage - Research a topic with full content
|
|
360
|
+
const fullResults = await exaSearchAndExtract(
|
|
361
|
+
'cutting-edge techniques in federated learning',
|
|
362
|
+
{
|
|
363
|
+
numResults: 10,
|
|
364
|
+
extractTop: 5,
|
|
365
|
+
category: 'research paper',
|
|
366
|
+
startDate: '2025-01-01',
|
|
367
|
+
highlights: {
|
|
368
|
+
query: 'privacy-preserving methods',
|
|
369
|
+
num_sentences: 3
|
|
370
|
+
},
|
|
371
|
+
summary: {
|
|
372
|
+
query: 'What are the main contributions?'
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
fullResults.forEach(result => {
|
|
378
|
+
console.log(`\n${'='.repeat(80)}`);
|
|
379
|
+
console.log(`Title: ${result.title}`);
|
|
380
|
+
console.log(`URL: ${result.url}`);
|
|
381
|
+
console.log(`Score: ${result.score}`);
|
|
382
|
+
console.log(`\nSummary: ${result.summary}`);
|
|
383
|
+
console.log('\nKey Points:');
|
|
384
|
+
result.highlights?.forEach(h => console.log(` • ${h}`));
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Domain-Specific Search
|
|
389
|
+
|
|
390
|
+
**Python Example:**
|
|
391
|
+
```python
|
|
392
|
+
def exa_github_search(query, language=None, min_stars=None):
|
|
393
|
+
"""
|
|
394
|
+
Search GitHub repositories with optional filters
|
|
395
|
+
"""
|
|
396
|
+
# Use category='github' for better results
|
|
397
|
+
results = exa_search(
|
|
398
|
+
query,
|
|
399
|
+
num_results=20,
|
|
400
|
+
search_type='neural',
|
|
401
|
+
category='github'
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
repos = []
|
|
405
|
+
for result in results['results']:
|
|
406
|
+
repo = {
|
|
407
|
+
'name': result['title'],
|
|
408
|
+
'url': result['url'],
|
|
409
|
+
'score': result['score']
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
# Extract content to get more details
|
|
413
|
+
content = exa_get_contents([result['id']], text=True)
|
|
414
|
+
if content['results']:
|
|
415
|
+
repo['description'] = content['results'][0]['text'][:200]
|
|
416
|
+
|
|
417
|
+
repos.append(repo)
|
|
418
|
+
|
|
419
|
+
return repos
|
|
420
|
+
|
|
421
|
+
# Find Rust web frameworks
|
|
422
|
+
rust_frameworks = exa_github_search('Rust web framework high performance')
|
|
423
|
+
for repo in rust_frameworks[:10]:
|
|
424
|
+
print(f"{repo['name']}: {repo['url']}")
|
|
425
|
+
print(f"Score: {repo['score']:.3f}\n")
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### News and Article Discovery
|
|
429
|
+
|
|
430
|
+
```javascript
|
|
431
|
+
async function exaNewsSearch(topic, options = {}) {
|
|
432
|
+
const now = new Date();
|
|
433
|
+
const defaultStartDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); // 7 days ago
|
|
434
|
+
|
|
435
|
+
const results = await exaSearch(topic, {
|
|
436
|
+
numResults: options.numResults || 20,
|
|
437
|
+
type: 'neural',
|
|
438
|
+
category: 'news',
|
|
439
|
+
startDate: options.startDate || defaultStartDate.toISOString(),
|
|
440
|
+
endDate: options.endDate || now.toISOString(),
|
|
441
|
+
includeDomains: options.includeDomains,
|
|
442
|
+
excludeDomains: options.excludeDomains
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// Get summaries for top articles
|
|
446
|
+
const topIds = results.results.slice(0, 10).map(r => r.id);
|
|
447
|
+
const contents = await exaGetContents(topIds, {
|
|
448
|
+
summary: {
|
|
449
|
+
query: options.summaryQuery || 'What are the key points of this article?'
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
return results.results.map((result, i) => ({
|
|
454
|
+
...result,
|
|
455
|
+
summary: contents.results[i]?.summary
|
|
456
|
+
}));
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Track AI news from reputable sources
|
|
460
|
+
const aiNews = await exaNewsSearch('artificial intelligence breakthroughs', {
|
|
461
|
+
numResults: 20,
|
|
462
|
+
includeDomains: [
|
|
463
|
+
'nature.com',
|
|
464
|
+
'science.org',
|
|
465
|
+
'technologyreview.com',
|
|
466
|
+
'arxiv.org'
|
|
467
|
+
],
|
|
468
|
+
summaryQuery: 'What is the key breakthrough or finding?'
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
console.log('Recent AI Breakthroughs:\n');
|
|
472
|
+
aiNews.forEach((article, i) => {
|
|
473
|
+
console.log(`${i + 1}. ${article.title}`);
|
|
474
|
+
console.log(` ${article.url}`);
|
|
475
|
+
console.log(` ${article.summary}\n`);
|
|
476
|
+
});
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
## MCP Server Integration
|
|
480
|
+
|
|
481
|
+
Add to `.codebuddy/mcp.json`:
|
|
482
|
+
|
|
483
|
+
```json
|
|
484
|
+
{
|
|
485
|
+
"mcpServers": {
|
|
486
|
+
"exa": {
|
|
487
|
+
"command": "npx",
|
|
488
|
+
"args": [
|
|
489
|
+
"-y",
|
|
490
|
+
"@exa-labs/exa-mcp-server"
|
|
491
|
+
],
|
|
492
|
+
"env": {
|
|
493
|
+
"EXA_API_KEY": "${EXA_API_KEY}"
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Available MCP Tools
|
|
501
|
+
|
|
502
|
+
1. **exa_search**
|
|
503
|
+
- Neural/semantic search for finding relevant content
|
|
504
|
+
- Parameters: `query` (string), `num_results` (number), `type` (neural/keyword), `category` (string)
|
|
505
|
+
- Returns: Ranked search results with scores
|
|
506
|
+
|
|
507
|
+
2. **exa_find_similar**
|
|
508
|
+
- Find content similar to a given URL
|
|
509
|
+
- Parameters: `url` (string), `num_results` (number), `category` (string)
|
|
510
|
+
- Returns: Similar pages ranked by relevance
|
|
511
|
+
|
|
512
|
+
3. **exa_get_contents**
|
|
513
|
+
- Extract full text, highlights, or summaries from URLs
|
|
514
|
+
- Parameters: `ids` (array), `text` (boolean), `highlights` (object), `summary` (object)
|
|
515
|
+
- Returns: Extracted content with optional summaries
|
|
516
|
+
|
|
517
|
+
4. **exa_search_and_contents**
|
|
518
|
+
- Combined search and content extraction
|
|
519
|
+
- Parameters: `query` (string), `num_results` (number), `text` (boolean)
|
|
520
|
+
- Returns: Search results with full content
|
|
521
|
+
|
|
522
|
+
Example MCP usage:
|
|
523
|
+
```
|
|
524
|
+
Ask Code Buddy: "Use exa_search to find neural network optimization papers from 2025"
|
|
525
|
+
Ask Code Buddy: "Find repositories similar to https://github.com/rust-lang/rust using exa_find_similar"
|
|
526
|
+
Ask Code Buddy: "Search for 'React Server Components tutorial' and extract full content using exa_search_and_contents"
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
## Common Workflows
|
|
530
|
+
|
|
531
|
+
### 1. Build Research Knowledge Base
|
|
532
|
+
|
|
533
|
+
**Goal:** Create a curated collection of high-quality sources on a topic
|
|
534
|
+
|
|
535
|
+
```javascript
|
|
536
|
+
async function buildKnowledgeBase(topic, options = {}) {
|
|
537
|
+
console.log(`Building knowledge base for: ${topic}\n`);
|
|
538
|
+
|
|
539
|
+
// Step 1: Find authoritative sources
|
|
540
|
+
const researchPapers = await exaSearch(`${topic} research papers`, {
|
|
541
|
+
numResults: 10,
|
|
542
|
+
category: 'research paper',
|
|
543
|
+
startDate: options.startDate || '2023-01-01'
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
// Step 2: Find practical guides and tutorials
|
|
547
|
+
const tutorials = await exaSearch(`comprehensive ${topic} tutorial guide`, {
|
|
548
|
+
numResults: 10,
|
|
549
|
+
type: 'neural'
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
// Step 3: Find GitHub implementations
|
|
553
|
+
const implementations = await exaSearch(`${topic} implementation`, {
|
|
554
|
+
numResults: 10,
|
|
555
|
+
category: 'github'
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
// Step 4: Extract content from top sources
|
|
559
|
+
const allIds = [
|
|
560
|
+
...researchPapers.results.slice(0, 3).map(r => r.id),
|
|
561
|
+
...tutorials.results.slice(0, 3).map(r => r.id),
|
|
562
|
+
...implementations.results.slice(0, 2).map(r => r.id)
|
|
563
|
+
];
|
|
564
|
+
|
|
565
|
+
const contents = await exaGetContents(allIds, {
|
|
566
|
+
text: true,
|
|
567
|
+
summary: {
|
|
568
|
+
query: 'Provide a comprehensive summary of the key concepts and methods'
|
|
569
|
+
},
|
|
570
|
+
highlights: {
|
|
571
|
+
query: topic,
|
|
572
|
+
num_sentences: 5,
|
|
573
|
+
highlights_per_url: 3
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
// Step 5: Organize knowledge base
|
|
578
|
+
const knowledgeBase = {
|
|
579
|
+
topic,
|
|
580
|
+
createdAt: new Date().toISOString(),
|
|
581
|
+
sections: {
|
|
582
|
+
research: researchPapers.results.slice(0, 5),
|
|
583
|
+
tutorials: tutorials.results.slice(0, 5),
|
|
584
|
+
implementations: implementations.results.slice(0, 5)
|
|
585
|
+
},
|
|
586
|
+
detailedContent: contents.results
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
// Step 6: Save to file
|
|
590
|
+
const fs = require('fs');
|
|
591
|
+
const filename = `knowledge-base-${topic.replace(/\s+/g, '-')}-${Date.now()}.json`;
|
|
592
|
+
fs.writeFileSync(filename, JSON.stringify(knowledgeBase, null, 2));
|
|
593
|
+
|
|
594
|
+
console.log(`Knowledge base saved to: ${filename}`);
|
|
595
|
+
console.log(`Total sources: ${Object.values(knowledgeBase.sections).flat().length}`);
|
|
596
|
+
|
|
597
|
+
return knowledgeBase;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Usage
|
|
601
|
+
const kb = await buildKnowledgeBase('differential privacy', {
|
|
602
|
+
startDate: '2024-01-01'
|
|
603
|
+
});
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### 2. Competitive Content Analysis
|
|
607
|
+
|
|
608
|
+
**Goal:** Analyze what competitors are publishing and find content gaps
|
|
609
|
+
|
|
610
|
+
```python
|
|
611
|
+
def competitive_content_analysis(competitors, topic):
|
|
612
|
+
"""
|
|
613
|
+
Analyze content published by competitors on a specific topic
|
|
614
|
+
competitors: list of domains
|
|
615
|
+
"""
|
|
616
|
+
analysis = {
|
|
617
|
+
'topic': topic,
|
|
618
|
+
'competitors': {},
|
|
619
|
+
'content_gaps': [],
|
|
620
|
+
'unique_angles': {}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
# Step 1: Search each competitor's content
|
|
624
|
+
for competitor in competitors:
|
|
625
|
+
results = exa_search(
|
|
626
|
+
topic,
|
|
627
|
+
num_results=20,
|
|
628
|
+
search_type='neural',
|
|
629
|
+
include_domains=[competitor]
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
# Step 2: Extract titles and get summaries
|
|
633
|
+
if results['results']:
|
|
634
|
+
ids = [r['id'] for r in results['results'][:5]]
|
|
635
|
+
contents = exa_get_contents(
|
|
636
|
+
ids,
|
|
637
|
+
summary={'query': 'What is the main angle or unique value proposition?'}
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
analysis['competitors'][competitor] = {
|
|
641
|
+
'article_count': len(results['results']),
|
|
642
|
+
'top_articles': [
|
|
643
|
+
{
|
|
644
|
+
'title': r['title'],
|
|
645
|
+
'url': r['url'],
|
|
646
|
+
'summary': contents['results'][i].get('summary', '')
|
|
647
|
+
}
|
|
648
|
+
for i, r in enumerate(results['results'][:5])
|
|
649
|
+
]
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
# Step 3: Find similar high-performing content from other sources
|
|
653
|
+
all_competitor_urls = []
|
|
654
|
+
for comp_data in analysis['competitors'].values():
|
|
655
|
+
all_competitor_urls.extend([a['url'] for a in comp_data['top_articles'][:2]])
|
|
656
|
+
|
|
657
|
+
# Step 4: Find what others are doing differently
|
|
658
|
+
for url in all_competitor_urls[:3]:
|
|
659
|
+
similar = exa_find_similar(url, num_results=10, exclude_source=True)
|
|
660
|
+
for result in similar['results']:
|
|
661
|
+
if not any(comp in result['url'] for comp in competitors):
|
|
662
|
+
analysis['unique_angles'][result['url']] = result['title']
|
|
663
|
+
|
|
664
|
+
# Step 5: Identify content gaps
|
|
665
|
+
broad_results = exa_search(
|
|
666
|
+
f'{topic} comprehensive guide',
|
|
667
|
+
num_results=30,
|
|
668
|
+
exclude_domains=competitors
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
analysis['content_gaps'] = [
|
|
672
|
+
{
|
|
673
|
+
'title': r['title'],
|
|
674
|
+
'url': r['url'],
|
|
675
|
+
'score': r['score']
|
|
676
|
+
}
|
|
677
|
+
for r in broad_results['results'][:10]
|
|
678
|
+
if r['score'] > 0.7 # High relevance but missing from competitors
|
|
679
|
+
]
|
|
680
|
+
|
|
681
|
+
return analysis
|
|
682
|
+
|
|
683
|
+
# Analyze competitor content strategy
|
|
684
|
+
competitors = [
|
|
685
|
+
'vercel.com',
|
|
686
|
+
'netlify.com',
|
|
687
|
+
'railway.app'
|
|
688
|
+
]
|
|
689
|
+
|
|
690
|
+
analysis = competitive_content_analysis(competitors, 'serverless deployment')
|
|
691
|
+
|
|
692
|
+
print(f"Content Analysis for: {analysis['topic']}\n")
|
|
693
|
+
for competitor, data in analysis['competitors'].items():
|
|
694
|
+
print(f"{competitor}: {data['article_count']} articles")
|
|
695
|
+
for article in data['top_articles']:
|
|
696
|
+
print(f" - {article['title']}")
|
|
697
|
+
|
|
698
|
+
print(f"\nContent Gaps ({len(analysis['content_gaps'])} opportunities):")
|
|
699
|
+
for gap in analysis['content_gaps'][:5]:
|
|
700
|
+
print(f" - {gap['title']} (score: {gap['score']:.2f})")
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
### 3. Trend Discovery and Monitoring
|
|
704
|
+
|
|
705
|
+
**Goal:** Discover emerging trends and track their evolution
|
|
706
|
+
|
|
707
|
+
```javascript
|
|
708
|
+
async function discoverTrends(baseTopic, timeWindow = 30) {
|
|
709
|
+
// Step 1: Search recent content
|
|
710
|
+
const endDate = new Date();
|
|
711
|
+
const startDate = new Date(endDate.getTime() - timeWindow * 24 * 60 * 60 * 1000);
|
|
712
|
+
|
|
713
|
+
const recentContent = await exaSearch(`latest developments in ${baseTopic}`, {
|
|
714
|
+
numResults: 50,
|
|
715
|
+
type: 'neural',
|
|
716
|
+
startDate: startDate.toISOString(),
|
|
717
|
+
endDate: endDate.toISOString()
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
// Step 2: Extract highlights to identify themes
|
|
721
|
+
const ids = recentContent.results.map(r => r.id);
|
|
722
|
+
const contents = await exaGetContents(ids, {
|
|
723
|
+
highlights: {
|
|
724
|
+
query: baseTopic,
|
|
725
|
+
num_sentences: 2,
|
|
726
|
+
highlights_per_url: 3
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
// Step 3: Analyze title patterns to identify trends
|
|
731
|
+
const titleWords = recentContent.results
|
|
732
|
+
.map(r => r.title.toLowerCase())
|
|
733
|
+
.join(' ')
|
|
734
|
+
.split(/\s+/)
|
|
735
|
+
.filter(word => word.length > 5);
|
|
736
|
+
|
|
737
|
+
const wordFreq = {};
|
|
738
|
+
titleWords.forEach(word => {
|
|
739
|
+
wordFreq[word] = (wordFreq[word] || 0) + 1;
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
const trendingTerms = Object.entries(wordFreq)
|
|
743
|
+
.sort((a, b) => b[1] - a[1])
|
|
744
|
+
.slice(0, 10)
|
|
745
|
+
.map(([term, count]) => ({ term, count }));
|
|
746
|
+
|
|
747
|
+
// Step 4: For each trending term, find representative content
|
|
748
|
+
const trendDetails = await Promise.all(
|
|
749
|
+
trendingTerms.slice(0, 5).map(async ({ term }) => {
|
|
750
|
+
const trendSearch = await exaSearch(`${baseTopic} ${term}`, {
|
|
751
|
+
numResults: 5,
|
|
752
|
+
type: 'neural',
|
|
753
|
+
startDate: startDate.toISOString()
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
return {
|
|
757
|
+
term,
|
|
758
|
+
examples: trendSearch.results.slice(0, 3).map(r => ({
|
|
759
|
+
title: r.title,
|
|
760
|
+
url: r.url
|
|
761
|
+
}))
|
|
762
|
+
};
|
|
763
|
+
})
|
|
764
|
+
);
|
|
765
|
+
|
|
766
|
+
// Step 5: Compare to older content to confirm novelty
|
|
767
|
+
const oldStartDate = new Date(startDate.getTime() - timeWindow * 24 * 60 * 60 * 1000);
|
|
768
|
+
const olderContent = await exaSearch(baseTopic, {
|
|
769
|
+
numResults: 30,
|
|
770
|
+
startDate: oldStartDate.toISOString(),
|
|
771
|
+
endDate: startDate.toISOString()
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
const oldTitleWords = olderContent.results
|
|
775
|
+
.map(r => r.title.toLowerCase())
|
|
776
|
+
.join(' ')
|
|
777
|
+
.split(/\s+/);
|
|
778
|
+
|
|
779
|
+
const emergingTrends = trendDetails.filter(trend => {
|
|
780
|
+
const oldFrequency = oldTitleWords.filter(w => w === trend.term).length;
|
|
781
|
+
return oldFrequency < 3; // Term appeared less than 3 times in older content
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
return {
|
|
785
|
+
baseTopic,
|
|
786
|
+
timeWindow: `${timeWindow} days`,
|
|
787
|
+
totalArticles: recentContent.results.length,
|
|
788
|
+
trendingTerms,
|
|
789
|
+
emergingTrends,
|
|
790
|
+
detectedAt: new Date().toISOString()
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// Monitor AI trends
|
|
795
|
+
const trends = await discoverTrends('artificial intelligence', 30);
|
|
796
|
+
|
|
797
|
+
console.log(`Trend Analysis: ${trends.baseTopic}`);
|
|
798
|
+
console.log(`Time Window: ${trends.timeWindow}`);
|
|
799
|
+
console.log(`\nTop Trending Terms:`);
|
|
800
|
+
trends.trendingTerms.forEach(({ term, count }) => {
|
|
801
|
+
console.log(` ${term}: ${count} mentions`);
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
console.log(`\nEmerging Trends (${trends.emergingTrends.length}):`);
|
|
805
|
+
trends.emergingTrends.forEach(trend => {
|
|
806
|
+
console.log(`\n ${trend.term.toUpperCase()}`);
|
|
807
|
+
trend.examples.forEach(ex => {
|
|
808
|
+
console.log(` - ${ex.title}`);
|
|
809
|
+
console.log(` ${ex.url}`);
|
|
810
|
+
});
|
|
811
|
+
});
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
### 4. Academic Research Pipeline
|
|
815
|
+
|
|
816
|
+
**Goal:** Build comprehensive research reports with citations
|
|
817
|
+
|
|
818
|
+
```python
|
|
819
|
+
import json
|
|
820
|
+
from collections import defaultdict
|
|
821
|
+
|
|
822
|
+
def academic_research_pipeline(research_question, num_papers=20):
|
|
823
|
+
"""
|
|
824
|
+
Comprehensive academic research workflow
|
|
825
|
+
"""
|
|
826
|
+
print(f"Research Question: {research_question}\n")
|
|
827
|
+
|
|
828
|
+
# Step 1: Find relevant research papers
|
|
829
|
+
papers = exa_search(
|
|
830
|
+
research_question,
|
|
831
|
+
num_results=num_papers,
|
|
832
|
+
search_type='neural',
|
|
833
|
+
category='research paper',
|
|
834
|
+
start_date='2020-01-01'
|
|
835
|
+
)
|
|
836
|
+
|
|
837
|
+
print(f"Found {len(papers['results'])} papers\n")
|
|
838
|
+
|
|
839
|
+
# Step 2: Get full content and summaries
|
|
840
|
+
paper_ids = [p['id'] for p in papers['results']]
|
|
841
|
+
contents = exa_get_contents(
|
|
842
|
+
paper_ids,
|
|
843
|
+
text=True,
|
|
844
|
+
summary={'query': 'What are the key contributions and methodology?'},
|
|
845
|
+
highlights={
|
|
846
|
+
'query': 'novel approach methodology results',
|
|
847
|
+
'num_sentences': 5,
|
|
848
|
+
'highlights_per_url': 5
|
|
849
|
+
}
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
# Step 3: Categorize papers by approach/method
|
|
853
|
+
categorized = defaultdict(list)
|
|
854
|
+
|
|
855
|
+
for i, paper in enumerate(papers['results']):
|
|
856
|
+
content = contents['results'][i]
|
|
857
|
+
summary = content.get('summary', '')
|
|
858
|
+
|
|
859
|
+
# Simple categorization (could use LLM for better results)
|
|
860
|
+
category = 'other'
|
|
861
|
+
if 'deep learning' in summary.lower():
|
|
862
|
+
category = 'deep learning'
|
|
863
|
+
elif 'statistical' in summary.lower() or 'bayesian' in summary.lower():
|
|
864
|
+
category = 'statistical methods'
|
|
865
|
+
elif 'survey' in summary.lower() or 'review' in summary.lower():
|
|
866
|
+
category = 'surveys'
|
|
867
|
+
|
|
868
|
+
categorized[category].append({
|
|
869
|
+
'title': paper['title'],
|
|
870
|
+
'url': paper['url'],
|
|
871
|
+
'summary': summary,
|
|
872
|
+
'score': paper['score'],
|
|
873
|
+
'published_date': paper.get('published_date'),
|
|
874
|
+
'highlights': content.get('highlights', [])
|
|
875
|
+
})
|
|
876
|
+
|
|
877
|
+
# Step 4: Find related work from top papers
|
|
878
|
+
related_work = []
|
|
879
|
+
for paper in papers['results'][:3]:
|
|
880
|
+
similar = exa_find_similar(
|
|
881
|
+
paper['url'],
|
|
882
|
+
num_results=5,
|
|
883
|
+
category='research paper'
|
|
884
|
+
)
|
|
885
|
+
related_work.extend(similar['results'])
|
|
886
|
+
|
|
887
|
+
# Step 5: Build research report
|
|
888
|
+
report = {
|
|
889
|
+
'research_question': research_question,
|
|
890
|
+
'generated_at': datetime.now().isoformat(),
|
|
891
|
+
'paper_count': len(papers['results']),
|
|
892
|
+
'categories': dict(categorized),
|
|
893
|
+
'related_work': [
|
|
894
|
+
{'title': r['title'], 'url': r['url']}
|
|
895
|
+
for r in related_work[:10]
|
|
896
|
+
],
|
|
897
|
+
'top_papers': []
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
# Add top 5 papers with full details
|
|
901
|
+
for i in range(min(5, len(papers['results']))):
|
|
902
|
+
paper = papers['results'][i]
|
|
903
|
+
content = contents['results'][i]
|
|
904
|
+
|
|
905
|
+
report['top_papers'].append({
|
|
906
|
+
'rank': i + 1,
|
|
907
|
+
'title': paper['title'],
|
|
908
|
+
'url': paper['url'],
|
|
909
|
+
'score': paper['score'],
|
|
910
|
+
'published_date': paper.get('published_date'),
|
|
911
|
+
'summary': content.get('summary'),
|
|
912
|
+
'key_findings': content.get('highlights', [])
|
|
913
|
+
})
|
|
914
|
+
|
|
915
|
+
# Step 6: Save report
|
|
916
|
+
filename = f"research-report-{datetime.now().strftime('%Y%m%d-%H%M%S')}.json"
|
|
917
|
+
with open(filename, 'w') as f:
|
|
918
|
+
json.dump(report, f, indent=2)
|
|
919
|
+
|
|
920
|
+
print(f"Research report saved to: {filename}")
|
|
921
|
+
|
|
922
|
+
# Print summary
|
|
923
|
+
print(f"\nResearch Summary:")
|
|
924
|
+
print(f"Categories found: {list(categorized.keys())}")
|
|
925
|
+
for category, papers_list in categorized.items():
|
|
926
|
+
print(f" {category}: {len(papers_list)} papers")
|
|
927
|
+
|
|
928
|
+
print(f"\nTop 3 Papers:")
|
|
929
|
+
for paper in report['top_papers'][:3]:
|
|
930
|
+
print(f"{paper['rank']}. {paper['title']}")
|
|
931
|
+
print(f" {paper['url']}")
|
|
932
|
+
print(f" Score: {paper['score']:.3f}")
|
|
933
|
+
print()
|
|
934
|
+
|
|
935
|
+
return report
|
|
936
|
+
|
|
937
|
+
# Usage
|
|
938
|
+
report = academic_research_pipeline(
|
|
939
|
+
'attention mechanisms in graph neural networks',
|
|
940
|
+
num_papers=30
|
|
941
|
+
)
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
### 5. Content Recommendation Engine
|
|
945
|
+
|
|
946
|
+
**Goal:** Build a personalized content recommendation system
|
|
947
|
+
|
|
948
|
+
```javascript
|
|
949
|
+
async function contentRecommendationEngine(userInterests, readHistory) {
|
|
950
|
+
const recommendations = {
|
|
951
|
+
personalized: [],
|
|
952
|
+
trending: [],
|
|
953
|
+
similar: [],
|
|
954
|
+
diverse: []
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
// Step 1: Search based on user interests
|
|
958
|
+
for (const interest of userInterests) {
|
|
959
|
+
const results = await exaSearch(`in-depth ${interest} analysis`, {
|
|
960
|
+
numResults: 10,
|
|
961
|
+
type: 'neural'
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
recommendations.personalized.push(...results.results.slice(0, 3));
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// Step 2: Find similar content to what user has read
|
|
968
|
+
for (const readUrl of readHistory.slice(-5)) {
|
|
969
|
+
try {
|
|
970
|
+
const similar = await exaFindSimilar(readUrl, {
|
|
971
|
+
numResults: 5,
|
|
972
|
+
excludeSourceDomain: true
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
recommendations.similar.push(...similar.results.slice(0, 2));
|
|
976
|
+
} catch (err) {
|
|
977
|
+
console.error(`Failed to find similar for ${readUrl}`);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// Step 3: Find trending content in user's domains
|
|
982
|
+
const endDate = new Date();
|
|
983
|
+
const startDate = new Date(endDate.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
984
|
+
|
|
985
|
+
const trending = await exaSearch(
|
|
986
|
+
userInterests.join(' OR '),
|
|
987
|
+
{
|
|
988
|
+
numResults: 20,
|
|
989
|
+
type: 'neural',
|
|
990
|
+
startDate: startDate.toISOString(),
|
|
991
|
+
endDate: endDate.toISOString()
|
|
992
|
+
}
|
|
993
|
+
);
|
|
994
|
+
|
|
995
|
+
recommendations.trending.push(...trending.results.slice(0, 5));
|
|
996
|
+
|
|
997
|
+
// Step 4: Diversify - find content in adjacent topics
|
|
998
|
+
const adjacentTopics = await Promise.all(
|
|
999
|
+
userInterests.slice(0, 2).map(async interest => {
|
|
1000
|
+
const results = await exaSearch(`${interest} applications`, {
|
|
1001
|
+
numResults: 5,
|
|
1002
|
+
type: 'neural'
|
|
1003
|
+
});
|
|
1004
|
+
return results.results;
|
|
1005
|
+
})
|
|
1006
|
+
);
|
|
1007
|
+
|
|
1008
|
+
recommendations.diverse.push(...adjacentTopics.flat().slice(0, 5));
|
|
1009
|
+
|
|
1010
|
+
// Step 5: Remove duplicates and rank
|
|
1011
|
+
const seen = new Set();
|
|
1012
|
+
const deduplicated = Object.keys(recommendations).reduce((acc, category) => {
|
|
1013
|
+
acc[category] = recommendations[category].filter(item => {
|
|
1014
|
+
if (seen.has(item.url)) return false;
|
|
1015
|
+
seen.add(item.url);
|
|
1016
|
+
return true;
|
|
1017
|
+
});
|
|
1018
|
+
return acc;
|
|
1019
|
+
}, {});
|
|
1020
|
+
|
|
1021
|
+
// Step 6: Get summaries for top recommendations
|
|
1022
|
+
const topIds = [
|
|
1023
|
+
...deduplicated.personalized.slice(0, 3),
|
|
1024
|
+
...deduplicated.trending.slice(0, 2)
|
|
1025
|
+
].map(r => r.id);
|
|
1026
|
+
|
|
1027
|
+
const contents = await exaGetContents(topIds, {
|
|
1028
|
+
summary: { query: 'Summarize the main points and why this is valuable' }
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
// Step 7: Attach summaries
|
|
1032
|
+
let contentIndex = 0;
|
|
1033
|
+
['personalized', 'trending'].forEach(category => {
|
|
1034
|
+
const limit = category === 'personalized' ? 3 : 2;
|
|
1035
|
+
deduplicated[category].slice(0, limit).forEach(rec => {
|
|
1036
|
+
rec.summary = contents.results[contentIndex++]?.summary;
|
|
1037
|
+
});
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
return deduplicated;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// Usage
|
|
1044
|
+
const recommendations = await contentRecommendationEngine(
|
|
1045
|
+
['machine learning', 'distributed systems', 'rust programming'],
|
|
1046
|
+
[
|
|
1047
|
+
'https://arxiv.org/abs/2106.09685',
|
|
1048
|
+
'https://jepsen.io/analyses',
|
|
1049
|
+
'https://blog.rust-lang.org/2024/12/05/Rust-2024.html'
|
|
1050
|
+
]
|
|
1051
|
+
);
|
|
1052
|
+
|
|
1053
|
+
console.log('=== PERSONALIZED RECOMMENDATIONS ===\n');
|
|
1054
|
+
recommendations.personalized.slice(0, 5).forEach((rec, i) => {
|
|
1055
|
+
console.log(`${i + 1}. ${rec.title}`);
|
|
1056
|
+
console.log(` ${rec.url}`);
|
|
1057
|
+
if (rec.summary) console.log(` ${rec.summary}\n`);
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
console.log('\n=== TRENDING THIS WEEK ===\n');
|
|
1061
|
+
recommendations.trending.slice(0, 5).forEach((rec, i) => {
|
|
1062
|
+
console.log(`${i + 1}. ${rec.title}`);
|
|
1063
|
+
console.log(` ${rec.url}`);
|
|
1064
|
+
if (rec.summary) console.log(` ${rec.summary}\n`);
|
|
1065
|
+
});
|
|
1066
|
+
```
|
|
1067
|
+
|
|
1068
|
+
## Best Practices
|
|
1069
|
+
|
|
1070
|
+
1. **Use Neural Search for Concepts:** Set `type: 'neural'` when searching by meaning/concept rather than exact keywords
|
|
1071
|
+
|
|
1072
|
+
2. **Category Filtering:** Use `category` parameter to narrow results (research paper, github, news, company, etc.)
|
|
1073
|
+
|
|
1074
|
+
3. **Autoprompt:** Enable `use_autoprompt: true` to let Exa optimize your query for better results
|
|
1075
|
+
|
|
1076
|
+
4. **Exclude Source Domain:** When finding similar content, set `exclude_source_domain: true` to avoid the original source
|
|
1077
|
+
|
|
1078
|
+
5. **Date Filtering:** Use `start_published_date` and `end_published_date` for time-sensitive searches
|
|
1079
|
+
|
|
1080
|
+
6. **Domain Control:**
|
|
1081
|
+
- Use `include_domains` to search only trusted sources
|
|
1082
|
+
- Use `exclude_domains` to filter out low-quality content
|
|
1083
|
+
|
|
1084
|
+
7. **Content Extraction:**
|
|
1085
|
+
- Use `highlights` with specific query for targeted extraction
|
|
1086
|
+
- Use `summary` with a question for AI-generated summaries
|
|
1087
|
+
- Set `num_sentences` to control highlight length
|
|
1088
|
+
|
|
1089
|
+
8. **Scoring:** Results include a `score` (0-1) indicating relevance - filter by score threshold for quality
|
|
1090
|
+
|
|
1091
|
+
## Troubleshooting
|
|
1092
|
+
|
|
1093
|
+
**API Key Issues:**
|
|
1094
|
+
```bash
|
|
1095
|
+
# Test API key
|
|
1096
|
+
curl -s https://api.exa.ai/search \
|
|
1097
|
+
-H "x-api-key: ${EXA_API_KEY}" \
|
|
1098
|
+
-H "Content-Type: application/json" \
|
|
1099
|
+
-d '{"query":"test","num_results":1}' | jq .
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
**Low Quality Results:**
|
|
1103
|
+
- Enable `use_autoprompt: true` for query optimization
|
|
1104
|
+
- Use more descriptive queries (Exa understands natural language)
|
|
1105
|
+
- Add category filter to narrow scope
|
|
1106
|
+
- Use `include_domains` to specify authoritative sources
|
|
1107
|
+
|
|
1108
|
+
**Rate Limiting:**
|
|
1109
|
+
- Free tier: 1000 searches/month
|
|
1110
|
+
- Implement caching for repeated queries
|
|
1111
|
+
- Batch content extraction requests
|
|
1112
|
+
|
|
1113
|
+
**Empty Results:**
|
|
1114
|
+
- Try neural search instead of keyword
|
|
1115
|
+
- Broaden date range or remove date filters
|
|
1116
|
+
- Remove overly restrictive domain filters
|
|
1117
|
+
- Rephrase query to be more descriptive
|
|
1118
|
+
|
|
1119
|
+
**Content Extraction Fails:**
|
|
1120
|
+
- Some sites block scraping - check `text` field for null
|
|
1121
|
+
- Try different URLs from search results
|
|
1122
|
+
- Use `highlights` instead of full `text` for preview
|