From 9c4d551b7debc8dbcfefbb61bb7021890bd7377b Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 6 Jul 2026 20:29:37 +0200 Subject: [PATCH] feat: add cate.browser.* to the host API typings and a kitchensink demo Mirror the new cate.browser.* namespace into the SDK kit (kit/cate-host.d.ts and the regenerated _kit copies) so extensions can type against it, and add a browser demo to cate.kitchensink (browser scope in the manifest, plus open and snapshot buttons in the panel). --- .../cate.aisession/src/_kit/cate-host.d.ts | 55 +++++++++++++++++++ extensions/cate.kitchensink/manifest.json | 2 +- extensions/cate.kitchensink/src/public/app.ts | 39 +++++++++++++ .../src/public/cate-host.d.ts | 48 ++++++++++++++++ .../cate.kitchensink/src/public/index.html | 10 ++++ extensions/cate.mcp/src/_kit/cate-host.d.ts | 55 +++++++++++++++++++ .../cate.mermaid/src/_kit/cate-host.d.ts | 55 +++++++++++++++++++ .../cate.sqlite/src/_kit/cate-host.d.ts | 55 +++++++++++++++++++ extensions/cate.usage/src/_kit/cate-host.d.ts | 55 +++++++++++++++++++ kit/cate-host.d.ts | 55 +++++++++++++++++++ 10 files changed, 428 insertions(+), 1 deletion(-) diff --git a/extensions/cate.aisession/src/_kit/cate-host.d.ts b/extensions/cate.aisession/src/_kit/cate-host.d.ts index 6bffc19..7a1fdc2 100644 --- a/extensions/cate.aisession/src/_kit/cate-host.d.ts +++ b/extensions/cate.aisession/src/_kit/cate-host.d.ts @@ -53,6 +53,40 @@ export interface CateDroppedFile { truncated?: boolean } +/** One open browser panel, as reported by `cate.browser.list()`. */ +export interface CateBrowserTab { + panelId: string + title: string + url: string + focused: boolean +} + +/** Navigation state of a browser panel, from `cate.browser.current()`. */ +export interface CateBrowserState { + url: string + title: string + canGoBack: boolean + canGoForward: boolean + loading: boolean +} + +/** One interactable element in an accessibility `snapshot()`. `ref` is an opaque + * handle to pass back to `click`/`type`; only valid for the snapshot it came + * from (re-snapshot after a navigation or mutation). */ +export interface CateBrowserRef { + ref: string + role: string + name: string + value?: string +} + +/** Accessibility snapshot of a browser panel, from `cate.browser.snapshot()`. */ +export interface CateBrowserSnapshot { + url: string + title: string + refs: CateBrowserRef[] +} + export interface CateHost { version(): Promise panel: CatePanel @@ -89,5 +123,26 @@ export interface CateHost { run(prompt: string): Promise cancel(): Promise } + /** Drive Cate's browser panels (requires the `browser` scope). These panels + * hold the user's real, logged-in browser session — cookies, auth, and all — + * so anything the user can reach while signed in, the extension can too. Treat + * it accordingly. Every method targets a single panel; `panelId` picks it, and + * when omitted the host uses the focused (or only) browser panel. `snapshot` + * returns opaque element `ref`s to feed back to `click`/`type`; re-snapshot + * after any navigation because refs don't survive it. `screenshot` returns a + * host filesystem `path` (a webview guest can't read it directly; a + * server-backed extension can — see docs/extensions.md). */ + browser: { + list(): Promise + open(opts: { url: string; panelId?: string }): Promise<{ panelId: string; url: string }> + back(opts?: { panelId?: string }): Promise<{ ok: true }> + forward(opts?: { panelId?: string }): Promise<{ ok: true }> + reload(opts?: { panelId?: string }): Promise<{ ok: true }> + current(opts?: { panelId?: string }): Promise + screenshot(opts?: { panelId?: string }): Promise<{ path: string }> + snapshot(opts?: { panelId?: string }): Promise + click(opts: { ref: string; panelId?: string }): Promise<{ ok: true }> + type(opts: { ref: string; text: string; panelId?: string }): Promise<{ ok: true }> + } storage: CateHostStorage } diff --git a/extensions/cate.kitchensink/manifest.json b/extensions/cate.kitchensink/manifest.json index 32a03f8..3456376 100644 --- a/extensions/cate.kitchensink/manifest.json +++ b/extensions/cate.kitchensink/manifest.json @@ -9,5 +9,5 @@ "readyPath": "/health", "portEnv": "PORT" }, - "cateApi": ["storage", "editor", "canvas", "theme", "ui", "workspace.read", "agent"] + "cateApi": ["storage", "editor", "canvas", "theme", "ui", "workspace.read", "agent", "browser"] } diff --git a/extensions/cate.kitchensink/src/public/app.ts b/extensions/cate.kitchensink/src/public/app.ts index 531824f..eb2a384 100644 --- a/extensions/cate.kitchensink/src/public/app.ts +++ b/extensions/cate.kitchensink/src/public/app.ts @@ -326,6 +326,44 @@ function initAgent(): void { }) } +// --- browser ---------------------------------------------------------------- + +// Drive Cate's browser panels through window.cate.browser (needs the `browser` +// scope). These panels hold the user's real logged-in session, so this is a +// deliberately small demo: open a URL, and snapshot the focused panel. +function initBrowser(): void { + if (!window.cate) return + const out = byId('browser-out') + + byId('browser-open').addEventListener('click', async () => { + const url = byId('browser-url').value.trim() + if (!url) return + out.textContent = 'opening…' + try { + const res = await cate.browser.open({ url }) + out.textContent = 'opened ' + JSON.stringify(res, null, 2) + log('browser.open ->', res) + } catch (err) { + out.textContent = 'failed: ' + String(err) + log('browser.open failed:', String(err)) + } + }) + + byId('browser-snapshot').addEventListener('click', async () => { + out.textContent = 'snapshotting…' + try { + const snap = await cate.browser.snapshot() + out.textContent = + `${snap.title} — ${snap.url}\n${snap.refs.length} refs\n` + + JSON.stringify(snap.refs.slice(0, 20), null, 2) + log('browser.snapshot ->', snap) + } catch (err) { + out.textContent = 'failed: ' + String(err) + log('browser.snapshot failed:', String(err)) + } + }) +} + initBridge() initNotes() initStorageApi() @@ -334,3 +372,4 @@ initHttp() initWs() initRoundtrip() initAgent() +initBrowser() diff --git a/extensions/cate.kitchensink/src/public/cate-host.d.ts b/extensions/cate.kitchensink/src/public/cate-host.d.ts index f371b44..b12fc0c 100644 --- a/extensions/cate.kitchensink/src/public/cate-host.d.ts +++ b/extensions/cate.kitchensink/src/public/cate-host.d.ts @@ -50,6 +50,39 @@ interface CatePanel { setTitle(title: string): Promise } +/** One open browser panel, as reported by `cate.browser.list()`. */ +interface CateBrowserTab { + panelId: string + title: string + url: string + focused: boolean +} + +/** Navigation state of a browser panel, from `cate.browser.current()`. */ +interface CateBrowserState { + url: string + title: string + canGoBack: boolean + canGoForward: boolean + loading: boolean +} + +/** One interactable element in an accessibility `snapshot()`. `ref` is opaque and + * only valid for the snapshot it came from. */ +interface CateBrowserRef { + ref: string + role: string + name: string + value?: string +} + +/** Accessibility snapshot of a browser panel, from `cate.browser.snapshot()`. */ +interface CateBrowserSnapshot { + url: string + title: string + refs: CateBrowserRef[] +} + interface CateHost { /** API version int, for feature detection. */ version(): Promise @@ -96,6 +129,21 @@ interface CateHost { /** Abort the in-flight turn of this extension's session. */ cancel(): Promise } + /** Drive Cate's browser panels (requires the `browser` scope). These panels + * hold the user's real, logged-in browser session, so treat the access as + * sensitive. `panelId` targets a panel; omit it for the focused one. */ + browser: { + list(): Promise + open(opts: { url: string; panelId?: string }): Promise<{ panelId: string; url: string }> + back(opts?: { panelId?: string }): Promise<{ ok: true }> + forward(opts?: { panelId?: string }): Promise<{ ok: true }> + reload(opts?: { panelId?: string }): Promise<{ ok: true }> + current(opts?: { panelId?: string }): Promise + screenshot(opts?: { panelId?: string }): Promise<{ path: string }> + snapshot(opts?: { panelId?: string }): Promise + click(opts: { ref: string; panelId?: string }): Promise<{ ok: true }> + type(opts: { ref: string; text: string; panelId?: string }): Promise<{ ok: true }> + } storage: CateHostStorage } diff --git a/extensions/cate.kitchensink/src/public/index.html b/extensions/cate.kitchensink/src/public/index.html index 546e1ab..1b2acf5 100644 --- a/extensions/cate.kitchensink/src/public/index.html +++ b/extensions/cate.kitchensink/src/public/index.html @@ -65,6 +65,16 @@

server to cate (CATE_API)


   
 
+  
+

browser

+
+ + + +
+

+  
+

agent

diff --git a/extensions/cate.mcp/src/_kit/cate-host.d.ts b/extensions/cate.mcp/src/_kit/cate-host.d.ts index 6bffc19..7a1fdc2 100644 --- a/extensions/cate.mcp/src/_kit/cate-host.d.ts +++ b/extensions/cate.mcp/src/_kit/cate-host.d.ts @@ -53,6 +53,40 @@ export interface CateDroppedFile { truncated?: boolean } +/** One open browser panel, as reported by `cate.browser.list()`. */ +export interface CateBrowserTab { + panelId: string + title: string + url: string + focused: boolean +} + +/** Navigation state of a browser panel, from `cate.browser.current()`. */ +export interface CateBrowserState { + url: string + title: string + canGoBack: boolean + canGoForward: boolean + loading: boolean +} + +/** One interactable element in an accessibility `snapshot()`. `ref` is an opaque + * handle to pass back to `click`/`type`; only valid for the snapshot it came + * from (re-snapshot after a navigation or mutation). */ +export interface CateBrowserRef { + ref: string + role: string + name: string + value?: string +} + +/** Accessibility snapshot of a browser panel, from `cate.browser.snapshot()`. */ +export interface CateBrowserSnapshot { + url: string + title: string + refs: CateBrowserRef[] +} + export interface CateHost { version(): Promise panel: CatePanel @@ -89,5 +123,26 @@ export interface CateHost { run(prompt: string): Promise cancel(): Promise } + /** Drive Cate's browser panels (requires the `browser` scope). These panels + * hold the user's real, logged-in browser session — cookies, auth, and all — + * so anything the user can reach while signed in, the extension can too. Treat + * it accordingly. Every method targets a single panel; `panelId` picks it, and + * when omitted the host uses the focused (or only) browser panel. `snapshot` + * returns opaque element `ref`s to feed back to `click`/`type`; re-snapshot + * after any navigation because refs don't survive it. `screenshot` returns a + * host filesystem `path` (a webview guest can't read it directly; a + * server-backed extension can — see docs/extensions.md). */ + browser: { + list(): Promise + open(opts: { url: string; panelId?: string }): Promise<{ panelId: string; url: string }> + back(opts?: { panelId?: string }): Promise<{ ok: true }> + forward(opts?: { panelId?: string }): Promise<{ ok: true }> + reload(opts?: { panelId?: string }): Promise<{ ok: true }> + current(opts?: { panelId?: string }): Promise + screenshot(opts?: { panelId?: string }): Promise<{ path: string }> + snapshot(opts?: { panelId?: string }): Promise + click(opts: { ref: string; panelId?: string }): Promise<{ ok: true }> + type(opts: { ref: string; text: string; panelId?: string }): Promise<{ ok: true }> + } storage: CateHostStorage } diff --git a/extensions/cate.mermaid/src/_kit/cate-host.d.ts b/extensions/cate.mermaid/src/_kit/cate-host.d.ts index 6bffc19..7a1fdc2 100644 --- a/extensions/cate.mermaid/src/_kit/cate-host.d.ts +++ b/extensions/cate.mermaid/src/_kit/cate-host.d.ts @@ -53,6 +53,40 @@ export interface CateDroppedFile { truncated?: boolean } +/** One open browser panel, as reported by `cate.browser.list()`. */ +export interface CateBrowserTab { + panelId: string + title: string + url: string + focused: boolean +} + +/** Navigation state of a browser panel, from `cate.browser.current()`. */ +export interface CateBrowserState { + url: string + title: string + canGoBack: boolean + canGoForward: boolean + loading: boolean +} + +/** One interactable element in an accessibility `snapshot()`. `ref` is an opaque + * handle to pass back to `click`/`type`; only valid for the snapshot it came + * from (re-snapshot after a navigation or mutation). */ +export interface CateBrowserRef { + ref: string + role: string + name: string + value?: string +} + +/** Accessibility snapshot of a browser panel, from `cate.browser.snapshot()`. */ +export interface CateBrowserSnapshot { + url: string + title: string + refs: CateBrowserRef[] +} + export interface CateHost { version(): Promise panel: CatePanel @@ -89,5 +123,26 @@ export interface CateHost { run(prompt: string): Promise cancel(): Promise } + /** Drive Cate's browser panels (requires the `browser` scope). These panels + * hold the user's real, logged-in browser session — cookies, auth, and all — + * so anything the user can reach while signed in, the extension can too. Treat + * it accordingly. Every method targets a single panel; `panelId` picks it, and + * when omitted the host uses the focused (or only) browser panel. `snapshot` + * returns opaque element `ref`s to feed back to `click`/`type`; re-snapshot + * after any navigation because refs don't survive it. `screenshot` returns a + * host filesystem `path` (a webview guest can't read it directly; a + * server-backed extension can — see docs/extensions.md). */ + browser: { + list(): Promise + open(opts: { url: string; panelId?: string }): Promise<{ panelId: string; url: string }> + back(opts?: { panelId?: string }): Promise<{ ok: true }> + forward(opts?: { panelId?: string }): Promise<{ ok: true }> + reload(opts?: { panelId?: string }): Promise<{ ok: true }> + current(opts?: { panelId?: string }): Promise + screenshot(opts?: { panelId?: string }): Promise<{ path: string }> + snapshot(opts?: { panelId?: string }): Promise + click(opts: { ref: string; panelId?: string }): Promise<{ ok: true }> + type(opts: { ref: string; text: string; panelId?: string }): Promise<{ ok: true }> + } storage: CateHostStorage } diff --git a/extensions/cate.sqlite/src/_kit/cate-host.d.ts b/extensions/cate.sqlite/src/_kit/cate-host.d.ts index 6bffc19..7a1fdc2 100644 --- a/extensions/cate.sqlite/src/_kit/cate-host.d.ts +++ b/extensions/cate.sqlite/src/_kit/cate-host.d.ts @@ -53,6 +53,40 @@ export interface CateDroppedFile { truncated?: boolean } +/** One open browser panel, as reported by `cate.browser.list()`. */ +export interface CateBrowserTab { + panelId: string + title: string + url: string + focused: boolean +} + +/** Navigation state of a browser panel, from `cate.browser.current()`. */ +export interface CateBrowserState { + url: string + title: string + canGoBack: boolean + canGoForward: boolean + loading: boolean +} + +/** One interactable element in an accessibility `snapshot()`. `ref` is an opaque + * handle to pass back to `click`/`type`; only valid for the snapshot it came + * from (re-snapshot after a navigation or mutation). */ +export interface CateBrowserRef { + ref: string + role: string + name: string + value?: string +} + +/** Accessibility snapshot of a browser panel, from `cate.browser.snapshot()`. */ +export interface CateBrowserSnapshot { + url: string + title: string + refs: CateBrowserRef[] +} + export interface CateHost { version(): Promise panel: CatePanel @@ -89,5 +123,26 @@ export interface CateHost { run(prompt: string): Promise cancel(): Promise } + /** Drive Cate's browser panels (requires the `browser` scope). These panels + * hold the user's real, logged-in browser session — cookies, auth, and all — + * so anything the user can reach while signed in, the extension can too. Treat + * it accordingly. Every method targets a single panel; `panelId` picks it, and + * when omitted the host uses the focused (or only) browser panel. `snapshot` + * returns opaque element `ref`s to feed back to `click`/`type`; re-snapshot + * after any navigation because refs don't survive it. `screenshot` returns a + * host filesystem `path` (a webview guest can't read it directly; a + * server-backed extension can — see docs/extensions.md). */ + browser: { + list(): Promise + open(opts: { url: string; panelId?: string }): Promise<{ panelId: string; url: string }> + back(opts?: { panelId?: string }): Promise<{ ok: true }> + forward(opts?: { panelId?: string }): Promise<{ ok: true }> + reload(opts?: { panelId?: string }): Promise<{ ok: true }> + current(opts?: { panelId?: string }): Promise + screenshot(opts?: { panelId?: string }): Promise<{ path: string }> + snapshot(opts?: { panelId?: string }): Promise + click(opts: { ref: string; panelId?: string }): Promise<{ ok: true }> + type(opts: { ref: string; text: string; panelId?: string }): Promise<{ ok: true }> + } storage: CateHostStorage } diff --git a/extensions/cate.usage/src/_kit/cate-host.d.ts b/extensions/cate.usage/src/_kit/cate-host.d.ts index 6bffc19..7a1fdc2 100644 --- a/extensions/cate.usage/src/_kit/cate-host.d.ts +++ b/extensions/cate.usage/src/_kit/cate-host.d.ts @@ -53,6 +53,40 @@ export interface CateDroppedFile { truncated?: boolean } +/** One open browser panel, as reported by `cate.browser.list()`. */ +export interface CateBrowserTab { + panelId: string + title: string + url: string + focused: boolean +} + +/** Navigation state of a browser panel, from `cate.browser.current()`. */ +export interface CateBrowserState { + url: string + title: string + canGoBack: boolean + canGoForward: boolean + loading: boolean +} + +/** One interactable element in an accessibility `snapshot()`. `ref` is an opaque + * handle to pass back to `click`/`type`; only valid for the snapshot it came + * from (re-snapshot after a navigation or mutation). */ +export interface CateBrowserRef { + ref: string + role: string + name: string + value?: string +} + +/** Accessibility snapshot of a browser panel, from `cate.browser.snapshot()`. */ +export interface CateBrowserSnapshot { + url: string + title: string + refs: CateBrowserRef[] +} + export interface CateHost { version(): Promise panel: CatePanel @@ -89,5 +123,26 @@ export interface CateHost { run(prompt: string): Promise cancel(): Promise } + /** Drive Cate's browser panels (requires the `browser` scope). These panels + * hold the user's real, logged-in browser session — cookies, auth, and all — + * so anything the user can reach while signed in, the extension can too. Treat + * it accordingly. Every method targets a single panel; `panelId` picks it, and + * when omitted the host uses the focused (or only) browser panel. `snapshot` + * returns opaque element `ref`s to feed back to `click`/`type`; re-snapshot + * after any navigation because refs don't survive it. `screenshot` returns a + * host filesystem `path` (a webview guest can't read it directly; a + * server-backed extension can — see docs/extensions.md). */ + browser: { + list(): Promise + open(opts: { url: string; panelId?: string }): Promise<{ panelId: string; url: string }> + back(opts?: { panelId?: string }): Promise<{ ok: true }> + forward(opts?: { panelId?: string }): Promise<{ ok: true }> + reload(opts?: { panelId?: string }): Promise<{ ok: true }> + current(opts?: { panelId?: string }): Promise + screenshot(opts?: { panelId?: string }): Promise<{ path: string }> + snapshot(opts?: { panelId?: string }): Promise + click(opts: { ref: string; panelId?: string }): Promise<{ ok: true }> + type(opts: { ref: string; text: string; panelId?: string }): Promise<{ ok: true }> + } storage: CateHostStorage } diff --git a/kit/cate-host.d.ts b/kit/cate-host.d.ts index e2c231c..e2343c2 100644 --- a/kit/cate-host.d.ts +++ b/kit/cate-host.d.ts @@ -52,6 +52,40 @@ export interface CateDroppedFile { truncated?: boolean } +/** One open browser panel, as reported by `cate.browser.list()`. */ +export interface CateBrowserTab { + panelId: string + title: string + url: string + focused: boolean +} + +/** Navigation state of a browser panel, from `cate.browser.current()`. */ +export interface CateBrowserState { + url: string + title: string + canGoBack: boolean + canGoForward: boolean + loading: boolean +} + +/** One interactable element in an accessibility `snapshot()`. `ref` is an opaque + * handle to pass back to `click`/`type`; only valid for the snapshot it came + * from (re-snapshot after a navigation or mutation). */ +export interface CateBrowserRef { + ref: string + role: string + name: string + value?: string +} + +/** Accessibility snapshot of a browser panel, from `cate.browser.snapshot()`. */ +export interface CateBrowserSnapshot { + url: string + title: string + refs: CateBrowserRef[] +} + export interface CateHost { version(): Promise panel: CatePanel @@ -88,5 +122,26 @@ export interface CateHost { run(prompt: string): Promise cancel(): Promise } + /** Drive Cate's browser panels (requires the `browser` scope). These panels + * hold the user's real, logged-in browser session — cookies, auth, and all — + * so anything the user can reach while signed in, the extension can too. Treat + * it accordingly. Every method targets a single panel; `panelId` picks it, and + * when omitted the host uses the focused (or only) browser panel. `snapshot` + * returns opaque element `ref`s to feed back to `click`/`type`; re-snapshot + * after any navigation because refs don't survive it. `screenshot` returns a + * host filesystem `path` (a webview guest can't read it directly; a + * server-backed extension can — see docs/extensions.md). */ + browser: { + list(): Promise + open(opts: { url: string; panelId?: string }): Promise<{ panelId: string; url: string }> + back(opts?: { panelId?: string }): Promise<{ ok: true }> + forward(opts?: { panelId?: string }): Promise<{ ok: true }> + reload(opts?: { panelId?: string }): Promise<{ ok: true }> + current(opts?: { panelId?: string }): Promise + screenshot(opts?: { panelId?: string }): Promise<{ path: string }> + snapshot(opts?: { panelId?: string }): Promise + click(opts: { ref: string; panelId?: string }): Promise<{ ok: true }> + type(opts: { ref: string; text: string; panelId?: string }): Promise<{ ok: true }> + } storage: CateHostStorage }