Skip to content

Commit ffac1d2

Browse files
committed
Add 'wrangler websearch search' command
Adds a new top-level 'websearch' namespace with a single 'search' subcommand: npx wrangler websearch search <query> npx wrangler websearch search <query> --limit 5 npx wrangler websearch search <query> --json Both --limit and --json are optional. limit defaults to 10 server-side and is capped at 20. Without --json the results render as a pretty table (#, title, url, description, lastModified); with --json the raw response body is printed verbatim. The command POSTs to /accounts/{accountId}/websearch/search with body { query, limit? } and uses requireAuth(config) for credentials, same pattern as 'wrangler ai-search search'. * src/websearch/types.ts: response shape (items + metadata) * src/websearch/client.ts: fetchResult wrapper * src/websearch/index.ts: namespace metadata * src/websearch/search.ts: command definition * src/index.ts: registry.define + registerNamespace * src/core/teams.d.ts: add 'Product: Web Search' to the Teams enum * src/utils/add-created-resource-config.ts: exclude 'web_search' from ValidKeys (singleton bindings can't go through the createdResource config helper; same exclusion as 'ai' and 'browser') * src/__tests__/index.test.ts: update top-level help snapshot to include the new namespace * .changeset: document the new command
1 parent 2fe7518 commit ffac1d2

9 files changed

Lines changed: 172 additions & 1 deletion

File tree

.changeset/add-web-search-binding.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,14 @@ Cloudflare Web Search is a managed, zero-setup web discovery primitive for agent
1616

1717
There is exactly one shared web corpus, so there is no namespace, instance, or other field to specify -- only the variable name. The binding exposes a single `search()` method that returns URLs and catalog metadata for a query. Web Search is discovery-only -- to read a result's content the caller invokes the global `fetch()` API against the result's `url`.
1818

19-
The binding is **always remote** in local development: Miniflare proxies to the production Web Search service via the remote-bindings transport. Adds the `websearch:read` OAuth scope to `wrangler login`.
19+
The binding is **always remote** in local development: Miniflare proxies to the production Web Search service via the remote-bindings transport. Adds the `websearch:run` OAuth scope to `wrangler login`.
20+
21+
Also adds a `wrangler websearch search` command for running ad-hoc queries from the CLI:
22+
23+
```sh
24+
npx wrangler websearch search "cloudflare workers"
25+
npx wrangler websearch search "cloudflare workers" --limit 5
26+
npx wrangler websearch search "cloudflare workers" --json
27+
```
28+
29+
`--limit` is optional (defaults to 10, capped at 20). `--json` prints the raw response; without it the results render as a pretty table.

packages/wrangler/src/__tests__/index.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ describe("wrangler", () => {
6969
wrangler types [path] 📝 Generate types from your Worker configuration
7070
wrangler versions 🫧 List, view, upload and deploy Versions of your Worker to Cloudflare
7171
wrangler vpc 🌐 Manage VPC [open beta]
72+
wrangler websearch 🔎 Run queries against Cloudflare Web Search [open beta]
7273
wrangler workflows 🔁 Manage Workflows
7374
7475
STORAGE & DATABASES
@@ -146,6 +147,7 @@ describe("wrangler", () => {
146147
wrangler types [path] 📝 Generate types from your Worker configuration
147148
wrangler versions 🫧 List, view, upload and deploy Versions of your Worker to Cloudflare
148149
wrangler vpc 🌐 Manage VPC [open beta]
150+
wrangler websearch 🔎 Run queries against Cloudflare Web Search [open beta]
149151
wrangler workflows 🔁 Manage Workflows
150152
151153
STORAGE & DATABASES

packages/wrangler/src/core/teams.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export type Teams =
1515
| "Product: Queues"
1616
| "Product: AI"
1717
| "Product: AI Search"
18+
| "Product: Web Search"
1819
| "Product: Hyperdrive"
1920
| "Product: Pipelines"
2021
| "Product: Vectorize"

packages/wrangler/src/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,8 @@ import { vpcServiceGetCommand } from "./vpc/get";
441441
import { vpcNamespace, vpcServiceNamespace } from "./vpc/index";
442442
import { vpcServiceListCommand } from "./vpc/list";
443443
import { vpcServiceUpdateCommand } from "./vpc/update";
444+
import { webSearchNamespace } from "./websearch/index";
445+
import { webSearchSearchCommand } from "./websearch/search";
444446
import { workflowsInstanceNamespace, workflowsNamespace } from "./workflows";
445447
import { workflowsDeleteCommand } from "./workflows/commands/delete";
446448
import { workflowsDescribeCommand } from "./workflows/commands/describe";
@@ -1443,6 +1445,16 @@ export function createCLIParser(argv: string[]) {
14431445
]);
14441446
registry.registerNamespace("ai-search");
14451447

1448+
// websearch
1449+
registry.define([
1450+
{ command: "wrangler websearch", definition: webSearchNamespace },
1451+
{
1452+
command: "wrangler websearch search",
1453+
definition: webSearchSearchCommand,
1454+
},
1455+
]);
1456+
registry.registerNamespace("websearch");
1457+
14461458
// cert - includes mtls-certificates and CA cert management
14471459
registry.define([
14481460
{ command: "wrangler cert", definition: certNamespace },

packages/wrangler/src/utils/add-created-resource-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type ValidKeys = Exclude<
2020
ConfigBindingFieldName,
2121
| "ai"
2222
| "browser"
23+
| "web_search"
2324
| "vars"
2425
| "wasm_modules"
2526
| "text_blobs"
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { fetchResult } from "../cfetch";
2+
import { requireAuth } from "../user";
3+
import type { WebSearchSearchResponse } from "./types";
4+
import type { Config } from "@cloudflare/workers-utils";
5+
6+
const jsonContentType = "application/json; charset=utf-8;";
7+
8+
export async function search(
9+
config: Config,
10+
body: { query: string; limit?: number }
11+
): Promise<WebSearchSearchResponse> {
12+
const accountId = await requireAuth(config);
13+
return await fetchResult<WebSearchSearchResponse>(
14+
config,
15+
`/accounts/${accountId}/websearch/search`,
16+
{
17+
method: "POST",
18+
headers: { "content-type": jsonContentType },
19+
body: JSON.stringify(body),
20+
}
21+
);
22+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { createNamespace } from "../core/create-command";
2+
3+
export const webSearchNamespace = createNamespace({
4+
metadata: {
5+
description: "🔎 Run queries against Cloudflare Web Search",
6+
status: "open beta",
7+
owner: "Product: Web Search",
8+
category: "Compute & AI",
9+
},
10+
});
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { createCommand } from "../core/create-command";
2+
import { logger } from "../logger";
3+
import { search } from "./client";
4+
5+
export const webSearchSearchCommand = createCommand({
6+
metadata: {
7+
description: "Run a query against Cloudflare Web Search",
8+
status: "open beta",
9+
owner: "Product: Web Search",
10+
},
11+
behaviour: {
12+
printBanner: (args) => !args.json,
13+
},
14+
args: {
15+
query: {
16+
type: "string",
17+
demandOption: true,
18+
description: "The search query text.",
19+
},
20+
limit: {
21+
type: "number",
22+
description:
23+
"Maximum number of results to return (defaults to 10, capped at 20).",
24+
},
25+
json: {
26+
type: "boolean",
27+
default: false,
28+
description: "Return output as clean JSON",
29+
},
30+
},
31+
positionalArgs: ["query"],
32+
async handler(args, { config }) {
33+
const body: { query: string; limit?: number } = { query: args.query };
34+
if (args.limit !== undefined) {
35+
body.limit = args.limit;
36+
}
37+
38+
const result = await search(config, body);
39+
40+
if (args.json) {
41+
logger.log(JSON.stringify(result, null, 2));
42+
return;
43+
}
44+
45+
logger.log(
46+
`Search query: "${result.metadata.query}" (${result.items.length} results, ${result.metadata.latencyMs}ms, request ${result.metadata.requestId})\n`
47+
);
48+
49+
if (result.items.length === 0) {
50+
logger.log("No results found.");
51+
return;
52+
}
53+
54+
logger.table(
55+
result.items.map((item, i) => ({
56+
"#": String(i + 1),
57+
title:
58+
item.title.length > 60 ? item.title.slice(0, 60) + "..." : item.title,
59+
url: item.url.length > 80 ? item.url.slice(0, 80) + "..." : item.url,
60+
description: item.description
61+
? item.description.length > 80
62+
? item.description.slice(0, 80) + "..."
63+
: item.description
64+
: "",
65+
lastModified: item.lastModifiedDate ?? "",
66+
}))
67+
);
68+
},
69+
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* A single Web Search result.
3+
*
4+
* Web Search is discovery-only -- results carry catalog metadata about a page
5+
* but never the page body. To read a result's content, fetch the URL yourself.
6+
*/
7+
export interface WebSearchResult {
8+
/** Canonical URL. */
9+
url: string;
10+
/** Page title. */
11+
title: string;
12+
/** Page-level description. May be absent. */
13+
description?: string;
14+
/**
15+
* Last-modified date for the page, when known. Naive (no timezone)
16+
* ISO-8601 datetime, e.g. "2025-11-30T04:39:48".
17+
*/
18+
lastModifiedDate?: string;
19+
/** Page meta image URL (typically og:image). May be absent. */
20+
imageUrl?: string;
21+
/** Optional favicon URL for UI hints. */
22+
faviconUrl?: string;
23+
}
24+
25+
/**
26+
* Per-response metadata for a Web Search query. Carries operational fields
27+
* useful for support and debugging.
28+
*/
29+
export interface WebSearchResponseMetadata {
30+
/** The query that was executed. */
31+
query: string;
32+
/** Opaque request identifier used for support and debugging. */
33+
requestId: string;
34+
/** End-to-end latency for this search request, in milliseconds. */
35+
latencyMs: number;
36+
}
37+
38+
/**
39+
* Response from a Web Search query.
40+
*/
41+
export interface WebSearchSearchResponse {
42+
items: WebSearchResult[];
43+
metadata: WebSearchResponseMetadata;
44+
}

0 commit comments

Comments
 (0)