@sveltium/mcp 0.1.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/README.md +146 -0
- package/package.json +50 -0
- package/scripts/postinstall.js +96 -0
- package/src/client/dom/console-capture.js +143 -0
- package/src/client/dom/element-finder.js +59 -0
- package/src/client/dom/event-dispatcher.js +180 -0
- package/src/client/dom/snapshot-builder.js +229 -0
- package/src/client/index.js +232 -0
- package/src/client/tool-executor.js +630 -0
- package/src/server/index.js +115 -0
- package/src/server/mcp-handler.js +722 -0
- package/src/server/ws-bridge.js +212 -0
- package/types/index.d.ts +58 -0
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tool Executor
|
|
5
|
+
*
|
|
6
|
+
* Executes MCP tools in the NW.js browser context
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
var SnapshotBuilder = require('./dom/snapshot-builder')
|
|
10
|
+
var ElementFinder = require('./dom/element-finder')
|
|
11
|
+
var EventDispatcher = require('./dom/event-dispatcher')
|
|
12
|
+
var ConsoleCapture = require('./dom/console-capture')
|
|
13
|
+
|
|
14
|
+
function ToolExecutor(win) {
|
|
15
|
+
this.win = win
|
|
16
|
+
this.doc = win.document
|
|
17
|
+
|
|
18
|
+
// Initialize DOM utilities
|
|
19
|
+
this.snapshot = new SnapshotBuilder(win)
|
|
20
|
+
this.finder = new ElementFinder(win)
|
|
21
|
+
this.events = new EventDispatcher(win)
|
|
22
|
+
this.console = new ConsoleCapture(win)
|
|
23
|
+
|
|
24
|
+
// Start console capture
|
|
25
|
+
this.console.start()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
ToolExecutor.prototype.execute = function(tool, args, callback) {
|
|
29
|
+
var self = this
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
switch (tool) {
|
|
33
|
+
case 'browser_snapshot':
|
|
34
|
+
this._snapshot(args, callback)
|
|
35
|
+
break
|
|
36
|
+
|
|
37
|
+
case 'browser_take_screenshot':
|
|
38
|
+
this._screenshot(args, callback)
|
|
39
|
+
break
|
|
40
|
+
|
|
41
|
+
case 'browser_click':
|
|
42
|
+
this._click(args, callback)
|
|
43
|
+
break
|
|
44
|
+
|
|
45
|
+
case 'browser_type':
|
|
46
|
+
this._type(args, callback)
|
|
47
|
+
break
|
|
48
|
+
|
|
49
|
+
case 'browser_evaluate':
|
|
50
|
+
this._evaluate(args, callback)
|
|
51
|
+
break
|
|
52
|
+
|
|
53
|
+
case 'browser_navigate':
|
|
54
|
+
this._navigate(args, callback)
|
|
55
|
+
break
|
|
56
|
+
|
|
57
|
+
case 'browser_console_messages':
|
|
58
|
+
this._consoleMessages(args, callback)
|
|
59
|
+
break
|
|
60
|
+
|
|
61
|
+
case 'browser_resize':
|
|
62
|
+
this._resize(args, callback)
|
|
63
|
+
break
|
|
64
|
+
|
|
65
|
+
case 'browser_wait_for':
|
|
66
|
+
this._waitFor(args, callback)
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
case 'browser_fill_form':
|
|
70
|
+
this._fillForm(args, callback)
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
case 'browser_press_key':
|
|
74
|
+
this._pressKey(args, callback)
|
|
75
|
+
break
|
|
76
|
+
|
|
77
|
+
case 'nwjs_reload':
|
|
78
|
+
this._reload(args, callback)
|
|
79
|
+
break
|
|
80
|
+
|
|
81
|
+
case 'nwjs_show_devtools':
|
|
82
|
+
this._showDevtools(args, callback)
|
|
83
|
+
break
|
|
84
|
+
|
|
85
|
+
case 'nwjs_close':
|
|
86
|
+
this._close(args, callback)
|
|
87
|
+
break
|
|
88
|
+
|
|
89
|
+
case 'nwjs_get_manifest':
|
|
90
|
+
this._getManifest(args, callback)
|
|
91
|
+
break
|
|
92
|
+
|
|
93
|
+
case 'nwjs_get_argv':
|
|
94
|
+
this._getArgv(args, callback)
|
|
95
|
+
break
|
|
96
|
+
|
|
97
|
+
case 'nwjs_minimize':
|
|
98
|
+
this._minimize(args, callback)
|
|
99
|
+
break
|
|
100
|
+
|
|
101
|
+
case 'nwjs_maximize':
|
|
102
|
+
this._maximize(args, callback)
|
|
103
|
+
break
|
|
104
|
+
|
|
105
|
+
case 'nwjs_restore':
|
|
106
|
+
this._restore(args, callback)
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
case 'nwjs_focus':
|
|
110
|
+
this._focus(args, callback)
|
|
111
|
+
break
|
|
112
|
+
|
|
113
|
+
case 'nwjs_get_bounds':
|
|
114
|
+
this._getBounds(args, callback)
|
|
115
|
+
break
|
|
116
|
+
|
|
117
|
+
case 'nwjs_set_bounds':
|
|
118
|
+
this._setBounds(args, callback)
|
|
119
|
+
break
|
|
120
|
+
|
|
121
|
+
case 'nwjs_zoom':
|
|
122
|
+
this._zoom(args, callback)
|
|
123
|
+
break
|
|
124
|
+
|
|
125
|
+
default:
|
|
126
|
+
callback(new Error('Unknown tool: ' + tool))
|
|
127
|
+
}
|
|
128
|
+
} catch (err) {
|
|
129
|
+
callback(err)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
ToolExecutor.prototype._snapshot = function(args, callback) {
|
|
134
|
+
var tree = this.snapshot.build()
|
|
135
|
+
callback(null, {
|
|
136
|
+
content: [{ type: 'text', text: tree }]
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
ToolExecutor.prototype._screenshot = function(args, callback) {
|
|
141
|
+
var self = this
|
|
142
|
+
|
|
143
|
+
// Try to get nw.gui
|
|
144
|
+
var nwGui = null
|
|
145
|
+
try {
|
|
146
|
+
nwGui = this.win.require('nw.gui')
|
|
147
|
+
} catch (e) {
|
|
148
|
+
nwGui = this.win.nw
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!nwGui || !nwGui.Window) {
|
|
152
|
+
callback(new Error('NW.js window API not available'))
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
var nwWin = nwGui.Window.get()
|
|
157
|
+
|
|
158
|
+
nwWin.capturePage(function(buffer) {
|
|
159
|
+
var base64 = buffer.toString('base64')
|
|
160
|
+
callback(null, {
|
|
161
|
+
content: [{ type: 'image', data: base64, mimeType: 'image/png' }]
|
|
162
|
+
})
|
|
163
|
+
}, { format: 'png', datatype: 'buffer' })
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
ToolExecutor.prototype._click = function(args, callback) {
|
|
167
|
+
var element = this.finder.findByRef(args.ref)
|
|
168
|
+
if (!element) {
|
|
169
|
+
callback(new Error('Element not found: ' + args.ref))
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
var button = args.button || 'left'
|
|
174
|
+
var doubleClick = args.doubleClick || false
|
|
175
|
+
|
|
176
|
+
this.events.click(element, button, doubleClick)
|
|
177
|
+
|
|
178
|
+
callback(null, {
|
|
179
|
+
content: [{ type: 'text', text: 'Clicked: ' + (args.element || args.ref) }]
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
ToolExecutor.prototype._type = function(args, callback) {
|
|
184
|
+
var element = this.finder.findByRef(args.ref)
|
|
185
|
+
if (!element) {
|
|
186
|
+
callback(new Error('Element not found: ' + args.ref))
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
var text = args.text || ''
|
|
191
|
+
var slowly = args.slowly || false
|
|
192
|
+
var submit = args.submit || false
|
|
193
|
+
|
|
194
|
+
this.events.type(element, text, slowly, submit)
|
|
195
|
+
|
|
196
|
+
callback(null, {
|
|
197
|
+
content: [{ type: 'text', text: 'Typed: ' + text }]
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
ToolExecutor.prototype._evaluate = function(args, callback) {
|
|
202
|
+
var code = args.function
|
|
203
|
+
var element = null
|
|
204
|
+
|
|
205
|
+
if (args.ref) {
|
|
206
|
+
element = this.finder.findByRef(args.ref)
|
|
207
|
+
if (!element) {
|
|
208
|
+
callback(new Error('Element not found: ' + args.ref))
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
var fn = null
|
|
214
|
+
try {
|
|
215
|
+
fn = this.win.eval('(' + code + ')')
|
|
216
|
+
} catch (e) {
|
|
217
|
+
callback(new Error('Failed to parse function: ' + e.message))
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
var result = null
|
|
222
|
+
try {
|
|
223
|
+
if (element) {
|
|
224
|
+
result = fn(element)
|
|
225
|
+
} else {
|
|
226
|
+
result = fn()
|
|
227
|
+
}
|
|
228
|
+
} catch (e) {
|
|
229
|
+
callback(new Error('Function execution failed: ' + e.message))
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Format result
|
|
234
|
+
var formatted = 'undefined'
|
|
235
|
+
if (result !== undefined) {
|
|
236
|
+
if (result === null) {
|
|
237
|
+
formatted = 'null'
|
|
238
|
+
} else if (typeof result === 'object') {
|
|
239
|
+
try {
|
|
240
|
+
formatted = JSON.stringify(result, null, 2)
|
|
241
|
+
} catch (e) {
|
|
242
|
+
formatted = String(result)
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
formatted = String(result)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
callback(null, {
|
|
250
|
+
content: [{ type: 'text', text: formatted }]
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
ToolExecutor.prototype._navigate = function(args, callback) {
|
|
255
|
+
this.win.location.href = args.url
|
|
256
|
+
callback(null, {
|
|
257
|
+
content: [{ type: 'text', text: 'Navigating to: ' + args.url }]
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
ToolExecutor.prototype._consoleMessages = function(args, callback) {
|
|
262
|
+
var level = args.level || 'info'
|
|
263
|
+
var messages = this.console.getMessages(level)
|
|
264
|
+
callback(null, {
|
|
265
|
+
content: [{ type: 'text', text: messages.join('\n') || '(no messages)' }]
|
|
266
|
+
})
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
ToolExecutor.prototype._resize = function(args, callback) {
|
|
270
|
+
var nwGui = null
|
|
271
|
+
try {
|
|
272
|
+
nwGui = this.win.require('nw.gui')
|
|
273
|
+
} catch (e) {
|
|
274
|
+
nwGui = this.win.nw
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (!nwGui || !nwGui.Window) {
|
|
278
|
+
callback(new Error('NW.js window API not available'))
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
var nwWin = nwGui.Window.get()
|
|
283
|
+
nwWin.resizeTo(args.width, args.height)
|
|
284
|
+
|
|
285
|
+
callback(null, {
|
|
286
|
+
content: [{ type: 'text', text: 'Resized to ' + args.width + 'x' + args.height }]
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
ToolExecutor.prototype._waitFor = function(args, callback) {
|
|
291
|
+
var self = this
|
|
292
|
+
|
|
293
|
+
if (args.time) {
|
|
294
|
+
setTimeout(function() {
|
|
295
|
+
callback(null, {
|
|
296
|
+
content: [{ type: 'text', text: 'Waited ' + args.time + ' seconds' }]
|
|
297
|
+
})
|
|
298
|
+
}, args.time * 1000)
|
|
299
|
+
return
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
var text = args.text
|
|
303
|
+
var textGone = args.textGone
|
|
304
|
+
var timeout = 10000
|
|
305
|
+
var interval = 100
|
|
306
|
+
var elapsed = 0
|
|
307
|
+
|
|
308
|
+
var check = function() {
|
|
309
|
+
var bodyText = self.doc.body ? self.doc.body.innerText : ''
|
|
310
|
+
|
|
311
|
+
if (text) {
|
|
312
|
+
if (bodyText.indexOf(text) !== -1) {
|
|
313
|
+
callback(null, {
|
|
314
|
+
content: [{ type: 'text', text: 'Found: ' + text }]
|
|
315
|
+
})
|
|
316
|
+
return
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (textGone) {
|
|
321
|
+
if (bodyText.indexOf(textGone) === -1) {
|
|
322
|
+
callback(null, {
|
|
323
|
+
content: [{ type: 'text', text: 'Gone: ' + textGone }]
|
|
324
|
+
})
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
elapsed += interval
|
|
330
|
+
if (elapsed >= timeout) {
|
|
331
|
+
callback(new Error('Timeout waiting for ' + (text || textGone)))
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
setTimeout(check, interval)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
check()
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
ToolExecutor.prototype._fillForm = function(args, callback) {
|
|
342
|
+
var self = this
|
|
343
|
+
var fields = args.fields || []
|
|
344
|
+
var filled = []
|
|
345
|
+
|
|
346
|
+
for (var i = 0; i < fields.length; i++) {
|
|
347
|
+
var field = fields[i]
|
|
348
|
+
var element = this.finder.findByRef(field.ref)
|
|
349
|
+
|
|
350
|
+
if (!element) {
|
|
351
|
+
callback(new Error('Field not found: ' + field.ref))
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (field.type === 'checkbox' || field.type === 'radio') {
|
|
356
|
+
element.checked = field.value === 'true'
|
|
357
|
+
} else if (field.type === 'combobox') {
|
|
358
|
+
element.value = field.value
|
|
359
|
+
this.events.dispatchInput(element)
|
|
360
|
+
} else {
|
|
361
|
+
element.value = field.value
|
|
362
|
+
this.events.dispatchInput(element)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
filled.push(field.name)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
callback(null, {
|
|
369
|
+
content: [{ type: 'text', text: 'Filled fields: ' + filled.join(', ') }]
|
|
370
|
+
})
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
ToolExecutor.prototype._pressKey = function(args, callback) {
|
|
374
|
+
this.events.pressKey(args.key)
|
|
375
|
+
callback(null, {
|
|
376
|
+
content: [{ type: 'text', text: 'Pressed: ' + args.key }]
|
|
377
|
+
})
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
ToolExecutor.prototype._reload = function(args, callback) {
|
|
381
|
+
var nwGui = this._getNwGui()
|
|
382
|
+
if (!nwGui) {
|
|
383
|
+
callback(new Error('NW.js API not available'))
|
|
384
|
+
return
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
var nwWin = nwGui.Window.get()
|
|
388
|
+
|
|
389
|
+
// Check if full relaunch is requested (restart the whole app)
|
|
390
|
+
if (args.relaunch) {
|
|
391
|
+
callback(null, {
|
|
392
|
+
content: [{ type: 'text', text: 'Relaunching app...' }]
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
// Relaunch using child_process.exec like XPCode does
|
|
396
|
+
setTimeout(function() {
|
|
397
|
+
var exec = require('child_process').exec
|
|
398
|
+
var execPath = process.execPath
|
|
399
|
+
|
|
400
|
+
// Quote the path and use "." for current app directory
|
|
401
|
+
var safeExecPath = '"' + execPath + '"'
|
|
402
|
+
var command = safeExecPath + ' .'
|
|
403
|
+
|
|
404
|
+
exec(command, { stdio: 'ignore', detached: true }, function() {})
|
|
405
|
+
|
|
406
|
+
// Close current window after short delay
|
|
407
|
+
setTimeout(function() {
|
|
408
|
+
nwWin.close()
|
|
409
|
+
}, 30)
|
|
410
|
+
}, 100)
|
|
411
|
+
return
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Just reload the window
|
|
415
|
+
if (args.ignoreCache) {
|
|
416
|
+
nwWin.reloadDev()
|
|
417
|
+
} else {
|
|
418
|
+
nwWin.reload()
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
callback(null, {
|
|
422
|
+
content: [{ type: 'text', text: 'Reloading app' + (args.ignoreCache ? ' (ignoring cache)' : '') }]
|
|
423
|
+
})
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
ToolExecutor.prototype._showDevtools = function(args, callback) {
|
|
427
|
+
var nwGui = this._getNwGui()
|
|
428
|
+
if (!nwGui) {
|
|
429
|
+
callback(new Error('NW.js API not available'))
|
|
430
|
+
return
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
var nwWin = nwGui.Window.get()
|
|
434
|
+
nwWin.showDevTools()
|
|
435
|
+
|
|
436
|
+
callback(null, {
|
|
437
|
+
content: [{ type: 'text', text: 'DevTools opened' }]
|
|
438
|
+
})
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
ToolExecutor.prototype._close = function(args, callback) {
|
|
442
|
+
var nwGui = this._getNwGui()
|
|
443
|
+
if (!nwGui) {
|
|
444
|
+
callback(new Error('NW.js API not available'))
|
|
445
|
+
return
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
callback(null, {
|
|
449
|
+
content: [{ type: 'text', text: 'Closing app' }]
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
// Close after sending response
|
|
453
|
+
var nwWin = nwGui.Window.get()
|
|
454
|
+
setTimeout(function() {
|
|
455
|
+
nwWin.close()
|
|
456
|
+
}, 100)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
ToolExecutor.prototype._getNwGui = function() {
|
|
460
|
+
try {
|
|
461
|
+
return this.win.require('nw.gui')
|
|
462
|
+
} catch (e) {
|
|
463
|
+
return this.win.nw
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
ToolExecutor.prototype._getManifest = function(args, callback) {
|
|
468
|
+
var nwGui = this._getNwGui()
|
|
469
|
+
if (!nwGui || !nwGui.App) {
|
|
470
|
+
callback(new Error('NW.js API not available'))
|
|
471
|
+
return
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
var manifest = nwGui.App.manifest || {}
|
|
475
|
+
var formatted = JSON.stringify(manifest, null, 2)
|
|
476
|
+
|
|
477
|
+
callback(null, {
|
|
478
|
+
content: [{ type: 'text', text: formatted }]
|
|
479
|
+
})
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
ToolExecutor.prototype._getArgv = function(args, callback) {
|
|
483
|
+
var nwGui = this._getNwGui()
|
|
484
|
+
if (!nwGui || !nwGui.App) {
|
|
485
|
+
callback(new Error('NW.js API not available'))
|
|
486
|
+
return
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
var result = {
|
|
490
|
+
argv: nwGui.App.argv || [],
|
|
491
|
+
fullArgv: nwGui.App.fullArgv || [],
|
|
492
|
+
dataPath: nwGui.App.dataPath || ''
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
callback(null, {
|
|
496
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
|
|
497
|
+
})
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
ToolExecutor.prototype._minimize = function(args, callback) {
|
|
501
|
+
var nwGui = this._getNwGui()
|
|
502
|
+
if (!nwGui) {
|
|
503
|
+
callback(new Error('NW.js API not available'))
|
|
504
|
+
return
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
var nwWin = nwGui.Window.get()
|
|
508
|
+
nwWin.minimize()
|
|
509
|
+
|
|
510
|
+
callback(null, {
|
|
511
|
+
content: [{ type: 'text', text: 'Window minimized' }]
|
|
512
|
+
})
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
ToolExecutor.prototype._maximize = function(args, callback) {
|
|
516
|
+
var nwGui = this._getNwGui()
|
|
517
|
+
if (!nwGui) {
|
|
518
|
+
callback(new Error('NW.js API not available'))
|
|
519
|
+
return
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
var nwWin = nwGui.Window.get()
|
|
523
|
+
nwWin.maximize()
|
|
524
|
+
|
|
525
|
+
callback(null, {
|
|
526
|
+
content: [{ type: 'text', text: 'Window maximized' }]
|
|
527
|
+
})
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
ToolExecutor.prototype._restore = function(args, callback) {
|
|
531
|
+
var nwGui = this._getNwGui()
|
|
532
|
+
if (!nwGui) {
|
|
533
|
+
callback(new Error('NW.js API not available'))
|
|
534
|
+
return
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
var nwWin = nwGui.Window.get()
|
|
538
|
+
nwWin.restore()
|
|
539
|
+
|
|
540
|
+
callback(null, {
|
|
541
|
+
content: [{ type: 'text', text: 'Window restored' }]
|
|
542
|
+
})
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
ToolExecutor.prototype._focus = function(args, callback) {
|
|
546
|
+
var nwGui = this._getNwGui()
|
|
547
|
+
if (!nwGui) {
|
|
548
|
+
callback(new Error('NW.js API not available'))
|
|
549
|
+
return
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
var nwWin = nwGui.Window.get()
|
|
553
|
+
nwWin.focus()
|
|
554
|
+
|
|
555
|
+
callback(null, {
|
|
556
|
+
content: [{ type: 'text', text: 'Window focused' }]
|
|
557
|
+
})
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
ToolExecutor.prototype._getBounds = function(args, callback) {
|
|
561
|
+
var nwGui = this._getNwGui()
|
|
562
|
+
if (!nwGui) {
|
|
563
|
+
callback(new Error('NW.js API not available'))
|
|
564
|
+
return
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
var nwWin = nwGui.Window.get()
|
|
568
|
+
var bounds = {
|
|
569
|
+
x: nwWin.x,
|
|
570
|
+
y: nwWin.y,
|
|
571
|
+
width: nwWin.width,
|
|
572
|
+
height: nwWin.height
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
callback(null, {
|
|
576
|
+
content: [{ type: 'text', text: JSON.stringify(bounds, null, 2) }]
|
|
577
|
+
})
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
ToolExecutor.prototype._setBounds = function(args, callback) {
|
|
581
|
+
var nwGui = this._getNwGui()
|
|
582
|
+
if (!nwGui) {
|
|
583
|
+
callback(new Error('NW.js API not available'))
|
|
584
|
+
return
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
var nwWin = nwGui.Window.get()
|
|
588
|
+
|
|
589
|
+
if (args.x !== undefined && args.y !== undefined) {
|
|
590
|
+
nwWin.moveTo(args.x, args.y)
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (args.width !== undefined && args.height !== undefined) {
|
|
594
|
+
nwWin.resizeTo(args.width, args.height)
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
var newBounds = {
|
|
598
|
+
x: nwWin.x,
|
|
599
|
+
y: nwWin.y,
|
|
600
|
+
width: nwWin.width,
|
|
601
|
+
height: nwWin.height
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
callback(null, {
|
|
605
|
+
content: [{ type: 'text', text: 'Bounds updated: ' + JSON.stringify(newBounds) }]
|
|
606
|
+
})
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
ToolExecutor.prototype._zoom = function(args, callback) {
|
|
610
|
+
var nwGui = this._getNwGui()
|
|
611
|
+
if (!nwGui) {
|
|
612
|
+
callback(new Error('NW.js API not available'))
|
|
613
|
+
return
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
var nwWin = nwGui.Window.get()
|
|
617
|
+
var level = args.level || 1.0
|
|
618
|
+
|
|
619
|
+
// NW.js uses zoomLevel which is logarithmic (0 = 100%, 1 = 120%, -1 = ~83%)
|
|
620
|
+
// Convert linear scale to NW.js zoom level
|
|
621
|
+
// Formula: zoomLevel = log(scale) / log(1.2)
|
|
622
|
+
var zoomLevel = Math.log(level) / Math.log(1.2)
|
|
623
|
+
nwWin.zoomLevel = zoomLevel
|
|
624
|
+
|
|
625
|
+
callback(null, {
|
|
626
|
+
content: [{ type: 'text', text: 'Zoom set to ' + (level * 100) + '%' }]
|
|
627
|
+
})
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
module.exports = ToolExecutor
|