Skip to content

Commit 59852e2

Browse files
authored
Merge pull request #10 from anil-bd/feat/help-examples-across-commands
feat(help): add Examples section to every customer-facing command's --help
2 parents e37dd51 + 6bed0be commit 59852e2

7 files changed

Lines changed: 232 additions & 0 deletions

File tree

src/__tests__/utils/help.test.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import {describe, it, expect} from 'vitest';
2+
import {Command} from 'commander';
3+
import {format_examples, add_examples} from '../../utils/help';
4+
import type {Example} from '../../utils/help';
5+
import {scraper_command} from '../../commands/scraper';
6+
import {discover_command} from '../../commands/discover';
7+
import {search_command} from '../../commands/search';
8+
import {pipelines_command} from '../../commands/dataset';
9+
import {scrape_command} from '../../commands/scrape';
10+
11+
describe('utils/help.format_examples', ()=>{
12+
it('returns empty string for no examples', ()=>{
13+
expect(format_examples([])).toBe('');
14+
});
15+
16+
it('renders a single example with comment + dollar-prefixed command', ()=>{
17+
const exs: Example[] = [{description: 'Do a thing', command: 'cmd x'}];
18+
const out = format_examples(exs);
19+
expect(out).toContain('\nExamples:\n');
20+
expect(out).toContain(' # Do a thing');
21+
expect(out).toContain(' $ cmd x');
22+
});
23+
24+
it('separates multiple examples with a blank line', ()=>{
25+
const exs: Example[] = [
26+
{description: 'A', command: 'cmd a'},
27+
{description: 'B', command: 'cmd b'},
28+
];
29+
const out = format_examples(exs);
30+
expect(out).toMatch(/cmd a\n\n # B/);
31+
});
32+
});
33+
34+
describe('utils/help.add_examples attaches to a Commander command', ()=>{
35+
it('appears in the rendered --help output', ()=>{
36+
const cmd = new Command('demo')
37+
.description('A demo command')
38+
.argument('<thing>', 'The thing');
39+
add_examples(cmd, [
40+
{description: 'Demo this', command: 'demo widget'},
41+
]);
42+
let captured = '';
43+
cmd.configureOutput({writeOut: (s)=>{ captured += s; }});
44+
cmd.outputHelp();
45+
expect(captured).toContain('Examples:');
46+
expect(captured).toContain('# Demo this');
47+
expect(captured).toContain('$ demo widget');
48+
});
49+
});
50+
51+
const render_help = (cmd: Command): string=>{
52+
let captured = '';
53+
cmd.configureOutput({
54+
writeOut: (s)=>{ captured += s; },
55+
writeErr: (s)=>{ captured += s; },
56+
});
57+
cmd.outputHelp();
58+
return captured;
59+
};
60+
61+
describe('every customer-facing command has Examples in --help', ()=>{
62+
const scraper_create =
63+
scraper_command.commands.find(c=>c.name() == 'create')!;
64+
const scraper_run =
65+
scraper_command.commands.find(c=>c.name() == 'run')!;
66+
67+
const cases: [string, Command][] = [
68+
['scraper create', scraper_create],
69+
['scraper run', scraper_run],
70+
['discover', discover_command],
71+
['search', search_command],
72+
['pipelines', pipelines_command],
73+
['scrape', scrape_command],
74+
];
75+
76+
for (const [name, cmd] of cases)
77+
{
78+
it(`${name} --help includes an Examples section`, ()=>{
79+
const help = render_help(cmd);
80+
const examples_idx = help.indexOf('\nExamples:\n');
81+
expect(examples_idx, `${name} is missing the Examples section`)
82+
.toBeGreaterThan(-1);
83+
const examples_block = help.slice(examples_idx);
84+
expect(examples_block,
85+
`${name} Examples section has no $ brightdata command`)
86+
.toMatch(/\$\s+brightdata\s/);
87+
expect(examples_block,
88+
`${name} examples leak the fake example.com domain`)
89+
.not.toMatch(/https?:\/\/(www\.)?example\.com/);
90+
});
91+
}
92+
});

src/commands/dataset.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {get, post} from '../utils/client';
44
import {print, dim, fail} from '../utils/output';
55
import {start as start_spinner} from '../utils/spinner';
66
import {parse_timeout, poll_until} from '../utils/polling';
7+
import {add_examples} from '../utils/help';
78
import type {
89
Webdata_format,
910
Webdata_opts,
@@ -305,4 +306,22 @@ const pipelines_command = new Command('pipelines')
305306
.option('-k, --api-key <key>', 'Override API key')
306307
.action(handle_pipelines);
307308

309+
add_examples(pipelines_command, [
310+
{
311+
description: 'List every available pipeline type',
312+
command: 'brightdata pipelines list',
313+
},
314+
{
315+
description: 'Scrape a single Amazon product page',
316+
command: 'brightdata pipelines amazon_product '
317+
+'https://www.amazon.com/dp/B08N5WRWNW --pretty',
318+
},
319+
{
320+
description: 'Pull a LinkedIn person profile by URL, save as CSV',
321+
command: 'brightdata pipelines linkedin_person_profile '
322+
+'https://www.linkedin.com/in/satyanadella --format csv '
323+
+'-o profile.csv',
324+
},
325+
]);
326+
308327
export {pipelines_command, handle_pipelines};

src/commands/discover.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {ensure_authenticated} from '../utils/auth';
44
import {start as start_spinner} from '../utils/spinner';
55
import {parse_timeout, poll_until} from '../utils/polling';
66
import {print, print_table, dim, fail, is_tty} from '../utils/output';
7+
import {add_examples} from '../utils/help';
78
import type {
89
Discover_request,
910
Discover_trigger_response,
@@ -184,5 +185,23 @@ const discover_command = new Command('discover')
184185
.option('-k, --api-key <key>', 'Override API key')
185186
.action(handle_discover);
186187

188+
add_examples(discover_command, [
189+
{
190+
description: 'Discover and rank URLs relevant to a query',
191+
command: 'brightdata discover "open-source AI agent frameworks"',
192+
},
193+
{
194+
description: 'Add an AI intent filter and cap results',
195+
command: 'brightdata discover "vector databases" '
196+
+'--intent "production-ready, self-hostable" --num-results 10',
197+
},
198+
{
199+
description: 'Localize to a country and include page content as '
200+
+'markdown',
201+
command: 'brightdata discover "best coffee in Berlin" '
202+
+'--country DE --include-content',
203+
},
204+
]);
205+
187206
export {discover_command, handle_discover, build_request, extract_status,
188207
format_markdown, print_discover_table};

src/commands/scrape.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {ensure_authenticated} from '../utils/auth';
55
import {resolve} from '../utils/config';
66
import {start as start_spinner} from '../utils/spinner';
77
import {print, success, fail, info} from '../utils/output';
8+
import {add_examples} from '../utils/help';
89
import type {
910
Scrape_format,
1011
Scrape_request,
@@ -106,4 +107,22 @@ const scrape_command = new Command('scrape')
106107
.option('-k, --api-key <key>', 'Override API key')
107108
.action(handle_scrape);
108109

110+
add_examples(scrape_command, [
111+
{
112+
description: 'Scrape a public page and get markdown (default '
113+
+'format)',
114+
command: 'brightdata scrape https://news.ycombinator.com',
115+
},
116+
{
117+
description: 'Return JSON with response metadata, save to a file',
118+
command: 'brightdata scrape https://news.ycombinator.com '
119+
+'--format json --pretty -o hn.json',
120+
},
121+
{
122+
description: 'Geo-target Germany with a mobile user agent',
123+
command: 'brightdata scrape https://www.google.com/search?q=heise '
124+
+'--country de --mobile',
125+
},
126+
]);
127+
109128
export {scrape_command, handle_scrape};

src/commands/scraper.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {readFileSync} from 'node:fs';
22
import {Command} from 'commander';
3+
import {add_examples} from '../utils/help';
34
import {post, get, type Body_hint, type Retry_config,
45
type Retry_event} from '../utils/client';
56
import {load as load_config} from '../utils/config';
@@ -902,6 +903,50 @@ const run_subcommand = new Command('run')
902903
.option('-k, --api-key <key>', 'Override API key')
903904
.action(handle_run_scraper);
904905

906+
add_examples(create_subcommand, [
907+
{
908+
description: 'Build a scraper for a public page (AI generation '
909+
+'takes 5 to 10 minutes)',
910+
command: 'brightdata scraper create https://news.ycombinator.com '
911+
+'"Extract the top 30 stories: title, url, points, author, '
912+
+'comment count."',
913+
},
914+
{
915+
description: 'Name the scraper and save the full AI output for '
916+
+'inspection',
917+
command: 'brightdata scraper create https://www.ycombinator.com/'
918+
+'companies?batch=W26 "For each company card, extract name, '
919+
+'vertical, tagline, link" --name yc-w26 --pretty -o create.json',
920+
},
921+
{
922+
description: 'Custom delivery webhook (default is a stub, set '
923+
+'this when wiring to your own backend)',
924+
command: 'brightdata scraper create https://news.ycombinator.com '
925+
+'"Extract top stories" --deliver-webhook '
926+
+'https://your-app.test/scraper-callback',
927+
},
928+
]);
929+
930+
add_examples(run_subcommand, [
931+
{
932+
description: 'Run a scraper against a single URL (async, polls '
933+
+'until done)',
934+
command: 'brightdata scraper run c_mp3tuab31lswoxvpws '
935+
+'https://news.ycombinator.com --pretty',
936+
},
937+
{
938+
description: 'Sync mode for small fast pages (server-side 25 to '
939+
+'50 second cap)',
940+
command: 'brightdata scraper run c_mp3tuab31lswoxvpws '
941+
+'https://news.ycombinator.com --sync',
942+
},
943+
{
944+
description: 'Save output as CSV (extension chooses format)',
945+
command: 'brightdata scraper run c_mp3tuab31lswoxvpws '
946+
+'https://news.ycombinator.com -o stories.csv',
947+
},
948+
]);
949+
905950
const scraper_command = new Command('scraper')
906951
.description('Build and manage Bright Data scrapers')
907952
.addCommand(create_subcommand)

src/commands/search.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {ensure_authenticated} from '../utils/auth';
44
import {resolve} from '../utils/config';
55
import {start as start_spinner} from '../utils/spinner';
66
import {print, print_table, fail, dim} from '../utils/output';
7+
import {add_examples} from '../utils/help';
78
import type {
89
Search_engine,
910
Search_type,
@@ -280,4 +281,21 @@ const search_command = new Command('search')
280281
.option('-k, --api-key <key>', 'Override API key')
281282
.action(handle_search);
282283

284+
add_examples(search_command, [
285+
{
286+
description: 'Web search via Google (default engine)',
287+
command: 'brightdata search "web scraping best practices 2026"',
288+
},
289+
{
290+
description: 'News search localized to a country',
291+
command: 'brightdata search "AI agent funding" '
292+
+'--type news --country us --pretty',
293+
},
294+
{
295+
description: 'Bing image search, mobile device profile',
296+
command: 'brightdata search "san francisco skyline" '
297+
+'--engine bing --type images --device mobile',
298+
},
299+
]);
300+
283301
export {search_command, handle_search};

src/utils/help.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import {Command} from 'commander';
2+
3+
type Example = {
4+
description: string;
5+
command: string;
6+
};
7+
8+
const format_examples = (examples: Example[]): string=>{
9+
if (!examples.length)
10+
return '';
11+
const blocks = examples.map(ex=>
12+
` # ${ex.description}\n $ ${ex.command}`).join('\n\n');
13+
return '\nExamples:\n'+blocks+'\n';
14+
};
15+
16+
const add_examples = (cmd: Command, examples: Example[]): Command=>
17+
cmd.addHelpText('after', format_examples(examples));
18+
19+
export {add_examples, format_examples};
20+
export type {Example};

0 commit comments

Comments
 (0)