@mokoconsulting/mcp-windows 3.0.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/.gitattributes +94 -0
- package/.gitmessage +9 -0
- package/.mokogitea/ISSUE_TEMPLATE/adr.md +110 -0
- package/.mokogitea/ISSUE_TEMPLATE/bug_report.md +48 -0
- package/.mokogitea/ISSUE_TEMPLATE/config.yml +18 -0
- package/.mokogitea/ISSUE_TEMPLATE/documentation.md +52 -0
- package/.mokogitea/ISSUE_TEMPLATE/feature_request.md +51 -0
- package/.mokogitea/ISSUE_TEMPLATE/mcp_api_integration.md +48 -0
- package/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md +67 -0
- package/.mokogitea/ISSUE_TEMPLATE/mcp_tool_request.md +49 -0
- package/.mokogitea/ISSUE_TEMPLATE/question.md +82 -0
- package/.mokogitea/ISSUE_TEMPLATE/rfc.md +126 -0
- package/.mokogitea/ISSUE_TEMPLATE/security.md +51 -0
- package/.mokogitea/ISSUE_TEMPLATE/version.md +24 -0
- package/.mokogitea/branch-protection.yml +251 -0
- package/.mokogitea/workflows/auto-assign.yml +76 -0
- package/.mokogitea/workflows/auto-bump.yml +66 -0
- package/.mokogitea/workflows/auto-dev-issue.yml +207 -0
- package/.mokogitea/workflows/auto-release.yml +421 -0
- package/.mokogitea/workflows/branch-cleanup.yml +48 -0
- package/.mokogitea/workflows/cascade-dev.yml +10 -0
- package/.mokogitea/workflows/changelog-validation.yml +101 -0
- package/.mokogitea/workflows/ci-generic.yml +191 -0
- package/.mokogitea/workflows/cleanup.yml +87 -0
- package/.mokogitea/workflows/codeql-analysis.yml +115 -0
- package/.mokogitea/workflows/copilot-agent.yml +44 -0
- package/.mokogitea/workflows/deploy-manual.yml +126 -0
- package/.mokogitea/workflows/enterprise-firewall-setup.yml +758 -0
- package/.mokogitea/workflows/gitleaks.yml +92 -0
- package/.mokogitea/workflows/issue-branch.yml +73 -0
- package/.mokogitea/workflows/mcp-auto-release.yml +278 -0
- package/.mokogitea/workflows/mcp-build-test.yml +65 -0
- package/.mokogitea/workflows/mcp-sdk-check.yml +109 -0
- package/.mokogitea/workflows/mcp-tool-inventory.yml +61 -0
- package/.mokogitea/workflows/notify.yml +70 -0
- package/.mokogitea/workflows/npm-publish.yml +113 -0
- package/.mokogitea/workflows/pr-check.yml +534 -0
- package/.mokogitea/workflows/pre-release.yml +252 -0
- package/.mokogitea/workflows/rc-revert.yml +66 -0
- package/.mokogitea/workflows/repo-health.yml +712 -0
- package/.mokogitea/workflows/repository-cleanup.yml +525 -0
- package/.mokogitea/workflows/security-audit.yml +82 -0
- package/.mokogitea/workflows/standards-compliance.yml +2614 -0
- package/.mokogitea/workflows/sync-version-on-merge.yml +133 -0
- package/.mokogitea/workflows/update-server.yml +312 -0
- package/.mokogitea/workflows/workflow-sync-trigger.yml +73 -0
- package/CHANGELOG.md +130 -0
- package/CLAUDE.md +49 -0
- package/CONTRIBUTING.md +161 -0
- package/ISSUES.md +601 -0
- package/Makefile +70 -0
- package/README.md +80 -0
- package/automation/ci-issue-reporter.sh +237 -0
- package/config.example.json +18 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +111 -0
- package/dist/shell.d.ts +50 -0
- package/dist/shell.js +209 -0
- package/dist/tools/apps.d.ts +3 -0
- package/dist/tools/apps.js +63 -0
- package/dist/tools/audio.d.ts +3 -0
- package/dist/tools/audio.js +142 -0
- package/dist/tools/audio_apps.d.ts +3 -0
- package/dist/tools/audio_apps.js +86 -0
- package/dist/tools/automation.d.ts +3 -0
- package/dist/tools/automation.js +261 -0
- package/dist/tools/bluetooth.d.ts +3 -0
- package/dist/tools/bluetooth.js +96 -0
- package/dist/tools/clipboard.d.ts +3 -0
- package/dist/tools/clipboard.js +118 -0
- package/dist/tools/config.d.ts +3 -0
- package/dist/tools/config.js +85 -0
- package/dist/tools/dialog.d.ts +3 -0
- package/dist/tools/dialog.js +72 -0
- package/dist/tools/display.d.ts +3 -0
- package/dist/tools/display.js +256 -0
- package/dist/tools/drives.d.ts +3 -0
- package/dist/tools/drives.js +98 -0
- package/dist/tools/environment.d.ts +3 -0
- package/dist/tools/environment.js +129 -0
- package/dist/tools/execute.d.ts +3 -0
- package/dist/tools/execute.js +28 -0
- package/dist/tools/filesystem.d.ts +3 -0
- package/dist/tools/filesystem.js +230 -0
- package/dist/tools/firewall.d.ts +3 -0
- package/dist/tools/firewall.js +108 -0
- package/dist/tools/hosts.d.ts +3 -0
- package/dist/tools/hosts.js +119 -0
- package/dist/tools/maintenance.d.ts +3 -0
- package/dist/tools/maintenance.js +236 -0
- package/dist/tools/netstat.d.ts +3 -0
- package/dist/tools/netstat.js +56 -0
- package/dist/tools/network.d.ts +3 -0
- package/dist/tools/network.js +70 -0
- package/dist/tools/notification.d.ts +3 -0
- package/dist/tools/notification.js +41 -0
- package/dist/tools/power.d.ts +3 -0
- package/dist/tools/power.js +104 -0
- package/dist/tools/printer.d.ts +3 -0
- package/dist/tools/printer.js +97 -0
- package/dist/tools/process.d.ts +3 -0
- package/dist/tools/process.js +54 -0
- package/dist/tools/process_kill.d.ts +3 -0
- package/dist/tools/process_kill.js +48 -0
- package/dist/tools/recycle_bin.d.ts +3 -0
- package/dist/tools/recycle_bin.js +108 -0
- package/dist/tools/registry.d.ts +3 -0
- package/dist/tools/registry.js +136 -0
- package/dist/tools/scheduler.d.ts +3 -0
- package/dist/tools/scheduler.js +116 -0
- package/dist/tools/service.d.ts +3 -0
- package/dist/tools/service.js +79 -0
- package/dist/tools/startup.d.ts +3 -0
- package/dist/tools/startup.js +159 -0
- package/dist/tools/storage.d.ts +3 -0
- package/dist/tools/storage.js +129 -0
- package/dist/tools/system.d.ts +3 -0
- package/dist/tools/system.js +84 -0
- package/dist/tools/system_mgmt.d.ts +3 -0
- package/dist/tools/system_mgmt.js +174 -0
- package/dist/tools/terminal.d.ts +3 -0
- package/dist/tools/terminal.js +80 -0
- package/dist/tools/theme.d.ts +3 -0
- package/dist/tools/theme.js +165 -0
- package/dist/tools/usb.d.ts +3 -0
- package/dist/tools/usb.js +52 -0
- package/dist/tools/virtual_desktop.d.ts +3 -0
- package/dist/tools/virtual_desktop.js +112 -0
- package/dist/tools/wifi.d.ts +3 -0
- package/dist/tools/wifi.js +136 -0
- package/dist/tools/window.d.ts +3 -0
- package/dist/tools/window.js +189 -0
- package/dist/tools/winget.d.ts +3 -0
- package/dist/tools/winget.js +79 -0
- package/dist/tools/wsl.d.ts +3 -0
- package/dist/tools/wsl.js +99 -0
- package/docs/API.md +63 -0
- package/docs/ARCHITECTURE.md +73 -0
- package/docs/INSTALLATION.md +102 -0
- package/docs/index.md +12 -0
- package/package.json +35 -0
- package/scripts/setup.mjs +123 -0
- package/src/index.ts +125 -0
- package/src/shell.ts +253 -0
- package/src/tools/apps.ts +76 -0
- package/src/tools/audio.ts +161 -0
- package/src/tools/audio_apps.ts +98 -0
- package/src/tools/automation.ts +297 -0
- package/src/tools/bluetooth.ts +114 -0
- package/src/tools/clipboard.ts +138 -0
- package/src/tools/config.ts +105 -0
- package/src/tools/dialog.ts +87 -0
- package/src/tools/display.ts +285 -0
- package/src/tools/drives.ts +124 -0
- package/src/tools/environment.ts +146 -0
- package/src/tools/execute.ts +35 -0
- package/src/tools/filesystem.ts +273 -0
- package/src/tools/firewall.ts +125 -0
- package/src/tools/hosts.ts +135 -0
- package/src/tools/maintenance.ts +299 -0
- package/src/tools/netstat.ts +72 -0
- package/src/tools/network.ts +84 -0
- package/src/tools/notification.ts +50 -0
- package/src/tools/power.ts +123 -0
- package/src/tools/printer.ts +114 -0
- package/src/tools/process.ts +80 -0
- package/src/tools/process_kill.ts +57 -0
- package/src/tools/recycle_bin.ts +126 -0
- package/src/tools/registry.ts +165 -0
- package/src/tools/scheduler.ts +140 -0
- package/src/tools/service.ts +102 -0
- package/src/tools/startup.ts +180 -0
- package/src/tools/storage.ts +141 -0
- package/src/tools/system.ts +99 -0
- package/src/tools/system_mgmt.ts +190 -0
- package/src/tools/terminal.ts +117 -0
- package/src/tools/theme.ts +205 -0
- package/src/tools/usb.ts +65 -0
- package/src/tools/virtual_desktop.ts +122 -0
- package/src/tools/wifi.ts +157 -0
- package/src/tools/window.ts +211 -0
- package/src/tools/winget.ts +100 -0
- package/src/tools/wsl.ts +112 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
*
|
|
4
|
+
* Tools: windows_theme_get (#52), windows_theme_set (#53),
|
|
5
|
+
* windows_focus_mode (#55), windows_default_apps (#56)
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { runPowerShell } from '../shell.js';
|
|
9
|
+
export function registerThemeTools(server) {
|
|
10
|
+
server.tool('windows_theme_get', 'Get current Windows theme: dark/light mode, accent color, wallpaper, transparency, taskbar alignment.', {}, async () => {
|
|
11
|
+
const ps = `
|
|
12
|
+
$personalize = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize'
|
|
13
|
+
$accent = 'HKCU:\\Software\\Microsoft\\Windows\\DWM'
|
|
14
|
+
$taskbar = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced'
|
|
15
|
+
$wallpaper = (Get-ItemProperty -Path 'HKCU:\\Control Panel\\Desktop' -Name Wallpaper -ErrorAction SilentlyContinue).Wallpaper
|
|
16
|
+
|
|
17
|
+
$appsDark = (Get-ItemProperty -Path $personalize -Name AppsUseLightTheme -ErrorAction SilentlyContinue).AppsUseLightTheme -eq 0
|
|
18
|
+
$systemDark = (Get-ItemProperty -Path $personalize -Name SystemUsesLightTheme -ErrorAction SilentlyContinue).SystemUsesLightTheme -eq 0
|
|
19
|
+
$transparency = (Get-ItemProperty -Path $personalize -Name EnableTransparency -ErrorAction SilentlyContinue).EnableTransparency -eq 1
|
|
20
|
+
$accentColor = (Get-ItemProperty -Path $accent -Name AccentColor -ErrorAction SilentlyContinue).AccentColor
|
|
21
|
+
$taskbarAlign = (Get-ItemProperty -Path $taskbar -Name TaskbarAl -ErrorAction SilentlyContinue).TaskbarAl
|
|
22
|
+
|
|
23
|
+
$accentHex = if ($accentColor) {
|
|
24
|
+
$b = ($accentColor -band 0xFF0000) -shr 16
|
|
25
|
+
$g = ($accentColor -band 0x00FF00) -shr 8
|
|
26
|
+
$r = ($accentColor -band 0x0000FF)
|
|
27
|
+
'#{0:X2}{1:X2}{2:X2}' -f $r, $g, $b
|
|
28
|
+
} else { 'Unknown' }
|
|
29
|
+
|
|
30
|
+
[PSCustomObject]@{
|
|
31
|
+
AppsDarkMode = $appsDark
|
|
32
|
+
SystemDarkMode = $systemDark
|
|
33
|
+
AccentColor = $accentHex
|
|
34
|
+
Transparency = $transparency
|
|
35
|
+
Wallpaper = $wallpaper
|
|
36
|
+
TaskbarAlignment = if ($taskbarAlign -eq 0) { 'Left' } else { 'Center' }
|
|
37
|
+
} | ConvertTo-Json -Compress`;
|
|
38
|
+
const result = await runPowerShell(ps, { timeout: 10000 });
|
|
39
|
+
if (result.exitCode !== 0) {
|
|
40
|
+
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
|
|
41
|
+
}
|
|
42
|
+
const t = JSON.parse(result.stdout);
|
|
43
|
+
return {
|
|
44
|
+
content: [{
|
|
45
|
+
type: 'text',
|
|
46
|
+
text: [
|
|
47
|
+
`Dark mode: Apps=${t.AppsDarkMode}, System=${t.SystemDarkMode}`,
|
|
48
|
+
`Accent color: ${t.AccentColor}`,
|
|
49
|
+
`Transparency: ${t.Transparency ? 'On' : 'Off'}`,
|
|
50
|
+
`Taskbar: ${t.TaskbarAlignment}`,
|
|
51
|
+
`Wallpaper: ${t.Wallpaper || '(none)'}`,
|
|
52
|
+
].join('\n'),
|
|
53
|
+
}],
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
server.tool('windows_theme_set', 'Set dark/light mode, accent color, wallpaper, transparency, or taskbar alignment.', {
|
|
57
|
+
dark_mode: z.enum(['on', 'off']).optional().describe('Set dark mode for apps and system'),
|
|
58
|
+
wallpaper: z.string().optional().describe('Wallpaper file path'),
|
|
59
|
+
wallpaper_fit: z.enum(['fill', 'fit', 'stretch', 'tile', 'center', 'span']).optional().describe('Wallpaper fit mode'),
|
|
60
|
+
transparency: z.enum(['on', 'off']).optional().describe('Transparency effects'),
|
|
61
|
+
taskbar_align: z.enum(['left', 'center']).optional().describe('Taskbar alignment'),
|
|
62
|
+
}, async ({ dark_mode, wallpaper, wallpaper_fit, transparency, taskbar_align }) => {
|
|
63
|
+
const commands = [];
|
|
64
|
+
if (dark_mode) {
|
|
65
|
+
const val = dark_mode === 'on' ? 0 : 1;
|
|
66
|
+
commands.push(`Set-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize' -Name AppsUseLightTheme -Value ${val}`);
|
|
67
|
+
commands.push(`Set-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize' -Name SystemUsesLightTheme -Value ${val}`);
|
|
68
|
+
commands.push(`"Dark mode: ${dark_mode}"`);
|
|
69
|
+
}
|
|
70
|
+
if (transparency) {
|
|
71
|
+
const val = transparency === 'on' ? 1 : 0;
|
|
72
|
+
commands.push(`Set-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize' -Name EnableTransparency -Value ${val}`);
|
|
73
|
+
commands.push(`"Transparency: ${transparency}"`);
|
|
74
|
+
}
|
|
75
|
+
if (taskbar_align) {
|
|
76
|
+
const val = taskbar_align === 'left' ? 0 : 1;
|
|
77
|
+
commands.push(`Set-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced' -Name TaskbarAl -Value ${val}`);
|
|
78
|
+
commands.push(`"Taskbar: ${taskbar_align}"`);
|
|
79
|
+
}
|
|
80
|
+
if (wallpaper) {
|
|
81
|
+
const fitMap = { fill: '10', fit: '6', stretch: '2', tile: '0', center: '0', span: '22' };
|
|
82
|
+
const fit = wallpaper_fit || 'fill';
|
|
83
|
+
commands.push(`
|
|
84
|
+
Set-ItemProperty -Path 'HKCU:\\Control Panel\\Desktop' -Name WallpaperStyle -Value '${fitMap[fit]}'
|
|
85
|
+
Set-ItemProperty -Path 'HKCU:\\Control Panel\\Desktop' -Name TileWallpaper -Value '${fit === 'tile' ? '1' : '0'}'
|
|
86
|
+
Add-Type -TypeDefinition 'using System.Runtime.InteropServices; public class Wallpaper { [DllImport("user32.dll", CharSet=CharSet.Auto)] public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni); }'
|
|
87
|
+
[Wallpaper]::SystemParametersInfo(0x0014, 0, '${wallpaper.replace(/'/g, "''")}', 0x01 -bor 0x02) | Out-Null
|
|
88
|
+
"Wallpaper set: ${wallpaper} (${fit})"
|
|
89
|
+
`);
|
|
90
|
+
}
|
|
91
|
+
if (commands.length === 0) {
|
|
92
|
+
return { content: [{ type: 'text', text: 'No changes specified.' }], isError: true };
|
|
93
|
+
}
|
|
94
|
+
const result = await runPowerShell(commands.join('\n'), { timeout: 15000 });
|
|
95
|
+
return {
|
|
96
|
+
content: [{ type: 'text', text: result.stdout || result.stderr }],
|
|
97
|
+
isError: result.exitCode !== 0,
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
server.tool('windows_focus_mode', 'Get or set Windows Focus Assist / Do Not Disturb mode.', {
|
|
101
|
+
action: z.enum(['get', 'priority', 'alarms', 'off']).default('get').describe('Get status or set mode'),
|
|
102
|
+
}, async ({ action }) => {
|
|
103
|
+
if (action === 'get') {
|
|
104
|
+
const ps = `
|
|
105
|
+
$regPath = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\CloudStore\\Store\\DefaultAccount\\Current\\default$windows.data.notifications.quiethourssettings\\windows.data.notifications.quiethourssettings'
|
|
106
|
+
$mode = 'Unknown'
|
|
107
|
+
try {
|
|
108
|
+
$val = (Get-ItemProperty -Path $regPath -ErrorAction Stop).Data
|
|
109
|
+
if ($val) {
|
|
110
|
+
# The focus assist state is encoded in the binary blob
|
|
111
|
+
$mode = 'Check via Settings app (binary registry format)'
|
|
112
|
+
}
|
|
113
|
+
} catch { $mode = 'Off (or unable to read)' }
|
|
114
|
+
"Focus Assist: $mode"`;
|
|
115
|
+
const result = await runPowerShell(ps, { timeout: 10000 });
|
|
116
|
+
return { content: [{ type: 'text', text: result.stdout || result.stderr }] };
|
|
117
|
+
}
|
|
118
|
+
// Setting focus assist requires ms-settings URI
|
|
119
|
+
const ps = `Start-Process 'ms-settings:quiethours'; "Opened Focus Assist settings. Mode requested: ${action}"`;
|
|
120
|
+
const result = await runPowerShell(ps, { timeout: 10000 });
|
|
121
|
+
return { content: [{ type: 'text', text: result.stdout || result.stderr }] };
|
|
122
|
+
});
|
|
123
|
+
server.tool('windows_default_apps', 'Get default applications or open the default apps settings page.', {
|
|
124
|
+
action: z.enum(['get', 'open_settings']).default('get').describe('Get defaults or open settings'),
|
|
125
|
+
extension: z.string().optional().describe('File extension to check (e.g. ".pdf")'),
|
|
126
|
+
}, async ({ action, extension }) => {
|
|
127
|
+
if (action === 'open_settings') {
|
|
128
|
+
await runPowerShell('Start-Process "ms-settings:defaultapps"');
|
|
129
|
+
return { content: [{ type: 'text', text: 'Opened Default Apps settings.' }] };
|
|
130
|
+
}
|
|
131
|
+
if (extension) {
|
|
132
|
+
const ps = `
|
|
133
|
+
$assoc = cmd /c assoc ${extension} 2>$null
|
|
134
|
+
$ftype = if ($assoc) { $type = ($assoc -split '=')[1]; cmd /c ftype $type 2>$null } else { $null }
|
|
135
|
+
[PSCustomObject]@{
|
|
136
|
+
Extension = '${extension}'
|
|
137
|
+
FileType = if ($assoc) { ($assoc -split '=')[1] } else { 'Not associated' }
|
|
138
|
+
OpensWith = if ($ftype) { ($ftype -split '=')[1] } else { 'Unknown' }
|
|
139
|
+
} | ConvertTo-Json -Compress`;
|
|
140
|
+
const result = await runPowerShell(ps, { timeout: 10000 });
|
|
141
|
+
if (result.exitCode !== 0) {
|
|
142
|
+
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
|
|
143
|
+
}
|
|
144
|
+
const info = JSON.parse(result.stdout);
|
|
145
|
+
return { content: [{ type: 'text', text: `${info.Extension} -> ${info.FileType} -> ${info.OpensWith}` }] };
|
|
146
|
+
}
|
|
147
|
+
// List common defaults
|
|
148
|
+
const ps = `
|
|
149
|
+
$defaults = @('.html','.pdf','.txt','.jpg','.png','.mp3','.mp4','.zip') | ForEach-Object {
|
|
150
|
+
$ext = $_
|
|
151
|
+
$assoc = cmd /c assoc $ext 2>$null
|
|
152
|
+
$type = if ($assoc) { ($assoc -split '=')[1] } else { 'N/A' }
|
|
153
|
+
[PSCustomObject]@{ Extension = $ext; FileType = $type }
|
|
154
|
+
}
|
|
155
|
+
$defaults | ConvertTo-Json -Depth 3 -Compress`;
|
|
156
|
+
const result = await runPowerShell(ps, { timeout: 15000 });
|
|
157
|
+
if (result.exitCode !== 0) {
|
|
158
|
+
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
|
|
159
|
+
}
|
|
160
|
+
const defaults = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
|
|
161
|
+
const lines = defaults.map((d) => ` ${d.Extension.padEnd(8)} ${d.FileType}`);
|
|
162
|
+
return { content: [{ type: 'text', text: `Default file associations:\n${lines.join('\n')}` }] };
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=theme.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
*
|
|
4
|
+
* Tool: windows_usb_devices (#49)
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { runPowerShell } from '../shell.js';
|
|
8
|
+
export function registerUsbTools(server) {
|
|
9
|
+
server.tool('windows_usb_devices', 'List connected USB devices with name, manufacturer, type, and status. Supports safe eject for storage.', {
|
|
10
|
+
eject: z.string().optional().describe('Drive letter to safely eject (e.g. "E:")'),
|
|
11
|
+
}, async ({ eject }) => {
|
|
12
|
+
if (eject) {
|
|
13
|
+
const letter = eject.replace(':', '').toUpperCase();
|
|
14
|
+
const ps = `
|
|
15
|
+
$vol = Get-CimInstance Win32_Volume -Filter "DriveLetter='${letter}:'" -ErrorAction Stop
|
|
16
|
+
$ejectResult = $vol | Invoke-CimMethod -MethodName Dismount -Arguments @{Force=$false} -ErrorAction Stop
|
|
17
|
+
if ($ejectResult.ReturnValue -eq 0) { "Safely ejected ${letter}:" } else { "Eject failed (code: $($ejectResult.ReturnValue)). Close all files on the drive first." }`;
|
|
18
|
+
const result = await runPowerShell(ps, { timeout: 15000 });
|
|
19
|
+
return {
|
|
20
|
+
content: [{ type: 'text', text: result.stdout || result.stderr }],
|
|
21
|
+
isError: result.exitCode !== 0,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const ps = `
|
|
25
|
+
Get-PnpDevice -Class USB -PresentOnly -ErrorAction SilentlyContinue | ForEach-Object {
|
|
26
|
+
[PSCustomObject]@{
|
|
27
|
+
Name = $_.FriendlyName
|
|
28
|
+
Status = $_.Status
|
|
29
|
+
Manufacturer = $_.Manufacturer
|
|
30
|
+
InstanceId = $_.InstanceId
|
|
31
|
+
Class = $_.Class
|
|
32
|
+
}
|
|
33
|
+
} | Where-Object { $_.Name } | Sort-Object Name | ConvertTo-Json -Depth 3 -Compress`;
|
|
34
|
+
const result = await runPowerShell(ps, { timeout: 15000 });
|
|
35
|
+
if (result.exitCode !== 0) {
|
|
36
|
+
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
|
|
37
|
+
}
|
|
38
|
+
if (!result.stdout) {
|
|
39
|
+
return { content: [{ type: 'text', text: 'No USB devices found.' }] };
|
|
40
|
+
}
|
|
41
|
+
const devices = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
|
|
42
|
+
const lines = devices.map((d) => {
|
|
43
|
+
const status = d.Status === 'OK' ? '[OK]' : `[${d.Status?.slice(0, 3) || '?'}]`;
|
|
44
|
+
return `${status} ${(d.Name || '').padEnd(45).slice(0, 45)} ${(d.Manufacturer || '').slice(0, 25)}`;
|
|
45
|
+
});
|
|
46
|
+
const header = `Sta ${'Name'.padEnd(45)} Manufacturer`;
|
|
47
|
+
return {
|
|
48
|
+
content: [{ type: 'text', text: `${header}\n${'─'.repeat(80)}\n${lines.join('\n')}\n\n${devices.length} USB devices` }],
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=usb.js.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
*
|
|
4
|
+
* Tool: windows_virtual_desktop (#54)
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { runPowerShell } from '../shell.js';
|
|
8
|
+
export function registerVirtualDesktopTools(server) {
|
|
9
|
+
server.tool('windows_virtual_desktop', 'List, create, switch, or remove virtual desktops.', {
|
|
10
|
+
action: z.enum(['list', 'create', 'switch', 'remove']).default('list').describe('Action'),
|
|
11
|
+
index: z.number().optional().describe('Desktop index (0-based, for switch/remove)'),
|
|
12
|
+
}, async ({ action, index }) => {
|
|
13
|
+
switch (action) {
|
|
14
|
+
case 'list': {
|
|
15
|
+
const ps = `
|
|
16
|
+
$desktops = Get-CimInstance -Namespace root\\cimv2 -ClassName Win32_Desktop -ErrorAction SilentlyContinue
|
|
17
|
+
# Virtual desktops are best accessed via COM, but we can detect count from registry
|
|
18
|
+
$vdKey = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\VirtualDesktops'
|
|
19
|
+
$ids = (Get-ItemProperty -Path $vdKey -Name VirtualDesktopIDs -ErrorAction SilentlyContinue).VirtualDesktopIDs
|
|
20
|
+
$currentId = (Get-ItemProperty -Path $vdKey -Name CurrentVirtualDesktop -ErrorAction SilentlyContinue).CurrentVirtualDesktop
|
|
21
|
+
$count = if ($ids) { $ids.Length / 16 } else { 1 }
|
|
22
|
+
[PSCustomObject]@{
|
|
23
|
+
Count = $count
|
|
24
|
+
CurrentIndex = 'Use Ctrl+Win+Left/Right to navigate'
|
|
25
|
+
Note = 'Windows does not expose virtual desktop names via public API'
|
|
26
|
+
} | ConvertTo-Json -Compress`;
|
|
27
|
+
const result = await runPowerShell(ps, { timeout: 10000 });
|
|
28
|
+
if (result.exitCode !== 0) {
|
|
29
|
+
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
|
|
30
|
+
}
|
|
31
|
+
const info = JSON.parse(result.stdout);
|
|
32
|
+
return { content: [{ type: 'text', text: `Virtual desktops: ${info.Count}\n${info.Note}` }] };
|
|
33
|
+
}
|
|
34
|
+
case 'create': {
|
|
35
|
+
// Use keyboard shortcut via SendKeys
|
|
36
|
+
const ps = `
|
|
37
|
+
Add-Type @'
|
|
38
|
+
using System;
|
|
39
|
+
using System.Runtime.InteropServices;
|
|
40
|
+
public class VDKeys {
|
|
41
|
+
[DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
|
|
42
|
+
public static void NewDesktop() {
|
|
43
|
+
// Ctrl+Win+D
|
|
44
|
+
keybd_event(0x11, 0, 0, UIntPtr.Zero); // Ctrl down
|
|
45
|
+
keybd_event(0x5B, 0, 0, UIntPtr.Zero); // Win down
|
|
46
|
+
keybd_event(0x44, 0, 0, UIntPtr.Zero); // D down
|
|
47
|
+
keybd_event(0x44, 0, 2, UIntPtr.Zero); // D up
|
|
48
|
+
keybd_event(0x5B, 0, 2, UIntPtr.Zero); // Win up
|
|
49
|
+
keybd_event(0x11, 0, 2, UIntPtr.Zero); // Ctrl up
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
'@
|
|
53
|
+
[VDKeys]::NewDesktop()
|
|
54
|
+
Start-Sleep -Milliseconds 500
|
|
55
|
+
"New virtual desktop created (switched to it)"`;
|
|
56
|
+
const result = await runPowerShell(ps, { timeout: 10000 });
|
|
57
|
+
return { content: [{ type: 'text', text: result.stdout || result.stderr }] };
|
|
58
|
+
}
|
|
59
|
+
case 'switch': {
|
|
60
|
+
if (index === undefined) {
|
|
61
|
+
return { content: [{ type: 'text', text: 'Switch requires index.' }], isError: true };
|
|
62
|
+
}
|
|
63
|
+
// Navigate left/right to reach target
|
|
64
|
+
const ps = `
|
|
65
|
+
Add-Type @'
|
|
66
|
+
using System;
|
|
67
|
+
using System.Runtime.InteropServices;
|
|
68
|
+
public class VDSwitch {
|
|
69
|
+
[DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
|
|
70
|
+
public static void Left() {
|
|
71
|
+
keybd_event(0x11, 0, 0, UIntPtr.Zero); keybd_event(0x5B, 0, 0, UIntPtr.Zero);
|
|
72
|
+
keybd_event(0x25, 0, 0, UIntPtr.Zero); keybd_event(0x25, 0, 2, UIntPtr.Zero);
|
|
73
|
+
keybd_event(0x5B, 0, 2, UIntPtr.Zero); keybd_event(0x11, 0, 2, UIntPtr.Zero);
|
|
74
|
+
}
|
|
75
|
+
public static void Right() {
|
|
76
|
+
keybd_event(0x11, 0, 0, UIntPtr.Zero); keybd_event(0x5B, 0, 0, UIntPtr.Zero);
|
|
77
|
+
keybd_event(0x27, 0, 0, UIntPtr.Zero); keybd_event(0x27, 0, 2, UIntPtr.Zero);
|
|
78
|
+
keybd_event(0x5B, 0, 2, UIntPtr.Zero); keybd_event(0x11, 0, 2, UIntPtr.Zero);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
'@
|
|
82
|
+
# Go far left first, then right to target index
|
|
83
|
+
for ($i = 0; $i -lt 20; $i++) { [VDSwitch]::Left(); Start-Sleep -Milliseconds 100 }
|
|
84
|
+
for ($i = 0; $i -lt ${index}; $i++) { [VDSwitch]::Right(); Start-Sleep -Milliseconds 100 }
|
|
85
|
+
"Switched to desktop ${index}"`;
|
|
86
|
+
const result = await runPowerShell(ps, { timeout: 15000 });
|
|
87
|
+
return { content: [{ type: 'text', text: result.stdout || result.stderr }] };
|
|
88
|
+
}
|
|
89
|
+
case 'remove': {
|
|
90
|
+
const ps = `
|
|
91
|
+
Add-Type @'
|
|
92
|
+
using System;
|
|
93
|
+
using System.Runtime.InteropServices;
|
|
94
|
+
public class VDClose {
|
|
95
|
+
[DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
|
|
96
|
+
public static void CloseDesktop() {
|
|
97
|
+
keybd_event(0x11, 0, 0, UIntPtr.Zero); keybd_event(0x5B, 0, 0, UIntPtr.Zero);
|
|
98
|
+
keybd_event(0x73, 0, 0, UIntPtr.Zero); // F4
|
|
99
|
+
keybd_event(0x73, 0, 2, UIntPtr.Zero);
|
|
100
|
+
keybd_event(0x5B, 0, 2, UIntPtr.Zero); keybd_event(0x11, 0, 2, UIntPtr.Zero);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
'@
|
|
104
|
+
[VDClose]::CloseDesktop()
|
|
105
|
+
"Current virtual desktop removed"`;
|
|
106
|
+
const result = await runPowerShell(ps, { timeout: 10000 });
|
|
107
|
+
return { content: [{ type: 'text', text: result.stdout || result.stderr }] };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=virtual_desktop.js.map
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
*
|
|
4
|
+
* Tools: windows_wifi_networks (#47), windows_wifi_connect (#48)
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { runPowerShell } from '../shell.js';
|
|
8
|
+
export function registerWifiTools(server) {
|
|
9
|
+
server.tool('windows_wifi_networks', 'Scan and list available Wi-Fi networks with signal strength, security, and saved profiles.', {
|
|
10
|
+
rescan: z.boolean().default(false).describe('Force a rescan before listing'),
|
|
11
|
+
saved: z.boolean().default(false).describe('List saved profiles instead of available networks'),
|
|
12
|
+
}, async ({ rescan, saved }) => {
|
|
13
|
+
if (saved) {
|
|
14
|
+
const ps = `netsh wlan show profiles | Select-String 'All User Profile' | ForEach-Object { ($_ -split ':')[1].Trim() }`;
|
|
15
|
+
const result = await runPowerShell(ps, { timeout: 10000 });
|
|
16
|
+
if (result.exitCode !== 0) {
|
|
17
|
+
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
|
|
18
|
+
}
|
|
19
|
+
const profiles = result.stdout ? result.stdout.split('\n').filter(Boolean) : [];
|
|
20
|
+
return {
|
|
21
|
+
content: [{ type: 'text', text: `Saved Wi-Fi profiles (${profiles.length}):\n${profiles.map(p => ` ${p}`).join('\n')}` }],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const scanCmd = rescan ? `netsh wlan scan; Start-Sleep -Seconds 3;` : '';
|
|
25
|
+
const ps = `
|
|
26
|
+
${scanCmd}
|
|
27
|
+
$output = netsh wlan show networks mode=bssid 2>&1
|
|
28
|
+
$networks = [System.Collections.Generic.List[PSObject]]::new()
|
|
29
|
+
$current = @{}
|
|
30
|
+
|
|
31
|
+
foreach ($line in ($output -split [char]10)) {
|
|
32
|
+
$line = $line.Trim()
|
|
33
|
+
if ($line -match '^SSID \\d+ : (.*)') {
|
|
34
|
+
if ($current.SSID) { $networks.Add([PSCustomObject]$current) }
|
|
35
|
+
$current = @{ SSID = $Matches[1]; Signal = ''; Auth = ''; Channel = ''; BSSID = '' }
|
|
36
|
+
}
|
|
37
|
+
elseif ($line -match '^Signal\\s+: (.*)') { $current.Signal = $Matches[1] }
|
|
38
|
+
elseif ($line -match '^Authentication\\s+: (.*)') { $current.Auth = $Matches[1] }
|
|
39
|
+
elseif ($line -match '^Channel\\s+: (.*)') { $current.Channel = $Matches[1] }
|
|
40
|
+
elseif ($line -match '^BSSID \\d+\\s+: (.*)') { $current.BSSID = $Matches[1] }
|
|
41
|
+
}
|
|
42
|
+
if ($current.SSID) { $networks.Add([PSCustomObject]$current) }
|
|
43
|
+
|
|
44
|
+
# Current connection
|
|
45
|
+
$connected = (netsh wlan show interfaces | Select-String 'SSID\\s+:' | Select-Object -First 1) -replace '.*:\\s*',''
|
|
46
|
+
|
|
47
|
+
$networks | Sort-Object { [int]($_.Signal -replace '%','') } -Descending | ConvertTo-Json -Depth 3 -Compress
|
|
48
|
+
Write-Output "---CONNECTED:$($connected.Trim())"`;
|
|
49
|
+
const result = await runPowerShell(ps, { timeout: 20000 });
|
|
50
|
+
if (result.exitCode !== 0) {
|
|
51
|
+
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
|
|
52
|
+
}
|
|
53
|
+
const outputLines = result.stdout.split('\n');
|
|
54
|
+
const connectedLine = outputLines.find(l => l.startsWith('---CONNECTED:'));
|
|
55
|
+
const connected = connectedLine ? connectedLine.replace('---CONNECTED:', '').trim() : '';
|
|
56
|
+
const jsonLine = outputLines.filter(l => !l.startsWith('---CONNECTED:')).join('\n');
|
|
57
|
+
let networks = [];
|
|
58
|
+
if (jsonLine.trim()) {
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(jsonLine);
|
|
61
|
+
networks = Array.isArray(parsed) ? parsed : [parsed];
|
|
62
|
+
}
|
|
63
|
+
catch { /* empty */ }
|
|
64
|
+
}
|
|
65
|
+
const lines = networks.map(n => {
|
|
66
|
+
const icon = n.SSID === connected ? ' *' : ' ';
|
|
67
|
+
return `${icon} ${n.Signal.padStart(4)} ${n.Auth.padEnd(18)} Ch ${(n.Channel || '?').padEnd(3)} ${n.SSID}`;
|
|
68
|
+
});
|
|
69
|
+
const header = ` ${'Sig'.padStart(4)} ${'Security'.padEnd(18)} ${'Ch'.padEnd(5)} SSID`;
|
|
70
|
+
return {
|
|
71
|
+
content: [{
|
|
72
|
+
type: 'text',
|
|
73
|
+
text: `Connected: ${connected || '(none)'}\n\n${header}\n${'─'.repeat(65)}\n${lines.join('\n')}\n\n${networks.length} networks (* = connected)`,
|
|
74
|
+
}],
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
server.tool('windows_wifi_connect', 'Connect to a Wi-Fi network, disconnect, or forget a saved profile.', {
|
|
78
|
+
action: z.enum(['connect', 'disconnect', 'forget']).describe('Action'),
|
|
79
|
+
ssid: z.string().optional().describe('Network SSID (for connect/forget)'),
|
|
80
|
+
password: z.string().optional().describe('Password (for connecting to a new network)'),
|
|
81
|
+
}, async ({ action, ssid, password }) => {
|
|
82
|
+
let ps;
|
|
83
|
+
switch (action) {
|
|
84
|
+
case 'disconnect':
|
|
85
|
+
ps = `netsh wlan disconnect; "Disconnected from Wi-Fi"`;
|
|
86
|
+
break;
|
|
87
|
+
case 'forget':
|
|
88
|
+
if (!ssid) {
|
|
89
|
+
return { content: [{ type: 'text', text: 'Forget requires ssid.' }], isError: true };
|
|
90
|
+
}
|
|
91
|
+
ps = `netsh wlan delete profile name="${ssid.replace(/"/g, '""')}" 2>&1; "Forgot network: ${ssid}"`;
|
|
92
|
+
break;
|
|
93
|
+
case 'connect':
|
|
94
|
+
if (!ssid) {
|
|
95
|
+
return { content: [{ type: 'text', text: 'Connect requires ssid.' }], isError: true };
|
|
96
|
+
}
|
|
97
|
+
// Check if profile exists
|
|
98
|
+
if (password) {
|
|
99
|
+
// Create a temporary profile XML for new networks
|
|
100
|
+
ps = `
|
|
101
|
+
$profileXml = @"
|
|
102
|
+
<?xml version="1.0"?>
|
|
103
|
+
<WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1">
|
|
104
|
+
<name>${ssid}</name>
|
|
105
|
+
<SSIDConfig><SSID><name>${ssid}</name></SSID></SSIDConfig>
|
|
106
|
+
<connectionType>ESS</connectionType>
|
|
107
|
+
<connectionMode>auto</connectionMode>
|
|
108
|
+
<MSM><security>
|
|
109
|
+
<authEncryption><authentication>WPA2PSK</authentication><encryption>AES</encryption><useOneX>false</useOneX></authEncryption>
|
|
110
|
+
<sharedKey><keyType>passPhrase</keyType><protected>false</protected><keyMaterial>${password}</keyMaterial></sharedKey>
|
|
111
|
+
</security></MSM>
|
|
112
|
+
</WLANProfile>
|
|
113
|
+
"@
|
|
114
|
+
$tempFile = [IO.Path]::GetTempFileName()
|
|
115
|
+
$profileXml | Out-File -Encoding UTF8 $tempFile
|
|
116
|
+
netsh wlan add profile filename="$tempFile" 2>&1 | Out-Null
|
|
117
|
+
Remove-Item $tempFile
|
|
118
|
+
netsh wlan connect name="${ssid.replace(/"/g, '""')}" 2>&1
|
|
119
|
+
Start-Sleep -Seconds 3
|
|
120
|
+
$iface = netsh wlan show interfaces
|
|
121
|
+
$connectedSsid = ($iface | Select-String 'SSID\\s+:' | Select-Object -First 1) -replace '.*:\\s*',''
|
|
122
|
+
if ($connectedSsid.Trim() -eq '${ssid}') { "Connected to ${ssid}" } else { "Connection attempt sent for ${ssid}" }`;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
ps = `netsh wlan connect name="${ssid.replace(/"/g, '""')}" 2>&1; Start-Sleep -Seconds 2; "Connect attempt sent for ${ssid}"`;
|
|
126
|
+
}
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
const result = await runPowerShell(ps, { timeout: 20000 });
|
|
130
|
+
return {
|
|
131
|
+
content: [{ type: 'text', text: result.stdout || result.stderr }],
|
|
132
|
+
isError: result.exitCode !== 0,
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=wifi.js.map
|