@youdotcom-oss/api 0.2.0 → 0.2.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/README.md CHANGED
@@ -19,11 +19,11 @@ Fast, lightweight API client and CLI tools for web search, AI answers, and conte
19
19
 
20
20
  ```bash
21
21
  # Use with bunx (no install needed) - Schema-driven JSON input
22
- bunx @youdotcom-oss/api search --json '{"query":"AI developments"}' --client Openclaw
22
+ bunx @youdotcom-oss/api search --json '{"query":"AI developments"}' --client ClaudeCode
23
23
 
24
24
  # Or install globally to use 'ydc' command
25
25
  bun i -g @youdotcom-oss/api
26
- ydc search --json '{"query":"AI developments"}' --client Openclaw
26
+ ydc search --json '{"query":"AI developments"}' --client ClaudeCode
27
27
 
28
28
  # Get comprehensive research with citations
29
29
  bunx @youdotcom-oss/api deep-search --json '{
@@ -137,14 +137,14 @@ ydc search --json '{"query":"..."}' [options]
137
137
 
138
138
  Examples:
139
139
  # Basic search
140
- ydc search --json '{"query":"machine learning"}' --client Openclaw
140
+ ydc search --json '{"query":"machine learning"}' --client ClaudeCode
141
141
 
142
142
  # Search with livecrawl (KEY FEATURE)
143
143
  ydc search --json '{
144
144
  "query":"documentation",
145
145
  "livecrawl":"web",
146
146
  "livecrawl_formats":"markdown"
147
- }' --client Openclaw
147
+ }' --client ClaudeCode
148
148
 
149
149
  # Advanced filters
150
150
  ydc search --json '{
@@ -153,10 +153,10 @@ Examples:
153
153
  "fileType":"pdf",
154
154
  "freshness":"month",
155
155
  "count":10
156
- }' --client Openclaw
156
+ }' --client ClaudeCode
157
157
 
158
158
  # Parse with jq
159
- api search --json '{"query":"AI"}' --client Openclaw | \
159
+ api search --json '{"query":"AI"}' --client ClaudeCode | \
160
160
  jq -r '.results.web[] | .title'
161
161
 
162
162
  # Extract livecrawl content
@@ -164,7 +164,7 @@ Examples:
164
164
  "query":"docs",
165
165
  "livecrawl":"web",
166
166
  "livecrawl_formats":"markdown"
167
- }' --client Openclaw | \
167
+ }' --client ClaudeCode | \
168
168
  jq -r '.results.web[0].contents.markdown'
169
169
  ```
170
170
 
@@ -190,18 +190,18 @@ ydc deep-search --json '{"query":"..."}' [options]
190
190
 
191
191
  Examples:
192
192
  # Comprehensive research with medium effort
193
- api deep-search --json '{"query":"What is quantum computing?"}' --client Openclaw
193
+ api deep-search --json '{"query":"What is quantum computing?"}' --client ClaudeCode
194
194
 
195
195
  # High-effort deep research (up to 5 minutes)
196
196
  api deep-search --json '{
197
197
  "query":"Latest breakthroughs in AI agents",
198
198
  "search_effort":"high"
199
- }' --client Openclaw
199
+ }' --client ClaudeCode
200
200
 
201
201
  # Parse answer and sources
202
202
  api deep-search --json '{
203
203
  "query":"AI trends 2026"
204
- }' --client Openclaw | \
204
+ }' --client ClaudeCode | \
205
205
  jq -r '.answer, "\nSources:", (.results[]? | "- \(.title): \(.url)")'
206
206
  ```
207
207
 
@@ -219,25 +219,25 @@ Examples:
219
219
  api contents --json '{
220
220
  "urls":["https://example.com"],
221
221
  "formats":["markdown"]
222
- }' --client Openclaw
222
+ }' --client ClaudeCode
223
223
 
224
224
  # Multiple formats
225
225
  api contents --json '{
226
226
  "urls":["https://example.com"],
227
227
  "formats":["markdown","html","metadata"]
228
- }' --client Openclaw
228
+ }' --client ClaudeCode
229
229
 
230
230
  # Multiple URLs
231
231
  api contents --json '{
232
232
  "urls":["https://a.com","https://b.com"],
233
233
  "formats":["markdown"]
234
- }' --client Openclaw
234
+ }' --client ClaudeCode
235
235
 
236
236
  # Save to file
237
237
  api contents --json '{
238
238
  "urls":["https://example.com"],
239
239
  "formats":["markdown"]
240
- }' --client Openclaw | \
240
+ }' --client ClaudeCode | \
241
241
  jq -r '.[0].markdown' > output.md
242
242
 
243
243
  # With timeout
@@ -245,7 +245,7 @@ Examples:
245
245
  "urls":["https://example.com"],
246
246
  "formats":["markdown","metadata"],
247
247
  "crawl_timeout":30
248
- }' --client Openclaw
248
+ }' --client ClaudeCode
249
249
  ```
250
250
 
251
251
  **Available contents parameters** (use `--schema` to see full schema):
@@ -363,7 +363,7 @@ console.log(response[0].metadata); // Structured metadata
363
363
  set -e
364
364
 
365
365
  # Capture result, check exit code
366
- if ! result=$(api search --json '{"query":"AI developments"}' --client Openclaw); then
366
+ if ! result=$(api search --json '{"query":"AI developments"}' --client ClaudeCode); then
367
367
  echo "Search failed with code $?"
368
368
  exit 1
369
369
  fi
@@ -377,7 +377,7 @@ echo "$result" | jq .
377
377
  ```bash
378
378
  #!/usr/bin/env bash
379
379
  for i in {1..3}; do
380
- if api search --json '{"query":"AI"}' --client Openclaw; then
380
+ if api search --json '{"query":"AI"}' --client ClaudeCode; then
381
381
  exit 0
382
382
  fi
383
383
  [ $i -lt 3 ] && sleep 5
@@ -390,9 +390,9 @@ exit 1
390
390
 
391
391
  ```bash
392
392
  #!/usr/bin/env bash
393
- ydc search --json '{"query":"AI"}' --client Openclaw &
394
- ydc search --json '{"query":"ML"}' --client Openclaw &
395
- ydc search --json '{"query":"LLM"}' --client Openclaw &
393
+ ydc search --json '{"query":"AI"}' --client ClaudeCode &
394
+ ydc search --json '{"query":"ML"}' --client ClaudeCode &
395
+ ydc search --json '{"query":"LLM"}' --client ClaudeCode &
396
396
  wait
397
397
  ```
398
398
 
@@ -408,18 +408,18 @@ search=$(api search --json '{
408
408
  "count":5,
409
409
  "livecrawl":"web",
410
410
  "livecrawl_formats":"markdown"
411
- }' --client Openclaw)
411
+ }' --client ClaudeCode)
412
412
 
413
413
  # Get comprehensive research with citations
414
414
  answer=$(api deep-search --json '{
415
415
  "query":"Summarize AI developments in 2026",
416
416
  "search_effort":"high"
417
- }' --client Openclaw)
417
+ }' --client ClaudeCode)
418
418
 
419
419
  # Extract top result URL and fetch content
420
420
  url=$(echo "$search" | jq -r '.results.web[0].url')
421
421
  ydc contents --json "{\"urls\":[\"$url\"],\"formats\":[\"markdown\"]}" \
422
- --client Openclaw | jq -r '.[0].markdown' > output.md
422
+ --client ClaudeCode | jq -r '.[0].markdown' > output.md
423
423
  ```
424
424
 
425
425
  ### Schema-Driven Agent
@@ -441,7 +441,7 @@ query=$(jq -n '{
441
441
  }')
442
442
 
443
443
  # Execute search
444
- ydc search --json "$query" --client Openclaw
444
+ ydc search --json "$query" --client ClaudeCode
445
445
  ```
446
446
 
447
447
  ## Agent Skills Integration
@@ -462,10 +462,10 @@ The [youdotcom-cli skill](https://github.com/youdotcom-oss/agent-skills/tree/mai
462
462
  ### Compatible Agents
463
463
 
464
464
  Works with any bash-capable agent supporting Agent Skills:
465
- - **OpenClaw** - Open source bash agent
466
465
  - **Claude Code** - Anthropic's coding tool
467
- - **Codex** - OpenAI's CLI agent
468
466
  - **Cursor** - AI-powered code editor
467
+ - **Droid** - Factory.ai agent
468
+ - **Codex** - OpenAI's CLI agent
469
469
  - **Roo Code** - VS Code extension
470
470
  - And more...
471
471
 
package/bin/cli.js CHANGED
@@ -15,7 +15,7 @@ import { parseArgs as parseArgs2 } from "node:util";
15
15
  // package.json
16
16
  var package_default = {
17
17
  name: "@youdotcom-oss/api",
18
- version: "0.2.0",
18
+ version: "0.2.2",
19
19
  description: "You.com API client with bundled CLI for agents supporting Agent Skills",
20
20
  license: "MIT",
21
21
  engines: {
@@ -4856,13 +4856,16 @@ var DeepSearchResponseSchema = object({
4856
4856
  // src/deep-search/deep-search.utils.ts
4857
4857
  var callDeepSearch = async ({
4858
4858
  deepSearchQuery,
4859
- YDC_API_KEY,
4859
+ YDC_API_KEY = process.env.YDC_API_KEY,
4860
4860
  getUserAgent
4861
4861
  }) => {
4862
+ if (!YDC_API_KEY) {
4863
+ throw new Error("YDC_API_KEY is required for Deep Search API");
4864
+ }
4862
4865
  const response = await fetch(DEEP_SEARCH_API_URL, {
4863
4866
  method: "POST",
4864
4867
  headers: new Headers({
4865
- "X-API-Key": YDC_API_KEY || "",
4868
+ "X-API-Key": YDC_API_KEY,
4866
4869
  "Content-Type": "application/json",
4867
4870
  "User-Agent": getUserAgent()
4868
4871
  }),
@@ -5222,7 +5225,7 @@ Output Format:
5222
5225
  Invalid args: Error message on stderr (exit 2)
5223
5226
 
5224
5227
  Examples:
5225
- ydc search --json '{"query":"AI developments"}' --client Openclaw
5228
+ ydc search --json '{"query":"AI developments"}' --client ClaudeCode
5226
5229
  ydc deep-search --json '{"query":"What are the latest breakthroughs in AI?","search_effort":"high"}' --client MyAgent
5227
5230
  ydc contents --json '{"urls":["https://example.com"],"formats":["markdown"]}'
5228
5231
  ydc search --schema # Get JSON schema for search --json input
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youdotcom-oss/api",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "You.com API client with bundled CLI for agents supporting Agent Skills",
5
5
  "license": "MIT",
6
6
  "engines": {
package/src/cli.ts CHANGED
@@ -62,7 +62,7 @@ Output Format:
62
62
  Invalid args: Error message on stderr (exit 2)
63
63
 
64
64
  Examples:
65
- ydc search --json '{"query":"AI developments"}' --client Openclaw
65
+ ydc search --json '{"query":"AI developments"}' --client ClaudeCode
66
66
  ydc deep-search --json '{"query":"What are the latest breakthroughs in AI?","search_effort":"high"}' --client MyAgent
67
67
  ydc contents --json '{"urls":["https://example.com"],"formats":["markdown"]}'
68
68
  ydc search --schema # Get JSON schema for search --json input
@@ -14,17 +14,21 @@ import { type DeepSearchQuery, DeepSearchResponseSchema } from './deep-search.sc
14
14
  */
15
15
  export const callDeepSearch = async ({
16
16
  deepSearchQuery,
17
- YDC_API_KEY,
17
+ YDC_API_KEY = process.env.YDC_API_KEY,
18
18
  getUserAgent,
19
19
  }: {
20
20
  deepSearchQuery: DeepSearchQuery
21
- YDC_API_KEY: string
21
+ YDC_API_KEY?: string
22
22
  getUserAgent: GetUserAgent
23
23
  }) => {
24
+ if (!YDC_API_KEY) {
25
+ throw new Error('YDC_API_KEY is required for Deep Search API')
26
+ }
27
+
24
28
  const response = await fetch(DEEP_SEARCH_API_URL, {
25
29
  method: 'POST',
26
30
  headers: new Headers({
27
- 'X-API-Key': YDC_API_KEY || '',
31
+ 'X-API-Key': YDC_API_KEY,
28
32
  'Content-Type': 'application/json',
29
33
  'User-Agent': getUserAgent(),
30
34
  }),
@@ -0,0 +1,109 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { DEEP_SEARCH_API_URL } from '../../shared/api.constants.ts'
3
+ import { buildDeepSearchRequest } from '../../shared/dry-run-utils.ts'
4
+
5
+ describe('buildDeepSearchRequest', () => {
6
+ const getUserAgent = () => 'test-agent'
7
+ const YDC_API_KEY = 'test-key'
8
+
9
+ test('builds basic deep-search request with query only', () => {
10
+ const request = buildDeepSearchRequest({
11
+ deepSearchQuery: { query: 'What is AI?' },
12
+ YDC_API_KEY,
13
+ getUserAgent,
14
+ })
15
+
16
+ expect(request.url).toBe(DEEP_SEARCH_API_URL)
17
+ expect(request.method).toBe('POST')
18
+ expect(request.headers['X-API-Key']).toBe('test-key')
19
+ expect(request.headers['Content-Type']).toBe('application/json')
20
+ expect(request.headers['User-Agent']).toBe('test-agent')
21
+
22
+ const body = JSON.parse(request.body!)
23
+ expect(body.query).toBe('What is AI?')
24
+ // search_effort not in body when not provided (default applied by schema validation, not dry-run)
25
+ expect(body.search_effort).toBeUndefined()
26
+ })
27
+
28
+ test('builds request with explicit medium search effort', () => {
29
+ const request = buildDeepSearchRequest({
30
+ deepSearchQuery: { query: 'What is AI?', search_effort: 'medium' },
31
+ YDC_API_KEY,
32
+ getUserAgent,
33
+ })
34
+
35
+ const body = JSON.parse(request.body!)
36
+ expect(body.query).toBe('What is AI?')
37
+ expect(body.search_effort).toBe('medium')
38
+ })
39
+
40
+ test('builds request with low search effort', () => {
41
+ const request = buildDeepSearchRequest({
42
+ deepSearchQuery: {
43
+ query: 'Quick explanation of JWT',
44
+ search_effort: 'low',
45
+ },
46
+ YDC_API_KEY,
47
+ getUserAgent,
48
+ })
49
+
50
+ const body = JSON.parse(request.body!)
51
+ expect(body.query).toBe('Quick explanation of JWT')
52
+ expect(body.search_effort).toBe('low')
53
+ })
54
+
55
+ test('builds request with high search effort', () => {
56
+ const request = buildDeepSearchRequest({
57
+ deepSearchQuery: {
58
+ query: 'Comprehensive analysis of climate change impacts',
59
+ search_effort: 'high',
60
+ },
61
+ YDC_API_KEY,
62
+ getUserAgent,
63
+ })
64
+
65
+ const body = JSON.parse(request.body!)
66
+ expect(body.query).toBe('Comprehensive analysis of climate change impacts')
67
+ expect(body.search_effort).toBe('high')
68
+ })
69
+
70
+ test('builds request with complex research question', () => {
71
+ const complexQuery = `What are the key differences between microservices and monolithic architecture?
72
+ Include pros and cons of each approach, best use cases, and migration strategies.`
73
+
74
+ const request = buildDeepSearchRequest({
75
+ deepSearchQuery: {
76
+ query: complexQuery,
77
+ search_effort: 'high',
78
+ },
79
+ YDC_API_KEY,
80
+ getUserAgent,
81
+ })
82
+
83
+ const body = JSON.parse(request.body!)
84
+ expect(body.query).toBe(complexQuery)
85
+ expect(body.search_effort).toBe('high')
86
+ })
87
+
88
+ test('uses correct API URL', () => {
89
+ const request = buildDeepSearchRequest({
90
+ deepSearchQuery: { query: 'test' },
91
+ YDC_API_KEY,
92
+ getUserAgent,
93
+ })
94
+
95
+ expect(request.url).toBe('https://api.you.com/v1/deep_search')
96
+ })
97
+
98
+ test('includes all required headers', () => {
99
+ const request = buildDeepSearchRequest({
100
+ deepSearchQuery: { query: 'test' },
101
+ YDC_API_KEY: 'my-api-key',
102
+ getUserAgent: () => 'CustomAgent/1.0',
103
+ })
104
+
105
+ expect(request.headers['X-API-Key']).toBe('my-api-key')
106
+ expect(request.headers['Content-Type']).toBe('application/json')
107
+ expect(request.headers['User-Agent']).toBe('CustomAgent/1.0')
108
+ })
109
+ })
@@ -0,0 +1,71 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { DeepSearchQuerySchema, SearchEffortSchema } from '../deep-search.schemas.ts'
3
+
4
+ describe('DeepSearchQuerySchema OpenAPI validation', () => {
5
+ test('accepts valid query parameters', () => {
6
+ const validQueries = [
7
+ { query: 'What is quantum computing?' },
8
+ { query: 'Explain machine learning', search_effort: 'low' },
9
+ { query: 'Latest AI developments', search_effort: 'medium' },
10
+ { query: 'Comprehensive research on climate change', search_effort: 'high' },
11
+ ]
12
+
13
+ for (const validQuery of validQueries) {
14
+ expect(() => DeepSearchQuerySchema.parse(validQuery)).not.toThrow()
15
+ }
16
+ })
17
+
18
+ test('rejects invalid query parameters', () => {
19
+ const invalidQueries = [
20
+ {}, // Missing query
21
+ { query: '' }, // Empty query
22
+ { query: 'test', search_effort: 'invalid' }, // Invalid search_effort
23
+ { query: 'test', search_effort: 'extreme' }, // Invalid effort level
24
+ ]
25
+
26
+ for (const invalidQuery of invalidQueries) {
27
+ expect(() => DeepSearchQuerySchema.parse(invalidQuery)).toThrow()
28
+ }
29
+ })
30
+
31
+ test('defaults search_effort to medium when not provided', () => {
32
+ const result = DeepSearchQuerySchema.parse({ query: 'test query' })
33
+ expect(result.search_effort).toBe('medium')
34
+ })
35
+
36
+ test('SearchEffortSchema accepts all valid effort levels', () => {
37
+ const validEfforts = ['low', 'medium', 'high']
38
+
39
+ for (const effort of validEfforts) {
40
+ expect(() => SearchEffortSchema.parse(effort)).not.toThrow()
41
+ }
42
+ })
43
+
44
+ test('SearchEffortSchema rejects invalid effort levels', () => {
45
+ const invalidEfforts = ['none', 'extreme', 'ultra', 'minimal', '']
46
+
47
+ for (const effort of invalidEfforts) {
48
+ expect(() => SearchEffortSchema.parse(effort)).toThrow()
49
+ }
50
+ })
51
+
52
+ test('accepts complex research questions', () => {
53
+ const complexQueries = [
54
+ {
55
+ query: 'What are the key differences between REST and GraphQL APIs, and when should each be used?',
56
+ search_effort: 'high',
57
+ },
58
+ {
59
+ query: 'Compare the advantages and disadvantages of microservices architecture versus monolithic architecture',
60
+ },
61
+ {
62
+ query: 'What happened in AI research during 2024? Provide a comprehensive summary with key breakthroughs.',
63
+ search_effort: 'high',
64
+ },
65
+ ]
66
+
67
+ for (const query of complexQueries) {
68
+ expect(() => DeepSearchQuerySchema.parse(query)).not.toThrow()
69
+ }
70
+ })
71
+ })
@@ -0,0 +1,139 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { callDeepSearch } from '../deep-search.utils.ts'
3
+
4
+ const getUserAgent = () => 'API/test (You.com;TEST)'
5
+
6
+ describe('callDeepSearch', () => {
7
+ test(
8
+ 'returns valid response structure for basic query',
9
+ async () => {
10
+ const result = await callDeepSearch({
11
+ deepSearchQuery: {
12
+ query: 'What is TypeScript?',
13
+ search_effort: 'low',
14
+ },
15
+ getUserAgent,
16
+ })
17
+
18
+ expect(result).toHaveProperty('answer')
19
+ expect(result).toHaveProperty('results')
20
+ expect(typeof result.answer).toBe('string')
21
+ expect(Array.isArray(result.results)).toBe(true)
22
+ expect(result.answer.length).toBeGreaterThan(0)
23
+ },
24
+ { retry: 2 },
25
+ )
26
+
27
+ test(
28
+ 'handles medium search effort',
29
+ async () => {
30
+ const result = await callDeepSearch({
31
+ deepSearchQuery: {
32
+ query: 'Explain REST API principles',
33
+ search_effort: 'medium',
34
+ },
35
+ getUserAgent,
36
+ })
37
+
38
+ expect(result).toHaveProperty('answer')
39
+ expect(result).toHaveProperty('results')
40
+ expect(typeof result.answer).toBe('string')
41
+ expect(Array.isArray(result.results)).toBe(true)
42
+ },
43
+ { retry: 2 },
44
+ )
45
+
46
+ test(
47
+ 'validates response schema with sources',
48
+ async () => {
49
+ const result = await callDeepSearch({
50
+ deepSearchQuery: {
51
+ query: 'What are the benefits of microservices?',
52
+ search_effort: 'low',
53
+ },
54
+ getUserAgent,
55
+ })
56
+
57
+ // Test that results have required properties
58
+ expect(result.results.length).toBeGreaterThan(0)
59
+
60
+ const source = result.results[0]
61
+ expect(source).toBeDefined()
62
+ expect(source).toHaveProperty('url')
63
+ expect(source).toHaveProperty('title')
64
+ expect(source).toHaveProperty('snippets')
65
+ expect(typeof source?.url).toBe('string')
66
+ expect(typeof source?.title).toBe('string')
67
+ expect(Array.isArray(source?.snippets)).toBe(true)
68
+ },
69
+ { retry: 2 },
70
+ )
71
+
72
+ test(
73
+ 'answer contains markdown with inline citations',
74
+ async () => {
75
+ const result = await callDeepSearch({
76
+ deepSearchQuery: {
77
+ query: 'What is JWT authentication?',
78
+ search_effort: 'low',
79
+ },
80
+ getUserAgent,
81
+ })
82
+
83
+ // Answer should be non-empty markdown string
84
+ expect(typeof result.answer).toBe('string')
85
+ expect(result.answer.length).toBeGreaterThan(0)
86
+
87
+ // Answer typically contains citations in the format [1], [2], etc.
88
+ // This is a soft check - citations may or may not be present
89
+ if (result.answer.includes('[')) {
90
+ expect(result.answer).toMatch(/\[\d+\]/)
91
+ }
92
+ },
93
+ { retry: 2 },
94
+ )
95
+
96
+ test(
97
+ 'handles complex multi-part questions',
98
+ async () => {
99
+ const result = await callDeepSearch({
100
+ deepSearchQuery: {
101
+ query: 'What is GraphQL and how does it differ from REST?',
102
+ search_effort: 'low',
103
+ },
104
+ getUserAgent,
105
+ })
106
+
107
+ expect(result).toHaveProperty('answer')
108
+ expect(result).toHaveProperty('results')
109
+ expect(result.results.length).toBeGreaterThan(0)
110
+
111
+ // Complex questions should have substantial answers
112
+ expect(result.answer.length).toBeGreaterThan(100)
113
+ },
114
+ { retry: 2 },
115
+ )
116
+
117
+ test(
118
+ 'sources include relevant snippets',
119
+ async () => {
120
+ const result = await callDeepSearch({
121
+ deepSearchQuery: {
122
+ query: 'What is Docker containerization?',
123
+ search_effort: 'low',
124
+ },
125
+ getUserAgent,
126
+ })
127
+
128
+ // Check that at least one source has snippets
129
+ const sourcesWithSnippets = result.results.filter((source) => source.snippets.length > 0)
130
+ expect(sourcesWithSnippets.length).toBeGreaterThan(0)
131
+
132
+ // Snippets should be non-empty strings
133
+ const firstSource = sourcesWithSnippets[0]
134
+ expect(firstSource).toBeDefined()
135
+ expect(firstSource?.snippets[0]?.length).toBeGreaterThan(0)
136
+ },
137
+ { retry: 2 },
138
+ )
139
+ })