unix-disk-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +17 -0
- package/README.md +150 -0
- package/config.sample.json +13 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +65 -0
- package/dist/commands/delete.d.ts +5 -0
- package/dist/commands/delete.js +254 -0
- package/dist/commands/setup.d.ts +5 -0
- package/dist/commands/setup.js +206 -0
- package/dist/config/index.d.ts +11 -0
- package/dist/config/index.js +59 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +13 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +15 -0
- package/dist/tools/discovery.d.ts +3 -0
- package/dist/tools/discovery.js +267 -0
- package/dist/tools/exploration.d.ts +3 -0
- package/dist/tools/exploration.js +311 -0
- package/dist/tools/staging.d.ts +6 -0
- package/dist/tools/staging.js +236 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
GNU GENERAL PUBLIC LICENSE
|
|
2
|
+
Version 3, 29 June 2007
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2026 juljus
|
|
5
|
+
|
|
6
|
+
This program is free software: you can redistribute it and/or modify
|
|
7
|
+
it under the terms of the GNU General Public License as published by
|
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
(at your option) any later version.
|
|
10
|
+
|
|
11
|
+
This program is distributed in the hope that it will be useful,
|
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
GNU General Public License for more details.
|
|
15
|
+
|
|
16
|
+
You should have received a copy of the GNU General Public License
|
|
17
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
package/README.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# unix-disk-mcp
|
|
2
|
+
|
|
3
|
+
AI-assisted disk cleanup for Unix systems (macOS and Linux). Let an LLM explore your filesystem, identify unused files, and suggest what to delete. **You stay in control** — the AI can only suggest and stage items, never delete them.
|
|
4
|
+
|
|
5
|
+
## Why?
|
|
6
|
+
|
|
7
|
+
Traditional disk cleaners use fixed rules. This tool lets AI *reason* about your actual usage:
|
|
8
|
+
- "3 Node.js installations via different methods"
|
|
9
|
+
- "40GB VM untouched for 14 months"
|
|
10
|
+
- "Docker images for deleted projects"
|
|
11
|
+
- "Homebrew packages nothing depends on"
|
|
12
|
+
|
|
13
|
+
## Security
|
|
14
|
+
|
|
15
|
+
The AI **cannot delete files**. Ever. This is architectural:
|
|
16
|
+
- ✅ Explore filesystem
|
|
17
|
+
- ✅ Suggest items to delete
|
|
18
|
+
- ✅ Stage items for deletion
|
|
19
|
+
- ❌ Cannot execute deletion
|
|
20
|
+
- ❌ Cannot run delete script
|
|
21
|
+
|
|
22
|
+
You run `unix-disk-mcp delete` manually to review and confirm.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install -g unix-disk-mcp
|
|
28
|
+
unix-disk-mcp setup
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The setup wizard configures everything. Or manually:
|
|
32
|
+
|
|
33
|
+
**1. Add to MCP client config:**
|
|
34
|
+
|
|
35
|
+
VS Code: `~/.config/Code/User/mcp.json` (Linux) or `~/Library/Application Support/Code/User/mcp.json` (macOS)
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"servers": {
|
|
39
|
+
"unix-disk-mcp": {
|
|
40
|
+
"type": "stdio",
|
|
41
|
+
"command": "unix-disk-mcp"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Claude Desktop: `~/.config/Claude/claude_desktop_config.json` (Linux) or `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"mcpServers": {
|
|
51
|
+
"unix-disk-mcp": {
|
|
52
|
+
"command": "unix-disk-mcp"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**2. Configure protected paths:**
|
|
59
|
+
|
|
60
|
+
Run `unix-disk-mcp config` to see config location, then edit:
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"protected_paths": ["/System", "/Library", "~/.ssh", "~/.gnupg"],
|
|
64
|
+
"ignore_patterns": [".git"],
|
|
65
|
+
"max_delete_size_gb": 10,
|
|
66
|
+
"dry_run": false
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
**1. Ask AI to explore:**
|
|
73
|
+
- "What's using disk space?"
|
|
74
|
+
- "Find large files I don't need"
|
|
75
|
+
- "Check for old Docker images"
|
|
76
|
+
|
|
77
|
+
**2. AI stages items:**
|
|
78
|
+
```
|
|
79
|
+
Staged for deletion:
|
|
80
|
+
1. ~/.cache/pip (2.3 GB)
|
|
81
|
+
2. ~/Downloads/old-installer.dmg (1.5 GB)
|
|
82
|
+
Total: 3.8 GB
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**3. You delete manually:**
|
|
86
|
+
```bash
|
|
87
|
+
unix-disk-mcp delete
|
|
88
|
+
# Reviews staged items, requires typing HUMAN, then y/N confirmation
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Tools Available to AI
|
|
92
|
+
|
|
93
|
+
**Exploration:**
|
|
94
|
+
- `list_directory` - Browse folders
|
|
95
|
+
- `get_disk_usage` - Disk space overview
|
|
96
|
+
- `find_large_items` - Find big files/folders (supports progressive depth exploration)
|
|
97
|
+
- `get_item_info` - Details on specific paths
|
|
98
|
+
|
|
99
|
+
**Discovery:**
|
|
100
|
+
- `list_applications` - Installed apps with last-opened dates (macOS only)
|
|
101
|
+
- `list_homebrew` - Homebrew packages
|
|
102
|
+
- `list_docker` - Docker images, containers, volumes
|
|
103
|
+
|
|
104
|
+
**Staging:**
|
|
105
|
+
- `stage_for_deletion` - Mark for deletion
|
|
106
|
+
- `unstage` - Remove from staging
|
|
107
|
+
- `get_staged` - View staged items
|
|
108
|
+
|
|
109
|
+
## Platform Support
|
|
110
|
+
|
|
111
|
+
**macOS:**
|
|
112
|
+
- Trash via AppleScript
|
|
113
|
+
- Accurate APFS disk usage (diskutil)
|
|
114
|
+
- App discovery via Spotlight
|
|
115
|
+
|
|
116
|
+
**Linux:**
|
|
117
|
+
- Trash via gio/trash-cli/freedesktop spec
|
|
118
|
+
- Disk usage via df
|
|
119
|
+
- App discovery not yet implemented (use find_large_items on app directories)
|
|
120
|
+
|
|
121
|
+
## Safety Features
|
|
122
|
+
|
|
123
|
+
1. AI cannot delete (architecturally separated)
|
|
124
|
+
2. Terminal check (blocks piped input)
|
|
125
|
+
3. Human verification required (type "HUMAN")
|
|
126
|
+
4. Protected paths cannot be staged
|
|
127
|
+
5. Items go to Trash (recoverable)
|
|
128
|
+
6. Deletion requires manual terminal command
|
|
129
|
+
7. Confirmation prompt before deletion
|
|
130
|
+
8. Deletion history logged
|
|
131
|
+
|
|
132
|
+
## Commands
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
unix-disk-mcp # Start MCP server (default)
|
|
136
|
+
unix-disk-mcp setup # Setup wizard
|
|
137
|
+
unix-disk-mcp delete # Delete staged items (manual only)
|
|
138
|
+
unix-disk-mcp config # Show config location
|
|
139
|
+
unix-disk-mcp help # Show help
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Config & Data
|
|
143
|
+
|
|
144
|
+
- **Config:** `~/.config/unix-disk-mcp/config.json`
|
|
145
|
+
- **Staged items:** `~/.local/share/unix-disk-mcp/staged.json`
|
|
146
|
+
- **History:** `~/.local/share/unix-disk-mcp/history.json`
|
|
147
|
+
|
|
148
|
+
## License
|
|
149
|
+
|
|
150
|
+
MIT
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Main entry point for macos-storage-mcp CLI
|
|
4
|
+
* Routes commands to appropriate handlers
|
|
5
|
+
*/
|
|
6
|
+
const command = process.argv[2];
|
|
7
|
+
async function main() {
|
|
8
|
+
switch (command) {
|
|
9
|
+
case undefined:
|
|
10
|
+
case "server":
|
|
11
|
+
// Start MCP server (default)
|
|
12
|
+
await import("./index.js");
|
|
13
|
+
break;
|
|
14
|
+
case "setup":
|
|
15
|
+
// Run setup wizard
|
|
16
|
+
const { runSetup } = await import("./commands/setup.js");
|
|
17
|
+
await runSetup();
|
|
18
|
+
break;
|
|
19
|
+
case "delete":
|
|
20
|
+
// Execute staged deletions
|
|
21
|
+
const { runDelete } = await import("./commands/delete.js");
|
|
22
|
+
await runDelete();
|
|
23
|
+
break;
|
|
24
|
+
case "config":
|
|
25
|
+
// Show config location
|
|
26
|
+
const { getConfigPath } = await import("./config/index.js");
|
|
27
|
+
console.log(getConfigPath());
|
|
28
|
+
break;
|
|
29
|
+
case "help":
|
|
30
|
+
case "--help":
|
|
31
|
+
case "-h":
|
|
32
|
+
printHelp();
|
|
33
|
+
break;
|
|
34
|
+
default:
|
|
35
|
+
console.error(`Unknown command: ${command}`);
|
|
36
|
+
printHelp();
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function printHelp() {
|
|
41
|
+
console.log(`
|
|
42
|
+
unix-disk-mcp - AI-assisted disk cleanup for Unix systems (macOS and Linux)
|
|
43
|
+
|
|
44
|
+
Usage:
|
|
45
|
+
unix-disk-mcp [command]
|
|
46
|
+
|
|
47
|
+
Commands:
|
|
48
|
+
server Start MCP server (default)
|
|
49
|
+
setup Run interactive setup wizard
|
|
50
|
+
delete Execute staged deletions (manual, with confirmation)
|
|
51
|
+
config Show config file location
|
|
52
|
+
help Show this help message
|
|
53
|
+
|
|
54
|
+
Examples:
|
|
55
|
+
unix-disk-mcp # Start server
|
|
56
|
+
unix-disk-mcp setup # Configure protected paths
|
|
57
|
+
unix-disk-mcp delete # Delete staged items
|
|
58
|
+
unix-disk-mcp config # Show config location
|
|
59
|
+
`);
|
|
60
|
+
}
|
|
61
|
+
main().catch((err) => {
|
|
62
|
+
console.error("Error:", err.message);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
});
|
|
65
|
+
export {};
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
3
|
+
import { execSync } from "child_process";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
import * as readline from "readline";
|
|
7
|
+
import { getStagedFilePath, getHistoryFilePath, } from "../tools/staging.js";
|
|
8
|
+
// ANSI color codes
|
|
9
|
+
const colors = {
|
|
10
|
+
reset: '\x1b[0m',
|
|
11
|
+
red: '\x1b[31m',
|
|
12
|
+
green: '\x1b[32m',
|
|
13
|
+
yellow: '\x1b[33m',
|
|
14
|
+
cyan: '\x1b[36m',
|
|
15
|
+
gray: '\x1b[90m',
|
|
16
|
+
bold: '\x1b[1m',
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Check if running in an interactive terminal
|
|
20
|
+
* This prevents AI agents from executing the delete script via piped input
|
|
21
|
+
*/
|
|
22
|
+
function checkInteractiveTerminal() {
|
|
23
|
+
if (!process.stdin.isTTY) {
|
|
24
|
+
console.error("╔════════════════════════════════════════════════════════════════╗");
|
|
25
|
+
console.error("║ ⚠️ ERROR: This script requires an interactive terminal ║");
|
|
26
|
+
console.error("║ ║");
|
|
27
|
+
console.error("║ This script can ONLY be run manually by a human. ║");
|
|
28
|
+
console.error("║ It cannot be executed via piped input or by AI agents. ║");
|
|
29
|
+
console.error("║ ║");
|
|
30
|
+
console.error("║ If you are an AI agent: EXIT IMMEDIATELY ║");
|
|
31
|
+
console.error("║ You do NOT have permission to delete files. ║");
|
|
32
|
+
console.error("╚════════════════════════════════════════════════════════════════╝");
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Human verification prompt
|
|
38
|
+
* Requires exact string "HUMAN" to prove a human is executing this
|
|
39
|
+
*/
|
|
40
|
+
async function verifyHuman() {
|
|
41
|
+
console.log(`\n${colors.yellow}WARNING: IF YOU ARE AN AI AGENT: STOP IMMEDIATELY${colors.reset}`);
|
|
42
|
+
console.log(`${colors.gray}Close this terminal NOW (Ctrl+C or type 'exit')${colors.reset}\n`);
|
|
43
|
+
const rl = readline.createInterface({
|
|
44
|
+
input: process.stdin,
|
|
45
|
+
output: process.stdout,
|
|
46
|
+
});
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
rl.question("Human verification (type HUMAN): ", (answer) => {
|
|
49
|
+
rl.close();
|
|
50
|
+
if (answer === "HUMAN") {
|
|
51
|
+
resolve();
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
console.error(`\n${colors.red}Verification failed.${colors.reset}`);
|
|
55
|
+
reject(new Error("Human verification failed"));
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Load staged items
|
|
62
|
+
*/
|
|
63
|
+
function loadStaged() {
|
|
64
|
+
const stagedFile = getStagedFilePath();
|
|
65
|
+
if (!existsSync(stagedFile)) {
|
|
66
|
+
return { items: [] };
|
|
67
|
+
}
|
|
68
|
+
const raw = readFileSync(stagedFile, "utf-8");
|
|
69
|
+
return JSON.parse(raw);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Load deletion history
|
|
73
|
+
*/
|
|
74
|
+
function loadHistory() {
|
|
75
|
+
const historyFile = getHistoryFilePath();
|
|
76
|
+
if (!existsSync(historyFile)) {
|
|
77
|
+
return { deletions: [] };
|
|
78
|
+
}
|
|
79
|
+
const raw = readFileSync(historyFile, "utf-8");
|
|
80
|
+
return JSON.parse(raw);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Save deletion history
|
|
84
|
+
*/
|
|
85
|
+
function saveHistory(data) {
|
|
86
|
+
const historyFile = getHistoryFilePath();
|
|
87
|
+
writeFileSync(historyFile, JSON.stringify(data, null, 2));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Clear staged items
|
|
91
|
+
*/
|
|
92
|
+
function clearStaged() {
|
|
93
|
+
const stagedFile = getStagedFilePath();
|
|
94
|
+
writeFileSync(stagedFile, JSON.stringify({ items: [] }, null, 2));
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Format bytes to human-readable size
|
|
98
|
+
*/
|
|
99
|
+
function formatSize(bytes) {
|
|
100
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
101
|
+
let size = bytes;
|
|
102
|
+
let unitIndex = 0;
|
|
103
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
104
|
+
size /= 1024;
|
|
105
|
+
unitIndex++;
|
|
106
|
+
}
|
|
107
|
+
return `${size.toFixed(2)} ${units[unitIndex]}`;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Move item to Trash using platform-specific method
|
|
111
|
+
*/
|
|
112
|
+
function moveToTrash(path) {
|
|
113
|
+
try {
|
|
114
|
+
if (process.platform === 'darwin') {
|
|
115
|
+
// macOS: Use AppleScript
|
|
116
|
+
const script = `
|
|
117
|
+
tell application "Finder"
|
|
118
|
+
move POSIX file "${path}" to trash
|
|
119
|
+
end tell
|
|
120
|
+
`;
|
|
121
|
+
execSync(`osascript -e '${script}'`, { stdio: "pipe" });
|
|
122
|
+
}
|
|
123
|
+
else if (process.platform === 'linux') {
|
|
124
|
+
// Linux: Try gio first, fall back to trash-cli
|
|
125
|
+
try {
|
|
126
|
+
execSync(`gio trash "${path}"`, { stdio: "pipe" });
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// Fallback: try trash-cli
|
|
130
|
+
try {
|
|
131
|
+
execSync(`trash-put "${path}"`, { stdio: "pipe" });
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Last resort: move to freedesktop trash
|
|
135
|
+
const trashDir = join(homedir(), '.local', 'share', 'Trash', 'files');
|
|
136
|
+
const infoDir = join(homedir(), '.local', 'share', 'Trash', 'info');
|
|
137
|
+
mkdirSync(trashDir, { recursive: true });
|
|
138
|
+
mkdirSync(infoDir, { recursive: true });
|
|
139
|
+
const basename = require('path').basename(path);
|
|
140
|
+
const timestamp = new Date().toISOString();
|
|
141
|
+
execSync(`mv "${path}" "${trashDir}/${basename}"`, { stdio: "pipe" });
|
|
142
|
+
// Create .trashinfo file
|
|
143
|
+
const infoContent = `[Trash Info]\nPath=${path}\nDeletionDate=${timestamp}`;
|
|
144
|
+
require('fs').writeFileSync(`${infoDir}/${basename}.trashinfo`, infoContent);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
return {
|
|
150
|
+
success: false,
|
|
151
|
+
error: `Platform ${process.platform} not supported`,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
return { success: true };
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
return {
|
|
158
|
+
success: false,
|
|
159
|
+
error: error.message || "Unknown error",
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Confirm deletion with user
|
|
165
|
+
*/
|
|
166
|
+
async function confirmDeletion(items) {
|
|
167
|
+
const totalSize = items.reduce((sum, item) => sum + item.size, 0);
|
|
168
|
+
console.log(`\n${colors.bold}Staged for deletion:${colors.reset}\n`);
|
|
169
|
+
items.forEach((item, index) => {
|
|
170
|
+
const reason = item.reason ? `${colors.gray} - ${item.reason}${colors.reset}` : "";
|
|
171
|
+
console.log(`${colors.cyan}${index + 1}. ${item.path}${colors.reset}`);
|
|
172
|
+
console.log(` ${colors.bold}${formatSize(item.size)}${colors.reset}${reason}\n`);
|
|
173
|
+
});
|
|
174
|
+
console.log(`${colors.bold}Total: ${items.length} items (${formatSize(totalSize)})${colors.reset}\n`);
|
|
175
|
+
const rl = readline.createInterface({
|
|
176
|
+
input: process.stdin,
|
|
177
|
+
output: process.stdout,
|
|
178
|
+
});
|
|
179
|
+
return new Promise((resolve) => {
|
|
180
|
+
rl.question("Move to Trash? [y/N]: ", (answer) => {
|
|
181
|
+
rl.close();
|
|
182
|
+
resolve(answer.toLowerCase() === "y");
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Main delete function
|
|
188
|
+
*/
|
|
189
|
+
export async function runDelete() {
|
|
190
|
+
console.log(`\n${colors.bold}unix-disk-mcp delete${colors.reset}`);
|
|
191
|
+
// Security check: Ensure interactive terminal
|
|
192
|
+
checkInteractiveTerminal();
|
|
193
|
+
// Load staged items
|
|
194
|
+
const staged = loadStaged();
|
|
195
|
+
if (staged.items.length === 0) {
|
|
196
|
+
console.log(`\n${colors.green}No items staged for deletion.${colors.reset}`);
|
|
197
|
+
process.exit(0);
|
|
198
|
+
}
|
|
199
|
+
// Human verification
|
|
200
|
+
try {
|
|
201
|
+
await verifyHuman();
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
// Final confirmation
|
|
207
|
+
const confirmed = await confirmDeletion(staged.items);
|
|
208
|
+
if (!confirmed) {
|
|
209
|
+
console.log(`\n${colors.gray}Cancelled.${colors.reset}`);
|
|
210
|
+
process.exit(0);
|
|
211
|
+
}
|
|
212
|
+
// Execute deletions
|
|
213
|
+
console.log(`\n${colors.cyan}Moving to Trash...${colors.reset}\n`);
|
|
214
|
+
const history = loadHistory();
|
|
215
|
+
const timestamp = new Date().toISOString();
|
|
216
|
+
let successCount = 0;
|
|
217
|
+
let failCount = 0;
|
|
218
|
+
for (const item of staged.items) {
|
|
219
|
+
const result = moveToTrash(item.path);
|
|
220
|
+
if (result.success) {
|
|
221
|
+
console.log(`${colors.green}[OK]${colors.reset} ${colors.gray}${item.path}${colors.reset}`);
|
|
222
|
+
successCount++;
|
|
223
|
+
history.deletions.push({
|
|
224
|
+
path: item.path,
|
|
225
|
+
size: item.size,
|
|
226
|
+
reason: item.reason,
|
|
227
|
+
deleted_at: timestamp,
|
|
228
|
+
errors: [],
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
console.log(`${colors.red}[FAIL]${colors.reset} ${colors.gray}${item.path}${colors.reset}`);
|
|
233
|
+
console.log(` ${colors.red}${result.error}${colors.reset}`);
|
|
234
|
+
failCount++;
|
|
235
|
+
history.deletions.push({
|
|
236
|
+
path: item.path,
|
|
237
|
+
size: item.size,
|
|
238
|
+
reason: item.reason,
|
|
239
|
+
deleted_at: timestamp,
|
|
240
|
+
errors: [result.error || "Unknown error"],
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Save history and clear staged
|
|
245
|
+
saveHistory(history);
|
|
246
|
+
clearStaged();
|
|
247
|
+
// Summary
|
|
248
|
+
console.log(`\n${colors.green}Moved ${successCount} items to Trash${colors.reset}`);
|
|
249
|
+
if (failCount > 0) {
|
|
250
|
+
console.log(`${colors.red}Failed: ${failCount} items${colors.reset}`);
|
|
251
|
+
}
|
|
252
|
+
console.log(`\n${colors.gray}History: ${getHistoryFilePath()}${colors.reset}\n`);
|
|
253
|
+
process.exit(failCount > 0 ? 1 : 0);
|
|
254
|
+
}
|