floorp-mcp 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arda Karaman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,270 @@
1
+ # floorp-mcp
2
+
3
+ [![CI](https://github.com/Frumane/floorp-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/Frumane/floorp-mcp/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5
+
6
+ > An **MCP (Model Context Protocol)** server that lets AI assistants — Claude Code,
7
+ > Claude Desktop, Cursor, and any MCP client — **read pages, take screenshots and
8
+ > manage tabs** in the [Floorp](https://floorp.app) browser, using your real,
9
+ > logged-in browsing session.
10
+
11
+ Think "Claude in Chrome", but for Floorp (and other Firefox-based browsers on the
12
+ roadmap).
13
+
14
+ ## How it works
15
+
16
+ Floorp ships a **built-in local automation server**. When you set
17
+ `floorp.mcp.enabled = true` in `about:config`, Floorp exposes an HTTP API on
18
+ `http://127.0.0.1:58261`. This project is a thin, well-documented MCP bridge that
19
+ translates MCP tool calls into requests against that API — **no browser extension
20
+ required**.
21
+
22
+ ```
23
+ Claude Code / Desktop / Cursor
24
+ │ MCP (stdio)
25
+
26
+ floorp-mcp ──HTTP──► Floorp :58261 ──► your real tabs
27
+ (this project) (built-in API)
28
+ ```
29
+
30
+ ## Requirements
31
+
32
+ - **Floorp** installed and running.
33
+ - In `about:config`, set **`floorp.mcp.enabled`** to `true`, then fully restart Floorp.
34
+ - **Node.js** ≥ 18.
35
+
36
+ ## Setup
37
+
38
+ ```bash
39
+ git clone https://github.com/Frumane/floorp-mcp
40
+ cd floorp-mcp
41
+ npm install
42
+ npm run build
43
+ ```
44
+
45
+ Register it with Claude Code (user-wide):
46
+
47
+ ```bash
48
+ claude mcp add floorp -s user -- node /absolute/path/to/floorp-mcp/dist/index.js
49
+ ```
50
+
51
+ …or add it to your MCP config manually:
52
+
53
+ ```json
54
+ {
55
+ "mcpServers": {
56
+ "floorp": {
57
+ "command": "node",
58
+ "args": ["/absolute/path/to/floorp-mcp/dist/index.js"]
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ ## Tools
65
+
66
+ **Tabs & reading**
67
+
68
+ | Tool | What it does |
69
+ |------|--------------|
70
+ | `list_tabs` | List all open tabs (title, URL, browserId, active, pinned). |
71
+ | `open_tab` | Open a new tab at a URL; **returns the new tab's `browserId`** so you can target it. |
72
+ | `get_active_tab` | Return the active tab's title, URL and browserId. |
73
+ | `navigate_tab` | Navigate an existing tab to a URL. |
74
+ | `close_tab` | Close a tab. |
75
+ | `read_page` | Read a tab's content as clean Markdown (or HTML / accessibility tree). Output is capped (default 25 KB) to protect the context. |
76
+ | `find` | **Fast element locator** — search a page server-side by visible text and/or tag; returns a compact list of ready-to-use CSS `selector`s (~1 KB) instead of the whole HTML. Use it to find a button/link/field, then act on the selector. |
77
+ | `snapshot` | Structured page map: Markdown with inline `fp:` refs + an element selector map — locate elements without grepping HTML, then act via a `ref`. |
78
+ | `screenshot` | Capture a screenshot of a tab (viewport or full page). |
79
+ | `launch_floorp` | Ensure Floorp is running — launches it if the API isn't reachable (Windows). |
80
+
81
+ **Interaction**
82
+
83
+ | Tool | What it does |
84
+ |------|--------------|
85
+ | `click` | Click an element by CSS selector **or a `ref` from `snapshot`**; auto-scrolls it into view first. |
86
+ | `type_text` | Type into an input/textarea — or a rich/contenteditable editor (Slate, ProseMirror…) — by CSS selector. |
87
+ | `fill_form` | Fill multiple fields at once. |
88
+ | `press_key` | Press a keyboard key (Enter, Tab, …). |
89
+ | `wait_for_element` | Wait for an element to attach / become visible / etc. |
90
+ | `get_value` | **Sensitive.** Read the current value of an input/textarea/select (can read password fields). |
91
+
92
+ Most tools target the **active tab** by default; pass a `browserId` (from
93
+ `list_tabs`) to target a specific tab.
94
+
95
+ **Real OS keyboard (Windows)** — for React/rich editors and bot-guarded submits
96
+ that ignore synthetic input:
97
+
98
+ | Tool | What it does |
99
+ |------|--------------|
100
+ | `real_type` | Type into the focused element via **genuine OS key events** (`isTrusted`). |
101
+ | `real_key` | Press a real key/combo, e.g. `"Enter"`, `"ctrl+a"`. |
102
+ | `real_clear` | Real Ctrl+A + Delete — reliably clears a rich/contenteditable field. |
103
+
104
+ These produce input a page can't distinguish from a human's, so they drive
105
+ React/Slate editors and submit composers that synthetic clicks/typing can't.
106
+ Workflow: `click` the field to focus it → `real_clear` / `real_type` / `real_key "Enter"`.
107
+
108
+ > **Safety guard:** OS keystrokes go to the foreground window, so before sending
109
+ > anything these tools bring Floorp to the foreground and **verify** it — if Floorp
110
+ > isn't running or can't be focused, they **abort without typing a single key**, so
111
+ > input can never leak into another app.
112
+
113
+ **Real OS mouse (Windows)** — genuine `isTrusted` clicks at screen coordinates:
114
+
115
+ | Tool | What it does |
116
+ |------|--------------|
117
+ | `window_bounds` | Floorp's window rectangle in screen pixels (to compute targets). |
118
+ | `move_cursor` | Move the real OS cursor to a screen pixel inside Floorp. |
119
+ | `real_click` | Real OS click (left/right, single/double) at a screen pixel inside Floorp. |
120
+
121
+ > **Double guard:** the click is sent only when Floorp is verified foreground **and**
122
+ > the point lies **inside Floorp's window rect** — a stray coordinate is refused, so
123
+ > a click can never land in another app/window. Coordinates are screen pixels
124
+ > (note display scaling/DPI when mapping from a screenshot).
125
+
126
+ **More interaction & queries**
127
+
128
+ | Tool | What it does |
129
+ |------|--------------|
130
+ | `hover` / `double_click` / `right_click` | Mouse gestures on an element (selector or `ref`). |
131
+ | `select_option` | Choose an option in a `<select>`. |
132
+ | `set_checked` | Check/uncheck a checkbox or radio. |
133
+ | `submit_form` | Submit a form. |
134
+ | `upload_file` | **Sensitive.** Set a file `<input>` by absolute path — restrict with `FLOORP_MCP_ALLOW_UPLOAD_DIRS`. |
135
+ | `get_attribute` | Read an element attribute (href, value, …). |
136
+ | `get_article` | Readability-extracted main article as Markdown. |
137
+ | `get_cookies` | **Sensitive.** Cookies visible to the page — values redacted unless `includeValues: true`. |
138
+ | `wait_for_network_idle` | Wait for network activity to settle. |
139
+ | `list_workspaces` / `switch_workspace` | Floorp workspaces (where supported). |
140
+
141
+ ## Security
142
+
143
+ Understand the threat model before enabling this. Two risks dominate:
144
+
145
+ 1. **Floorp's automation API has no authentication by default.** While
146
+ `floorp.mcp.enabled` is on, **any local process** can drive your logged-in
147
+ browser via `127.0.0.1:58261` — not just this server. There is also no
148
+ Origin check, so hostile web pages may attempt CSRF/DNS-rebinding tricks
149
+ against it. Mitigations:
150
+ - Turn `floorp.mcp.enabled` **off** when you're not using automation.
151
+ - Set the `FLOORP_MCP_TOKEN` environment variable — this server then sends it
152
+ as a `Bearer` token on every request (effective on Floorp builds that
153
+ enforce a token; harmless otherwise).
154
+ 2. **Prompt injection ("lethal trifecta").** The assistant reads untrusted page
155
+ content *and* can act on your authenticated sessions (click, type, submit,
156
+ navigate, real OS input). A malicious page could try to instruct the
157
+ assistant to act against you. Treat everything read from a page as untrusted;
158
+ don't run automation unattended on sites you don't trust.
159
+
160
+ Hardening built into this server:
161
+
162
+ - **Real OS input is double-guarded:** keys/clicks are sent only after verifying
163
+ Floorp is the foreground window, and mouse clicks must land inside Floorp's
164
+ window rectangle — otherwise it aborts *without* sending anything. PowerShell
165
+ payloads are passed base64-encoded via process-private environment variables
166
+ (no shell interpolation, no temp script files on disk).
167
+ - **URL scheme + host allowlist:** `open_tab`/`navigate_tab` accept only `http(s)`
168
+ (and `about:blank`) by default, and **refuse loopback/private hosts**
169
+ (`127.0.0.1`, `localhost`, `10/8`, `172.16/12`, `192.168/16`, `169.254/16`,
170
+ IPv6 ULA/link-local). This stops a prompt-injected agent from pivoting the
171
+ browser onto Floorp's own API or your LAN and reading the response back. Lift
172
+ with `FLOORP_MCP_ALLOW_PRIVILEGED_URLS=1`. Optionally pin navigation to a
173
+ domain allowlist with `FLOORP_MCP_ALLOW_DOMAINS`.
174
+ - **Cookie values are redacted by default** in `get_cookies`; raw values require
175
+ an explicit `includeValues: true`.
176
+ - **`get_value` can read secrets:** browsers let same-origin JS read password
177
+ fields, so this tool *can* return a typed password. It's flagged SENSITIVE —
178
+ use it only on fields the user asked about, never to harvest credentials.
179
+ - **Upload allowlist:** set `FLOORP_MCP_ALLOW_UPLOAD_DIRS` (`;`-separated
180
+ directories) to confine `upload_file`. Paths are canonicalised with realpath
181
+ (symlinks resolved) and checked so `..`, a symlink, a same-prefix sibling
182
+ directory, or a UNC path can't escape the allowed folders.
183
+ - **`find` skips hidden elements** (inline `display:none`/`visibility:hidden`,
184
+ `hidden`, `type=hidden`, `aria-hidden`) so a page can't lure the agent into
185
+ clicking an invisible button via text search.
186
+ - **Input bounds:** numeric/text tool parameters are range- and length-capped
187
+ (coordinates, timeouts, `maxChars`, `find` limit, typed text, form fields) to
188
+ prevent resource-exhaustion / crash inputs.
189
+ - **Truncated API errors & validated port:** Floorp error bodies are truncated
190
+ before reaching the model; `FLOORP_MCP_PORT` is validated as 1–65535.
191
+ - **No `evaluate` tool:** arbitrary page-JS execution is deliberately not exposed.
192
+
193
+ What is **not** defended (inherent / Floorp-side): a malicious *local* process can
194
+ still read or impersonate the unauthenticated loopback API (plaintext, no TLS), and
195
+ prompt injection from a page you choose to automate can still drive legitimate
196
+ actions on that page. Disable `floorp.mcp.enabled` when idle and don't automate
197
+ untrusted sites unattended.
198
+
199
+ | Environment variable | Effect |
200
+ |---|---|
201
+ | `FLOORP_MCP_TOKEN` | Sent as `Authorization: Bearer …` to the Floorp API. |
202
+ | `FLOORP_MCP_PORT` | API port (default `58261`, validated 1–65535). |
203
+ | `FLOORP_MCP_ALLOW_PRIVILEGED_URLS` | `1` allows non-http(s) URLs **and** loopback/private hosts in open/navigate. |
204
+ | `FLOORP_MCP_ALLOW_DOMAINS` | Comma-separated domain allowlist for navigation (subdomains included). Unset = any public host. |
205
+ | `FLOORP_MCP_ALLOW_UPLOAD_DIRS` | Restrict `upload_file` to these directories (`;`-separated). |
206
+ | `FLOORP_PATH` | Full path to `floorp.exe` for `launch_floorp`. |
207
+
208
+ ## Performance
209
+
210
+ - **HTTP tool calls are cheap** — a full attach → act → detach round-trip against
211
+ Floorp's local API is ~5–6 ms. `find` searches the page server-side and returns
212
+ ~1 KB of ready-to-use selectors instead of dumping the whole HTML, and
213
+ `read_page` is capped (default 25 KB) so a page read can't flood the context.
214
+ - **Real OS input uses a persistent PowerShell host.** Spawning `powershell.exe`
215
+ (~700 ms) and compiling the P/Invoke helper (~600 ms) used to happen on *every*
216
+ `real_*`/`move_cursor`/`window_bounds` call (~1.9 s each). Now one host is
217
+ started lazily, compiles once, and runs a read-eval loop — so the first call
218
+ pays ~1.6 s but every call after is **~350 ms** for a guarded key/click (~5×
219
+ faster) and a few ms for a window-bounds query. The foreground/bounds safety
220
+ guards still run on every command; the host is recycled if it hangs or dies.
221
+
222
+ ## Notes & limitations
223
+
224
+ Learned from driving real apps (incl. Google Flow):
225
+
226
+ - **Rich editors:** `type_text` handles plain inputs *and* contenteditable editors
227
+ (Slate, ProseMirror, Lexical) — it falls back to dispatching a real text-input
228
+ event when an element has no `.value`. Reliably *clearing* such editors isn't
229
+ solved yet (no `select-all`/`evaluate`).
230
+ - **Submitting React composers:** many chat/prompt composers submit on a real
231
+ **Enter keydown**, not on a synthetic click of the send button. Prefer
232
+ `press_key` `"Enter"` over `click` for those.
233
+ - **Trusted events:** you cannot forge `isTrusted=true` from page JavaScript — it
234
+ is a browser security invariant. Floorp injects input at a privileged layer, so
235
+ ordinary clicks/keys behave like real ones; but flows guarded by reCAPTCHA or
236
+ strict bot-detection may still refuse automated submission.
237
+ - **`evaluate`:** the page-JS eval endpoint returns HTTP 404 on some Floorp builds,
238
+ so it is not exposed as a tool here.
239
+ - **Multiple windows:** when more than one window is open, the "active tab" is
240
+ ambiguous (each window has its own active tab). Prefer the `browserId` returned
241
+ by `open_tab`, or one from `list_tabs`, and pass it explicitly to every tool.
242
+
243
+ ## Roadmap
244
+
245
+ - [x] Tab management, page reading, screenshots
246
+ - [x] Interaction tools: click, type, fill forms, key presses, read field values
247
+ - [x] Real OS keyboard (Windows): `real_type` / `real_key` / `real_clear`, with a
248
+ foreground safety guard — drives React/Slate editors & bot-guarded submits
249
+ - [x] `snapshot` (fingerprint refs + selector map) + `click` by `ref` + auto-scroll-into-view
250
+ - [x] `launch_floorp` — start Floorp if not running (Windows)
251
+ - [x] Extra tools: hover, double/right-click, select_option, set_checked, submit,
252
+ upload_file, get_attribute, get_article, get_cookies, wait_for_network_idle, workspaces
253
+ - [x] Real OS mouse (Windows): `window_bounds` / `move_cursor` / `real_click`, with a
254
+ foreground + in-window-bounds double guard
255
+ - [ ] WebDriver BiDi engine — non-Floorp Firefox forks + JS `evaluate` + element-relative native input
256
+ - [ ] macOS / Linux native-input backends
257
+ - [ ] JS `evaluate` (available in newer Floorp builds; older ones return HTTP 404)
258
+ - [ ] Optional bearer-token auth
259
+ - [ ] Support for other Firefox-based browsers (WebDriver BiDi fallback)
260
+
261
+ ## Acknowledgements
262
+
263
+ Built against the automation API exposed by Floorp. The official
264
+ [`Floorp-Projects/floorp-mcp-server`](https://github.com/Floorp-Projects/floorp-mcp-server)
265
+ was a useful reference for mapping the endpoint surface. This is an independent,
266
+ clean-room MIT-licensed implementation.
267
+
268
+ ## License
269
+
270
+ [MIT](./LICENSE) © Arda Karaman
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Thin HTTP client for Floorp's built-in automation API.
3
+ *
4
+ * Floorp exposes this API on http://127.0.0.1:58261 once `floorp.mcp.enabled`
5
+ * is set to `true` in about:config. The model is instance-based: to operate on
6
+ * a tab you first obtain an `instanceId` (by attaching to an existing tab or by
7
+ * creating a new one), then issue per-instance commands.
8
+ *
9
+ * Lifecycle (verified against a live Floorp):
10
+ * - attach(browserId) -> ephemeral handle to an EXISTING tab
11
+ * - createTab(url) -> opens a NEW tab, returns a handle
12
+ * - detach(instanceId) [DELETE] -> releases the handle, tab stays open
13
+ * - closeTab(instanceId) [close] -> actually closes the tab
14
+ */
15
+ export interface TabInfo {
16
+ browserId: string;
17
+ windowId: string;
18
+ title: string;
19
+ url: string;
20
+ selected: boolean;
21
+ pinned: boolean;
22
+ }
23
+ export interface CreateTabOptions {
24
+ background?: boolean;
25
+ waitForLoad?: boolean;
26
+ }
27
+ export type TextMode = "full" | "scoped" | "visible";
28
+ export type ElementState = "attached" | "visible" | "hidden" | "detached";
29
+ export interface EvaluateResult {
30
+ success: boolean;
31
+ result?: unknown;
32
+ resultType?: string;
33
+ error?: string;
34
+ errorType?: string;
35
+ }
36
+ export declare class FloorpClient {
37
+ private readonly baseUrl;
38
+ private readonly token;
39
+ constructor(port?: number, token?: string);
40
+ private request;
41
+ private static stripImagePrefix;
42
+ health(): Promise<boolean>;
43
+ listTabs(): Promise<TabInfo[]>;
44
+ /** The currently selected tab. Throws if none is reported. */
45
+ activeTab(): Promise<TabInfo>;
46
+ /** Open a NEW tab and return its instance handle. */
47
+ createTab(url: string, opts?: CreateTabOptions): Promise<string>;
48
+ /** Resolve the live browserId behind an instance handle. */
49
+ getInstanceBrowserId(instanceId: string): Promise<string | null>;
50
+ /** Attach an ephemeral handle to an EXISTING tab. */
51
+ attach(browserId: string): Promise<string | null>;
52
+ /** Release a handle WITHOUT closing the tab. */
53
+ detach(instanceId: string): Promise<void>;
54
+ /** Actually close the tab behind a handle. */
55
+ closeTab(instanceId: string): Promise<void>;
56
+ navigate(instanceId: string, url: string): Promise<void>;
57
+ getUri(instanceId: string): Promise<string | null>;
58
+ getTitle(instanceId: string): Promise<string | null>;
59
+ /** Page content as clean Markdown. */
60
+ getText(instanceId: string, mode?: TextMode): Promise<string>;
61
+ getHtml(instanceId: string, selector?: string): Promise<string>;
62
+ getAccessibilityTree(instanceId: string): Promise<unknown>;
63
+ /** Viewport screenshot as base64 PNG (no data-URL prefix). */
64
+ screenshot(instanceId: string): Promise<string | null>;
65
+ /** Full-page screenshot as base64 PNG (no data-URL prefix). */
66
+ fullPageScreenshot(instanceId: string): Promise<string | null>;
67
+ /** POST a per-instance action; treat an explicit `{ ok: false }` as failure. */
68
+ private action;
69
+ /** Scroll an element (by selector or fingerprint) into view. */
70
+ scrollTo(instanceId: string, selector?: string, fingerprint?: string): Promise<void>;
71
+ click(instanceId: string, selector?: string, opts?: {
72
+ button?: "left" | "right" | "middle";
73
+ clickCount?: number;
74
+ force?: boolean;
75
+ fingerprint?: string;
76
+ }): Promise<void>;
77
+ /**
78
+ * Structured page snapshot: clean Markdown text with inline fingerprint refs
79
+ * (`<!--fp:...-->`) plus an "Element Selector Map" (fp | tag | text). Lets an
80
+ * agent locate elements without grepping raw HTML, then act via a `ref`.
81
+ */
82
+ snapshot(instanceId: string, mode?: TextMode): Promise<string>;
83
+ /** Set the value of an input/textarea. */
84
+ input(instanceId: string, selector: string, value: string, opts?: {
85
+ typingMode?: boolean;
86
+ typingDelayMs?: number;
87
+ }): Promise<void>;
88
+ clearInput(instanceId: string, selector: string): Promise<void>;
89
+ /** Fill several fields at once: keys are CSS selectors, values are strings. */
90
+ fillForm(instanceId: string, formData: Record<string, string>): Promise<void>;
91
+ pressKey(instanceId: string, key: string): Promise<void>;
92
+ /**
93
+ * Insert text into a rich / contenteditable editor (Slate, ProseMirror, Lexical…)
94
+ * by dispatching a real text-input event. Use this when `input` fails because the
95
+ * element has no `.value` (i.e. it is not a plain <input>/<textarea>).
96
+ */
97
+ dispatchTextInput(instanceId: string, selector: string, text: string): Promise<void>;
98
+ /** Read the current value of an input/textarea/select. */
99
+ getValue(instanceId: string, selector: string): Promise<string | null>;
100
+ waitForElement(instanceId: string, selector: string, state?: ElementState, timeout?: number): Promise<boolean>;
101
+ /**
102
+ * Evaluate JavaScript in the page context (supports async/await).
103
+ * NOTE: not exposed in all Floorp builds — older ones return HTTP 404.
104
+ */
105
+ evaluate(instanceId: string, script: string): Promise<EvaluateResult>;
106
+ hover(instanceId: string, selector?: string, fingerprint?: string): Promise<void>;
107
+ doubleClick(instanceId: string, selector?: string, fingerprint?: string): Promise<void>;
108
+ rightClick(instanceId: string, selector?: string, fingerprint?: string): Promise<void>;
109
+ /** Choose an option in a <select> by value. */
110
+ selectOption(instanceId: string, selector: string, value: string): Promise<void>;
111
+ /** Check/uncheck a checkbox or radio. */
112
+ setChecked(instanceId: string, selector: string, checked: boolean): Promise<void>;
113
+ /** Submit a form (by a selector inside/of the form). */
114
+ submitForm(instanceId: string, selector?: string): Promise<void>;
115
+ /** Set a file input's file by absolute path. */
116
+ uploadFile(instanceId: string, selector: string, filePath: string): Promise<void>;
117
+ getAttribute(instanceId: string, name: string, selector?: string, fingerprint?: string): Promise<string | null>;
118
+ /** Readability-extracted main article (title, byline, markdown). */
119
+ getArticle(instanceId: string): Promise<{
120
+ title?: string;
121
+ byline?: string;
122
+ markdown?: string;
123
+ length?: number;
124
+ } | null>;
125
+ getCookies(instanceId: string): Promise<unknown[]>;
126
+ /** Wait until network activity settles (good after navigation/SPA loads). */
127
+ waitForNetworkIdle(instanceId: string, timeout?: number): Promise<boolean>;
128
+ listWorkspaces(): Promise<Array<{
129
+ id: string;
130
+ name: string;
131
+ }>>;
132
+ switchWorkspace(id: string): Promise<boolean>;
133
+ }