diff --git a/public/appicons/io.pilot.bowmark.png b/public/appicons/io.pilot.bowmark.png new file mode 100644 index 0000000..3b07ebc Binary files /dev/null and b/public/appicons/io.pilot.bowmark.png differ diff --git a/public/brand/apps/bowmark.png b/public/brand/apps/bowmark.png new file mode 100644 index 0000000..3b07ebc Binary files /dev/null and b/public/brand/apps/bowmark.png differ diff --git a/scripts/gen-apps.mjs b/scripts/gen-apps.mjs index 23bb838..3924ff9 100644 --- a/scripts/gen-apps.mjs +++ b/scripts/gen-apps.mjs @@ -33,7 +33,7 @@ const CAT_HUE = Object.fromEntries(CATEGORIES.map((c) => [c.id, c.hue])); const CATMAP = { 'io.pilot.postgres': 'data', 'io.pilot.duckdb': 'data', 'io.pilot.redis': 'data', 'io.pilot.sixtyfour': 'data', 'io.pilot.cosift': 'ai', 'io.telepat.ideon-free': 'ai', - 'io.pilot.plainweb': 'web', 'io.pilot.otto': 'web', + 'io.pilot.plainweb': 'web', 'io.pilot.otto': 'web', 'io.pilot.bowmark': 'web', 'io.pilot.smolmachines': 'infra', 'io.pilot.miren': 'infra', 'io.pilot.docker': 'infra', 'io.pilot.aegis': 'security', 'io.pilot.slipstream': 'finance', 'io.pilot.wallet': 'finance', @@ -55,6 +55,7 @@ const ICON_MAP = { 'io.pilot.wallet': { image: 'png', fit: 'contain', bg: '#ffffff' }, 'io.telepat.ideon-free': { image: 'png', fit: 'cover', bg: '#0b0b0a' }, 'io.pilot.aegis': { image: 'svg', fit: 'contain', bg: '#ffffff' }, + 'io.pilot.bowmark': { image: 'png', fit: 'cover', bg: '#0b0b0a' }, }; function relLum(hex) { @@ -83,7 +84,7 @@ const APP_IDS = [ 'io.pilot.postgres', 'io.pilot.duckdb', 'io.pilot.redis', 'io.pilot.sixtyfour', 'io.pilot.cosift', 'io.telepat.ideon-free', 'io.pilot.plainweb', 'io.pilot.otto', 'io.pilot.smolmachines', 'io.pilot.miren', 'io.pilot.docker', 'io.pilot.aegis', - 'io.pilot.slipstream', 'io.pilot.wallet', + 'io.pilot.slipstream', 'io.pilot.wallet', 'io.pilot.bowmark', ]; const FEATURED = ['io.pilot.postgres', 'io.pilot.duckdb', 'io.pilot.docker']; const LINUX_ONLY = new Set(['io.pilot.docker']); diff --git a/src/data/app-methods.json b/src/data/app-methods.json index ccbdd7b..7ed134b 100644 --- a/src/data/app-methods.json +++ b/src/data/app-methods.json @@ -1,4 +1,18 @@ { +"io.pilot.bowmark": [ +{ +"name": "bowmark.ask", +"summary": "Look up a navigation recipe for a (site, task) pair \u2014 a URL shortcut or short UI procedure verified by prior crawls. status=ok \u2192 execute it open-loop; no_useful_data/site_not_supported \u2192 browse manually; ambiguous_scope \u2192 retry with scopeHint; rate_limited \u2192 back off. Pass variants:{auth_state:'logged_in'} for signed-in surfaces. Intent, not a URL." +}, +{ +"name": "bowmark.report_outcome", +"summary": "After running a recipe from ask, report how it executed (envelope_id, success, evidence). success=true only if every step ran as written; false on any retry, JS-eval fallback, or extra click. Feeds the re-crawl loop that keeps recipes healthy." +}, +{ +"name": "bowmark.help", +"summary": "Discovery: every method with params, kind, and latency class." +} +], "io.pilot.docker": [ { "name": "docker.engine_start", diff --git a/src/data/app-overrides.json b/src/data/app-overrides.json index ea28a4c..0ed2586 100644 --- a/src/data/app-overrides.json +++ b/src/data/app-overrides.json @@ -1,4 +1,55 @@ { +"io.pilot.bowmark": { +"name": "Bowmark", +"tagline": "Navigation cheatsheets for public websites \u2014 recipes that skip explore-and-discover.", +"description": "Bowmark gives a browsing agent **pre-computed navigation recipes** for public websites \u2014 a parameterized URL `shortcut` or a short `ui_procedure` of UI steps, each verified by prior crawls. Instead of exploring and discovering a site's structure on every task (snapshotting the DOM, guessing selectors, burning tokens), the agent calls `ask({ site, task })` once and executes the returned recipe open-loop. On known sites that means roughly **5x fewer tokens** and far less latency.\n\n**Managed-key:** Pilot holds one shared Bowmark account behind the broker and meters each caller, so you install it **keyless** and never handle an API key. It's plain request/response REST \u2014 no websockets, no server-side browser, no async jobs. Your agent runs the recipe in its own browser; Bowmark only supplies the recipe.\n\n**Methods.** `bowmark.ask` \u2014 look up a recipe for a (site, task); on `status: ok` you get a URL `shortcut` (a `template` with `{name}` slots) and/or a `ui_procedure` of steps to run open-loop, plus an `id`. `bowmark.report_outcome` \u2014 after running a recipe, report whether it executed cleanly (`success` + `evidence`); this feeds the re-crawl loop that keeps recipes healthy, so report `false` on any deviation even if you still got the answer.\n\n**Syntax & edge cases.** `site` is a registrable domain, optionally with a product surface (`google.com`, `docs.stripe.com`, `google.com/maps`); `task` is intent, never a URL. Request the signed-in view with `variants: { auth_state: \"logged_in\", role: \"owner\" }` (also `locale` / `region` / `currency`). Non-`ok` statuses: `no_useful_data` / `site_not_supported` \u2192 browse manually; `ambiguous_scope` \u2192 retry with `scopeHint`; `rate_limited` \u2192 back off until `error.retry_after` (only new synthesis is capped). Skip it for localhost, RFC1918 IPs, and open-ended search with no destination.", +"vendor": "Bowmark AI", +"vendorUrl": "https://bowmark.ai", +"license": "Proprietary", +"sourceUrl": "https://github.com/bowmark-ai/plugin", +"homepage": "https://bowmark.ai", +"version": "0.1.0", +"categoriesRaw": [ +"web", +"browser", +"automation" +], +"keywords": [ +"browser", +"navigation", +"recipes", +"cheatsheets", +"websites", +"playwright", +"puppeteer", +"computer-use", +"scraping" +], +"bundleBytes": 5070051, +"installedBytes": 9105408, +"changelog": [ +{ +"version": "0.1.0", +"notes": [ +"Initial release \u2014 managed-key REST adapter over the Bowmark API (bowmark.ask \u2192 /v1/ask, bowmark.report_outcome \u2192 /v1/outcomes).", +"One shared Bowmark account behind the Pilot broker, metered per caller; the installed adapter is keyless." +] +} +], +"minPilotVersion": "1.10.0", +"runtimes": [ +"go" +], +"protection": "shareable", +"publishedAt": "2026-07-03", +"grants": [ +"fs.read:$APP/config.json", +"key.sign:self", +"net.dial:broker.pilotprotocol.network", +"audit.log:*" +], +"inCatalogue": true +}, "io.pilot.docker": { "name": "Docker", "tagline": "Run Docker from an agent \u2014 a local Docker Engine + CLI on Linux, real containers", diff --git a/src/data/apps.ts b/src/data/apps.ts index f0ea74c..0ee4788 100644 --- a/src/data/apps.ts +++ b/src/data/apps.ts @@ -1686,6 +1686,102 @@ export const apps: App[] = [ ], "publishedAt": "2026-06-08", "updatedAt": "2026-06-08" + }, + { + "id": "io.pilot.bowmark", + "name": "Bowmark", + "tagline": "Navigation cheatsheets for public websites — recipes that skip explore-and-discover", + "description": "Bowmark gives a browsing agent **pre-computed navigation recipes** for public websites — a parameterized URL `shortcut` or a short `ui_procedure` of UI steps, each verified by prior crawls. Instead of exploring and discovering a site's structure on every task (snapshotting the DOM, guessing selectors, burning tokens), the agent calls `ask({ site, task })` once and executes the returned recipe open-loop. On known sites that means roughly **5x fewer tokens** and far less latency.\n\n**Managed-key:** Pilot holds one shared Bowmark account behind the broker and meters each caller, so you install it **keyless** and never handle an API key. It's plain request/response REST — no websockets, no server-side browser, no async jobs. Your agent runs the recipe in its own browser; Bowmark only supplies the recipe.\n\n**Methods.** `bowmark.ask` — look up a recipe for a (site, task); on `status: ok` you get a URL `shortcut` (a `template` with `{name}` slots) and/or a `ui_procedure` of steps to run open-loop, plus an `id`. `bowmark.report_outcome` — after running a recipe, report whether it executed cleanly (`success` + `evidence`); this feeds the re-crawl loop that keeps recipes healthy, so report `false` on any deviation even if you still got the answer.\n\n**Syntax & edge cases.** `site` is a registrable domain, optionally with a product surface (`google.com`, `docs.stripe.com`, `google.com/maps`); `task` is intent, never a URL. Request the signed-in view with `variants: { auth_state: \"logged_in\", role: \"owner\" }` (also `locale` / `region` / `currency`). Non-`ok` statuses: `no_useful_data` / `site_not_supported` → browse manually; `ambiguous_scope` → retry with `scopeHint`; `rate_limited` → back off until `error.retry_after` (only new synthesis is capped). Skip it for localhost, RFC1918 IPs, and open-ended search with no destination.", + "categories": [ + "web" + ], + "primaryCategory": "web", + "keywords": [ + "browser", + "navigation", + "recipes", + "cheatsheets", + "websites", + "playwright", + "puppeteer", + "computer-use", + "scraping" + ], + "version": "0.1.0", + "vendor": "Bowmark AI", + "vendorUrl": "https://bowmark.ai", + "license": "Proprietary", + "sourceUrl": "https://github.com/bowmark-ai/plugin", + "homepage": "https://bowmark.ai", + "methods": [ + { + "name": "bowmark.ask", + "summary": "Look up a navigation recipe for a (site, task) pair — a URL shortcut or short UI procedure verified by prior crawls. status=ok → execute it open-loop; no_useful_data/site_not_supported → browse manually; ambiguous_scope → retry with scopeHint; rate_limited → back off. Pass variants:{auth_state:'logged_in'} for signed-in surfaces. Intent, not a URL." + }, + { + "name": "bowmark.report_outcome", + "summary": "After running a recipe from ask, report how it executed (envelope_id, success, evidence). success=true only if every step ran as written; false on any retry, JS-eval fallback, or extra click. Feeds the re-crawl loop that keeps recipes healthy." + }, + { + "name": "bowmark.help", + "summary": "Discovery: every method with params, kind, and latency class." + } + ], + "changelog": [ + { + "version": "0.1.0", + "notes": [ + "Initial release — managed-key REST adapter over the Bowmark API (bowmark.ask → /v1/ask, bowmark.report_outcome → /v1/outcomes).", + "One shared Bowmark account behind the Pilot broker, metered per caller; the installed adapter is keyless." + ] + } + ], + "grants": [ + "fs.read:$APP/config.json", + "key.sign:self", + "net.dial:broker.pilotprotocol.network", + "audit.log:*" + ], + "bundles": [ + { + "platform": "darwin-arm64", + "bytes": 5374254 + }, + { + "platform": "darwin-amd64", + "bytes": 4968650 + }, + { + "platform": "linux-arm64", + "bytes": 5019350 + }, + { + "platform": "linux-amd64", + "bytes": 4613746 + } + ], + "installedBytes": 9105408, + "depends": [], + "protection": "shareable", + "featured": false, + "real": true, + "inCatalogue": true, + "icon": { + "mode": "image", + "img": "/appicons/io.pilot.bowmark.png", + "fit": "cover", + "pos": "center", + "color": "#0b0b0a", + "ink": false, + "file": null, + "hue": 200 + }, + "minPilotVersion": "1.10.0", + "runtimes": [ + "go" + ], + "publishedAt": "2026-07-03", + "updatedAt": "2026-07-03" } ]; diff --git a/src/pages/app-store.astro b/src/pages/app-store.astro index e852902..96555be 100644 --- a/src/pages/app-store.astro +++ b/src/pages/app-store.astro @@ -17,7 +17,7 @@ const canonicalUrl = 'https://pilotprotocol.network/app-store'; const featured = featuredApps(); const hero = featured[0]; const sideFeatured = featured.slice(1, 4); -const freshPinned = ['io.pilot.miren', 'io.pilot.smolmachines', 'io.pilot.wallet', 'io.pilot.slipstream']; +const freshPinned = ['io.pilot.bowmark', 'io.pilot.miren', 'io.pilot.smolmachines', 'io.pilot.wallet', 'io.pilot.slipstream']; const freshExclude = new Set(['io.pilot.postgres', 'io.pilot.docker']); const fresh = [ ...freshPinned.map((id) => apps.find((a) => a.id === id)).filter(Boolean), diff --git a/src/pages/plain/app-store.astro b/src/pages/plain/app-store.astro index a3b5959..a252030 100644 --- a/src/pages/plain/app-store.astro +++ b/src/pages/plain/app-store.astro @@ -1,7 +1,7 @@ --- // Auto-generated by scripts/regen-plain.mjs. Edit the marketing source and re-run. // plain-source: src/pages/app-store.astro -// plain-source-sha256: 64660b72f524cf3191438052d800df3205349be2e992faf75971fe0b979803e7 +// plain-source-sha256: 38f6e44e971b262e6046c479d9ddeabc7ed10570d01e7536bd5ae11bda3d1fa7 import PlainLayout from '../../layouts/PlainLayout.astro'; ---