@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.
Files changed (305) hide show
  1. package/.codebuddy/skills/bundled/brave-search/SKILL.md +490 -0
  2. package/.codebuddy/skills/bundled/exa-search/SKILL.md +1122 -0
  3. package/.codebuddy/skills/bundled/perplexity/SKILL.md +748 -0
  4. package/.codebuddy/skills/bundled/playwright/SKILL.md +520 -0
  5. package/.codebuddy/skills/bundled/puppeteer/SKILL.md +708 -0
  6. package/.codebuddy/skills/bundled/web-fetch/SKILL.md +1003 -0
  7. package/README.md +56 -0
  8. package/dist/agent/agent-state.d.ts +3 -3
  9. package/dist/agent/agent-state.js +6 -6
  10. package/dist/agent/agent-state.js.map +1 -1
  11. package/dist/agent/base-agent.d.ts +4 -4
  12. package/dist/agent/base-agent.js +22 -9
  13. package/dist/agent/base-agent.js.map +1 -1
  14. package/dist/agent/cache-trace.d.ts +56 -0
  15. package/dist/agent/cache-trace.js +98 -0
  16. package/dist/agent/cache-trace.js.map +1 -0
  17. package/dist/agent/codebuddy-agent.js +4 -2
  18. package/dist/agent/codebuddy-agent.js.map +1 -1
  19. package/dist/agent/execution/agent-executor.d.ts +4 -4
  20. package/dist/agent/execution/agent-executor.js +46 -14
  21. package/dist/agent/execution/agent-executor.js.map +1 -1
  22. package/dist/agent/facades/agent-context-facade.js +1 -3
  23. package/dist/agent/facades/agent-context-facade.js.map +1 -1
  24. package/dist/agent/facades/message-history-manager.js +14 -12
  25. package/dist/agent/facades/message-history-manager.js.map +1 -1
  26. package/dist/agent/facades/session-facade.d.ts +3 -3
  27. package/dist/agent/facades/session-facade.js +6 -6
  28. package/dist/agent/facades/session-facade.js.map +1 -1
  29. package/dist/agent/history-repair.d.ts +37 -0
  30. package/dist/agent/history-repair.js +124 -0
  31. package/dist/agent/history-repair.js.map +1 -0
  32. package/dist/agent/index.d.ts +3 -3
  33. package/dist/agent/index.js +3 -3
  34. package/dist/agent/index.js.map +1 -1
  35. package/dist/agent/isolation/agent-workspace.d.ts +1 -0
  36. package/dist/agent/isolation/agent-workspace.js +10 -0
  37. package/dist/agent/isolation/agent-workspace.js.map +1 -1
  38. package/dist/agent/specialized/archive-agent.d.ts +3 -0
  39. package/dist/agent/specialized/archive-agent.js +71 -31
  40. package/dist/agent/specialized/archive-agent.js.map +1 -1
  41. package/dist/agent/specialized/index.d.ts +9 -8
  42. package/dist/agent/specialized/index.js +16 -8
  43. package/dist/agent/specialized/index.js.map +1 -1
  44. package/dist/agent/specialized/security-review/agent.js +19 -8
  45. package/dist/agent/specialized/security-review/agent.js.map +1 -1
  46. package/dist/agent/tool-executor.js +5 -0
  47. package/dist/agent/tool-executor.js.map +1 -1
  48. package/dist/agent/turn-diff-tracker.d.ts +79 -0
  49. package/dist/agent/turn-diff-tracker.js +195 -0
  50. package/dist/agent/turn-diff-tracker.js.map +1 -0
  51. package/dist/browser/controller.js +8 -4
  52. package/dist/browser/controller.js.map +1 -1
  53. package/dist/browser-automation/browser-manager.js +8 -1
  54. package/dist/browser-automation/browser-manager.js.map +1 -1
  55. package/dist/checkpoints/checkpoint-versioning.js +78 -20
  56. package/dist/checkpoints/checkpoint-versioning.js.map +1 -1
  57. package/dist/cli/config-loader.js +2 -4
  58. package/dist/cli/config-loader.js.map +1 -1
  59. package/dist/codebuddy/client.js +70 -11
  60. package/dist/codebuddy/client.js.map +1 -1
  61. package/dist/codebuddy/tools.d.ts +1 -7
  62. package/dist/codebuddy/tools.js +2 -30
  63. package/dist/codebuddy/tools.js.map +1 -1
  64. package/dist/commands/cli/daemon-commands.d.ts +14 -0
  65. package/dist/commands/cli/daemon-commands.js +166 -0
  66. package/dist/commands/cli/daemon-commands.js.map +1 -0
  67. package/dist/commands/cli/speak-command.d.ts +10 -0
  68. package/dist/commands/cli/speak-command.js +97 -0
  69. package/dist/commands/cli/speak-command.js.map +1 -0
  70. package/dist/commands/cli/utility-commands.d.ts +10 -0
  71. package/dist/commands/cli/utility-commands.js +88 -0
  72. package/dist/commands/cli/utility-commands.js.map +1 -0
  73. package/dist/commands/handlers/fcs-handlers.js +1 -1
  74. package/dist/commands/handlers/fcs-handlers.js.map +1 -1
  75. package/dist/commands/handlers/memory-handlers.js +2 -1
  76. package/dist/commands/handlers/memory-handlers.js.map +1 -1
  77. package/dist/commands/handlers/vibe-handlers.js +0 -1
  78. package/dist/commands/handlers/vibe-handlers.js.map +1 -1
  79. package/dist/commands/handlers/worktree-handlers.js +11 -0
  80. package/dist/commands/handlers/worktree-handlers.js.map +1 -1
  81. package/dist/commands/index.d.ts +8 -7
  82. package/dist/commands/index.js +10 -8
  83. package/dist/commands/index.js.map +1 -1
  84. package/dist/commands/mcp.d.ts +1 -0
  85. package/dist/commands/mcp.js +66 -7
  86. package/dist/commands/mcp.js.map +1 -1
  87. package/dist/commands/pipeline.js +25 -13
  88. package/dist/commands/pipeline.js.map +1 -1
  89. package/dist/config/hot-reload/watcher.js +4 -4
  90. package/dist/config/hot-reload/watcher.js.map +1 -1
  91. package/dist/config/model-tools.d.ts +41 -0
  92. package/dist/config/model-tools.js +194 -0
  93. package/dist/config/model-tools.js.map +1 -0
  94. package/dist/context/context-manager-v2.d.ts +2 -1
  95. package/dist/context/context-manager-v2.js +34 -5
  96. package/dist/context/context-manager-v2.js.map +1 -1
  97. package/dist/context/index.d.ts +12 -12
  98. package/dist/context/index.js +25 -12
  99. package/dist/context/index.js.map +1 -1
  100. package/dist/daemon/daemon-manager.js +23 -19
  101. package/dist/daemon/daemon-manager.js.map +1 -1
  102. package/dist/database/database-manager.d.ts +4 -0
  103. package/dist/database/database-manager.js +16 -7
  104. package/dist/database/database-manager.js.map +1 -1
  105. package/dist/desktop-automation/nutjs-provider.js +89 -0
  106. package/dist/desktop-automation/nutjs-provider.js.map +1 -1
  107. package/dist/errors/index.d.ts +4 -4
  108. package/dist/errors/index.js +8 -4
  109. package/dist/errors/index.js.map +1 -1
  110. package/dist/fcs/builtins.d.ts +2 -6
  111. package/dist/fcs/builtins.js +2 -568
  112. package/dist/fcs/builtins.js.map +1 -1
  113. package/dist/fcs/codebuddy-bindings.d.ts +3 -43
  114. package/dist/fcs/codebuddy-bindings.js +2 -606
  115. package/dist/fcs/codebuddy-bindings.js.map +1 -1
  116. package/dist/fcs/index.d.ts +2 -27
  117. package/dist/fcs/index.js +2 -53
  118. package/dist/fcs/index.js.map +1 -1
  119. package/dist/fcs/lexer.d.ts +2 -37
  120. package/dist/fcs/lexer.js +2 -459
  121. package/dist/fcs/lexer.js.map +1 -1
  122. package/dist/fcs/parser.d.ts +2 -68
  123. package/dist/fcs/parser.js +2 -893
  124. package/dist/fcs/parser.js.map +1 -1
  125. package/dist/fcs/runtime.d.ts +2 -59
  126. package/dist/fcs/runtime.js +2 -623
  127. package/dist/fcs/runtime.js.map +1 -1
  128. package/dist/fcs/script-registry.d.ts +3 -69
  129. package/dist/fcs/script-registry.js +2 -219
  130. package/dist/fcs/script-registry.js.map +1 -1
  131. package/dist/fcs/sync-bindings.d.ts +3 -101
  132. package/dist/fcs/sync-bindings.js +2 -410
  133. package/dist/fcs/sync-bindings.js.map +1 -1
  134. package/dist/fcs/types.d.ts +2 -285
  135. package/dist/fcs/types.js +2 -103
  136. package/dist/fcs/types.js.map +1 -1
  137. package/dist/hooks/index.d.ts +4 -4
  138. package/dist/hooks/index.js +4 -4
  139. package/dist/hooks/index.js.map +1 -1
  140. package/dist/hooks/use-input-handler.d.ts +1 -1
  141. package/dist/index.js +20 -330
  142. package/dist/index.js.map +1 -1
  143. package/dist/input/voice-control.js +11 -5
  144. package/dist/input/voice-control.js.map +1 -1
  145. package/dist/integrations/json-rpc/server.d.ts +9 -0
  146. package/dist/integrations/json-rpc/server.js +43 -13
  147. package/dist/integrations/json-rpc/server.js.map +1 -1
  148. package/dist/integrations/mcp/mcp-server.js +1 -1
  149. package/dist/integrations/mcp/mcp-server.js.map +1 -1
  150. package/dist/integrations/notification-integrations.d.ts +1 -0
  151. package/dist/integrations/notification-integrations.js +6 -1
  152. package/dist/integrations/notification-integrations.js.map +1 -1
  153. package/dist/mcp/client.js +2 -1
  154. package/dist/mcp/client.js.map +1 -1
  155. package/dist/mcp/config.js +89 -5
  156. package/dist/mcp/config.js.map +1 -1
  157. package/dist/mcp/mcp-client.js +65 -14
  158. package/dist/mcp/mcp-client.js.map +1 -1
  159. package/dist/mcp/transports.d.ts +0 -1
  160. package/dist/mcp/transports.js +1 -5
  161. package/dist/mcp/transports.js.map +1 -1
  162. package/dist/mcp/types.d.ts +2 -0
  163. package/dist/memory/index.d.ts +2 -2
  164. package/dist/memory/index.js +2 -2
  165. package/dist/memory/index.js.map +1 -1
  166. package/dist/persistence/session-lock.d.ts +42 -0
  167. package/dist/persistence/session-lock.js +165 -0
  168. package/dist/persistence/session-lock.js.map +1 -0
  169. package/dist/persistence/session-store.d.ts +18 -3
  170. package/dist/persistence/session-store.js +90 -21
  171. package/dist/persistence/session-store.js.map +1 -1
  172. package/dist/plugins/conflict-detection.js +2 -1
  173. package/dist/plugins/conflict-detection.js.map +1 -1
  174. package/dist/plugins/index.d.ts +3 -3
  175. package/dist/plugins/index.js +3 -3
  176. package/dist/plugins/index.js.map +1 -1
  177. package/dist/plugins/isolated-plugin-runner.d.ts +6 -0
  178. package/dist/plugins/isolated-plugin-runner.js +19 -1
  179. package/dist/plugins/isolated-plugin-runner.js.map +1 -1
  180. package/dist/providers/local-llm-provider.js +28 -8
  181. package/dist/providers/local-llm-provider.js.map +1 -1
  182. package/dist/sandbox/docker-sandbox.js +7 -4
  183. package/dist/sandbox/docker-sandbox.js.map +1 -1
  184. package/dist/scripting/builtins.d.ts +8 -3
  185. package/dist/scripting/builtins.js +506 -355
  186. package/dist/scripting/builtins.js.map +1 -1
  187. package/dist/scripting/codebuddy-bindings.d.ts +47 -0
  188. package/dist/scripting/codebuddy-bindings.js +488 -0
  189. package/dist/scripting/codebuddy-bindings.js.map +1 -0
  190. package/dist/scripting/index.d.ts +33 -30
  191. package/dist/scripting/index.js +41 -36
  192. package/dist/scripting/index.js.map +1 -1
  193. package/dist/scripting/lexer.d.ts +31 -13
  194. package/dist/scripting/lexer.js +379 -292
  195. package/dist/scripting/lexer.js.map +1 -1
  196. package/dist/scripting/parser.d.ts +63 -44
  197. package/dist/scripting/parser.js +700 -473
  198. package/dist/scripting/parser.js.map +1 -1
  199. package/dist/scripting/runtime.d.ts +55 -24
  200. package/dist/scripting/runtime.js +600 -288
  201. package/dist/scripting/runtime.js.map +1 -1
  202. package/dist/scripting/script-registry.d.ts +54 -0
  203. package/dist/scripting/script-registry.js +202 -0
  204. package/dist/scripting/script-registry.js.map +1 -0
  205. package/dist/scripting/sync-bindings.d.ts +105 -0
  206. package/dist/scripting/sync-bindings.js +353 -0
  207. package/dist/scripting/sync-bindings.js.map +1 -0
  208. package/dist/scripting/types.d.ts +297 -199
  209. package/dist/scripting/types.js +86 -60
  210. package/dist/scripting/types.js.map +1 -1
  211. package/dist/search/usearch-index.js +42 -7
  212. package/dist/search/usearch-index.js.map +1 -1
  213. package/dist/security/bash-parser.d.ts +51 -0
  214. package/dist/security/bash-parser.js +327 -0
  215. package/dist/security/bash-parser.js.map +1 -0
  216. package/dist/security/index.d.ts +7 -5
  217. package/dist/security/index.js +8 -7
  218. package/dist/security/index.js.map +1 -1
  219. package/dist/security/skill-scanner.d.ts +36 -0
  220. package/dist/security/skill-scanner.js +149 -0
  221. package/dist/security/skill-scanner.js.map +1 -0
  222. package/dist/security/trust-folders.d.ts +1 -0
  223. package/dist/security/trust-folders.js +19 -1
  224. package/dist/security/trust-folders.js.map +1 -1
  225. package/dist/server/auth/index.d.ts +2 -2
  226. package/dist/server/auth/index.js +2 -2
  227. package/dist/server/auth/index.js.map +1 -1
  228. package/dist/server/middleware/index.d.ts +5 -5
  229. package/dist/server/middleware/index.js +5 -5
  230. package/dist/server/middleware/index.js.map +1 -1
  231. package/dist/server/middleware/rate-limit.js +15 -3
  232. package/dist/server/middleware/rate-limit.js.map +1 -1
  233. package/dist/server/websocket/handler.js +54 -6
  234. package/dist/server/websocket/handler.js.map +1 -1
  235. package/dist/skills/eligibility.js +26 -4
  236. package/dist/skills/eligibility.js.map +1 -1
  237. package/dist/tasks/background-tasks.js +5 -1
  238. package/dist/tasks/background-tasks.js.map +1 -1
  239. package/dist/tools/apply-patch.d.ts +55 -0
  240. package/dist/tools/apply-patch.js +273 -0
  241. package/dist/tools/apply-patch.js.map +1 -0
  242. package/dist/tools/hooks/default-hooks.d.ts +1 -1
  243. package/dist/tools/hooks/default-hooks.js +2 -1
  244. package/dist/tools/hooks/default-hooks.js.map +1 -1
  245. package/dist/tools/index.d.ts +10 -10
  246. package/dist/tools/index.js +11 -11
  247. package/dist/tools/index.js.map +1 -1
  248. package/dist/tools/registry/bash-tools.js +6 -3
  249. package/dist/tools/registry/bash-tools.js.map +1 -1
  250. package/dist/tools/registry/misc-tools.js +1 -2
  251. package/dist/tools/registry/misc-tools.js.map +1 -1
  252. package/dist/tools/registry/search-tools.js +1 -1
  253. package/dist/tools/registry/search-tools.js.map +1 -1
  254. package/dist/tools/registry/text-editor-tools.js +1 -1
  255. package/dist/tools/registry/text-editor-tools.js.map +1 -1
  256. package/dist/tools/registry/todo-tools.js +37 -5
  257. package/dist/tools/registry/todo-tools.js.map +1 -1
  258. package/dist/tools/registry/tool-registry.js +5 -4
  259. package/dist/tools/registry/tool-registry.js.map +1 -1
  260. package/dist/tools/registry/web-tools.d.ts +1 -1
  261. package/dist/tools/registry/web-tools.js +28 -8
  262. package/dist/tools/registry/web-tools.js.map +1 -1
  263. package/dist/tools/text-editor.d.ts +1 -1
  264. package/dist/tools/text-editor.js +23 -5
  265. package/dist/tools/text-editor.js.map +1 -1
  266. package/dist/tools/web-search.d.ts +52 -37
  267. package/dist/tools/web-search.js +368 -163
  268. package/dist/tools/web-search.js.map +1 -1
  269. package/dist/types/errors.d.ts +1 -1
  270. package/dist/types/errors.js +2 -8
  271. package/dist/types/errors.js.map +1 -1
  272. package/dist/types/index.d.ts +2 -1
  273. package/dist/types/index.js +1 -2
  274. package/dist/types/index.js.map +1 -1
  275. package/dist/ui/components/ChatInterface.d.ts +1 -1
  276. package/dist/ui/index.d.ts +17 -21
  277. package/dist/ui/index.js +25 -22
  278. package/dist/ui/index.js.map +1 -1
  279. package/dist/utils/config-validation/schema.d.ts +15 -15
  280. package/dist/utils/head-tail-truncation.d.ts +34 -0
  281. package/dist/utils/head-tail-truncation.js +98 -0
  282. package/dist/utils/head-tail-truncation.js.map +1 -0
  283. package/dist/utils/logger.js +3 -9
  284. package/dist/utils/logger.js.map +1 -1
  285. package/dist/utils/sanitize.d.ts +5 -0
  286. package/dist/utils/sanitize.js +19 -0
  287. package/dist/utils/sanitize.js.map +1 -1
  288. package/dist/utils/settings-manager.js +4 -4
  289. package/dist/utils/settings-manager.js.map +1 -1
  290. package/dist/workflows/index.d.ts +4 -279
  291. package/dist/workflows/index.js +8 -822
  292. package/dist/workflows/index.js.map +1 -1
  293. package/dist/workflows/state-manager.d.ts +77 -0
  294. package/dist/workflows/state-manager.js +198 -0
  295. package/dist/workflows/state-manager.js.map +1 -0
  296. package/dist/workflows/step-manager.d.ts +39 -0
  297. package/dist/workflows/step-manager.js +196 -0
  298. package/dist/workflows/step-manager.js.map +1 -0
  299. package/dist/workflows/types.d.ts +87 -0
  300. package/dist/workflows/types.js +5 -0
  301. package/dist/workflows/types.js.map +1 -0
  302. package/dist/workflows/workflow-engine.d.ts +34 -0
  303. package/dist/workflows/workflow-engine.js +354 -0
  304. package/dist/workflows/workflow-engine.js.map +1 -0
  305. 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