@aexol/spectral 0.8.6 → 0.8.9

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.
Files changed (85) hide show
  1. package/dist/auth-helper.d.ts +49 -0
  2. package/dist/auth-helper.d.ts.map +1 -0
  3. package/dist/auth-helper.js +191 -0
  4. package/dist/commands/login-oauth.d.ts +2 -9
  5. package/dist/commands/login-oauth.d.ts.map +1 -1
  6. package/dist/commands/login-oauth.js +10 -105
  7. package/dist/commands/serve.d.ts.map +1 -1
  8. package/dist/commands/serve.js +68 -1
  9. package/dist/designer/systems.d.ts.map +1 -1
  10. package/dist/designer/systems.js +5 -0
  11. package/dist/extensions/aexol-mcp.d.ts.map +1 -1
  12. package/dist/extensions/aexol-mcp.js +33 -2
  13. package/dist/extensions/browser/browser-service.d.ts +104 -0
  14. package/dist/extensions/browser/browser-service.d.ts.map +1 -0
  15. package/dist/extensions/browser/browser-service.js +333 -0
  16. package/dist/extensions/browser/config.d.ts +24 -0
  17. package/dist/extensions/browser/config.d.ts.map +1 -0
  18. package/dist/extensions/browser/config.js +17 -0
  19. package/dist/extensions/browser/index.d.ts +35 -0
  20. package/dist/extensions/browser/index.d.ts.map +1 -0
  21. package/dist/extensions/browser/index.js +126 -0
  22. package/dist/extensions/browser/stream/signaling-client.d.ts +28 -0
  23. package/dist/extensions/browser/stream/signaling-client.d.ts.map +1 -0
  24. package/dist/extensions/browser/stream/signaling-client.js +69 -0
  25. package/dist/extensions/browser/stream/stream-injector.d.ts +30 -0
  26. package/dist/extensions/browser/stream/stream-injector.d.ts.map +1 -0
  27. package/dist/extensions/browser/stream/stream-injector.js +161 -0
  28. package/dist/extensions/browser/stream/stream-tool.d.ts +10 -0
  29. package/dist/extensions/browser/stream/stream-tool.d.ts.map +1 -0
  30. package/dist/extensions/browser/stream/stream-tool.js +182 -0
  31. package/dist/extensions/browser/tools/console.d.ts +7 -0
  32. package/dist/extensions/browser/tools/console.d.ts.map +1 -0
  33. package/dist/extensions/browser/tools/console.js +54 -0
  34. package/dist/extensions/browser/tools/core.d.ts +7 -0
  35. package/dist/extensions/browser/tools/core.d.ts.map +1 -0
  36. package/dist/extensions/browser/tools/core.js +115 -0
  37. package/dist/extensions/browser/tools/dialog.d.ts +11 -0
  38. package/dist/extensions/browser/tools/dialog.d.ts.map +1 -0
  39. package/dist/extensions/browser/tools/dialog.js +54 -0
  40. package/dist/extensions/browser/tools/drop.d.ts +7 -0
  41. package/dist/extensions/browser/tools/drop.d.ts.map +1 -0
  42. package/dist/extensions/browser/tools/drop.js +93 -0
  43. package/dist/extensions/browser/tools/evaluate.d.ts +7 -0
  44. package/dist/extensions/browser/tools/evaluate.d.ts.map +1 -0
  45. package/dist/extensions/browser/tools/evaluate.js +105 -0
  46. package/dist/extensions/browser/tools/input.d.ts +7 -0
  47. package/dist/extensions/browser/tools/input.d.ts.map +1 -0
  48. package/dist/extensions/browser/tools/input.js +290 -0
  49. package/dist/extensions/browser/tools/network.d.ts +7 -0
  50. package/dist/extensions/browser/tools/network.d.ts.map +1 -0
  51. package/dist/extensions/browser/tools/network.js +116 -0
  52. package/dist/extensions/browser/tools/screenshot.d.ts +7 -0
  53. package/dist/extensions/browser/tools/screenshot.d.ts.map +1 -0
  54. package/dist/extensions/browser/tools/screenshot.js +153 -0
  55. package/dist/extensions/browser/tools/tabs.d.ts +7 -0
  56. package/dist/extensions/browser/tools/tabs.d.ts.map +1 -0
  57. package/dist/extensions/browser/tools/tabs.js +72 -0
  58. package/dist/extensions/browser/tools/wait.d.ts +7 -0
  59. package/dist/extensions/browser/tools/wait.d.ts.map +1 -0
  60. package/dist/extensions/browser/tools/wait.js +64 -0
  61. package/dist/extensions/kanban-bridge.d.ts.map +1 -1
  62. package/dist/extensions/kanban-bridge.js +70 -3
  63. package/dist/mcp/ui-server.d.ts.map +1 -1
  64. package/dist/mcp/ui-server.js +23 -10
  65. package/dist/relay/models-fetch.d.ts +1 -1
  66. package/dist/relay/models-fetch.js +4 -4
  67. package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts +60 -0
  68. package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts.map +1 -0
  69. package/dist/sdk/coding-agent/core/extensions/native-extensions.js +108 -0
  70. package/dist/sdk/coding-agent/core/resource-loader.d.ts.map +1 -1
  71. package/dist/sdk/coding-agent/core/resource-loader.js +23 -1
  72. package/dist/sdk/coding-agent/core/settings-manager.d.ts +5 -0
  73. package/dist/sdk/coding-agent/core/settings-manager.d.ts.map +1 -1
  74. package/dist/sdk/coding-agent/core/settings-manager.js +12 -0
  75. package/dist/server/agent-bridge.d.ts +2 -2
  76. package/dist/server/agent-bridge.d.ts.map +1 -1
  77. package/dist/server/agent-bridge.js +1 -1
  78. package/dist/server/session-stream.d.ts.map +1 -1
  79. package/dist/server/session-stream.js +24 -5
  80. package/dist/server/sqlite-adapter.d.ts +41 -0
  81. package/dist/server/sqlite-adapter.d.ts.map +1 -0
  82. package/dist/server/sqlite-adapter.js +128 -0
  83. package/dist/server/storage.d.ts.map +1 -1
  84. package/dist/server/storage.js +2 -2
  85. package/package.json +5 -2
@@ -0,0 +1,104 @@
1
+ /**
2
+ * BrowserService — singleton managing Playwright browser lifecycle.
3
+ *
4
+ * One browser instance, one persistent context, multiple pages (tabs).
5
+ * Collects console messages and network requests automatically.
6
+ * Follows the same singleton pattern as fetcher-mcp's BrowserService.
7
+ *
8
+ * Playwright is loaded lazily on first use (not at import time). This allows
9
+ * the module to load in standalone Bun binaries where playwright is unavailable.
10
+ * Tools will report a clear "Browser unavailable" error instead of crashing.
11
+ */
12
+ import type { Browser, BrowserContext, Page } from "playwright";
13
+ import type { BrowserConfig } from "./config.js";
14
+ export interface NetworkRequest {
15
+ index: number;
16
+ url: string;
17
+ method: string;
18
+ status: number;
19
+ statusText: string;
20
+ headers: Record<string, string>;
21
+ responseHeaders: Record<string, string>;
22
+ postData?: string;
23
+ body?: string;
24
+ timestamp: number;
25
+ }
26
+ export declare class BrowserService {
27
+ private browser;
28
+ private context;
29
+ private pages;
30
+ private activePageIndex;
31
+ private config;
32
+ private consoleBuffer;
33
+ private networkBuffer;
34
+ private nextNetworkIndex;
35
+ private pendingDialogs;
36
+ constructor(config?: Partial<BrowserConfig>);
37
+ /** Get or create the singleton instance. */
38
+ static getInstance(config?: Partial<BrowserConfig>): BrowserService;
39
+ /** Reset singleton (for testing). */
40
+ static resetInstance(): void;
41
+ /** Check whether Playwright is installed and loadable (static, does NOT start a browser). */
42
+ static isAvailable(): boolean;
43
+ /**
44
+ * Human-readable message explaining why the browser is unavailable.
45
+ * Returns null when the browser is available.
46
+ */
47
+ static unavailabilityReason(): string | null;
48
+ /** Ensure browser, context, and at least one page are ready. */
49
+ ensureBrowser(): Promise<{
50
+ browser: Browser;
51
+ context: BrowserContext;
52
+ page: Page;
53
+ }>;
54
+ /** Get the currently active page. */
55
+ getActivePage(): Page;
56
+ /** Navigate to a URL on the active page. */
57
+ navigate(url: string): Promise<{
58
+ title: string;
59
+ url: string;
60
+ }>;
61
+ /** Navigate back. */
62
+ navigateBack(): Promise<{
63
+ title: string;
64
+ url: string;
65
+ }>;
66
+ /** Close the active page. If last page, create a blank one. */
67
+ closePage(): Promise<void>;
68
+ /** Resize the viewport. */
69
+ resize(width: number, height: number): Promise<void>;
70
+ listTabs(): Array<{
71
+ index: number;
72
+ title: string;
73
+ url: string;
74
+ }>;
75
+ createTab(): Promise<{
76
+ index: number;
77
+ }>;
78
+ closeTab(index: number): Promise<void>;
79
+ selectTab(index: number): Promise<{
80
+ index: number;
81
+ }>;
82
+ private installDialogInterceptor;
83
+ getPendingDialogs(): Array<{
84
+ message: string;
85
+ type: string;
86
+ }>;
87
+ handleDialog(accept: boolean, promptText?: string): Promise<string>;
88
+ getConsoleMessages(level?: "error" | "warning" | "info" | "debug", all?: boolean): Array<{
89
+ type: string;
90
+ text: string;
91
+ pageUrl: string;
92
+ }>;
93
+ clearConsoleMessages(): void;
94
+ trackNetworkRequest(page: Page): void;
95
+ getNetworkRequests(): NetworkRequest[];
96
+ getNetworkRequest(index: number): NetworkRequest | undefined;
97
+ clearNetworkRequests(): void;
98
+ /** Close browser, context, and all pages. */
99
+ close(): Promise<void>;
100
+ /** Reset console/network buffers. */
101
+ resetBuffers(): void;
102
+ getConfig(): BrowserConfig;
103
+ }
104
+ //# sourceMappingURL=browser-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-service.d.ts","sourceRoot":"","sources":["../../../src/extensions/browser/browser-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,333 @@
1
+ /**
2
+ * BrowserService — singleton managing Playwright browser lifecycle.
3
+ *
4
+ * One browser instance, one persistent context, multiple pages (tabs).
5
+ * Collects console messages and network requests automatically.
6
+ * Follows the same singleton pattern as fetcher-mcp's BrowserService.
7
+ *
8
+ * Playwright is loaded lazily on first use (not at import time). This allows
9
+ * the module to load in standalone Bun binaries where playwright is unavailable.
10
+ * Tools will report a clear "Browser unavailable" error instead of crashing.
11
+ */
12
+ import { createRequire } from "node:module";
13
+ import { getBrowserConfig } from "./config.js";
14
+ const _require = createRequire(import.meta.url);
15
+ let _pw = null;
16
+ let _pwError = null;
17
+ function loadPlaywright() {
18
+ if (_pw)
19
+ return _pw;
20
+ if (_pwError)
21
+ throw new Error(_pwError);
22
+ try {
23
+ _pw = _require("playwright");
24
+ return _pw;
25
+ }
26
+ catch (err) {
27
+ const msg = err instanceof Error ? err.message : String(err);
28
+ _pwError = `Browser automation unavailable: playwright could not be loaded (${msg}).`;
29
+ throw new Error(_pwError);
30
+ }
31
+ }
32
+ let instance = null;
33
+ export class BrowserService {
34
+ browser = null;
35
+ context = null;
36
+ pages = [];
37
+ activePageIndex = 0;
38
+ config;
39
+ consoleBuffer = [];
40
+ networkBuffer = new Map();
41
+ nextNetworkIndex = 0;
42
+ pendingDialogs = [];
43
+ constructor(config) {
44
+ this.config = { ...getBrowserConfig(), ...config };
45
+ }
46
+ /** Get or create the singleton instance. */
47
+ static getInstance(config) {
48
+ if (!instance) {
49
+ instance = new BrowserService(config);
50
+ }
51
+ return instance;
52
+ }
53
+ /** Reset singleton (for testing). */
54
+ static resetInstance() {
55
+ instance = null;
56
+ }
57
+ /** Check whether Playwright is installed and loadable (static, does NOT start a browser). */
58
+ static isAvailable() {
59
+ try {
60
+ loadPlaywright();
61
+ return true;
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ }
67
+ /**
68
+ * Human-readable message explaining why the browser is unavailable.
69
+ * Returns null when the browser is available.
70
+ */
71
+ static unavailabilityReason() {
72
+ if (_pwError)
73
+ return _pwError;
74
+ if (_pw)
75
+ return null;
76
+ try {
77
+ loadPlaywright();
78
+ return null;
79
+ }
80
+ catch (err) {
81
+ return err instanceof Error ? err.message : String(err);
82
+ }
83
+ }
84
+ /** Ensure browser, context, and at least one page are ready. */
85
+ async ensureBrowser() {
86
+ if (this.browser?.isConnected() && this.context && this.pages.length > 0) {
87
+ const page = this.pages[this.activePageIndex];
88
+ return { browser: this.browser, context: this.context, page };
89
+ }
90
+ await this.close();
91
+ const pw = loadPlaywright();
92
+ const launchOptions = {
93
+ headless: this.config.headless,
94
+ args: [
95
+ "--no-sandbox",
96
+ "--disable-setuid-sandbox",
97
+ "--disable-dev-shm-usage",
98
+ ],
99
+ };
100
+ switch (this.config.browserName) {
101
+ case "firefox":
102
+ this.browser = await pw.firefox.launch(launchOptions);
103
+ break;
104
+ case "webkit":
105
+ this.browser = await pw.webkit.launch(launchOptions);
106
+ break;
107
+ default:
108
+ this.browser = await pw.chromium.launch(launchOptions);
109
+ }
110
+ this.context = await this.browser.newContext({
111
+ viewport: this.config.viewport,
112
+ });
113
+ // Auto-collect console messages and network requests
114
+ this.context.on("page", (page) => {
115
+ page.on("console", (msg) => {
116
+ this.consoleBuffer.push({
117
+ type: msg.type(),
118
+ text: msg.text(),
119
+ pageUrl: page.url(),
120
+ });
121
+ });
122
+ this.trackNetworkRequest(page);
123
+ this.installDialogInterceptor(page);
124
+ });
125
+ const page = await this.context.newPage();
126
+ this.trackNetworkRequest(page);
127
+ this.installDialogInterceptor(page);
128
+ this.pages = [page];
129
+ this.activePageIndex = 0;
130
+ return { browser: this.browser, context: this.context, page };
131
+ }
132
+ /** Get the currently active page. */
133
+ getActivePage() {
134
+ if (this.pages.length === 0) {
135
+ throw new Error("No browser pages available. Call ensureBrowser() first.");
136
+ }
137
+ return this.pages[this.activePageIndex];
138
+ }
139
+ /** Navigate to a URL on the active page. */
140
+ async navigate(url) {
141
+ const { page } = await this.ensureBrowser();
142
+ // Auto-prepend https if missing
143
+ const normalizedUrl = url.startsWith("http") ? url : `https://${url}`;
144
+ await page.goto(normalizedUrl, {
145
+ timeout: this.config.navigationTimeout,
146
+ waitUntil: "domcontentloaded",
147
+ });
148
+ return { title: await page.title(), url: page.url() };
149
+ }
150
+ /** Navigate back. */
151
+ async navigateBack() {
152
+ const page = this.getActivePage();
153
+ await page.goBack({ timeout: this.config.navigationTimeout });
154
+ return { title: await page.title(), url: page.url() };
155
+ }
156
+ /** Close the active page. If last page, create a blank one. */
157
+ async closePage() {
158
+ if (this.pages.length === 0)
159
+ return;
160
+ const page = this.pages[this.activePageIndex];
161
+ await page.close();
162
+ this.pages.splice(this.activePageIndex, 1);
163
+ if (this.pages.length === 0 && this.context) {
164
+ const newPage = await this.context.newPage();
165
+ this.pages = [newPage];
166
+ this.activePageIndex = 0;
167
+ }
168
+ else {
169
+ this.activePageIndex = Math.min(this.activePageIndex, this.pages.length - 1);
170
+ }
171
+ }
172
+ /** Resize the viewport. */
173
+ async resize(width, height) {
174
+ const page = this.getActivePage();
175
+ await page.setViewportSize({ width, height });
176
+ this.config.viewport = { width, height };
177
+ }
178
+ // --- Tab management ---
179
+ listTabs() {
180
+ return this.pages.map((p, i) => ({
181
+ index: i,
182
+ title: p.url() ? `Tab ${i}` : p.url(),
183
+ url: p.url(),
184
+ }));
185
+ }
186
+ async createTab() {
187
+ if (!this.context)
188
+ await this.ensureBrowser();
189
+ const page = await this.context.newPage();
190
+ this.trackNetworkRequest(page);
191
+ this.installDialogInterceptor(page);
192
+ this.pages.push(page);
193
+ this.activePageIndex = this.pages.length - 1;
194
+ return { index: this.activePageIndex };
195
+ }
196
+ async closeTab(index) {
197
+ if (this.pages.length <= 1)
198
+ return; // Keep at least one page
199
+ if (index < 0 || index >= this.pages.length)
200
+ return;
201
+ const page = this.pages[index];
202
+ await page.close();
203
+ this.pages.splice(index, 1);
204
+ if (this.activePageIndex >= this.pages.length) {
205
+ this.activePageIndex = this.pages.length - 1;
206
+ }
207
+ }
208
+ async selectTab(index) {
209
+ if (index < 0 || index >= this.pages.length) {
210
+ throw new Error(`Tab index ${index} out of range (0-${this.pages.length - 1})`);
211
+ }
212
+ this.activePageIndex = index;
213
+ const page = this.getActivePage();
214
+ await page.bringToFront();
215
+ return { index };
216
+ }
217
+ // --- Dialog interception ---
218
+ installDialogInterceptor(page) {
219
+ page.on("dialog", (dialog) => {
220
+ // Hold the dialog until agent handles it via browser_handle_dialog
221
+ this.pendingDialogs.push({
222
+ message: dialog.message(),
223
+ type: dialog.type(),
224
+ resolve: (text) => {
225
+ dialog.accept(text).catch(() => { });
226
+ },
227
+ reject: () => {
228
+ dialog.dismiss().catch(() => { });
229
+ },
230
+ });
231
+ });
232
+ }
233
+ getPendingDialogs() {
234
+ return this.pendingDialogs.map((d) => ({ message: d.message, type: d.type }));
235
+ }
236
+ async handleDialog(accept, promptText) {
237
+ if (this.pendingDialogs.length === 0) {
238
+ throw new Error("No pending dialogs.");
239
+ }
240
+ const dialog = this.pendingDialogs.shift();
241
+ if (accept) {
242
+ dialog.resolve(promptText);
243
+ }
244
+ else {
245
+ dialog.reject(new Error("Dialog dismissed"));
246
+ }
247
+ return dialog.message;
248
+ }
249
+ // --- Console messages ---
250
+ getConsoleMessages(level, all = false) {
251
+ const msgs = all ? [...this.consoleBuffer] : [...this.consoleBuffer];
252
+ if (level) {
253
+ const severity = { error: 0, warning: 1, info: 2, debug: 3 };
254
+ const threshold = severity[level];
255
+ return msgs.filter((m) => (severity[m.type] ?? 3) <= threshold);
256
+ }
257
+ return msgs;
258
+ }
259
+ clearConsoleMessages() {
260
+ this.consoleBuffer = [];
261
+ }
262
+ // --- Network requests ---
263
+ trackNetworkRequest(page) {
264
+ page.on("request", (request) => {
265
+ const index = this.nextNetworkIndex++;
266
+ const entry = {
267
+ index,
268
+ url: request.url(),
269
+ method: request.method(),
270
+ status: 0,
271
+ statusText: "",
272
+ headers: request.headers(),
273
+ responseHeaders: {},
274
+ postData: request.postData() ?? undefined,
275
+ timestamp: Date.now(),
276
+ };
277
+ this.networkBuffer.set(index, entry);
278
+ request.response().then((response) => {
279
+ if (!response)
280
+ return;
281
+ entry.status = response.status();
282
+ entry.statusText = response.statusText();
283
+ entry.responseHeaders = response.headers();
284
+ }).catch(() => {
285
+ // Request may fail or be aborted
286
+ });
287
+ });
288
+ }
289
+ getNetworkRequests() {
290
+ return Array.from(this.networkBuffer.values()).sort((a, b) => a.index - b.index);
291
+ }
292
+ getNetworkRequest(index) {
293
+ return this.networkBuffer.get(index);
294
+ }
295
+ clearNetworkRequests() {
296
+ this.networkBuffer.clear();
297
+ this.nextNetworkIndex = 0;
298
+ }
299
+ // --- Lifecycle ---
300
+ /** Close browser, context, and all pages. */
301
+ async close() {
302
+ for (const page of this.pages) {
303
+ try {
304
+ await page.close();
305
+ }
306
+ catch { /* ignore */ }
307
+ }
308
+ this.pages = [];
309
+ this.activePageIndex = 0;
310
+ if (this.context) {
311
+ try {
312
+ await this.context.close();
313
+ }
314
+ catch { /* ignore */ }
315
+ this.context = null;
316
+ }
317
+ if (this.browser) {
318
+ try {
319
+ await this.browser.close();
320
+ }
321
+ catch { /* ignore */ }
322
+ this.browser = null;
323
+ }
324
+ }
325
+ /** Reset console/network buffers. */
326
+ resetBuffers() {
327
+ this.clearConsoleMessages();
328
+ this.clearNetworkRequests();
329
+ }
330
+ getConfig() {
331
+ return { ...this.config };
332
+ }
333
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Browser extension configuration.
3
+ */
4
+ export interface BrowserConfig {
5
+ /** Default browser name to launch. */
6
+ browserName: "chromium" | "firefox" | "webkit";
7
+ /** Whether to run headless. */
8
+ headless: boolean;
9
+ /** Default viewport size. */
10
+ viewport: {
11
+ width: number;
12
+ height: number;
13
+ };
14
+ /** Default navigation timeout (ms). */
15
+ navigationTimeout: number;
16
+ /** Default action timeout (ms). */
17
+ actionTimeout: number;
18
+ /** Directory to save screenshots and output files. */
19
+ outputDir: string;
20
+ /** Whether to enable debug mode (keep browser visible, verbose logging). */
21
+ debug: boolean;
22
+ }
23
+ export declare function getBrowserConfig(): BrowserConfig;
24
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/extensions/browser/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,aAAa;IAC5B,sCAAsC;IACtC,WAAW,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC/C,+BAA+B;IAC/B,QAAQ,EAAE,OAAO,CAAC;IAClB,6BAA6B;IAC7B,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,uCAAuC;IACvC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mCAAmC;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,wBAAgB,gBAAgB,IAAI,aAAa,CAahD"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Browser extension configuration.
3
+ */
4
+ export function getBrowserConfig() {
5
+ return {
6
+ browserName: process.env.BROWSER_NAME ?? "chromium",
7
+ headless: process.env.BROWSER_HEADLESS === "true" ? true : false,
8
+ viewport: {
9
+ width: Number(process.env.BROWSER_VIEWPORT_WIDTH) || 1280,
10
+ height: Number(process.env.BROWSER_VIEWPORT_HEIGHT) || 720,
11
+ },
12
+ navigationTimeout: Number(process.env.BROWSER_NAVIGATION_TIMEOUT) || 60_000,
13
+ actionTimeout: Number(process.env.BROWSER_ACTION_TIMEOUT) || 5_000,
14
+ outputDir: process.env.BROWSER_OUTPUT_DIR ?? ".spectral/browser-output",
15
+ debug: process.env.BROWSER_DEBUG === "true" ? true : false,
16
+ };
17
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Browser Extension for spectral.
3
+ *
4
+ * Provides native browser automation tools via Playwright, registered as
5
+ * spectral extension tools (not MCP). This enables the coding agent to
6
+ * control a real browser: navigate, click, type, evaluate JS, take
7
+ * screenshots, inspect network traffic, and more.
8
+ *
9
+ * All 20 tools match the same API surface as playwright-mcp, but without
10
+ * the MCP protocol layer — they call Playwright directly from Node.js.
11
+ *
12
+ * Architecture:
13
+ * - BrowserService (singleton) — one browser, one context, multiple pages
14
+ * - Tools register via ext.registerTool() — same pattern as kanban-bridge
15
+ * - Browser auto-closes on session_shutdown
16
+ *
17
+ * Registered tools (20):
18
+ * Core: browser_navigate, browser_navigate_back, browser_close, browser_resize
19
+ * Tabs: browser_tabs
20
+ * Input: browser_click, browser_type, browser_press_key, browser_hover,
21
+ * browser_drag, browser_select_option, browser_fill_form, browser_file_upload
22
+ * Evaluate: browser_evaluate, browser_run_code_unsafe
23
+ * Screenshot: browser_take_screenshot, browser_snapshot
24
+ * Network: browser_network_requests, browser_network_request
25
+ * Console: browser_console_messages
26
+ * Dialog: browser_handle_dialog
27
+ * Wait: browser_wait_for
28
+ * Drop: browser_drop
29
+ *
30
+ * Usage:
31
+ * spectral --extension ./cli-node/src/extensions/browser/index.ts
32
+ */
33
+ import type { ExtensionAPI } from "../../sdk/coding-agent/index.js";
34
+ export default function browserExtension(ext: ExtensionAPI): Promise<void>;
35
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/browser/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAcpE,wBAA8B,gBAAgB,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA4D/E"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Browser Extension for spectral.
3
+ *
4
+ * Provides native browser automation tools via Playwright, registered as
5
+ * spectral extension tools (not MCP). This enables the coding agent to
6
+ * control a real browser: navigate, click, type, evaluate JS, take
7
+ * screenshots, inspect network traffic, and more.
8
+ *
9
+ * All 20 tools match the same API surface as playwright-mcp, but without
10
+ * the MCP protocol layer — they call Playwright directly from Node.js.
11
+ *
12
+ * Architecture:
13
+ * - BrowserService (singleton) — one browser, one context, multiple pages
14
+ * - Tools register via ext.registerTool() — same pattern as kanban-bridge
15
+ * - Browser auto-closes on session_shutdown
16
+ *
17
+ * Registered tools (20):
18
+ * Core: browser_navigate, browser_navigate_back, browser_close, browser_resize
19
+ * Tabs: browser_tabs
20
+ * Input: browser_click, browser_type, browser_press_key, browser_hover,
21
+ * browser_drag, browser_select_option, browser_fill_form, browser_file_upload
22
+ * Evaluate: browser_evaluate, browser_run_code_unsafe
23
+ * Screenshot: browser_take_screenshot, browser_snapshot
24
+ * Network: browser_network_requests, browser_network_request
25
+ * Console: browser_console_messages
26
+ * Dialog: browser_handle_dialog
27
+ * Wait: browser_wait_for
28
+ * Drop: browser_drop
29
+ *
30
+ * Usage:
31
+ * spectral --extension ./cli-node/src/extensions/browser/index.ts
32
+ */
33
+ import { BrowserService } from "./browser-service.js";
34
+ import { registerCoreTools } from "./tools/core.js";
35
+ import { registerTabsTools } from "./tools/tabs.js";
36
+ import { registerInputTools } from "./tools/input.js";
37
+ import { registerEvaluateTools } from "./tools/evaluate.js";
38
+ import { registerScreenshotTools } from "./tools/screenshot.js";
39
+ import { registerNetworkTools } from "./tools/network.js";
40
+ import { registerConsoleTools } from "./tools/console.js";
41
+ import { registerDialogTools } from "./tools/dialog.js";
42
+ import { registerWaitTools } from "./tools/wait.js";
43
+ import { registerDropTools } from "./tools/drop.js";
44
+ import { registerStreamTools } from "./stream/stream-tool.js";
45
+ export default async function browserExtension(ext) {
46
+ const available = BrowserService.isAvailable();
47
+ if (!available) {
48
+ const reason = BrowserService.unavailabilityReason() ?? "unknown reason";
49
+ process.stderr.write(`[browser] Browser automation unavailable: ${reason}\n` +
50
+ `[browser] Install Playwright to enable browser features: npx playwright install chromium\n`);
51
+ // Extension still loads cleanly — just logs the warning. Tools are not
52
+ // registered, so the agent won't see browser tools and won't attempt to use them.
53
+ return;
54
+ }
55
+ const bs = BrowserService.getInstance();
56
+ // Auto-close browser when the session ends
57
+ ext.on("session_shutdown", async () => {
58
+ try {
59
+ await bs.close();
60
+ process.stderr.write("[browser] Browser closed.\n");
61
+ }
62
+ catch (err) {
63
+ const msg = err instanceof Error ? err.message : String(err);
64
+ process.stderr.write(`[browser] Error closing browser: ${msg}\n`);
65
+ }
66
+ });
67
+ // Register all tool groups
68
+ const registrars = [
69
+ { name: "core", fn: () => registerCoreTools(ext, bs) },
70
+ { name: "tabs", fn: () => registerTabsTools(ext, bs) },
71
+ { name: "input", fn: () => registerInputTools(ext, bs) },
72
+ { name: "evaluate", fn: () => registerEvaluateTools(ext, bs) },
73
+ { name: "screenshot", fn: () => registerScreenshotTools(ext, bs) },
74
+ { name: "network", fn: () => registerNetworkTools(ext, bs) },
75
+ { name: "console", fn: () => registerConsoleTools(ext, bs) },
76
+ { name: "dialog", fn: () => registerDialogTools(ext, bs) },
77
+ { name: "wait", fn: () => registerWaitTools(ext, bs) },
78
+ { name: "drop", fn: () => registerDropTools(ext, bs) },
79
+ { name: "stream", fn: () => registerStreamTools(ext, bs) },
80
+ ];
81
+ let totalRegistered = 0;
82
+ for (const registrar of registrars) {
83
+ try {
84
+ const before = countRegisteredTools(ext);
85
+ registrar.fn();
86
+ const after = countRegisteredTools(ext);
87
+ totalRegistered += after - before;
88
+ }
89
+ catch (err) {
90
+ const msg = err instanceof Error ? err.message : String(err);
91
+ process.stderr.write(`[browser] Failed to register ${registrar.name} tools: ${msg}\n`);
92
+ }
93
+ }
94
+ process.stderr.write(`[browser] Registered ${totalRegistered} browser automation tool(s). ` +
95
+ `Browser: ${bs.getConfig().browserName}, ` +
96
+ `headless: ${bs.getConfig().headless}.\n`);
97
+ }
98
+ /** Count how many tools are registered (best-effort). */
99
+ function countRegisteredTools(ext) {
100
+ try {
101
+ // getToolDefinition is available on ExtensionAPI
102
+ let count = 0;
103
+ const knownTools = [
104
+ "browser_navigate", "browser_navigate_back", "browser_close", "browser_resize",
105
+ "browser_tabs",
106
+ "browser_click", "browser_type", "browser_press_key", "browser_hover",
107
+ "browser_drag", "browser_select_option", "browser_fill_form", "browser_file_upload",
108
+ "browser_evaluate", "browser_run_code_unsafe",
109
+ "browser_take_screenshot", "browser_snapshot",
110
+ "browser_network_requests", "browser_network_request",
111
+ "browser_console_messages",
112
+ "browser_handle_dialog",
113
+ "browser_wait_for",
114
+ "browser_drop",
115
+ "browser_stream_start", "browser_stream_stop", "browser_stream_status",
116
+ ];
117
+ for (const name of knownTools) {
118
+ if (ext.getToolDefinition(name))
119
+ count++;
120
+ }
121
+ return count;
122
+ }
123
+ catch {
124
+ return 0;
125
+ }
126
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Stream signaling client — connects to the backend WebSocket endpoint
3
+ * for WebRTC signaling relay.
4
+ *
5
+ * The backend only relays SDP offers/answers and ICE candidates between
6
+ * the streamer (this extension) and viewers. No media passes through.
7
+ */
8
+ export interface StreamSignalingConfig {
9
+ signalingUrl: string;
10
+ sessionId: string;
11
+ iceServers: Array<{
12
+ urls: string | string[];
13
+ username?: string;
14
+ credential?: string;
15
+ }>;
16
+ }
17
+ export declare class SignalingClient {
18
+ private readonly config;
19
+ private ws;
20
+ private connected;
21
+ constructor(config: StreamSignalingConfig);
22
+ connect(): Promise<void>;
23
+ private handleMessage;
24
+ send(data: Record<string, unknown>): void;
25
+ close(): void;
26
+ isConnected(): boolean;
27
+ }
28
+ //# sourceMappingURL=signaling-client.d.ts.map