amalgm 0.0.0 → 0.0.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.
- package/README.md +37 -1
- package/bin/amalgm.js +8 -2
- package/lib/auth-store.js +223 -0
- package/lib/cli.js +1000 -0
- package/lib/paths.js +30 -0
- package/lib/supervisor.js +467 -0
- package/lib/tunnel-chat.js +328 -0
- package/lib/tunnel-events.js +499 -0
- package/package.json +29 -3
- package/runtime/README.md +4 -0
- package/runtime/lib/chatInput.js +306 -0
- package/runtime/lib/harnesses.js +988 -0
- package/runtime/lib/local/amalgmStore.js +128 -0
- package/runtime/lib/local/credentialResolver.js +425 -0
- package/runtime/lib/mcpApps/registry.js +619 -0
- package/runtime/package.json +5 -0
- package/runtime/scripts/amalgm-mcp/agents/rest.js +165 -0
- package/runtime/scripts/amalgm-mcp/agents/store.js +153 -0
- package/runtime/scripts/amalgm-mcp/agents/talk.js +1156 -0
- package/runtime/scripts/amalgm-mcp/agents/tools.js +210 -0
- package/runtime/scripts/amalgm-mcp/artifacts/advertise.js +132 -0
- package/runtime/scripts/amalgm-mcp/artifacts/rest.js +103 -0
- package/runtime/scripts/amalgm-mcp/artifacts/store.js +141 -0
- package/runtime/scripts/amalgm-mcp/artifacts/supervisor.js +402 -0
- package/runtime/scripts/amalgm-mcp/artifacts/tools.js +176 -0
- package/runtime/scripts/amalgm-mcp/browser/page.js +637 -0
- package/runtime/scripts/amalgm-mcp/browser/tools.js +688 -0
- package/runtime/scripts/amalgm-mcp/config.js +138 -0
- package/runtime/scripts/amalgm-mcp/credentials/rest.js +45 -0
- package/runtime/scripts/amalgm-mcp/deps.js +40 -0
- package/runtime/scripts/amalgm-mcp/email/inbound.js +215 -0
- package/runtime/scripts/amalgm-mcp/events/executor.js +179 -0
- package/runtime/scripts/amalgm-mcp/events/ingress.js +113 -0
- package/runtime/scripts/amalgm-mcp/events/matcher.js +125 -0
- package/runtime/scripts/amalgm-mcp/events/rest.js +200 -0
- package/runtime/scripts/amalgm-mcp/events/ring-buffer.js +19 -0
- package/runtime/scripts/amalgm-mcp/events/store.js +98 -0
- package/runtime/scripts/amalgm-mcp/events/tools.js +306 -0
- package/runtime/scripts/amalgm-mcp/events/webhook-url.js +28 -0
- package/runtime/scripts/amalgm-mcp/fs/rest.js +293 -0
- package/runtime/scripts/amalgm-mcp/index.js +100 -0
- package/runtime/scripts/amalgm-mcp/lib/chat-runner.js +167 -0
- package/runtime/scripts/amalgm-mcp/lib/email-md.js +288 -0
- package/runtime/scripts/amalgm-mcp/lib/mcp-resolver.js +63 -0
- package/runtime/scripts/amalgm-mcp/lib/prefs.js +393 -0
- package/runtime/scripts/amalgm-mcp/lib/storage.js +92 -0
- package/runtime/scripts/amalgm-mcp/lib/supabase.js +118 -0
- package/runtime/scripts/amalgm-mcp/lib/tool-result.js +177 -0
- package/runtime/scripts/amalgm-mcp/local/rest.js +80 -0
- package/runtime/scripts/amalgm-mcp/mcp-connections/rest.js +151 -0
- package/runtime/scripts/amalgm-mcp/notify/index.js +107 -0
- package/runtime/scripts/amalgm-mcp/server/http.js +335 -0
- package/runtime/scripts/amalgm-mcp/server/mcp.js +116 -0
- package/runtime/scripts/amalgm-mcp/slack/inbound.js +200 -0
- package/runtime/scripts/amalgm-mcp/tasks/executor.js +225 -0
- package/runtime/scripts/amalgm-mcp/tasks/rest.js +110 -0
- package/runtime/scripts/amalgm-mcp/tasks/schedule-normalization.js +85 -0
- package/runtime/scripts/amalgm-mcp/tasks/scheduler.js +105 -0
- package/runtime/scripts/amalgm-mcp/tasks/store.js +139 -0
- package/runtime/scripts/amalgm-mcp/tasks/tools.js +391 -0
- package/runtime/scripts/amalgm-mcp/user-api-keys/rest.js +105 -0
- package/runtime/scripts/amalgm-mcp/workspace/rest.js +389 -0
- package/runtime/scripts/chat-core/adapters/claude.js +163 -0
- package/runtime/scripts/chat-core/adapters/codex.js +313 -0
- package/runtime/scripts/chat-core/adapters/opencode.js +412 -0
- package/runtime/scripts/chat-core/auth.js +177 -0
- package/runtime/scripts/chat-core/contract.js +326 -0
- package/runtime/scripts/chat-core/credentials/store.js +212 -0
- package/runtime/scripts/chat-core/egress.js +87 -0
- package/runtime/scripts/chat-core/engine.js +195 -0
- package/runtime/scripts/chat-core/event-schema.js +231 -0
- package/runtime/scripts/chat-core/events.js +190 -0
- package/runtime/scripts/chat-core/index.js +11 -0
- package/runtime/scripts/chat-core/input.js +50 -0
- package/runtime/scripts/chat-core/normalizers/claude.js +450 -0
- package/runtime/scripts/chat-core/normalizers/codex.js +380 -0
- package/runtime/scripts/chat-core/normalizers/normalizer_spec.md +259 -0
- package/runtime/scripts/chat-core/normalizers/opencode.js +552 -0
- package/runtime/scripts/chat-core/normalizers/tool_contract.md +123 -0
- package/runtime/scripts/chat-core/normalizers/usage_contract.md +304 -0
- package/runtime/scripts/chat-core/parts.js +253 -0
- package/runtime/scripts/chat-core/recorder.js +65 -0
- package/runtime/scripts/chat-core/runtime.js +86 -0
- package/runtime/scripts/chat-core/server.js +163 -0
- package/runtime/scripts/chat-core/sse.js +196 -0
- package/runtime/scripts/chat-core/stores.js +100 -0
- package/runtime/scripts/chat-core/tool-display.js +149 -0
- package/runtime/scripts/chat-core/tool-shape.js +143 -0
- package/runtime/scripts/chat-core/tooling/mcp-bundle.js +161 -0
- package/runtime/scripts/chat-core/tooling/mcp-relay.js +97 -0
- package/runtime/scripts/chat-core/tooling/native-binaries.js +608 -0
- package/runtime/scripts/chat-core/tooling/system-prompt.js +20 -0
- package/runtime/scripts/chat-core/usage.js +343 -0
- package/runtime/scripts/chat-server/config.js +110 -0
- package/runtime/scripts/chat-server/db.js +529 -0
- package/runtime/scripts/chat-server/index.js +33 -0
- package/runtime/scripts/chat-server/model-catalog.js +327 -0
- package/runtime/scripts/chat-server.js +75 -0
- package/runtime/scripts/credential-adapter.js +129 -0
- package/runtime/scripts/fs-watcher.js +888 -0
- package/runtime/scripts/local-gateway.js +852 -0
- package/runtime/scripts/platform-context.txt +246 -0
- package/runtime/scripts/port-monitor.js +175 -0
- package/runtime/scripts/proxy-token-store.js +162 -0
- package/runtime/scripts/runtime-auth.js +163 -0
- package/runtime/scripts/test-claude-code-models.js +87 -0
- package/runtime/tsconfig.json +15 -0
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser MCP tools. In desktop local mode they drive Amalgm's visible
|
|
3
|
+
* Electron browser surface; elsewhere they fall back to Playwright.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { textResult, errorResult } = require('../lib/tool-result');
|
|
7
|
+
const {
|
|
8
|
+
clipboardBrowser,
|
|
9
|
+
closeBrowser,
|
|
10
|
+
clickBrowser,
|
|
11
|
+
cuaBrowser,
|
|
12
|
+
evaluateBrowser,
|
|
13
|
+
getTextBrowser,
|
|
14
|
+
locatorCountBrowser,
|
|
15
|
+
locatorTextBrowser,
|
|
16
|
+
navigateBrowser,
|
|
17
|
+
pressBrowser,
|
|
18
|
+
screenshotBrowser,
|
|
19
|
+
snapshotBrowser,
|
|
20
|
+
startVideoBrowser,
|
|
21
|
+
stateBrowser,
|
|
22
|
+
stopVideoBrowser,
|
|
23
|
+
tabNavigationBrowser,
|
|
24
|
+
tabsGetBrowser,
|
|
25
|
+
tabsListBrowser,
|
|
26
|
+
tabsNewBrowser,
|
|
27
|
+
tabsSelectBrowser,
|
|
28
|
+
tabsSelectedBrowser,
|
|
29
|
+
typeBrowser,
|
|
30
|
+
waitForLoadStateBrowser,
|
|
31
|
+
waitForSelectorBrowser,
|
|
32
|
+
waitForURLBrowser,
|
|
33
|
+
} = require('./page');
|
|
34
|
+
|
|
35
|
+
const tabTargetProperties = {
|
|
36
|
+
tabId: { type: 'string', description: 'Explicit browser tab id returned by browser_tabs_* or browser_navigate' },
|
|
37
|
+
browserSessionId: { type: 'string', description: 'Stable browser automation session id' },
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const locatorProperties = {
|
|
41
|
+
...tabTargetProperties,
|
|
42
|
+
selector: { type: 'string', description: 'CSS selector for the target element' },
|
|
43
|
+
text: { type: 'string', description: 'Visible text to match' },
|
|
44
|
+
role: { type: 'string', description: 'ARIA role, e.g. button, link, textbox' },
|
|
45
|
+
name: { type: 'string', description: 'Accessible name or visible name used with role/text matching' },
|
|
46
|
+
label: { type: 'string', description: 'Form label text' },
|
|
47
|
+
placeholder: { type: 'string', description: 'Input placeholder text' },
|
|
48
|
+
testId: { type: 'string', description: 'data-testid, data-test-id, or data-test value' },
|
|
49
|
+
exact: { type: 'boolean', description: 'Require exact text/name match' },
|
|
50
|
+
strict: { type: 'boolean', description: 'Require the locator to resolve to exactly one visible element' },
|
|
51
|
+
timeoutMs: { type: 'number', description: 'Maximum wait time in milliseconds' },
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
function jsonText(value) {
|
|
55
|
+
return textResult(JSON.stringify(value, null, 2));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function targetLine(result) {
|
|
59
|
+
const bits = [];
|
|
60
|
+
if (result?.tabId) bits.push(`Tab: ${result.tabId}`);
|
|
61
|
+
if (result?.browserSessionId) bits.push(`Session: ${result.browserSessionId}`);
|
|
62
|
+
return bits.length ? `\n${bits.join('\n')}` : '';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function requireFiniteCoordinate(value, name) {
|
|
66
|
+
const number = Number(value);
|
|
67
|
+
if (!Number.isFinite(number)) throw new Error(`${name} must be a finite number`);
|
|
68
|
+
return number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = [
|
|
72
|
+
{
|
|
73
|
+
name: 'browser_tabs_list',
|
|
74
|
+
description: 'List visible Amalgm browser-use tabs/surfaces.',
|
|
75
|
+
inputSchema: { type: 'object', properties: {} },
|
|
76
|
+
async handler(_args, ctx) {
|
|
77
|
+
try {
|
|
78
|
+
return jsonText(await tabsListBrowser(ctx));
|
|
79
|
+
} catch (err) {
|
|
80
|
+
return errorResult(`List browser tabs failed: ${err.message}`);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'browser_tabs_selected',
|
|
86
|
+
description: 'Return the most recently used browser-use tab.',
|
|
87
|
+
inputSchema: { type: 'object', properties: {} },
|
|
88
|
+
async handler(_args, ctx) {
|
|
89
|
+
try {
|
|
90
|
+
return jsonText(await tabsSelectedBrowser(ctx));
|
|
91
|
+
} catch (err) {
|
|
92
|
+
return errorResult(`Get selected browser tab failed: ${err.message}`);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'browser_tabs_new',
|
|
98
|
+
description: 'Create a new visible browser-use tab/surface, optionally navigating it to a URL.',
|
|
99
|
+
inputSchema: {
|
|
100
|
+
type: 'object',
|
|
101
|
+
properties: {
|
|
102
|
+
url: { type: 'string', description: 'Optional URL to open' },
|
|
103
|
+
browserSessionId: { type: 'string', description: 'Optional session id; generated if omitted' },
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
async handler(args, ctx) {
|
|
107
|
+
try {
|
|
108
|
+
return jsonText(await tabsNewBrowser(args, ctx));
|
|
109
|
+
} catch (err) {
|
|
110
|
+
return errorResult(`Create browser tab failed: ${err.message}`);
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'browser_tabs_get',
|
|
116
|
+
description: 'Get state for a browser tab by tabId or browserSessionId.',
|
|
117
|
+
inputSchema: { type: 'object', properties: tabTargetProperties },
|
|
118
|
+
async handler(args, ctx) {
|
|
119
|
+
try {
|
|
120
|
+
return jsonText(await tabsGetBrowser(args, ctx));
|
|
121
|
+
} catch (err) {
|
|
122
|
+
return errorResult(`Get browser tab failed: ${err.message}`);
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: 'browser_tabs_select',
|
|
128
|
+
description: 'Select/focus a browser-use tab by tabId or browserSessionId.',
|
|
129
|
+
inputSchema: { type: 'object', properties: tabTargetProperties },
|
|
130
|
+
async handler(args, ctx) {
|
|
131
|
+
try {
|
|
132
|
+
return jsonText(await tabsSelectBrowser(args, ctx));
|
|
133
|
+
} catch (err) {
|
|
134
|
+
return errorResult(`Select browser tab failed: ${err.message}`);
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: 'browser_tabs_close',
|
|
140
|
+
description: 'Close a browser-use tab by tabId or browserSessionId.',
|
|
141
|
+
inputSchema: { type: 'object', properties: tabTargetProperties },
|
|
142
|
+
async handler(args, ctx) {
|
|
143
|
+
try {
|
|
144
|
+
await closeBrowser(args, ctx);
|
|
145
|
+
return textResult('Browser tab closed.');
|
|
146
|
+
} catch (err) {
|
|
147
|
+
return errorResult(`Close browser tab failed: ${err.message}`);
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: 'browser_state',
|
|
153
|
+
description: 'Return URL/title/loading state for the current or specified browser tab.',
|
|
154
|
+
inputSchema: { type: 'object', properties: tabTargetProperties },
|
|
155
|
+
async handler(args, ctx) {
|
|
156
|
+
try {
|
|
157
|
+
return jsonText(await stateBrowser(args, ctx));
|
|
158
|
+
} catch (err) {
|
|
159
|
+
return errorResult(`Browser state failed: ${err.message}`);
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: 'browser_navigate',
|
|
165
|
+
description: 'Navigate a visible browser tab to a URL. Returns tabId and browserSessionId for follow-up calls.',
|
|
166
|
+
inputSchema: {
|
|
167
|
+
type: 'object',
|
|
168
|
+
properties: {
|
|
169
|
+
...tabTargetProperties,
|
|
170
|
+
url: { type: 'string', description: 'The URL to navigate to' },
|
|
171
|
+
},
|
|
172
|
+
required: ['url'],
|
|
173
|
+
},
|
|
174
|
+
async handler(args, ctx) {
|
|
175
|
+
if (!args.url) return errorResult('url is required');
|
|
176
|
+
try {
|
|
177
|
+
const result = await navigateBrowser(args, ctx);
|
|
178
|
+
return textResult(`Navigated to ${result.url || args.url}\nStatus: ${result.status}\nTitle: ${result.title || ''}${targetLine(result)}`);
|
|
179
|
+
} catch (err) {
|
|
180
|
+
return errorResult(`Navigation failed: ${err.message}`);
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: 'browser_back',
|
|
186
|
+
description: 'Navigate the browser tab back in history.',
|
|
187
|
+
inputSchema: { type: 'object', properties: tabTargetProperties },
|
|
188
|
+
async handler(args, ctx) {
|
|
189
|
+
try {
|
|
190
|
+
return jsonText(await tabNavigationBrowser('back', args, ctx));
|
|
191
|
+
} catch (err) {
|
|
192
|
+
return errorResult(`Browser back failed: ${err.message}`);
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: 'browser_forward',
|
|
198
|
+
description: 'Navigate the browser tab forward in history.',
|
|
199
|
+
inputSchema: { type: 'object', properties: tabTargetProperties },
|
|
200
|
+
async handler(args, ctx) {
|
|
201
|
+
try {
|
|
202
|
+
return jsonText(await tabNavigationBrowser('forward', args, ctx));
|
|
203
|
+
} catch (err) {
|
|
204
|
+
return errorResult(`Browser forward failed: ${err.message}`);
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
name: 'browser_reload',
|
|
210
|
+
description: 'Reload the browser tab.',
|
|
211
|
+
inputSchema: { type: 'object', properties: tabTargetProperties },
|
|
212
|
+
async handler(args, ctx) {
|
|
213
|
+
try {
|
|
214
|
+
return jsonText(await tabNavigationBrowser('reload', args, ctx));
|
|
215
|
+
} catch (err) {
|
|
216
|
+
return errorResult(`Browser reload failed: ${err.message}`);
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: 'browser_screenshot',
|
|
222
|
+
description: 'Take a screenshot of the current or specified browser tab. Returns a base64-encoded PNG.',
|
|
223
|
+
inputSchema: {
|
|
224
|
+
type: 'object',
|
|
225
|
+
properties: {
|
|
226
|
+
...tabTargetProperties,
|
|
227
|
+
full_page: { type: 'boolean', description: 'Capture the full scrollable page (Playwright fallback only; Electron captures viewport)' },
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
async handler(args, ctx) {
|
|
231
|
+
try {
|
|
232
|
+
const capture = await screenshotBrowser({ ...args, fullPage: args.full_page || false }, ctx);
|
|
233
|
+
return {
|
|
234
|
+
content: [
|
|
235
|
+
{ type: 'image', data: capture.base64, mimeType: 'image/png' },
|
|
236
|
+
{ type: 'text', text: `Screenshot captured (${capture.bytes} bytes, ${capture.mode})${targetLine(capture)}` },
|
|
237
|
+
],
|
|
238
|
+
};
|
|
239
|
+
} catch (err) {
|
|
240
|
+
return errorResult(`Screenshot failed: ${err.message}`);
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: 'browser_snapshot',
|
|
246
|
+
description: 'Get a token-efficient DOM/text snapshot of the current or specified browser tab.',
|
|
247
|
+
inputSchema: { type: 'object', properties: tabTargetProperties },
|
|
248
|
+
async handler(args, ctx) {
|
|
249
|
+
try {
|
|
250
|
+
const snapshot = await snapshotBrowser(args, ctx);
|
|
251
|
+
let result = `Page: ${snapshot.title}\nURL: ${snapshot.url}${targetLine(snapshot)}\n\n`;
|
|
252
|
+
result += (snapshot.snapshotText || '(empty)').slice(0, 10_000);
|
|
253
|
+
return textResult(result);
|
|
254
|
+
} catch (err) {
|
|
255
|
+
return errorResult(`Snapshot failed: ${err.message}`);
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: 'browser_click',
|
|
261
|
+
description: 'Click an element by selector, text, role/name, label, placeholder, or testId.',
|
|
262
|
+
inputSchema: { type: 'object', properties: locatorProperties },
|
|
263
|
+
async handler(args, ctx) {
|
|
264
|
+
if (!args.selector && !args.text && !args.role && !args.label && !args.placeholder && !args.testId && !args.name) {
|
|
265
|
+
return errorResult('A selector, text, role, label, placeholder, testId, or name is required');
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
const result = await clickBrowser(args, ctx);
|
|
269
|
+
return textResult(`${result.description}${targetLine(result)}`);
|
|
270
|
+
} catch (err) {
|
|
271
|
+
return errorResult(`Click failed: ${err.message}`);
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: 'browser_type',
|
|
277
|
+
description: 'Type text into a focused element or a target matched by selector/role/label/placeholder/testId.',
|
|
278
|
+
inputSchema: {
|
|
279
|
+
type: 'object',
|
|
280
|
+
properties: {
|
|
281
|
+
...locatorProperties,
|
|
282
|
+
text: { type: 'string', description: 'The text to type' },
|
|
283
|
+
clear: { type: 'boolean', description: 'Clear the field before typing (default: true)' },
|
|
284
|
+
},
|
|
285
|
+
required: ['text'],
|
|
286
|
+
},
|
|
287
|
+
async handler(args, ctx) {
|
|
288
|
+
if (typeof args.text !== 'string') return errorResult('text is required');
|
|
289
|
+
try {
|
|
290
|
+
const result = await typeBrowser(args, ctx);
|
|
291
|
+
return textResult(`${result.description}${targetLine(result)}`);
|
|
292
|
+
} catch (err) {
|
|
293
|
+
return errorResult(`Type failed: ${err.message}`);
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: 'browser_press',
|
|
299
|
+
description: 'Press a key on a target element or the focused element.',
|
|
300
|
+
inputSchema: {
|
|
301
|
+
type: 'object',
|
|
302
|
+
properties: {
|
|
303
|
+
...locatorProperties,
|
|
304
|
+
key: { type: 'string', description: 'Key to press, e.g. Enter, Escape, A' },
|
|
305
|
+
},
|
|
306
|
+
required: ['key'],
|
|
307
|
+
},
|
|
308
|
+
async handler(args, ctx) {
|
|
309
|
+
try {
|
|
310
|
+
return jsonText(await pressBrowser(args, ctx));
|
|
311
|
+
} catch (err) {
|
|
312
|
+
return errorResult(`Press failed: ${err.message}`);
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
name: 'browser_get_text',
|
|
318
|
+
description: 'Extract text content from the current page or a CSS selector.',
|
|
319
|
+
inputSchema: {
|
|
320
|
+
type: 'object',
|
|
321
|
+
properties: {
|
|
322
|
+
...tabTargetProperties,
|
|
323
|
+
selector: { type: 'string', description: 'CSS selector to extract (omit for full page)' },
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
async handler(args, ctx) {
|
|
327
|
+
try {
|
|
328
|
+
let text = (await getTextBrowser(args, ctx)).text;
|
|
329
|
+
if (text && text.length > 15_000) {
|
|
330
|
+
text = `${text.slice(0, 15_000)}\n\n... (truncated, use a selector for specific content)`;
|
|
331
|
+
}
|
|
332
|
+
return textResult(text || '(empty)');
|
|
333
|
+
} catch (err) {
|
|
334
|
+
return errorResult(`Get text failed: ${err.message}`);
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
name: 'browser_locator_count',
|
|
340
|
+
description: 'Count visible elements matching a locator.',
|
|
341
|
+
inputSchema: { type: 'object', properties: locatorProperties },
|
|
342
|
+
async handler(args, ctx) {
|
|
343
|
+
try {
|
|
344
|
+
return jsonText(await locatorCountBrowser(args, ctx));
|
|
345
|
+
} catch (err) {
|
|
346
|
+
return errorResult(`Locator count failed: ${err.message}`);
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: 'browser_locator_text',
|
|
352
|
+
description: 'Return visible text for the first element matching a locator, plus match count.',
|
|
353
|
+
inputSchema: { type: 'object', properties: locatorProperties },
|
|
354
|
+
async handler(args, ctx) {
|
|
355
|
+
try {
|
|
356
|
+
return jsonText(await locatorTextBrowser(args, ctx));
|
|
357
|
+
} catch (err) {
|
|
358
|
+
return errorResult(`Locator text failed: ${err.message}`);
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
name: 'browser_evaluate',
|
|
364
|
+
description: 'Execute JavaScript in the browser page context.',
|
|
365
|
+
inputSchema: {
|
|
366
|
+
type: 'object',
|
|
367
|
+
properties: {
|
|
368
|
+
...tabTargetProperties,
|
|
369
|
+
script: { type: 'string' },
|
|
370
|
+
},
|
|
371
|
+
required: ['script'],
|
|
372
|
+
},
|
|
373
|
+
async handler(args, ctx) {
|
|
374
|
+
if (!args.script) return errorResult('script is required');
|
|
375
|
+
try {
|
|
376
|
+
const { result } = await evaluateBrowser(args, ctx);
|
|
377
|
+
const output = typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result);
|
|
378
|
+
return textResult(output);
|
|
379
|
+
} catch (err) {
|
|
380
|
+
return errorResult(`Evaluate failed: ${err.message}`);
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
name: 'browser_wait_for_load_state',
|
|
386
|
+
description: 'Wait for the browser tab to reach load, domcontentloaded, or networkidle.',
|
|
387
|
+
inputSchema: {
|
|
388
|
+
type: 'object',
|
|
389
|
+
properties: {
|
|
390
|
+
...tabTargetProperties,
|
|
391
|
+
state: { type: 'string', enum: ['load', 'domcontentloaded', 'networkidle'] },
|
|
392
|
+
timeoutMs: { type: 'number' },
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
async handler(args, ctx) {
|
|
396
|
+
try {
|
|
397
|
+
return jsonText(await waitForLoadStateBrowser(args, ctx));
|
|
398
|
+
} catch (err) {
|
|
399
|
+
return errorResult(`Wait for load state failed: ${err.message}`);
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
name: 'browser_wait_for_url',
|
|
405
|
+
description: 'Wait for the browser tab URL to match a string. Supports * wildcards in Electron mode.',
|
|
406
|
+
inputSchema: {
|
|
407
|
+
type: 'object',
|
|
408
|
+
properties: {
|
|
409
|
+
...tabTargetProperties,
|
|
410
|
+
url: { type: 'string' },
|
|
411
|
+
waitUntil: { type: 'string', enum: ['commit', 'load', 'domcontentloaded', 'networkidle'] },
|
|
412
|
+
timeoutMs: { type: 'number' },
|
|
413
|
+
},
|
|
414
|
+
required: ['url'],
|
|
415
|
+
},
|
|
416
|
+
async handler(args, ctx) {
|
|
417
|
+
try {
|
|
418
|
+
return jsonText(await waitForURLBrowser(args, ctx));
|
|
419
|
+
} catch (err) {
|
|
420
|
+
return errorResult(`Wait for URL failed: ${err.message}`);
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
name: 'browser_wait_for_selector',
|
|
426
|
+
description: 'Wait for a CSS selector to become attached, detached, visible, or hidden.',
|
|
427
|
+
inputSchema: {
|
|
428
|
+
type: 'object',
|
|
429
|
+
properties: {
|
|
430
|
+
...tabTargetProperties,
|
|
431
|
+
selector: { type: 'string' },
|
|
432
|
+
state: { type: 'string', enum: ['attached', 'detached', 'visible', 'hidden'] },
|
|
433
|
+
timeoutMs: { type: 'number' },
|
|
434
|
+
},
|
|
435
|
+
required: ['selector'],
|
|
436
|
+
},
|
|
437
|
+
async handler(args, ctx) {
|
|
438
|
+
try {
|
|
439
|
+
return jsonText(await waitForSelectorBrowser(args, ctx));
|
|
440
|
+
} catch (err) {
|
|
441
|
+
return errorResult(`Wait for selector failed: ${err.message}`);
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
name: 'browser_cua_click',
|
|
447
|
+
description: 'Click viewport coordinates in the visible browser tab.',
|
|
448
|
+
inputSchema: {
|
|
449
|
+
type: 'object',
|
|
450
|
+
properties: {
|
|
451
|
+
...tabTargetProperties,
|
|
452
|
+
x: { type: 'number' },
|
|
453
|
+
y: { type: 'number' },
|
|
454
|
+
button: { type: 'number', description: '1 left, 2 middle, 3 right' },
|
|
455
|
+
keypress: { type: 'array', items: { type: 'string' } },
|
|
456
|
+
},
|
|
457
|
+
required: ['x', 'y'],
|
|
458
|
+
},
|
|
459
|
+
async handler(args, ctx) {
|
|
460
|
+
try {
|
|
461
|
+
return jsonText(await cuaBrowser('cua_click', { ...args, x: requireFiniteCoordinate(args.x, 'x'), y: requireFiniteCoordinate(args.y, 'y') }, ctx));
|
|
462
|
+
} catch (err) {
|
|
463
|
+
return errorResult(`CUA click failed: ${err.message}`);
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
name: 'browser_cua_double_click',
|
|
469
|
+
description: 'Double-click viewport coordinates in the visible browser tab.',
|
|
470
|
+
inputSchema: {
|
|
471
|
+
type: 'object',
|
|
472
|
+
properties: { ...tabTargetProperties, x: { type: 'number' }, y: { type: 'number' }, keypress: { type: 'array', items: { type: 'string' } } },
|
|
473
|
+
required: ['x', 'y'],
|
|
474
|
+
},
|
|
475
|
+
async handler(args, ctx) {
|
|
476
|
+
try {
|
|
477
|
+
return jsonText(await cuaBrowser('cua_double_click', { ...args, x: requireFiniteCoordinate(args.x, 'x'), y: requireFiniteCoordinate(args.y, 'y') }, ctx));
|
|
478
|
+
} catch (err) {
|
|
479
|
+
return errorResult(`CUA double-click failed: ${err.message}`);
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
name: 'browser_cua_move',
|
|
485
|
+
description: 'Move the visible automation cursor to viewport coordinates.',
|
|
486
|
+
inputSchema: {
|
|
487
|
+
type: 'object',
|
|
488
|
+
properties: { ...tabTargetProperties, x: { type: 'number' }, y: { type: 'number' }, keys: { type: 'array', items: { type: 'string' } } },
|
|
489
|
+
required: ['x', 'y'],
|
|
490
|
+
},
|
|
491
|
+
async handler(args, ctx) {
|
|
492
|
+
try {
|
|
493
|
+
return jsonText(await cuaBrowser('cua_move', { ...args, x: requireFiniteCoordinate(args.x, 'x'), y: requireFiniteCoordinate(args.y, 'y') }, ctx));
|
|
494
|
+
} catch (err) {
|
|
495
|
+
return errorResult(`CUA move failed: ${err.message}`);
|
|
496
|
+
}
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
name: 'browser_cua_scroll',
|
|
501
|
+
description: 'Scroll the visible browser tab from viewport coordinates.',
|
|
502
|
+
inputSchema: {
|
|
503
|
+
type: 'object',
|
|
504
|
+
properties: {
|
|
505
|
+
...tabTargetProperties,
|
|
506
|
+
x: { type: 'number' },
|
|
507
|
+
y: { type: 'number' },
|
|
508
|
+
scrollX: { type: 'number' },
|
|
509
|
+
scrollY: { type: 'number' },
|
|
510
|
+
keypress: { type: 'array', items: { type: 'string' } },
|
|
511
|
+
},
|
|
512
|
+
required: ['x', 'y', 'scrollX', 'scrollY'],
|
|
513
|
+
},
|
|
514
|
+
async handler(args, ctx) {
|
|
515
|
+
try {
|
|
516
|
+
return jsonText(await cuaBrowser('cua_scroll', {
|
|
517
|
+
...args,
|
|
518
|
+
x: requireFiniteCoordinate(args.x, 'x'),
|
|
519
|
+
y: requireFiniteCoordinate(args.y, 'y'),
|
|
520
|
+
scrollX: Number(args.scrollX || 0),
|
|
521
|
+
scrollY: Number(args.scrollY || 0),
|
|
522
|
+
}, ctx));
|
|
523
|
+
} catch (err) {
|
|
524
|
+
return errorResult(`CUA scroll failed: ${err.message}`);
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
name: 'browser_cua_type',
|
|
530
|
+
description: 'Type text at the currently focused browser element.',
|
|
531
|
+
inputSchema: {
|
|
532
|
+
type: 'object',
|
|
533
|
+
properties: { ...tabTargetProperties, text: { type: 'string' } },
|
|
534
|
+
required: ['text'],
|
|
535
|
+
},
|
|
536
|
+
async handler(args, ctx) {
|
|
537
|
+
try {
|
|
538
|
+
return jsonText(await cuaBrowser('cua_type', args, ctx));
|
|
539
|
+
} catch (err) {
|
|
540
|
+
return errorResult(`CUA type failed: ${err.message}`);
|
|
541
|
+
}
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
name: 'browser_cua_keypress',
|
|
546
|
+
description: 'Press a key combination in the visible browser tab.',
|
|
547
|
+
inputSchema: {
|
|
548
|
+
type: 'object',
|
|
549
|
+
properties: { ...tabTargetProperties, keys: { type: 'array', items: { type: 'string' } } },
|
|
550
|
+
required: ['keys'],
|
|
551
|
+
},
|
|
552
|
+
async handler(args, ctx) {
|
|
553
|
+
try {
|
|
554
|
+
return jsonText(await cuaBrowser('cua_keypress', args, ctx));
|
|
555
|
+
} catch (err) {
|
|
556
|
+
return errorResult(`CUA keypress failed: ${err.message}`);
|
|
557
|
+
}
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
name: 'browser_cua_drag',
|
|
562
|
+
description: 'Drag through a list of viewport points.',
|
|
563
|
+
inputSchema: {
|
|
564
|
+
type: 'object',
|
|
565
|
+
properties: {
|
|
566
|
+
...tabTargetProperties,
|
|
567
|
+
path: { type: 'array', items: { type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' } }, required: ['x', 'y'] } },
|
|
568
|
+
keys: { type: 'array', items: { type: 'string' } },
|
|
569
|
+
},
|
|
570
|
+
required: ['path'],
|
|
571
|
+
},
|
|
572
|
+
async handler(args, ctx) {
|
|
573
|
+
try {
|
|
574
|
+
return jsonText(await cuaBrowser('cua_drag', args, ctx));
|
|
575
|
+
} catch (err) {
|
|
576
|
+
return errorResult(`CUA drag failed: ${err.message}`);
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
name: 'browser_cua_screenshot',
|
|
582
|
+
description: 'Capture the visible browser tab viewport.',
|
|
583
|
+
inputSchema: { type: 'object', properties: tabTargetProperties },
|
|
584
|
+
async handler(args, ctx) {
|
|
585
|
+
try {
|
|
586
|
+
const capture = await screenshotBrowser({ ...args, fullPage: false }, ctx);
|
|
587
|
+
return {
|
|
588
|
+
content: [
|
|
589
|
+
{ type: 'image', data: capture.base64, mimeType: 'image/png' },
|
|
590
|
+
{ type: 'text', text: `Visible browser screenshot captured (${capture.bytes} bytes)${targetLine(capture)}` },
|
|
591
|
+
],
|
|
592
|
+
};
|
|
593
|
+
} catch (err) {
|
|
594
|
+
return errorResult(`CUA screenshot failed: ${err.message}`);
|
|
595
|
+
}
|
|
596
|
+
},
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
name: 'browser_clipboard_read_text',
|
|
600
|
+
description: 'Read plain text from the browser page clipboard API.',
|
|
601
|
+
inputSchema: { type: 'object', properties: tabTargetProperties },
|
|
602
|
+
async handler(args, ctx) {
|
|
603
|
+
try {
|
|
604
|
+
return jsonText(await clipboardBrowser('clipboard_read_text', args, ctx));
|
|
605
|
+
} catch (err) {
|
|
606
|
+
return errorResult(`Clipboard read failed: ${err.message}`);
|
|
607
|
+
}
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
name: 'browser_clipboard_write_text',
|
|
612
|
+
description: 'Write plain text through the browser page clipboard API.',
|
|
613
|
+
inputSchema: {
|
|
614
|
+
type: 'object',
|
|
615
|
+
properties: { ...tabTargetProperties, text: { type: 'string' } },
|
|
616
|
+
required: ['text'],
|
|
617
|
+
},
|
|
618
|
+
async handler(args, ctx) {
|
|
619
|
+
try {
|
|
620
|
+
return jsonText(await clipboardBrowser('clipboard_write_text', args, ctx));
|
|
621
|
+
} catch (err) {
|
|
622
|
+
return errorResult(`Clipboard write failed: ${err.message}`);
|
|
623
|
+
}
|
|
624
|
+
},
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
name: 'browser_start_video',
|
|
628
|
+
description: 'Start recording the visible browser session to a local WebM video. Saved when browser_stop_video is called.',
|
|
629
|
+
inputSchema: {
|
|
630
|
+
type: 'object',
|
|
631
|
+
properties: {
|
|
632
|
+
...tabTargetProperties,
|
|
633
|
+
fps: { type: 'number', description: 'Recording frames per second (default: 12, max: 30)' },
|
|
634
|
+
session_id: { type: 'string', description: 'Deprecated alias for recording_id. Optional recording folder name under ~/.amalgm/browser-sessions' },
|
|
635
|
+
recording_id: { type: 'string', description: 'Optional recording folder name under ~/.amalgm/browser-sessions' },
|
|
636
|
+
},
|
|
637
|
+
},
|
|
638
|
+
async handler(args, ctx) {
|
|
639
|
+
try {
|
|
640
|
+
const result = await startVideoBrowser({
|
|
641
|
+
...args,
|
|
642
|
+
fps: args.fps,
|
|
643
|
+
sessionId: args.recording_id || args.session_id,
|
|
644
|
+
}, ctx);
|
|
645
|
+
return textResult(`Browser video recording started (${result.fps || 'default'} fps).${targetLine(result)}`);
|
|
646
|
+
} catch (err) {
|
|
647
|
+
return errorResult(`Start video failed: ${err.message}`);
|
|
648
|
+
}
|
|
649
|
+
},
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
name: 'browser_stop_video',
|
|
653
|
+
description: 'Stop browser video recording and save it under ~/.amalgm/browser-sessions/.',
|
|
654
|
+
inputSchema: { type: 'object', properties: tabTargetProperties },
|
|
655
|
+
async handler(args, ctx) {
|
|
656
|
+
try {
|
|
657
|
+
const result = await stopVideoBrowser(args, ctx);
|
|
658
|
+
if (!result.path) return textResult(`Browser video recording stopped.${targetLine(result)}`);
|
|
659
|
+
const urlLine = result.url ? `\nOpen: ${result.url}` : '';
|
|
660
|
+
return {
|
|
661
|
+
content: [
|
|
662
|
+
{ type: 'text', text: `Browser video saved: ${result.path}\nBytes: ${result.bytes || 0}${urlLine}${targetLine(result)}` },
|
|
663
|
+
],
|
|
664
|
+
metadata: {
|
|
665
|
+
browserVideo: result.browserVideo || {
|
|
666
|
+
path: result.path,
|
|
667
|
+
relativePath: result.relativePath,
|
|
668
|
+
url: result.url,
|
|
669
|
+
bytes: result.bytes,
|
|
670
|
+
mimeType: result.mimeType || 'video/webm',
|
|
671
|
+
},
|
|
672
|
+
},
|
|
673
|
+
};
|
|
674
|
+
} catch (err) {
|
|
675
|
+
return errorResult(`Stop video failed: ${err.message}`);
|
|
676
|
+
}
|
|
677
|
+
},
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
name: 'browser_close',
|
|
681
|
+
description: 'Close the current or specified browser tab/surface.',
|
|
682
|
+
inputSchema: { type: 'object', properties: tabTargetProperties },
|
|
683
|
+
async handler(args, ctx) {
|
|
684
|
+
await closeBrowser(args, ctx);
|
|
685
|
+
return textResult('Browser closed. A new instance will launch on the next browser_* call.');
|
|
686
|
+
},
|
|
687
|
+
},
|
|
688
|
+
];
|