@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,84 @@
|
|
|
1
|
+
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
*
|
|
4
|
+
* Tool: windows_network_info (#22)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
+
import { runPowerShell } from '../shell.js';
|
|
9
|
+
|
|
10
|
+
export function registerNetworkTools(server: McpServer): void {
|
|
11
|
+
server.tool(
|
|
12
|
+
'windows_network_info',
|
|
13
|
+
'Get network configuration: adapters, IPs, DNS, gateway, Wi-Fi status, and connectivity.',
|
|
14
|
+
{},
|
|
15
|
+
async () => {
|
|
16
|
+
const ps = `
|
|
17
|
+
$adapters = Get-NetAdapter | Where-Object { $_.Status -eq 'Up' } | ForEach-Object {
|
|
18
|
+
$ipInfo = Get-NetIPAddress -InterfaceIndex $_.ifIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue
|
|
19
|
+
$dns = (Get-DnsClientServerAddress -InterfaceIndex $_.ifIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue).ServerAddresses
|
|
20
|
+
$gw = (Get-NetRoute -InterfaceIndex $_.ifIndex -DestinationPrefix '0.0.0.0/0' -ErrorAction SilentlyContinue).NextHop
|
|
21
|
+
[PSCustomObject]@{
|
|
22
|
+
Name = $_.Name
|
|
23
|
+
Description = $_.InterfaceDescription
|
|
24
|
+
Type = $_.MediaType
|
|
25
|
+
Status = $_.Status
|
|
26
|
+
Speed = $_.LinkSpeed
|
|
27
|
+
MAC = $_.MacAddress
|
|
28
|
+
IP = if ($ipInfo) { $ipInfo.IPAddress -join ', ' } else { 'N/A' }
|
|
29
|
+
Subnet = if ($ipInfo) { $ipInfo.PrefixLength -join ', ' } else { 'N/A' }
|
|
30
|
+
Gateway = if ($gw) { $gw -join ', ' } else { 'N/A' }
|
|
31
|
+
DNS = if ($dns) { $dns -join ', ' } else { 'N/A' }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Wi-Fi info
|
|
36
|
+
$wifi = $null
|
|
37
|
+
try {
|
|
38
|
+
$wifiProfile = netsh wlan show interfaces 2>$null
|
|
39
|
+
if ($wifiProfile) {
|
|
40
|
+
$ssid = ($wifiProfile | Select-String 'SSID\s+:' | Select-Object -First 1) -replace '.*:\s*',''
|
|
41
|
+
$signal = ($wifiProfile | Select-String 'Signal' | Select-Object -First 1) -replace '.*:\s*',''
|
|
42
|
+
$auth = ($wifiProfile | Select-String 'Authentication' | Select-Object -First 1) -replace '.*:\s*',''
|
|
43
|
+
$wifi = [PSCustomObject]@{ SSID = $ssid.Trim(); Signal = $signal.Trim(); Security = $auth.Trim() }
|
|
44
|
+
}
|
|
45
|
+
} catch {}
|
|
46
|
+
|
|
47
|
+
# Connectivity
|
|
48
|
+
$connected = Test-Connection -ComputerName 8.8.8.8 -Count 1 -Quiet -ErrorAction SilentlyContinue
|
|
49
|
+
|
|
50
|
+
[PSCustomObject]@{
|
|
51
|
+
Adapters = $adapters
|
|
52
|
+
WiFi = $wifi
|
|
53
|
+
InternetConnected = $connected
|
|
54
|
+
} | ConvertTo-Json -Depth 4 -Compress`;
|
|
55
|
+
|
|
56
|
+
const result = await runPowerShell(ps, { timeout: 15000 });
|
|
57
|
+
if (result.exitCode !== 0) {
|
|
58
|
+
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const info = JSON.parse(result.stdout);
|
|
62
|
+
const lines: string[] = [];
|
|
63
|
+
|
|
64
|
+
lines.push(`Internet: ${info.InternetConnected ? 'Connected' : 'Disconnected'}`);
|
|
65
|
+
|
|
66
|
+
if (info.WiFi) {
|
|
67
|
+
lines.push(`Wi-Fi: ${info.WiFi.SSID} (${info.WiFi.Signal}, ${info.WiFi.Security})`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
lines.push('');
|
|
71
|
+
lines.push('Adapters:');
|
|
72
|
+
|
|
73
|
+
const adapters = Array.isArray(info.Adapters) ? info.Adapters : [info.Adapters];
|
|
74
|
+
for (const a of adapters.filter(Boolean)) {
|
|
75
|
+
lines.push(` ${a.Name} (${a.Description})`);
|
|
76
|
+
lines.push(` IP: ${a.IP}/${a.Subnet} Gateway: ${a.Gateway}`);
|
|
77
|
+
lines.push(` DNS: ${a.DNS}`);
|
|
78
|
+
lines.push(` MAC: ${a.MAC} Speed: ${a.Speed}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
82
|
+
},
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
*
|
|
4
|
+
* Tool: windows_notification_send (#20)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { runPowerShell } from '../shell.js';
|
|
10
|
+
|
|
11
|
+
export function registerNotificationTools(server: McpServer): void {
|
|
12
|
+
server.tool(
|
|
13
|
+
'windows_notification_send',
|
|
14
|
+
'Send a Windows toast notification with title, body, and optional icon.',
|
|
15
|
+
{
|
|
16
|
+
title: z.string().describe('Notification title'),
|
|
17
|
+
body: z.string().describe('Notification body text'),
|
|
18
|
+
icon: z.string().optional().describe('Path to icon file (optional)'),
|
|
19
|
+
sound: z.boolean().default(true).describe('Play notification sound'),
|
|
20
|
+
},
|
|
21
|
+
async ({ title, body, icon, sound }) => {
|
|
22
|
+
const iconParam = icon
|
|
23
|
+
? `$notify.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon('${icon.replace(/'/g, "''")}');`
|
|
24
|
+
: `$notify.Icon = [System.Drawing.SystemIcons]::Information;`;
|
|
25
|
+
|
|
26
|
+
const ps = `
|
|
27
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
28
|
+
Add-Type -AssemblyName System.Drawing
|
|
29
|
+
|
|
30
|
+
$notify = New-Object System.Windows.Forms.NotifyIcon
|
|
31
|
+
${iconParam}
|
|
32
|
+
$notify.Visible = $true
|
|
33
|
+
$notify.BalloonTipTitle = '${title.replace(/'/g, "''")}'
|
|
34
|
+
$notify.BalloonTipText = '${body.replace(/'/g, "''")}'
|
|
35
|
+
$notify.BalloonTipIcon = [System.Windows.Forms.ToolTipIcon]::Info
|
|
36
|
+
$notify.ShowBalloonTip(5000)
|
|
37
|
+
|
|
38
|
+
# Keep alive briefly so notification displays
|
|
39
|
+
Start-Sleep -Milliseconds 100
|
|
40
|
+
$notify.Dispose()
|
|
41
|
+
"Notification sent: ${title.replace(/"/g, '\\"')}"`;
|
|
42
|
+
|
|
43
|
+
const result = await runPowerShell(ps, { timeout: 10000 });
|
|
44
|
+
return {
|
|
45
|
+
content: [{ type: 'text', text: result.stdout || result.stderr }],
|
|
46
|
+
isError: result.exitCode !== 0,
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
*
|
|
4
|
+
* Tools: windows_power_get (#12), windows_power_action (#13)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { runPowerShell } from '../shell.js';
|
|
10
|
+
|
|
11
|
+
export function registerPowerTools(server: McpServer): void {
|
|
12
|
+
server.tool(
|
|
13
|
+
'windows_power_get',
|
|
14
|
+
'Get power state: battery level, AC/battery, power plan, screen/sleep timeouts.',
|
|
15
|
+
{},
|
|
16
|
+
async () => {
|
|
17
|
+
const ps = `
|
|
18
|
+
$battery = Get-CimInstance Win32_Battery -ErrorAction SilentlyContinue
|
|
19
|
+
$plan = powercfg /getactivescheme 2>$null
|
|
20
|
+
$planName = if ($plan) { ($plan -replace '^.*\\((.*)\\).*$','$1').Trim() } else { 'Unknown' }
|
|
21
|
+
|
|
22
|
+
# Get timeout values
|
|
23
|
+
$acScreen = (powercfg /query SCHEME_CURRENT SUB_VIDEO VIDEOIDLE 2>$null | Select-String 'Current AC Power Setting Index' | ForEach-Object { ($_ -split '0x')[1] }) -as [int]
|
|
24
|
+
$dcScreen = (powercfg /query SCHEME_CURRENT SUB_VIDEO VIDEOIDLE 2>$null | Select-String 'Current DC Power Setting Index' | ForEach-Object { ($_ -split '0x')[1] }) -as [int]
|
|
25
|
+
$acSleep = (powercfg /query SCHEME_CURRENT SUB_SLEEP STANDBYIDLE 2>$null | Select-String 'Current AC Power Setting Index' | ForEach-Object { ($_ -split '0x')[1] }) -as [int]
|
|
26
|
+
$dcSleep = (powercfg /query SCHEME_CURRENT SUB_SLEEP STANDBYIDLE 2>$null | Select-String 'Current DC Power Setting Index' | ForEach-Object { ($_ -split '0x')[1] }) -as [int]
|
|
27
|
+
|
|
28
|
+
[PSCustomObject]@{
|
|
29
|
+
HasBattery = $null -ne $battery
|
|
30
|
+
BatteryPct = if ($battery) { $battery.EstimatedChargeRemaining } else { $null }
|
|
31
|
+
Charging = if ($battery) { $battery.BatteryStatus -eq 2 } else { $null }
|
|
32
|
+
ACPower = if ($battery) { $battery.BatteryStatus -eq 2 -or $battery.BatteryStatus -eq 6 } else { $true }
|
|
33
|
+
TimeRemaining = if ($battery -and $battery.EstimatedRunTime -and $battery.EstimatedRunTime -lt 71582788) { "$([math]::Floor($battery.EstimatedRunTime / 60))h $($battery.EstimatedRunTime % 60)m" } else { $null }
|
|
34
|
+
PowerPlan = $planName
|
|
35
|
+
ScreenTimeout_AC = if ($acScreen) { "$([math]::Floor($acScreen / 60))m" } else { 'Never' }
|
|
36
|
+
ScreenTimeout_DC = if ($dcScreen) { "$([math]::Floor($dcScreen / 60))m" } else { 'Never' }
|
|
37
|
+
SleepTimeout_AC = if ($acSleep) { "$([math]::Floor($acSleep / 60))m" } else { 'Never' }
|
|
38
|
+
SleepTimeout_DC = if ($dcSleep) { "$([math]::Floor($dcSleep / 60))m" } else { 'Never' }
|
|
39
|
+
} | ConvertTo-Json -Compress`;
|
|
40
|
+
|
|
41
|
+
const result = await runPowerShell(ps, { timeout: 30000 });
|
|
42
|
+
if (result.exitCode !== 0) {
|
|
43
|
+
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const info = JSON.parse(result.stdout);
|
|
47
|
+
const lines: string[] = [];
|
|
48
|
+
|
|
49
|
+
if (info.HasBattery) {
|
|
50
|
+
const icon = info.Charging ? '🔌' : '🔋';
|
|
51
|
+
lines.push(`${icon} Battery: ${info.BatteryPct}%${info.Charging ? ' (charging)' : ''}`);
|
|
52
|
+
if (info.TimeRemaining) lines.push(`Estimated remaining: ${info.TimeRemaining}`);
|
|
53
|
+
} else {
|
|
54
|
+
lines.push(`AC Power (no battery)`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
lines.push(`Power Plan: ${info.PowerPlan}`);
|
|
58
|
+
lines.push(``);
|
|
59
|
+
lines.push(`Screen timeout: ${info.ScreenTimeout_AC} (AC) / ${info.ScreenTimeout_DC} (battery)`);
|
|
60
|
+
lines.push(`Sleep timeout: ${info.SleepTimeout_AC} (AC) / ${info.SleepTimeout_DC} (battery)`);
|
|
61
|
+
|
|
62
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
server.tool(
|
|
67
|
+
'windows_power_action',
|
|
68
|
+
'Execute power actions: sleep, hibernate, lock, shutdown, restart, or switch power plan.',
|
|
69
|
+
{
|
|
70
|
+
action: z.enum(['sleep', 'hibernate', 'lock', 'shutdown', 'restart', 'logoff', 'plan']).describe('Power action'),
|
|
71
|
+
delay: z.number().optional().describe('Delay in seconds (for shutdown/restart)'),
|
|
72
|
+
cancel: z.boolean().optional().describe('Cancel a scheduled shutdown/restart'),
|
|
73
|
+
plan: z.enum(['balanced', 'performance', 'powersaver']).optional().describe('Power plan to switch to (when action=plan)'),
|
|
74
|
+
},
|
|
75
|
+
async ({ action, delay, cancel, plan }) => {
|
|
76
|
+
if (cancel) {
|
|
77
|
+
const result = await runPowerShell('shutdown /a 2>&1; "Scheduled shutdown cancelled"');
|
|
78
|
+
return { content: [{ type: 'text', text: result.stdout || result.stderr }] };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let ps: string;
|
|
82
|
+
const delayArg = delay ? `/t ${delay}` : '';
|
|
83
|
+
|
|
84
|
+
switch (action) {
|
|
85
|
+
case 'sleep':
|
|
86
|
+
ps = `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Application]::SetSuspendState('Suspend', $false, $false); "Sleeping..."`;
|
|
87
|
+
break;
|
|
88
|
+
case 'hibernate':
|
|
89
|
+
ps = `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Application]::SetSuspendState('Hibernate', $false, $false); "Hibernating..."`;
|
|
90
|
+
break;
|
|
91
|
+
case 'lock':
|
|
92
|
+
ps = `rundll32.exe user32.dll,LockWorkStation; "Workstation locked"`;
|
|
93
|
+
break;
|
|
94
|
+
case 'shutdown':
|
|
95
|
+
ps = `shutdown /s /f ${delayArg}; "Shutdown initiated${delay ? ` in ${delay}s` : ''}"`;
|
|
96
|
+
break;
|
|
97
|
+
case 'restart':
|
|
98
|
+
ps = `shutdown /r /f ${delayArg}; "Restart initiated${delay ? ` in ${delay}s` : ''}"`;
|
|
99
|
+
break;
|
|
100
|
+
case 'logoff':
|
|
101
|
+
ps = `shutdown /l; "Logging off..."`;
|
|
102
|
+
break;
|
|
103
|
+
case 'plan':
|
|
104
|
+
if (!plan) {
|
|
105
|
+
return { content: [{ type: 'text', text: 'Specify a plan: balanced, performance, or powersaver' }], isError: true };
|
|
106
|
+
}
|
|
107
|
+
const planGuids: Record<string, string> = {
|
|
108
|
+
balanced: '381b4222-f694-41f0-9685-ff5bb260df2e',
|
|
109
|
+
performance: '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c',
|
|
110
|
+
powersaver: 'a1841308-3541-4fab-bc81-f71556f20b4a',
|
|
111
|
+
};
|
|
112
|
+
ps = `powercfg /setactive ${planGuids[plan]}; $p = powercfg /getactivescheme; "Switched to: $($p -replace '^.*\\((.*)\\).*$','$1')"`;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const result = await runPowerShell(ps, { timeout: 30000 });
|
|
117
|
+
return {
|
|
118
|
+
content: [{ type: 'text', text: result.stdout || result.stderr }],
|
|
119
|
+
isError: result.exitCode !== 0,
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
*
|
|
4
|
+
* Tool: windows_printer_list (#50)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { runPowerShell } from '../shell.js';
|
|
10
|
+
|
|
11
|
+
export function registerPrinterTools(server: McpServer): void {
|
|
12
|
+
server.tool(
|
|
13
|
+
'windows_printer_list',
|
|
14
|
+
'List printers, show print queues, set default, or clear a queue.',
|
|
15
|
+
{
|
|
16
|
+
action: z.enum(['list', 'queue', 'set_default', 'clear_queue']).default('list').describe('Action'),
|
|
17
|
+
printer: z.string().optional().describe('Printer name (for queue/set_default/clear_queue)'),
|
|
18
|
+
},
|
|
19
|
+
async ({ action, printer }) => {
|
|
20
|
+
switch (action) {
|
|
21
|
+
case 'list': {
|
|
22
|
+
const ps = `
|
|
23
|
+
Get-Printer -ErrorAction SilentlyContinue | ForEach-Object {
|
|
24
|
+
$default = if ((Get-CimInstance Win32_Printer -Filter "Name='$($_.Name.Replace("'","''"))'" -ErrorAction SilentlyContinue).Default) { $true } else { $false }
|
|
25
|
+
[PSCustomObject]@{
|
|
26
|
+
Name = $_.Name
|
|
27
|
+
Status = $_.PrinterStatus
|
|
28
|
+
Type = $_.Type
|
|
29
|
+
Port = $_.PortName
|
|
30
|
+
Driver = $_.DriverName
|
|
31
|
+
Default = $default
|
|
32
|
+
Shared = $_.Shared
|
|
33
|
+
}
|
|
34
|
+
} | ConvertTo-Json -Depth 3 -Compress`;
|
|
35
|
+
|
|
36
|
+
const result = await runPowerShell(ps, { timeout: 45000 });
|
|
37
|
+
if (result.exitCode !== 0) {
|
|
38
|
+
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
|
|
39
|
+
}
|
|
40
|
+
if (!result.stdout) {
|
|
41
|
+
return { content: [{ type: 'text', text: 'No printers found.' }] };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const printers = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
|
|
45
|
+
const lines = printers.map((p: { Name: string; Status: string; Driver: string; Port: string; Default: boolean }) => {
|
|
46
|
+
const def = p.Default ? ' *' : ' ';
|
|
47
|
+
return `${def} ${(p.Name || '').padEnd(35).slice(0, 35)} ${(p.Driver || '').padEnd(25).slice(0, 25)} ${p.Port || ''}`;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const header = ` ${'Name'.padEnd(35)} ${'Driver'.padEnd(25)} Port`;
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: 'text', text: `${header}\n${'─'.repeat(80)}\n${lines.join('\n')}\n\n${printers.length} printers (* = default)` }],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
case 'queue': {
|
|
57
|
+
if (!printer) {
|
|
58
|
+
return { content: [{ type: 'text', text: 'Queue requires printer name.' }], isError: true };
|
|
59
|
+
}
|
|
60
|
+
const ps = `
|
|
61
|
+
Get-PrintJob -PrinterName '${printer.replace(/'/g, "''")}' -ErrorAction Stop | ForEach-Object {
|
|
62
|
+
[PSCustomObject]@{
|
|
63
|
+
Id = $_.Id
|
|
64
|
+
Document = $_.DocumentName
|
|
65
|
+
Status = $_.JobStatus
|
|
66
|
+
Pages = $_.TotalPages
|
|
67
|
+
Size = "$([math]::Round($_.Size / 1KB, 1)) KB"
|
|
68
|
+
Submitted = $_.SubmittedTime.ToString('yyyy-MM-dd HH:mm')
|
|
69
|
+
}
|
|
70
|
+
} | ConvertTo-Json -Depth 3 -Compress`;
|
|
71
|
+
|
|
72
|
+
const result = await runPowerShell(ps, { timeout: 10000 });
|
|
73
|
+
if (result.exitCode !== 0) {
|
|
74
|
+
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
|
|
75
|
+
}
|
|
76
|
+
if (!result.stdout) {
|
|
77
|
+
return { content: [{ type: 'text', text: `Print queue for "${printer}" is empty.` }] };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const jobs = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
|
|
81
|
+
const lines = jobs.map((j: { Id: number; Document: string; Status: string; Pages: number; Size: string }) =>
|
|
82
|
+
` #${j.Id} ${(j.Document || '').padEnd(30).slice(0, 30)} ${(j.Status || '').padEnd(12)} ${j.Pages} pg ${j.Size}`,
|
|
83
|
+
);
|
|
84
|
+
return { content: [{ type: 'text', text: `Queue for "${printer}":\n${lines.join('\n')}` }] };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
case 'set_default': {
|
|
88
|
+
if (!printer) {
|
|
89
|
+
return { content: [{ type: 'text', text: 'set_default requires printer name.' }], isError: true };
|
|
90
|
+
}
|
|
91
|
+
const ps = `
|
|
92
|
+
$p = Get-CimInstance Win32_Printer -Filter "Name='${printer.replace(/'/g, "''")}'" -ErrorAction Stop
|
|
93
|
+
Invoke-CimMethod -InputObject $p -MethodName SetDefaultPrinter | Out-Null
|
|
94
|
+
"Default printer set to: ${printer}"`;
|
|
95
|
+
const result = await runPowerShell(ps, { timeout: 10000 });
|
|
96
|
+
return { content: [{ type: 'text', text: result.stdout || result.stderr }] };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
case 'clear_queue': {
|
|
100
|
+
if (!printer) {
|
|
101
|
+
return { content: [{ type: 'text', text: 'clear_queue requires printer name.' }], isError: true };
|
|
102
|
+
}
|
|
103
|
+
const ps = `
|
|
104
|
+
$jobs = Get-PrintJob -PrinterName '${printer.replace(/'/g, "''")}' -ErrorAction Stop
|
|
105
|
+
$count = @($jobs).Count
|
|
106
|
+
$jobs | Remove-PrintJob -ErrorAction SilentlyContinue
|
|
107
|
+
"Cleared $count job(s) from ${printer}"`;
|
|
108
|
+
const result = await runPowerShell(ps, { timeout: 10000 });
|
|
109
|
+
return { content: [{ type: 'text', text: result.stdout || result.stderr }] };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
);
|
|
114
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
*
|
|
4
|
+
* Tool: windows_process_list — List running processes (#2)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { runPowerShell } from '../shell.js';
|
|
10
|
+
|
|
11
|
+
interface ProcessInfo {
|
|
12
|
+
PID: number;
|
|
13
|
+
Name: string;
|
|
14
|
+
CPU: number;
|
|
15
|
+
MemoryMB: number;
|
|
16
|
+
WindowTitle: string;
|
|
17
|
+
Path: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function registerProcessTools(server: McpServer): void {
|
|
21
|
+
server.tool(
|
|
22
|
+
'windows_process_list',
|
|
23
|
+
'List running processes with PID, name, CPU, memory, window title, and path.',
|
|
24
|
+
{
|
|
25
|
+
filter: z.string().optional().describe('Filter by process name (substring match)'),
|
|
26
|
+
sort: z.enum(['cpu', 'memory', 'name']).default('memory').describe('Sort order'),
|
|
27
|
+
limit: z.number().default(50).describe('Max results to return'),
|
|
28
|
+
},
|
|
29
|
+
async ({ filter, sort, limit }) => {
|
|
30
|
+
const filterClause = filter
|
|
31
|
+
? `| Where-Object { $_.Name -like '*${filter.replace(/'/g, "''")}*' }`
|
|
32
|
+
: '';
|
|
33
|
+
|
|
34
|
+
const sortClause = sort === 'cpu'
|
|
35
|
+
? '| Sort-Object CPU -Descending'
|
|
36
|
+
: sort === 'name'
|
|
37
|
+
? '| Sort-Object Name'
|
|
38
|
+
: '| Sort-Object WorkingSet64 -Descending';
|
|
39
|
+
|
|
40
|
+
const ps = `
|
|
41
|
+
Get-Process ${filterClause} ${sortClause} | Select-Object -First ${limit} |
|
|
42
|
+
ForEach-Object {
|
|
43
|
+
[PSCustomObject]@{
|
|
44
|
+
PID = $_.Id
|
|
45
|
+
Name = $_.ProcessName
|
|
46
|
+
CPU = [math]::Round($_.CPU, 1)
|
|
47
|
+
MemoryMB = [math]::Round($_.WorkingSet64 / 1MB, 1)
|
|
48
|
+
WindowTitle = $_.MainWindowTitle
|
|
49
|
+
Path = $_.Path
|
|
50
|
+
}
|
|
51
|
+
} | ConvertTo-Json -Depth 3 -Compress`;
|
|
52
|
+
|
|
53
|
+
const result = await runPowerShell(ps);
|
|
54
|
+
|
|
55
|
+
if (result.exitCode !== 0) {
|
|
56
|
+
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let processes: ProcessInfo[] = [];
|
|
60
|
+
if (result.stdout) {
|
|
61
|
+
const parsed = JSON.parse(result.stdout);
|
|
62
|
+
processes = Array.isArray(parsed) ? parsed : [parsed];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const lines = processes.map(p =>
|
|
66
|
+
`${String(p.PID).padStart(7)} ${p.Name.padEnd(25).slice(0, 25)} ${String(p.CPU ?? 0).padStart(8)}s ${String(p.MemoryMB).padStart(8)} MB ${p.WindowTitle || ''}`,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const header = `${'PID'.padStart(7)} ${'Name'.padEnd(25)} ${'CPU'.padStart(8)} ${'Memory'.padStart(8)} Window Title`;
|
|
70
|
+
const separator = '-'.repeat(90);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
content: [{
|
|
74
|
+
type: 'text',
|
|
75
|
+
text: `${header}\n${separator}\n${lines.join('\n')}\n\n${processes.length} processes`,
|
|
76
|
+
}],
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
*
|
|
4
|
+
* Tool: windows_process_kill (#3)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { runPowerShell } from '../shell.js';
|
|
10
|
+
|
|
11
|
+
export function registerProcessKillTools(server: McpServer): void {
|
|
12
|
+
server.tool(
|
|
13
|
+
'windows_process_kill',
|
|
14
|
+
'Terminate running processes by PID or name.',
|
|
15
|
+
{
|
|
16
|
+
pid: z.number().optional().describe('Process ID to kill'),
|
|
17
|
+
name: z.string().optional().describe('Process name to kill (all matching)'),
|
|
18
|
+
force: z.boolean().default(false).describe('Force termination'),
|
|
19
|
+
},
|
|
20
|
+
async ({ pid, name, force }) => {
|
|
21
|
+
if (!pid && !name) {
|
|
22
|
+
return { content: [{ type: 'text', text: 'Provide either pid or name.' }], isError: true };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const forceFlag = force ? ' -Force' : '';
|
|
26
|
+
let ps: string;
|
|
27
|
+
|
|
28
|
+
if (pid) {
|
|
29
|
+
ps = `
|
|
30
|
+
$p = Get-Process -Id ${pid} -ErrorAction SilentlyContinue
|
|
31
|
+
if ($p) {
|
|
32
|
+
$n = $p.ProcessName
|
|
33
|
+
Stop-Process -Id ${pid}${forceFlag} -ErrorAction Stop
|
|
34
|
+
"Killed PID ${pid} ($n)"
|
|
35
|
+
} else {
|
|
36
|
+
"No process with PID ${pid}"
|
|
37
|
+
}`;
|
|
38
|
+
} else {
|
|
39
|
+
ps = `
|
|
40
|
+
$procs = Get-Process -Name '${name!.replace(/'/g, "''")}' -ErrorAction SilentlyContinue
|
|
41
|
+
if ($procs) {
|
|
42
|
+
$count = @($procs).Count
|
|
43
|
+
$procs | Stop-Process${forceFlag} -ErrorAction Stop
|
|
44
|
+
"Killed $count process(es) named '${name}'"
|
|
45
|
+
} else {
|
|
46
|
+
"No processes named '${name}'"
|
|
47
|
+
}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const result = await runPowerShell(ps);
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: 'text', text: result.stdout || result.stderr }],
|
|
53
|
+
isError: result.exitCode !== 0,
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
*
|
|
4
|
+
* Tool: windows_recycle_bin (#26)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { runPowerShell } from '../shell.js';
|
|
10
|
+
|
|
11
|
+
export function registerRecycleBinTools(server: McpServer): void {
|
|
12
|
+
server.tool(
|
|
13
|
+
'windows_recycle_bin',
|
|
14
|
+
'Manage the Recycle Bin: list items, get size, restore items, or empty.',
|
|
15
|
+
{
|
|
16
|
+
action: z.enum(['list', 'size', 'empty', 'restore']).default('list').describe('Action'),
|
|
17
|
+
filter: z.string().optional().describe('Filter by filename (for list/restore)'),
|
|
18
|
+
limit: z.number().default(30).describe('Max items (for list)'),
|
|
19
|
+
},
|
|
20
|
+
async ({ action, filter, limit }) => {
|
|
21
|
+
switch (action) {
|
|
22
|
+
case 'size': {
|
|
23
|
+
const ps = `
|
|
24
|
+
$shell = New-Object -ComObject Shell.Application
|
|
25
|
+
$bin = $shell.Namespace(10)
|
|
26
|
+
$items = $bin.Items()
|
|
27
|
+
$count = $items.Count
|
|
28
|
+
$totalSize = 0
|
|
29
|
+
for ($i = 0; $i -lt $count; $i++) {
|
|
30
|
+
$totalSize += $bin.GetDetailsOf($items.Item($i), 2) -replace '[^0-9]','' -as [long]
|
|
31
|
+
}
|
|
32
|
+
[PSCustomObject]@{
|
|
33
|
+
Count = $count
|
|
34
|
+
SizeMB = [math]::Round($totalSize / 1MB, 1)
|
|
35
|
+
} | ConvertTo-Json -Compress`;
|
|
36
|
+
|
|
37
|
+
const result = await runPowerShell(ps, { timeout: 15000 });
|
|
38
|
+
if (result.exitCode !== 0) {
|
|
39
|
+
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
|
|
40
|
+
}
|
|
41
|
+
const info = JSON.parse(result.stdout);
|
|
42
|
+
return { content: [{ type: 'text', text: `Recycle Bin: ${info.Count} items, ${info.SizeMB} MB` }] };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
case 'list': {
|
|
46
|
+
const filterClause = filter
|
|
47
|
+
? `| Where-Object { $_.Name -like '*${filter.replace(/'/g, "''")}*' }`
|
|
48
|
+
: '';
|
|
49
|
+
|
|
50
|
+
const ps = `
|
|
51
|
+
$shell = New-Object -ComObject Shell.Application
|
|
52
|
+
$bin = $shell.Namespace(10)
|
|
53
|
+
$items = @()
|
|
54
|
+
foreach ($item in $bin.Items()) {
|
|
55
|
+
$items += [PSCustomObject]@{
|
|
56
|
+
Name = $item.Name
|
|
57
|
+
OriginalPath = $bin.GetDetailsOf($item, 1)
|
|
58
|
+
Size = $bin.GetDetailsOf($item, 2)
|
|
59
|
+
DeletedDate = $bin.GetDetailsOf($item, 3)
|
|
60
|
+
Type = $bin.GetDetailsOf($item, 4)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
$items ${filterClause} | Select-Object -First ${limit} | ConvertTo-Json -Depth 3 -Compress`;
|
|
64
|
+
|
|
65
|
+
const result = await runPowerShell(ps, { timeout: 20000 });
|
|
66
|
+
if (result.exitCode !== 0) {
|
|
67
|
+
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
|
|
68
|
+
}
|
|
69
|
+
if (!result.stdout) {
|
|
70
|
+
return { content: [{ type: 'text', text: 'Recycle Bin is empty.' }] };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const items = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
|
|
74
|
+
const lines = items.map((i: { Name: string; OriginalPath: string; Size: string; DeletedDate: string }) =>
|
|
75
|
+
`${(i.Name || '').padEnd(35).slice(0, 35)} ${(i.Size || '').padStart(10)} ${(i.DeletedDate || '').padEnd(20)} ${(i.OriginalPath || '').slice(0, 40)}`,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const header = `${'Name'.padEnd(35)} ${'Size'.padStart(10)} ${'Deleted'.padEnd(20)} Original Path`;
|
|
79
|
+
return {
|
|
80
|
+
content: [{ type: 'text', text: `${header}\n${'─'.repeat(110)}\n${lines.join('\n')}\n\n${items.length} items` }],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
case 'empty': {
|
|
85
|
+
const ps = `
|
|
86
|
+
$shell = New-Object -ComObject Shell.Application
|
|
87
|
+
$count = $shell.Namespace(10).Items().Count
|
|
88
|
+
if ($count -eq 0) { "Recycle Bin is already empty." }
|
|
89
|
+
else {
|
|
90
|
+
Clear-RecycleBin -Force -ErrorAction Stop
|
|
91
|
+
"Emptied Recycle Bin ($count items removed)"
|
|
92
|
+
}`;
|
|
93
|
+
const result = await runPowerShell(ps, { timeout: 15000 });
|
|
94
|
+
return {
|
|
95
|
+
content: [{ type: 'text', text: result.stdout || result.stderr }],
|
|
96
|
+
isError: result.exitCode !== 0,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
case 'restore': {
|
|
101
|
+
if (!filter) {
|
|
102
|
+
return { content: [{ type: 'text', text: 'Restore requires a filter to identify which item(s) to restore.' }], isError: true };
|
|
103
|
+
}
|
|
104
|
+
const ps = `
|
|
105
|
+
$shell = New-Object -ComObject Shell.Application
|
|
106
|
+
$bin = $shell.Namespace(10)
|
|
107
|
+
$restored = 0
|
|
108
|
+
foreach ($item in $bin.Items()) {
|
|
109
|
+
if ($item.Name -like '*${filter.replace(/'/g, "''")}*') {
|
|
110
|
+
$origPath = $bin.GetDetailsOf($item, 1)
|
|
111
|
+
$item.InvokeVerb('undelete')
|
|
112
|
+
$restored++
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if ($restored -gt 0) { "Restored $restored item(s)" } else { "No matching items found" }`;
|
|
116
|
+
|
|
117
|
+
const result = await runPowerShell(ps, { timeout: 15000 });
|
|
118
|
+
return {
|
|
119
|
+
content: [{ type: 'text', text: result.stdout || result.stderr }],
|
|
120
|
+
isError: result.exitCode !== 0,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
}
|