@wingman-ai/gateway 0.3.0 → 0.3.1

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 (120) hide show
  1. package/README.md +8 -0
  2. package/dist/agent/config/agentConfig.cjs +12 -0
  3. package/dist/agent/config/agentConfig.d.ts +22 -0
  4. package/dist/agent/config/agentConfig.js +10 -1
  5. package/dist/agent/config/agentLoader.cjs +9 -0
  6. package/dist/agent/config/agentLoader.js +9 -0
  7. package/dist/agent/config/toolRegistry.cjs +17 -0
  8. package/dist/agent/config/toolRegistry.d.ts +15 -0
  9. package/dist/agent/config/toolRegistry.js +17 -0
  10. package/dist/agent/tests/agentConfig.test.cjs +6 -1
  11. package/dist/agent/tests/agentConfig.test.js +6 -1
  12. package/dist/agent/tests/browserControlHelpers.test.cjs +35 -0
  13. package/dist/agent/tests/browserControlHelpers.test.d.ts +1 -0
  14. package/dist/agent/tests/browserControlHelpers.test.js +29 -0
  15. package/dist/agent/tests/browserControlTool.test.cjs +2117 -0
  16. package/dist/agent/tests/browserControlTool.test.d.ts +1 -0
  17. package/dist/agent/tests/browserControlTool.test.js +2111 -0
  18. package/dist/agent/tests/toolRegistry.test.cjs +6 -0
  19. package/dist/agent/tests/toolRegistry.test.js +6 -0
  20. package/dist/agent/tools/browser_control.cjs +1282 -0
  21. package/dist/agent/tools/browser_control.d.ts +478 -0
  22. package/dist/agent/tools/browser_control.js +1242 -0
  23. package/dist/cli/commands/agent.cjs +16 -2
  24. package/dist/cli/commands/agent.js +16 -2
  25. package/dist/cli/commands/browser.cjs +603 -0
  26. package/dist/cli/commands/browser.d.ts +13 -0
  27. package/dist/cli/commands/browser.js +566 -0
  28. package/dist/cli/commands/gateway.cjs +18 -7
  29. package/dist/cli/commands/gateway.d.ts +5 -1
  30. package/dist/cli/commands/gateway.js +18 -7
  31. package/dist/cli/commands/init.cjs +134 -45
  32. package/dist/cli/commands/init.js +134 -45
  33. package/dist/cli/commands/skill.cjs +3 -2
  34. package/dist/cli/commands/skill.js +3 -2
  35. package/dist/cli/config/loader.cjs +15 -0
  36. package/dist/cli/config/loader.js +15 -0
  37. package/dist/cli/config/schema.cjs +51 -2
  38. package/dist/cli/config/schema.d.ts +49 -0
  39. package/dist/cli/config/schema.js +44 -1
  40. package/dist/cli/core/workspace.cjs +89 -0
  41. package/dist/cli/core/workspace.d.ts +1 -0
  42. package/dist/cli/core/workspace.js +55 -0
  43. package/dist/cli/index.cjs +53 -5
  44. package/dist/cli/index.js +53 -5
  45. package/dist/cli/types/browser.cjs +18 -0
  46. package/dist/cli/types/browser.d.ts +9 -0
  47. package/dist/cli/types/browser.js +0 -0
  48. package/dist/gateway/browserRelayServer.cjs +338 -0
  49. package/dist/gateway/browserRelayServer.d.ts +38 -0
  50. package/dist/gateway/browserRelayServer.js +301 -0
  51. package/dist/gateway/http/agents.cjs +22 -0
  52. package/dist/gateway/http/agents.js +22 -0
  53. package/dist/gateway/http/fs.cjs +57 -0
  54. package/dist/gateway/http/fs.js +58 -1
  55. package/dist/gateway/server.cjs +43 -6
  56. package/dist/gateway/server.d.ts +4 -1
  57. package/dist/gateway/server.js +36 -5
  58. package/dist/gateway/transport/websocket.cjs +45 -10
  59. package/dist/gateway/transport/websocket.d.ts +1 -0
  60. package/dist/gateway/transport/websocket.js +41 -9
  61. package/dist/gateway/types.d.ts +4 -0
  62. package/dist/tests/agents-api.test.cjs +52 -0
  63. package/dist/tests/agents-api.test.js +53 -1
  64. package/dist/tests/browser-command.test.cjs +264 -0
  65. package/dist/tests/browser-command.test.d.ts +1 -0
  66. package/dist/tests/browser-command.test.js +258 -0
  67. package/dist/tests/browser-relay-server.test.cjs +20 -0
  68. package/dist/tests/browser-relay-server.test.d.ts +1 -0
  69. package/dist/tests/browser-relay-server.test.js +14 -0
  70. package/dist/tests/cli-config-loader.test.cjs +43 -0
  71. package/dist/tests/cli-config-loader.test.js +43 -0
  72. package/dist/tests/cli-init.test.cjs +25 -2
  73. package/dist/tests/cli-init.test.js +25 -2
  74. package/dist/tests/cli-workspace-root.test.cjs +114 -0
  75. package/dist/tests/cli-workspace-root.test.d.ts +1 -0
  76. package/dist/tests/cli-workspace-root.test.js +108 -0
  77. package/dist/tests/fs-api.test.cjs +138 -0
  78. package/dist/tests/fs-api.test.d.ts +1 -0
  79. package/dist/tests/fs-api.test.js +132 -0
  80. package/dist/tests/gateway-command-workspace.test.cjs +150 -0
  81. package/dist/tests/gateway-command-workspace.test.d.ts +1 -0
  82. package/dist/tests/gateway-command-workspace.test.js +144 -0
  83. package/dist/tests/gateway-request-execution-overrides.test.cjs +42 -0
  84. package/dist/tests/gateway-request-execution-overrides.test.d.ts +1 -0
  85. package/dist/tests/gateway-request-execution-overrides.test.js +36 -0
  86. package/dist/tests/gateway.test.cjs +31 -0
  87. package/dist/tests/gateway.test.js +31 -0
  88. package/dist/tests/websocket-transport.test.cjs +31 -0
  89. package/dist/tests/websocket-transport.test.d.ts +1 -0
  90. package/dist/tests/websocket-transport.test.js +25 -0
  91. package/dist/webui/assets/index-BW9nM0J2.css +11 -0
  92. package/dist/webui/assets/{index-0nUBsUUq.js → index-C8-oboEC.js} +107 -107
  93. package/dist/webui/index.html +2 -2
  94. package/extensions/wingman-browser-extension/README.md +27 -0
  95. package/extensions/wingman-browser-extension/background.js +416 -0
  96. package/extensions/wingman-browser-extension/manifest.json +19 -0
  97. package/extensions/wingman-browser-extension/options.html +156 -0
  98. package/extensions/wingman-browser-extension/options.js +106 -0
  99. package/package.json +8 -6
  100. package/{.wingman → templates}/agents/README.md +2 -1
  101. package/{.wingman → templates}/agents/coding/agent.md +0 -1
  102. package/{.wingman → templates}/agents/coding-v2/agent.md +0 -1
  103. package/{.wingman → templates}/agents/game-dev/agent.md +8 -1
  104. package/{.wingman → templates}/agents/game-dev/art-generation.md +1 -0
  105. package/{.wingman → templates}/agents/main/agent.md +5 -0
  106. package/{.wingman → templates}/agents/researcher/agent.md +9 -0
  107. package/{.wingman → templates}/agents/stock-trader/agent.md +1 -0
  108. package/dist/webui/assets/index-kk7OrD-G.css +0 -11
  109. /package/{.wingman → templates}/agents/coding-v2/implementor.md +0 -0
  110. /package/{.wingman → templates}/agents/game-dev/asset-refinement.md +0 -0
  111. /package/{.wingman → templates}/agents/game-dev/planning-idea.md +0 -0
  112. /package/{.wingman → templates}/agents/game-dev/ui-specialist.md +0 -0
  113. /package/{.wingman → templates}/agents/stock-trader/chain-curator.md +0 -0
  114. /package/{.wingman → templates}/agents/stock-trader/goal-translator.md +0 -0
  115. /package/{.wingman → templates}/agents/stock-trader/guardrails-veto.md +0 -0
  116. /package/{.wingman → templates}/agents/stock-trader/path-planner.md +0 -0
  117. /package/{.wingman → templates}/agents/stock-trader/regime-analyst.md +0 -0
  118. /package/{.wingman → templates}/agents/stock-trader/risk.md +0 -0
  119. /package/{.wingman → templates}/agents/stock-trader/selection.md +0 -0
  120. /package/{.wingman → templates}/agents/stock-trader/strategy-composer.md +0 -0
@@ -12,8 +12,8 @@
12
12
  href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&family=Sora:wght@300;400;500;600;700&display=swap"
13
13
  rel="stylesheet"
14
14
  />
15
- <script type="module" crossorigin src="/assets/index-0nUBsUUq.js"></script>
16
- <link rel="stylesheet" crossorigin href="/assets/index-kk7OrD-G.css">
15
+ <script type="module" crossorigin src="/assets/index-C8-oboEC.js"></script>
16
+ <link rel="stylesheet" crossorigin href="/assets/index-BW9nM0J2.css">
17
17
  </head>
18
18
  <body>
19
19
  <div id="root"></div>
@@ -0,0 +1,27 @@
1
+ # Wingman Chrome Extension (Browser Relay)
2
+
3
+ This is Wingman's first-party browser relay extension.
4
+
5
+ ## What it does
6
+
7
+ - Lets you attach/detach the active tab with one click.
8
+ - Bridges Chrome DevTools events/commands between the tab and Wingman's local relay endpoint.
9
+ - Exposes a small options page to configure/test relay port connectivity.
10
+
11
+ ## Install (developer mode)
12
+
13
+ 1. Register the bundled extension path:
14
+
15
+ ```bash
16
+ wingman browser extension install --default
17
+ wingman browser extension pair
18
+ wingman browser extension path wingman
19
+ ```
20
+
21
+ 2. In Chrome, open `chrome://extensions`, enable Developer mode, and load the unpacked folder.
22
+
23
+ ## Settings
24
+
25
+ - `relayPort` (stored in `chrome.storage.local`), default: `18792`.
26
+ - `relayToken` (stored in `chrome.storage.local`), generated by
27
+ `wingman browser extension pair`.
@@ -0,0 +1,416 @@
1
+ const DEFAULT_RELAY_PORT = 18792;
2
+ const RELAY_HOST = "127.0.0.1";
3
+ const CONNECT_TIMEOUT_MS = 5000;
4
+ const HEALTHCHECK_TIMEOUT_MS = 1500;
5
+
6
+ const BADGE = {
7
+ attached: { text: "WM", color: "#2563eb" },
8
+ connecting: { text: "..", color: "#d97706" },
9
+ error: { text: "!", color: "#dc2626" },
10
+ idle: { text: "", color: "#000000" },
11
+ };
12
+
13
+ let relaySocket = null;
14
+ let relayConnectPromise = null;
15
+
16
+ const attachedTabs = new Map();
17
+ const sessionToTab = new Map();
18
+
19
+ let debuggerListenersInstalled = false;
20
+
21
+ function clampPort(rawValue) {
22
+ const parsed = Number.parseInt(String(rawValue || ""), 10);
23
+ if (!Number.isFinite(parsed) || parsed <= 0 || parsed > 65535) {
24
+ return DEFAULT_RELAY_PORT;
25
+ }
26
+ return parsed;
27
+ }
28
+
29
+ async function getRelayPort() {
30
+ const { relayPort } = await chrome.storage.local.get(["relayPort"]);
31
+ return clampPort(relayPort);
32
+ }
33
+
34
+ async function getRelayToken() {
35
+ const { relayToken } = await chrome.storage.local.get(["relayToken"]);
36
+ if (typeof relayToken !== "string") return "";
37
+ return relayToken.trim();
38
+ }
39
+
40
+ function getRelayHttpBase(port) {
41
+ return `http://${RELAY_HOST}:${port}`;
42
+ }
43
+
44
+ function getRelayWsUrl(port, token) {
45
+ const query = token ? `?token=${encodeURIComponent(token)}` : "";
46
+ return `ws://${RELAY_HOST}:${port}/extension${query}`;
47
+ }
48
+
49
+ function setBadge(tabId, state) {
50
+ const config = BADGE[state] || BADGE.idle;
51
+ void chrome.action.setBadgeText({ tabId, text: config.text });
52
+ void chrome.action.setBadgeBackgroundColor({ tabId, color: config.color });
53
+ void chrome.action.setBadgeTextColor({ tabId, color: "#ffffff" }).catch(() =>
54
+ undefined,
55
+ );
56
+ }
57
+
58
+ function setActionTitle(tabId, title) {
59
+ void chrome.action.setTitle({ tabId, title }).catch(() => undefined);
60
+ }
61
+
62
+ async function checkRelayHealth(port) {
63
+ const controller = new AbortController();
64
+ const timeout = setTimeout(() => controller.abort(), HEALTHCHECK_TIMEOUT_MS);
65
+ try {
66
+ const response = await fetch(`${getRelayHttpBase(port)}/`, {
67
+ method: "HEAD",
68
+ signal: controller.signal,
69
+ });
70
+ if (!response.ok) {
71
+ throw new Error(`relay health check returned HTTP ${response.status}`);
72
+ }
73
+ } finally {
74
+ clearTimeout(timeout);
75
+ }
76
+ }
77
+
78
+ function sendRelay(payload) {
79
+ if (!relaySocket || relaySocket.readyState !== WebSocket.OPEN) {
80
+ throw new Error("relay socket is not connected");
81
+ }
82
+ relaySocket.send(JSON.stringify(payload));
83
+ }
84
+
85
+ function handleSocketClosed(reason) {
86
+ relaySocket = null;
87
+ console.debug("Wingman relay socket closed:", reason);
88
+
89
+ for (const tabId of attachedTabs.keys()) {
90
+ setBadge(tabId, "error");
91
+ setActionTitle(tabId, "Wingman Relay disconnected (click to reconnect)");
92
+ }
93
+ }
94
+
95
+ function installSocketHandlers(socket) {
96
+ socket.onmessage = (event) => {
97
+ void handleRelayMessage(String(event.data || ""));
98
+ };
99
+ socket.onclose = (event) => {
100
+ handleSocketClosed(`code=${event.code}`);
101
+ };
102
+ socket.onerror = () => {
103
+ handleSocketClosed("websocket error");
104
+ };
105
+ }
106
+
107
+ function ensureDebuggerListeners() {
108
+ if (debuggerListenersInstalled) {
109
+ return;
110
+ }
111
+ debuggerListenersInstalled = true;
112
+ chrome.debugger.onEvent.addListener(onDebuggerEvent);
113
+ chrome.debugger.onDetach.addListener(onDebuggerDetach);
114
+ }
115
+
116
+ async function ensureRelayConnection() {
117
+ if (relaySocket && relaySocket.readyState === WebSocket.OPEN) {
118
+ return;
119
+ }
120
+
121
+ if (relayConnectPromise) {
122
+ await relayConnectPromise;
123
+ return;
124
+ }
125
+
126
+ relayConnectPromise = (async () => {
127
+ const relayPort = await getRelayPort();
128
+ const relayToken = await getRelayToken();
129
+ if (!relayToken) {
130
+ throw new Error(
131
+ "Relay token is not configured. Run `wingman browser extension pair` and set the token in extension options.",
132
+ );
133
+ }
134
+ await checkRelayHealth(relayPort);
135
+
136
+ const wsUrl = getRelayWsUrl(relayPort, relayToken);
137
+ const socket = new WebSocket(wsUrl);
138
+
139
+ await new Promise((resolve, reject) => {
140
+ const timeout = setTimeout(() => {
141
+ reject(new Error("relay websocket connection timed out"));
142
+ }, CONNECT_TIMEOUT_MS);
143
+
144
+ socket.onopen = () => {
145
+ try {
146
+ socket.send(
147
+ JSON.stringify({
148
+ method: "hello",
149
+ params: {
150
+ token: relayToken,
151
+ clientType: "extension",
152
+ version: chrome.runtime.getManifest().version,
153
+ },
154
+ }),
155
+ );
156
+ } catch (error) {
157
+ clearTimeout(timeout);
158
+ reject(error);
159
+ }
160
+ };
161
+
162
+ socket.onmessage = (event) => {
163
+ let message;
164
+ try {
165
+ message = JSON.parse(String(event.data || ""));
166
+ } catch {
167
+ return;
168
+ }
169
+ if (message?.method === "hello_ack") {
170
+ clearTimeout(timeout);
171
+ resolve();
172
+ }
173
+ };
174
+
175
+ socket.onerror = () => {
176
+ clearTimeout(timeout);
177
+ reject(new Error("relay websocket connection failed"));
178
+ };
179
+
180
+ socket.onclose = (event) => {
181
+ clearTimeout(timeout);
182
+ reject(new Error(`relay websocket closed before open (code=${event.code})`));
183
+ };
184
+ });
185
+
186
+ relaySocket = socket;
187
+ installSocketHandlers(socket);
188
+ ensureDebuggerListeners();
189
+ })();
190
+
191
+ try {
192
+ await relayConnectPromise;
193
+ } finally {
194
+ relayConnectPromise = null;
195
+ }
196
+ }
197
+
198
+ function buildSessionId(tabId) {
199
+ return `wm-tab-${tabId}-${Date.now().toString(36)}`;
200
+ }
201
+
202
+ async function attachTab(tabId) {
203
+ if (attachedTabs.has(tabId)) {
204
+ return attachedTabs.get(tabId);
205
+ }
206
+
207
+ setBadge(tabId, "connecting");
208
+ setActionTitle(tabId, "Wingman Relay connecting...");
209
+
210
+ const debuggee = { tabId };
211
+ await chrome.debugger.attach(debuggee, "1.3");
212
+
213
+ try {
214
+ await chrome.debugger.sendCommand(debuggee, "Page.enable");
215
+ } catch {
216
+ // Some pages deny Page domain. Continue.
217
+ }
218
+
219
+ const info = await chrome.debugger.sendCommand(debuggee, "Target.getTargetInfo");
220
+ const targetId = String(info?.targetInfo?.targetId || "").trim();
221
+ if (!targetId) {
222
+ throw new Error("Target.getTargetInfo returned no targetId");
223
+ }
224
+
225
+ const sessionId = buildSessionId(tabId);
226
+ const tabState = { sessionId, targetId };
227
+ attachedTabs.set(tabId, tabState);
228
+ sessionToTab.set(sessionId, tabId);
229
+
230
+ sendRelay({
231
+ method: "forwardCDPEvent",
232
+ params: {
233
+ method: "Target.attachedToTarget",
234
+ params: {
235
+ sessionId,
236
+ targetInfo: {
237
+ targetId,
238
+ type: "page",
239
+ attached: true,
240
+ },
241
+ waitingForDebugger: false,
242
+ },
243
+ },
244
+ });
245
+
246
+ setBadge(tabId, "attached");
247
+ setActionTitle(tabId, "Wingman Relay attached (click to detach)");
248
+
249
+ return tabState;
250
+ }
251
+
252
+ async function detachTab(tabId, reason = "manual-detach") {
253
+ const state = attachedTabs.get(tabId);
254
+ if (!state) {
255
+ setBadge(tabId, "idle");
256
+ setActionTitle(tabId, "Wingman Relay (click to attach)");
257
+ return;
258
+ }
259
+
260
+ attachedTabs.delete(tabId);
261
+ sessionToTab.delete(state.sessionId);
262
+
263
+ try {
264
+ sendRelay({
265
+ method: "forwardCDPEvent",
266
+ params: {
267
+ method: "Target.detachedFromTarget",
268
+ params: {
269
+ sessionId: state.sessionId,
270
+ targetId: state.targetId,
271
+ reason,
272
+ },
273
+ },
274
+ });
275
+ } catch {
276
+ // Ignore relay disconnects during detach.
277
+ }
278
+
279
+ try {
280
+ await chrome.debugger.detach({ tabId });
281
+ } catch {
282
+ // Detach may fail if Chrome already detached.
283
+ }
284
+
285
+ setBadge(tabId, "idle");
286
+ setActionTitle(tabId, "Wingman Relay (click to attach)");
287
+ }
288
+
289
+ async function toggleTab(tabId) {
290
+ if (attachedTabs.has(tabId)) {
291
+ await detachTab(tabId, "toggle");
292
+ return;
293
+ }
294
+
295
+ try {
296
+ await ensureRelayConnection();
297
+ await attachTab(tabId);
298
+ } catch (error) {
299
+ setBadge(tabId, "error");
300
+ setActionTitle(tabId, "Wingman Relay unavailable (open extension options)");
301
+ await chrome.runtime.openOptionsPage().catch(() => undefined);
302
+ throw error;
303
+ }
304
+ }
305
+
306
+ async function forwardCdpCommand(command) {
307
+ const sessionId = String(command?.params?.sessionId || "").trim();
308
+ const method = String(command?.params?.method || "").trim();
309
+ const params = command?.params?.params || {};
310
+
311
+ if (!sessionId || !method) {
312
+ throw new Error("forwardCDPCommand missing sessionId or method");
313
+ }
314
+
315
+ const tabId = sessionToTab.get(sessionId);
316
+ if (!tabId) {
317
+ throw new Error(`unknown sessionId: ${sessionId}`);
318
+ }
319
+
320
+ return chrome.debugger.sendCommand({ tabId }, method, params);
321
+ }
322
+
323
+ async function handleRelayMessage(rawMessage) {
324
+ let message;
325
+ try {
326
+ message = JSON.parse(rawMessage);
327
+ } catch {
328
+ return;
329
+ }
330
+
331
+ if (message?.method === "ping") {
332
+ sendRelay({ method: "pong" });
333
+ return;
334
+ }
335
+
336
+ if (
337
+ typeof message?.id === "number" &&
338
+ message?.method === "forwardCDPCommand"
339
+ ) {
340
+ try {
341
+ const result = await forwardCdpCommand(message);
342
+ sendRelay({ id: message.id, result });
343
+ } catch (error) {
344
+ sendRelay({
345
+ id: message.id,
346
+ error: error instanceof Error ? error.message : String(error),
347
+ });
348
+ }
349
+ }
350
+ }
351
+
352
+ function sendDebuggerEventToRelay(tabId, method, params) {
353
+ const tabState = attachedTabs.get(tabId);
354
+ if (!tabState) {
355
+ return;
356
+ }
357
+
358
+ try {
359
+ sendRelay({
360
+ method: "forwardCDPEvent",
361
+ params: {
362
+ method,
363
+ params: {
364
+ sessionId: tabState.sessionId,
365
+ targetId: tabState.targetId,
366
+ ...params,
367
+ },
368
+ },
369
+ });
370
+ } catch {
371
+ // Relay may be disconnected. Ignore here.
372
+ }
373
+ }
374
+
375
+ function onDebuggerEvent(debuggee, method, params) {
376
+ const tabId = debuggee?.tabId;
377
+ if (typeof tabId !== "number") {
378
+ return;
379
+ }
380
+ sendDebuggerEventToRelay(tabId, method, params || {});
381
+ }
382
+
383
+ function onDebuggerDetach(debuggee, reason) {
384
+ const tabId = debuggee?.tabId;
385
+ if (typeof tabId !== "number") {
386
+ return;
387
+ }
388
+ void detachTab(tabId, reason || "debugger-detached");
389
+ }
390
+
391
+ chrome.action.onClicked.addListener((tab) => {
392
+ const tabId = tab?.id;
393
+ if (typeof tabId !== "number") {
394
+ return;
395
+ }
396
+ void toggleTab(tabId);
397
+ });
398
+
399
+ chrome.tabs.onRemoved.addListener((tabId) => {
400
+ if (!attachedTabs.has(tabId)) {
401
+ return;
402
+ }
403
+ void detachTab(tabId, "tab-closed");
404
+ });
405
+
406
+ chrome.runtime.onSuspend.addListener(() => {
407
+ for (const tabId of attachedTabs.keys()) {
408
+ void chrome.debugger.detach({ tabId }).catch(() => undefined);
409
+ }
410
+ });
411
+
412
+ chrome.runtime.onInstalled.addListener(async () => {
413
+ const relayPort = await getRelayPort();
414
+ const relayToken = await getRelayToken();
415
+ await chrome.storage.local.set({ relayPort, relayToken });
416
+ });
@@ -0,0 +1,19 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "Wingman Browser Relay",
4
+ "version": "0.2.0",
5
+ "description": "Connect active Chrome tabs to Wingman's local browser relay.",
6
+ "permissions": ["debugger", "tabs", "activeTab", "storage"],
7
+ "host_permissions": ["http://127.0.0.1/*", "http://localhost/*"],
8
+ "background": {
9
+ "service_worker": "background.js",
10
+ "type": "module"
11
+ },
12
+ "action": {
13
+ "default_title": "Wingman Relay (click to attach or detach)"
14
+ },
15
+ "options_ui": {
16
+ "page": "options.html",
17
+ "open_in_tab": true
18
+ }
19
+ }
@@ -0,0 +1,156 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Wingman Browser Relay Options</title>
7
+ <style>
8
+ :root {
9
+ color-scheme: light dark;
10
+ font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
11
+ }
12
+
13
+ body {
14
+ margin: 0;
15
+ padding: 24px;
16
+ background: linear-gradient(180deg, #f7f9fc, #eef3fb);
17
+ color: #0f172a;
18
+ }
19
+
20
+ @media (prefers-color-scheme: dark) {
21
+ body {
22
+ background: linear-gradient(180deg, #0f172a, #111827);
23
+ color: #e5e7eb;
24
+ }
25
+ }
26
+
27
+ .card {
28
+ max-width: 760px;
29
+ margin: 0 auto;
30
+ background: rgba(255, 255, 255, 0.9);
31
+ border: 1px solid rgba(15, 23, 42, 0.12);
32
+ border-radius: 14px;
33
+ padding: 20px;
34
+ box-shadow: 0 8px 30px rgba(15, 23, 42, 0.1);
35
+ }
36
+
37
+ @media (prefers-color-scheme: dark) {
38
+ .card {
39
+ background: rgba(17, 24, 39, 0.9);
40
+ border-color: rgba(229, 231, 235, 0.2);
41
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.35);
42
+ }
43
+ }
44
+
45
+ h1 {
46
+ margin-top: 0;
47
+ margin-bottom: 8px;
48
+ font-size: 22px;
49
+ }
50
+
51
+ p {
52
+ margin: 0 0 10px;
53
+ line-height: 1.45;
54
+ }
55
+
56
+ .group {
57
+ margin-top: 16px;
58
+ }
59
+
60
+ label {
61
+ display: block;
62
+ font-weight: 600;
63
+ margin-bottom: 6px;
64
+ }
65
+
66
+ input {
67
+ width: 160px;
68
+ padding: 8px 10px;
69
+ border-radius: 8px;
70
+ border: 1px solid rgba(15, 23, 42, 0.3);
71
+ background: #ffffff;
72
+ color: #0f172a;
73
+ }
74
+
75
+ @media (prefers-color-scheme: dark) {
76
+ input {
77
+ border-color: rgba(229, 231, 235, 0.3);
78
+ background: #111827;
79
+ color: #e5e7eb;
80
+ }
81
+ }
82
+
83
+ .buttons {
84
+ margin-top: 10px;
85
+ display: flex;
86
+ gap: 8px;
87
+ }
88
+
89
+ button {
90
+ border: 0;
91
+ border-radius: 8px;
92
+ padding: 8px 12px;
93
+ cursor: pointer;
94
+ font-weight: 600;
95
+ background: #2563eb;
96
+ color: white;
97
+ }
98
+
99
+ button.secondary {
100
+ background: #475569;
101
+ }
102
+
103
+ code {
104
+ font-family: ui-monospace, Menlo, Consolas, monospace;
105
+ }
106
+
107
+ #status {
108
+ margin-top: 12px;
109
+ min-height: 18px;
110
+ font-size: 13px;
111
+ }
112
+
113
+ #status[data-level="ok"] {
114
+ color: #15803d;
115
+ }
116
+
117
+ #status[data-level="error"] {
118
+ color: #dc2626;
119
+ }
120
+ </style>
121
+ </head>
122
+ <body>
123
+ <div class="card">
124
+ <h1>Wingman Browser Relay</h1>
125
+ <p>Click the extension button on a tab to attach or detach relay control.</p>
126
+ <p>
127
+ Docs: <a href="https://wingman.ai/docs/configuration/browser-automation" target="_blank" rel="noreferrer">wingman.ai/docs/configuration/browser-automation</a>
128
+ </p>
129
+
130
+ <div class="group">
131
+ <label for="relay-port">Relay Port</label>
132
+ <input id="relay-port" inputmode="numeric" pattern="[0-9]*" />
133
+ </div>
134
+
135
+ <div class="group">
136
+ <label for="relay-token">Relay Token</label>
137
+ <input id="relay-token" type="password" autocomplete="off" />
138
+ <p>
139
+ Generate with <code>wingman browser extension pair</code>, then paste
140
+ here.
141
+ </p>
142
+ <div class="buttons">
143
+ <button id="save" type="button">Save</button>
144
+ <button id="test" type="button" class="secondary">Test Connection</button>
145
+ </div>
146
+ </div>
147
+
148
+ <p class="group">
149
+ Relay URL: <code id="relay-url">http://127.0.0.1:18792/</code>
150
+ </p>
151
+ <p id="status"></p>
152
+ </div>
153
+
154
+ <script type="module" src="options.js"></script>
155
+ </body>
156
+ </html>