@wobuxcm/dsh-win-tools 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/LICENSES/Apache-2.0.txt +202 -0
- package/README.md +88 -2
- package/THIRD-PARTY-NOTICES.md +36 -0
- package/bin/agent-desktop-win32-x64.exe +0 -0
- package/bin/package.json +3 -0
- package/bin/web.js +147 -0
- package/cordis.patch.yml +4 -0
- package/index.js +348 -0
- package/lib/core.js +583 -0
- package/lib/cua/engine.js +322 -0
- package/lib/cua/input.js +205 -0
- package/lib/cua/native.js +490 -0
- package/lib/cua/safety.js +162 -0
- package/lib/cua/screen.js +300 -0
- package/package.json +56 -4
package/index.js
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-win-tools — one tool surface for Windows desktop control.
|
|
3
|
+
*
|
|
4
|
+
* pc_snapshot / pc_act -> agent-desktop native CLI (accessibility refs)
|
|
5
|
+
* pc_act(point) / pc_key -> dsh-cua-win lib (SendInput, absolute coordinates)
|
|
6
|
+
* pc_screen / pc_window -> dsh-cua-win lib (GDI capture, user32 window calls)
|
|
7
|
+
* pc_browser -> dsh-browser web.js CLI (DOM, no pixels)
|
|
8
|
+
* pc_pause -> operator switch, toggles %DSH_HOME%/cua.stop
|
|
9
|
+
*
|
|
10
|
+
* apply() never throws: a broken backend degrades to fewer tools.
|
|
11
|
+
*/
|
|
12
|
+
import { pathToFileURL } from 'node:url'
|
|
13
|
+
import { readFileSync } from 'node:fs'
|
|
14
|
+
import { basename } from 'node:path'
|
|
15
|
+
import { dirname, join } from 'node:path'
|
|
16
|
+
|
|
17
|
+
import * as core from './lib/core.js'
|
|
18
|
+
|
|
19
|
+
async function loadDefineTool() {
|
|
20
|
+
const errors = []
|
|
21
|
+
const candidates = ['@deepseek-ai/dsh-tools']
|
|
22
|
+
const asarParts = ['app.asar', 'dsh', 'node_modules', '@deepseek-ai', 'dsh-tools', 'lib', 'index.js']
|
|
23
|
+
try {
|
|
24
|
+
const resources = process.resourcesPath
|
|
25
|
+
if (typeof resources === 'string' && resources !== '') candidates.push(pathToFileURL(join(resources, ...asarParts)).href)
|
|
26
|
+
} catch {}
|
|
27
|
+
try {
|
|
28
|
+
const exe = process.execPath
|
|
29
|
+
if (typeof exe === 'string' && exe !== '') {
|
|
30
|
+
let dir = dirname(exe)
|
|
31
|
+
for (let depth = 0; depth < 7; depth += 1) {
|
|
32
|
+
candidates.push(pathToFileURL(join(dir, 'resources', ...asarParts)).href)
|
|
33
|
+
const parent = dirname(dir)
|
|
34
|
+
if (parent === dir) break
|
|
35
|
+
dir = parent
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
} catch {}
|
|
39
|
+
for (const candidate of candidates) {
|
|
40
|
+
try {
|
|
41
|
+
const mod = await import(candidate)
|
|
42
|
+
if (typeof mod.defineTool === 'function') return mod.defineTool
|
|
43
|
+
errors.push(candidate + ': no defineTool export')
|
|
44
|
+
} catch (error) {
|
|
45
|
+
errors.push(candidate + ': ' + (error instanceof Error ? error.message : String(error)))
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
throw new Error(errors.join(' | '))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const name = 'win-tools'
|
|
52
|
+
export const inject = ['tools']
|
|
53
|
+
|
|
54
|
+
let defineTool = null
|
|
55
|
+
let defineToolError = null
|
|
56
|
+
try { defineTool = await loadDefineTool() } catch (error) { defineToolError = error instanceof Error ? error.message : String(error) }
|
|
57
|
+
|
|
58
|
+
const OUTPUT_SCHEMA = { type: 'object', additionalProperties: true }
|
|
59
|
+
const render = (_a, value) => [{ type: 'text', text: JSON.stringify(value) }]
|
|
60
|
+
|
|
61
|
+
export function apply(ctx) {
|
|
62
|
+
if (!defineTool) {
|
|
63
|
+
try { ctx?.logger?.warn?.('win-tools unavailable: ' + defineToolError) } catch {}
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const state = { saveImage: null }
|
|
68
|
+
const refStore = new Map()
|
|
69
|
+
ctx.inject(['attachments'], (actx) => {
|
|
70
|
+
actx.effect(() => {
|
|
71
|
+
const attachments = actx.get('attachments')
|
|
72
|
+
state.saveImage = attachments && typeof attachments.saveImages === 'function'
|
|
73
|
+
? (images) => attachments.saveImages(images).catch(() => null)
|
|
74
|
+
: null
|
|
75
|
+
return () => { state.saveImage = null }
|
|
76
|
+
}, 'win-tools: attachments')
|
|
77
|
+
})
|
|
78
|
+
ctx.effect(() => {
|
|
79
|
+
const disposers = []
|
|
80
|
+
const tools = [
|
|
81
|
+
defineTool({
|
|
82
|
+
name: 'pc_display',
|
|
83
|
+
description: 'Report the display layout: per-monitor bounds in physical pixels, primary flag, and system DPI scale.',
|
|
84
|
+
parameters: {},
|
|
85
|
+
output: { schema: OUTPUT_SCHEMA, render },
|
|
86
|
+
async execute() { return core.display() },
|
|
87
|
+
}),
|
|
88
|
+
|
|
89
|
+
defineTool({
|
|
90
|
+
name: 'pc_status',
|
|
91
|
+
description: 'Show the agent-desktop adapter health, platform info, permission state and CLI version (same payload as the old desktop_status).',
|
|
92
|
+
parameters: {},
|
|
93
|
+
output: { schema: OUTPUT_SCHEMA, render },
|
|
94
|
+
async execute() { return core.status() },
|
|
95
|
+
}),
|
|
96
|
+
|
|
97
|
+
defineTool({
|
|
98
|
+
name: 'pc_clipboard',
|
|
99
|
+
description: 'Read or write the Windows text clipboard (PowerShell Get-Clipboard/Set-Clipboard).',
|
|
100
|
+
parameters: {
|
|
101
|
+
mode: { type: 'string', required: true, enum: ['get', 'set'] },
|
|
102
|
+
text: { type: 'string', description: 'Text to write when mode=set.' },
|
|
103
|
+
},
|
|
104
|
+
output: { schema: OUTPUT_SCHEMA, render },
|
|
105
|
+
async execute(args) { return core.clipboard(args) },
|
|
106
|
+
}),
|
|
107
|
+
|
|
108
|
+
defineTool({
|
|
109
|
+
name: 'pc_notify',
|
|
110
|
+
description: 'Show a Windows tray balloon notification with a title and message.',
|
|
111
|
+
parameters: {
|
|
112
|
+
title: { type: 'string', description: 'Balloon title (default DSH).' },
|
|
113
|
+
message: { type: 'string', description: 'Balloon body.' },
|
|
114
|
+
},
|
|
115
|
+
output: { schema: OUTPUT_SCHEMA, render },
|
|
116
|
+
async execute(args) { return core.notify(args) },
|
|
117
|
+
}),
|
|
118
|
+
|
|
119
|
+
defineTool({
|
|
120
|
+
name: 'pc_pause',
|
|
121
|
+
description: 'Operator switch for desktop control. pause creates %DSH_HOME%/cua.stop and every write action (pc_act, pc_key, pc_window focus) is refused until resume. status reports the current state. The global hotkey Ctrl+Alt+Shift+F12 toggles the same file.',
|
|
122
|
+
parameters: {
|
|
123
|
+
action: { type: 'string', required: true, enum: ['pause', 'resume', 'status'], description: 'pause | resume | status' },
|
|
124
|
+
},
|
|
125
|
+
output: { schema: OUTPUT_SCHEMA, render },
|
|
126
|
+
async execute(args) {
|
|
127
|
+
if (args.action === 'status') return { ok: true, paused: core.isPaused(), file: core.PATHS.pauseFile }
|
|
128
|
+
return core.setPaused(args.action === 'pause')
|
|
129
|
+
},
|
|
130
|
+
}),
|
|
131
|
+
|
|
132
|
+
defineTool({
|
|
133
|
+
name: 'pc_snapshot',
|
|
134
|
+
description: 'Read the Windows accessibility tree and return @ref ids for pc_act. Prefer this over screenshots for standard apps: no pixels, no OCR, stable refs. Use --surface for menus/taskbar/start-menu when the window tree is empty.',
|
|
135
|
+
parameters: {
|
|
136
|
+
app: { type: 'string', description: 'Filter to an application by name.' },
|
|
137
|
+
windowId: { type: 'string', description: 'Scope to one window id (see pc_window list).' },
|
|
138
|
+
surface: { type: 'string', description: 'window | focused | menu | taskbar | start-menu | system-tray | action-center ...' },
|
|
139
|
+
root: { type: 'string', description: 'Start traversal from this @ref.' },
|
|
140
|
+
maxDepth: { type: 'integer', description: 'Tree depth ceiling (default 10).' },
|
|
141
|
+
interactiveOnly: { type: 'boolean', description: 'Only interactive elements.' },
|
|
142
|
+
skeleton: { type: 'boolean', description: 'Shallow overview with children_count.' },
|
|
143
|
+
compact: { type: 'boolean', description: 'Collapse single-child unnamed nodes.' },
|
|
144
|
+
includeBounds: { type: 'boolean', description: 'Include x/y/width/height for every element.' },
|
|
145
|
+
waitFor: { type: 'string', description: 'Poll until a "role:text" element appears.' },
|
|
146
|
+
timeoutMs: { type: 'integer', description: 'Observation deadline (default 6000).' },
|
|
147
|
+
},
|
|
148
|
+
output: { schema: OUTPUT_SCHEMA, render },
|
|
149
|
+
async execute(args) { return core.snapshot(args) },
|
|
150
|
+
}),
|
|
151
|
+
|
|
152
|
+
defineTool({
|
|
153
|
+
name: 'pc_act',
|
|
154
|
+
description: 'Act on a snapshot @ref (accessibility action: click, double-click, right-click, type, set-value, clear, press, focus, select, toggle, check, uncheck, expand, collapse, scroll, scroll-to, hover) or on physical coordinates when only x/y are given (SendInput, works on self-drawn windows). Ref actions need no focus; point actions need the target window in front (pc_window focus).',
|
|
155
|
+
parameters: {
|
|
156
|
+
action: { type: 'string', required: true, description: 'Ref action verb, or click/double-click/right-click/move/drag/type for coordinates.' },
|
|
157
|
+
ref: { type: 'string', description: 'Qualified @ref from pc_snapshot.' },
|
|
158
|
+
x: { type: 'number', description: 'Physical screen x (point mode).' },
|
|
159
|
+
y: { type: 'number', description: 'Physical screen y (point mode).' },
|
|
160
|
+
button: { type: 'string', enum: ['left', 'right', 'middle'], description: 'Point-mode mouse button.' },
|
|
161
|
+
text: { type: 'string', description: 'Text for type / set-value.' },
|
|
162
|
+
keys: { type: 'string', description: 'Key combo for press, e.g. "ctrl+s".' },
|
|
163
|
+
direction: { type: 'string', description: 'scroll direction: up|down|left|right.' },
|
|
164
|
+
amount: { type: 'integer', description: 'scroll units (default 3).' },
|
|
165
|
+
from: { type: 'array', items: { type: 'number' }, description: 'Drag start [x,y].' },
|
|
166
|
+
to: { type: 'array', items: { type: 'number' }, description: 'Drag end [x,y].' },
|
|
167
|
+
durationMs: { type: 'integer', description: 'Drag duration (default 300).' },
|
|
168
|
+
headed: { type: 'boolean', description: 'Prefer physical delivery for a ref action.' },
|
|
169
|
+
timeoutMs: { type: 'integer', description: 'Action deadline.' },
|
|
170
|
+
},
|
|
171
|
+
output: { schema: OUTPUT_SCHEMA, render },
|
|
172
|
+
async execute(args) {
|
|
173
|
+
if (args.ref || args.action === 'press') return core.actByRef(args)
|
|
174
|
+
if (args.x === undefined || args.y === undefined) return { ok: false, code: 'BAD_ARGS', message: 'provide ref, or x/y for point mode' }
|
|
175
|
+
return core.actByPoint(args)
|
|
176
|
+
},
|
|
177
|
+
}),
|
|
178
|
+
|
|
179
|
+
defineTool({
|
|
180
|
+
name: 'pc_key',
|
|
181
|
+
description: 'Press a key combo through SendInput (layout-independent), e.g. "enter", "ctrl+shift+tab", "win". Refused while paused.',
|
|
182
|
+
parameters: {
|
|
183
|
+
keys: { type: 'string', required: true, description: 'Combo like "ctrl+s".' },
|
|
184
|
+
times: { type: 'integer', description: 'Repeat count (1..20).' },
|
|
185
|
+
},
|
|
186
|
+
output: { schema: OUTPUT_SCHEMA, render },
|
|
187
|
+
async execute(args) { return core.keyCombo(args) },
|
|
188
|
+
}),
|
|
189
|
+
|
|
190
|
+
defineTool({
|
|
191
|
+
name: 'pc_screen',
|
|
192
|
+
description: 'Capture a pixel screenshot (GDI, DPI-aware) and save it to a PNG path. Returns the path so the caller can read the image when vision is needed. No annotation is drawn.',
|
|
193
|
+
parameters: {
|
|
194
|
+
target: { type: 'string', enum: ['screen', 'window', 'rect'], description: 'Capture target (default screen).' },
|
|
195
|
+
window: { type: 'string', description: 'Window title substring or hwnd (target=window).' },
|
|
196
|
+
rect: { type: 'array', items: { type: 'integer' }, description: '[x,y,w,h] physical pixels (target=rect).' },
|
|
197
|
+
scale: { type: 'integer', description: 'Longest edge of the saved PNG (default 1600).' },
|
|
198
|
+
out: { type: 'string', description: 'Absolute output path.' },
|
|
199
|
+
annotate: { type: 'boolean', description: 'Draw accessibility bounding boxes and index labels onto the image (adds one UIA snapshot, ~3 s).' },
|
|
200
|
+
},
|
|
201
|
+
output: {
|
|
202
|
+
schema: OUTPUT_SCHEMA,
|
|
203
|
+
render: (_a, value) => {
|
|
204
|
+
const blocks = [{ type: 'text', text: JSON.stringify(value) }]
|
|
205
|
+
const img = value && value.image
|
|
206
|
+
if (img) {
|
|
207
|
+
const ref = refStore.get(img.attachment_id)
|
|
208
|
+
if (ref) blocks.push({ type: 'image', attachment: ref })
|
|
209
|
+
}
|
|
210
|
+
return blocks
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
async execute(args) {
|
|
214
|
+
const shot = args.annotate ? await core.annotatedScreen(args) : await core.screenshot(args)
|
|
215
|
+
if (!shot || !shot.ok || !state.saveImage) return shot
|
|
216
|
+
try {
|
|
217
|
+
const imgPath = shot.annotatedPath || shot.path
|
|
218
|
+
const png = readFileSync(imgPath)
|
|
219
|
+
const refs = await state.saveImage([{ data: new Uint8Array(png), mediaType: 'image/png', name: basename(imgPath) }])
|
|
220
|
+
const ref = refs && refs[0]
|
|
221
|
+
if (!ref) return shot
|
|
222
|
+
refStore.set(String(ref.attachmentId), ref)
|
|
223
|
+
return {
|
|
224
|
+
...shot,
|
|
225
|
+
image: {
|
|
226
|
+
attachment_id: String(ref.attachmentId),
|
|
227
|
+
media_type: ref.mediaType,
|
|
228
|
+
bytes: ref.bytes,
|
|
229
|
+
width: ref.width,
|
|
230
|
+
height: ref.height,
|
|
231
|
+
...(ref.name !== undefined ? { name: ref.name } : {}),
|
|
232
|
+
},
|
|
233
|
+
}
|
|
234
|
+
} catch (error) {
|
|
235
|
+
return { ...shot, imageError: String(error && error.message ? error.message : error) }
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
}),
|
|
239
|
+
|
|
240
|
+
defineTool({
|
|
241
|
+
name: 'pc_find',
|
|
242
|
+
description: 'Search the accessibility tree by role/name/value/text without a full snapshot. Cheaper than pc_snapshot when you already know what you are looking for; returns candidate elements with @ref ids.',
|
|
243
|
+
parameters: {
|
|
244
|
+
app: { type: 'string', description: 'Filter to an application by name.' },
|
|
245
|
+
windowId: { type: 'string', description: 'Scope to one window id.' },
|
|
246
|
+
role: { type: 'string', description: 'Accessibility role: button, textfield, checkbox, listitem, menuitem ...' },
|
|
247
|
+
name: { type: 'string', description: 'Accessible name or label.' },
|
|
248
|
+
value: { type: 'string', description: 'Current value.' },
|
|
249
|
+
text: { type: 'string', description: 'Text in name, value, title or description.' },
|
|
250
|
+
timeoutMs: { type: 'integer', description: 'Search deadline (default 15000).' },
|
|
251
|
+
},
|
|
252
|
+
output: { schema: OUTPUT_SCHEMA, render },
|
|
253
|
+
async execute(args) { return core.findElements(args) },
|
|
254
|
+
}),
|
|
255
|
+
|
|
256
|
+
defineTool({
|
|
257
|
+
name: 'pc_read',
|
|
258
|
+
description: 'Read one property (text, value, title, bounds, role, states) or one state (visible, enabled, checked, focused, expanded, selected) of an element by @ref. Read-only and much cheaper than re-snapshotting.',
|
|
259
|
+
parameters: {
|
|
260
|
+
what: { type: 'string', required: true, description: 'Property or state name.' },
|
|
261
|
+
ref: { type: 'string', required: true, description: 'Qualified @ref from pc_snapshot or pc_find.' },
|
|
262
|
+
timeoutMs: { type: 'integer', description: 'Deadline (default 8000).' },
|
|
263
|
+
},
|
|
264
|
+
output: { schema: OUTPUT_SCHEMA, render },
|
|
265
|
+
async execute(args) { return core.readElement(args) },
|
|
266
|
+
}),
|
|
267
|
+
|
|
268
|
+
defineTool({
|
|
269
|
+
name: 'pc_window',
|
|
270
|
+
description: 'List visible titled windows or bring one to the foreground. Window ids come from this list.',
|
|
271
|
+
parameters: {
|
|
272
|
+
action: { type: 'string', required: true, enum: ['list', 'focus'], description: 'list | focus' },
|
|
273
|
+
app: { type: 'string', description: 'list: filter by process/app name substring.' },
|
|
274
|
+
target: { type: 'string', description: 'focus: decimal hwnd or title substring.' },
|
|
275
|
+
},
|
|
276
|
+
output: { schema: OUTPUT_SCHEMA, render },
|
|
277
|
+
async execute(args) {
|
|
278
|
+
if (args.action === 'list') return core.listWindows(args.app)
|
|
279
|
+
return core.focusWindow(args.target)
|
|
280
|
+
},
|
|
281
|
+
}),
|
|
282
|
+
|
|
283
|
+
defineTool({
|
|
284
|
+
name: 'pc_browser',
|
|
285
|
+
description: 'Browser DOM operations through dsh-browser (no screenshots): title, text, table (rows of a CSS selector pointing at <tr>), links, click, type, shot. Use --profile for persistent logins, --cdp to attach to a running browser.',
|
|
286
|
+
parameters: {
|
|
287
|
+
op: { type: 'string', required: true, enum: ['title', 'text', 'table', 'links', 'click', 'type', 'shot'], description: 'Operation.' },
|
|
288
|
+
url: { type: 'string', required: true, description: 'Target URL.' },
|
|
289
|
+
arg: { type: 'string', description: 'Selector (table/click/type), max chars (text), max links (links), or output png (shot).' },
|
|
290
|
+
text: { type: 'string', description: 'Text for op=type (appended after the selector).' },
|
|
291
|
+
profile: { type: 'string', description: 'Persistent profile directory.' },
|
|
292
|
+
cdp: { type: 'string', description: 'CDP endpoint of a running browser.' },
|
|
293
|
+
headful: { type: 'boolean', description: 'Show the browser window.' },
|
|
294
|
+
wait: { type: 'integer', description: 'Extra settle time in ms.' },
|
|
295
|
+
timeoutMs: { type: 'integer', description: 'Overall deadline.' },
|
|
296
|
+
},
|
|
297
|
+
output: { schema: OUTPUT_SCHEMA, render },
|
|
298
|
+
async execute(args) { return core.browser(args) },
|
|
299
|
+
}),
|
|
300
|
+
defineTool({
|
|
301
|
+
name: 'pc_wait',
|
|
302
|
+
description: 'Poll a screen condition in-process (no model round-trips) and optionally act once it matches: window_title_contains {pattern}, pixel_matches {x,y,color,tolerance}, region_ratio {rect,color,tolerance,minRatio}. Refused while paused.',
|
|
303
|
+
parameters: {
|
|
304
|
+
condition: { type: 'object', additionalProperties: false, required: true, description: 'One condition object.', properties: {
|
|
305
|
+
window_title_contains: { type: 'object', additionalProperties: false, properties: { pattern: { type: 'string', required: true } } },
|
|
306
|
+
pixel_matches: { type: 'object', additionalProperties: false, properties: { x: { type: 'number', required: true }, y: { type: 'number', required: true }, color: { type: 'string', required: true, description: "#rrggbb" }, tolerance: { type: 'integer' } } },
|
|
307
|
+
region_ratio: { type: 'object', additionalProperties: false, properties: { rect: { type: 'array', items: { type: 'integer' }, required: true }, color: { type: 'string', required: true }, tolerance: { type: 'integer' }, minRatio: { type: 'number' } } },
|
|
308
|
+
image_changed: { type: 'object', additionalProperties: false, properties: { rect: { type: 'array', items: { type: 'integer' }, required: true }, tolerance: { type: 'integer' }, minChangeRatio: { type: 'number' } } },
|
|
309
|
+
} },
|
|
310
|
+
action: { type: 'object', additionalProperties: false, description: 'Optional action executed on match.', properties: { type: { type: 'string', enum: ['click', 'double_click', 'move', 'type_text', 'key_press'] }, x: { type: 'number' }, y: { type: 'number' }, button: { type: 'string', enum: ['left', 'right', 'middle'] }, text: { type: 'string' }, keys: { type: 'string' }, times: { type: 'integer' } } },
|
|
311
|
+
timeoutMs: { type: 'integer', description: 'Wait ceiling (default 30000, max 600000).' },
|
|
312
|
+
pollMs: { type: 'integer', description: 'Poll interval (default 120).' },
|
|
313
|
+
},
|
|
314
|
+
timeoutMs: 600000,
|
|
315
|
+
output: { schema: OUTPUT_SCHEMA, render },
|
|
316
|
+
async execute(args) { return core.waitFor(args) },
|
|
317
|
+
}),
|
|
318
|
+
|
|
319
|
+
defineTool({
|
|
320
|
+
name: 'pc_app',
|
|
321
|
+
description: 'List, launch, close, resize, minimize, maximize or restore applications. Launch/close use PowerShell Start-Process/Stop-Process; window verbs use ShowWindow/MoveWindow on a target window title or hwnd.',
|
|
322
|
+
parameters: {
|
|
323
|
+
action: { type: 'string', required: true, enum: ['list', 'launch', 'close', 'resize', 'minimize', 'maximize', 'restore'] },
|
|
324
|
+
app: { type: 'string', description: 'Executable path/name for launch or close.' },
|
|
325
|
+
args: { type: 'array', items: { type: 'string' }, description: 'Launch arguments.' },
|
|
326
|
+
target: { type: 'string', description: 'Window title substring or hwnd for close/resize/minimize/maximize/restore.' },
|
|
327
|
+
width: { type: 'integer', description: 'Resize width.' },
|
|
328
|
+
height: { type: 'integer', description: 'Resize height.' },
|
|
329
|
+
activate: { type: 'boolean', description: 'Wait for the launched window (default true).' },
|
|
330
|
+
},
|
|
331
|
+
output: { schema: OUTPUT_SCHEMA, render },
|
|
332
|
+
async execute(args) { return core.appControl(args) },
|
|
333
|
+
}),
|
|
334
|
+
]
|
|
335
|
+
for (const tool of tools) {
|
|
336
|
+
try {
|
|
337
|
+
disposers.push(ctx.tools.register(tool))
|
|
338
|
+
} catch (error) {
|
|
339
|
+
core.log({ tool: 'register', name: tool && tool.name, error: String(error && error.message ? error.message : error) })
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return () => { for (const d of disposers) { try { d() } catch {} } }
|
|
343
|
+
}, 'win-tools: tools')
|
|
344
|
+
} catch (error) {
|
|
345
|
+
core.log({ tool: 'registration-failed', error: String(error && error.message ? error.message : error) })
|
|
346
|
+
try { ctx?.logger?.warn?.('win-tools registration failed: ' + (error instanceof Error ? error.message : String(error))) } catch {}
|
|
347
|
+
}
|
|
348
|
+
}
|