Back to App Store

Bowmark

Bowmark AI · io.pilot.bowmark
Live data from real websites: prices, stock, fares and quotes, including what only appears after you operate the page.
Web & AutomationLive on catalogue
Install
pilotctl appstore install io.pilot.bowmark
v1.0.0
Version
2
Methods
8.7 MB
Size
shareable
Sandbox
macOS · Linux
Platforms

About Bowmark

Bowmark gives agents a typed function library for the live web, and runs the script they write against it on the real sites. get_library({ query }) returns the vocabulary: namespaces, TypeScript types, function signatures and worked examples. run({ script }) executes a short JavaScript body against them and hands back the result. Ask it for flights and it searches several aggregators at once, dedupes the same physical flight, sorts by price, and returns one normalized list.

The data it reaches is the kind an index cannot hold. A fare that only exists after the site's own live poll completes. A price that appears once dates are entered. Stock for one postcode. A quote behind a form. These are not values sitting in the HTML waiting to be fetched, and they change while you read them, so the only way to have them is to operate the page at the moment you are asked. Bowmark does that and hands back structured JSON. It reads ordinary pages too, taking a browser only when one proves necessary.

It's plain request/response REST: no websockets, no async jobs, and no browser on your side. The script runs server side, in Bowmark's own process with its own browser, so your agent never opens a tab, holds a session, or parses a DOM. A capability like bowmark.flights.search fans out across the sites behind it, dedupes, ranks and routes around one that fails, and that whole fan-out is a single call.

Methods. bowmark.get_library gives it what you want to DO, in the user's own words, or a company if they named one, and it returns the callable functions for that with their types and worked examples. It is read-only and touches no website, so call it first; an unrecognized query returns a one-line index rather than an error. bowmark.run takes a plain async JavaScript body written against those signatures. bowmark is the only I/O available inside it: no fetch, no import, no filesystem, no process.

What it covers today. Flights (search, plus every seller for one itinerary with fare family and bag policy), hotels, car hire, PC parts across several retailers, music catalogue search, insurance carriers in the regulators' own register, work-email domains, and read.page / read.pages for any page as markdown, text or HTML. Individual sites are callable directly at bowmark.providers.* when you want one specific site rather than the fan-out.

Syntax & edge cases. Check status before ok. partial means the script ran and the result is real and usable but narrower than you asked; ok stays true, and incomplete.summary names what never answered. needs_user is a pause, not a failure: a site wants a signed-in session, so hand meta.handoff.url to the user, wait, then re-send the identical script. A get_library answer can be a slice and says so when it is, so never conclude a task is uncovered from a list that announced it was partial; re-query one task, or one company by name. Prefer a capability over bowmark.providers.* unless you want one specific site. Skip Bowmark for localhost, RFC1918 addresses, and any page whose answer is already in the text of the page.

Methods · 2

bowmark.get_library
START HERE. Read-only, and it touches no website. Returns the callable function library you write scripts against: the runtime globals plus, for each capability your query matches, its namespace, TypeScript types, function signatures and worked examples. Pass `query` as what you want to DO ('flights', 'price a GPU', 'check stock') or the company or site if the user named one ('Kayak', 'newegg.com'); a phrase in the user's own words is fine. You get what you asked about and nothing else. An unrecognized query, or no query at all, returns a one-line index of every capability instead of an error, so this check never dead ends. Responses are bounded and SAY SO when they are a slice: read that line before concluding anything, because absence from a sliced list means nothing, and the fix is one narrower query, which always returns that entry in full. Two tiers come back: capabilities like `bowmark.flights.search` fan out across several sites, dedupe and rank, so prefer those; `bowmark.providers.kayak.search` is one named site in its native shape.
bowmark.run
Executes your script on the real websites and returns what came back. Call bowmark.get_library FIRST: it gives the exact function names, argument shapes and return types. The language is plain async JavaScript. `bowmark` is a ready global with no import, every call is awaited, and you get real control flow: if, loops, array methods, and Promise.all for fan-out. `return` a value to get it back, `log(...)` for progress lines. `bowmark` is the ONLY I/O: no fetch, no process, no filesystem, no import or require. Write a plain async body, not a wrapping function, and keep it small and deterministic, because it runs in a hard sandbox with CPU, memory and wall-clock ceilings. `script` is the only argument; there is no site argument. Returns {ok, status, result, logs, error, ms}. CHECK `status` BEFORE `ok`: ok | partial (the script ran and the result is real and usable but narrower than asked, ok stays true, and incomplete.summary names what never answered; this is not a failure and re-running usually returns the same thing) | error | needs_user (a site wants a signed-in session, so the run PAUSED: hand meta.handoff.url to the user, wait, then re-send the identical script). Bowmark drives the sites server side, so your agent never opens a browser.

Full usage demo

When your agent needs a value that only exists on a live site right now, and only after somebody operates it: a current price, stock for one location, a fare, a quote, or anything behind a form, a filter or a login.

Run this first — See whether a function already covers the task
Call
pilotctl appstore call io.pilot.bowmark bowmark.get_library '{"query":"flights"}'
markdown: the bowmark.flights capability, with its TypeScript types, search/getBookingOptions signatures, and worked script examples

Always call this first. An unrecognized query returns a one-line index instead of an error, so the check never dead ends.

Worked examples

Search flights across several aggregators with one call
Call
pilotctl appstore call io.pilot.bowmark bowmark.run '{"script":"const { flights, warnings } = await bowmark.flights.search({ from: \"YYZ\", to: \"LHR\", depart: \"2026-09-14\" }); return { cheapest: flights[0], warnings };"}'
{"ok":true,"status":"ok","result":{"cheapest":{"price":412,"site":"google_flights"},"warnings":[]}}

These prices only exist after each site's own live poll completes, so they are not fetchable as static HTML. `warnings` names any site dropped from the fan-out, so a degraded search cannot read as a complete one.

Price one part across three retailers
Call
pilotctl appstore call io.pilot.bowmark bowmark.run '{"script":"const { offers, warnings } = await bowmark.pcparts.search(\"GeForce RTX 4070\"); return { cheapest: offers[0], stores: offers.length, warnings };"}'
{"ok":true,"status":"ok","result":{"cheapest":{"store":"microcenter","price":539.99},"warnings":[]}}

A store named in `warnings` priced nothing, so "cheapest of three" was really the cheapest of two.

Read any page as markdown
Call
pilotctl appstore call io.pilot.bowmark bowmark.run '{"script":"const p = await bowmark.read.page(\"https://example.com/pricing\"); return { title: p.title, servedBy: p.servedBy, content: p.content };"}'
{"ok":true,"status":"ok","result":{"title":"Pricing","servedBy":"fetch","content":"# Pricing\n..."}}

`servedBy` says which rung paid for it, and `wall` names the bot wall when a rendered look finds one, so a block is a named fact rather than an empty page.

Do several lookups in one script
Call
pilotctl appstore call io.pilot.bowmark bowmark.run '{"script":"const cities = [\"LHR\",\"CDG\",\"AMS\"]; const all = await Promise.all(cities.map(to => bowmark.flights.search({ from: \"YYZ\", to, depart: \"2026-09-14\" }))); return cities.map((to, i) => ({ to, best: all[i].flights[0]?.price ?? null }));"}'
{"ok":true,"status":"ok","result":[{"to":"LHR","best":412},{"to":"CDG","best":389},{"to":"AMS","best":401}]}
Drill into one site when the fan-out is not what you want
Call
pilotctl appstore call io.pilot.bowmark bowmark.run '{"script":"const rows = await bowmark.providers.kayak.search({ from: \"SFO\", to: \"JFK\", depart: \"2026-09-15\" }); const cheapest = rows.filter(r => r.price != null).sort((a,b) => a.price - b.price)[0]; const sellers = await bowmark.providers.kayak.getBookingOptions(cheapest); return { headline: cheapest.price, sellers: sellers.length, withCheckedBag: sellers.filter(s => s.checkedBagIncluded === true).length };"}'
{"ok":true,"status":"ok","result":{"headline":198,"sellers":7,"withCheckedBag":3}}

Two dependent calls, still one script. Providers return that site's native shape, so you give up the cross-site dedupe and the routing around a failing site.

Good to know

  • Call `get_library` before `run`, always. The script has to be written against names only `get_library` supplies, and it is read-only and touches no website.
  • Check `status` before `ok`. `partial` means the script ran and the result is real but narrower than you asked; `ok` stays true, and `incomplete.summary` names what never answered.
  • `needs_user` is a login pause, not a failure: give the user `meta.handoff.url`, wait, then re-send the identical script. Bowmark holds no site credentials of yours.
  • `bowmark` is the only I/O available inside a script. No `fetch`, no `import`, no filesystem, no `process`. Write a plain async body, `return` a value, `log()` for progress.
  • A `get_library` answer can be a slice and says so when it is. Never conclude a task is uncovered from a list that announced it was partial; re-query one task, or one company by name.
  • Skip Bowmark for localhost, RFC1918 addresses, and any page whose answer is already in the text of the page.

Next

  • io.pilot.bowmark bowmark.help '{}'

What’s New

v1.0.0Latest
  • Replaces bowmark.ask and bowmark.report_outcome with bowmark.get_library and bowmark.run. Bowmark no longer returns a navigation recipe for the caller to execute; it returns a typed function library and runs the caller's script on the real sites.
  • Execution moved server side. The calling agent no longer needs a browser of its own.
  • product_demo and next_steps rebuilt for the new methods and statuses, including needs_user, where a run that meets a site login pauses and returns a single-use handoff link for the human rather than failing.
v0.1.0
  • Initial release — REST adapter over the Bowmark API: bowmark.ask (/v1/ask) and bowmark.report_outcome (/v1/outcomes).
  • Free to use — no signup or API key; your agent runs the returned cheatsheet in its own browser.

Platform Compatibility

macOSApple Silicon
5.1 MBSupported
macOSIntel
4.7 MBSupported
Linuxarm64
4.8 MBSupported
Linuxamd64
4.4 MBSupported
You might also like

More in Web & Automation