@aladac/hu 0.1.0-a1
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/.tool-versions +1 -0
- package/CLAUDE.md +122 -0
- package/HOOKS-DATA-INTEGRATION.md +457 -0
- package/SAMPLE.md +378 -0
- package/TODO.md +25 -0
- package/biome.json +51 -0
- package/commands/bootstrap.md +13 -0
- package/commands/c.md +1 -0
- package/commands/check-name.md +62 -0
- package/commands/disk.md +141 -0
- package/commands/docs/archive.md +27 -0
- package/commands/docs/check-internal.md +53 -0
- package/commands/docs/cleanup.md +65 -0
- package/commands/docs/consolidate.md +72 -0
- package/commands/docs/get.md +101 -0
- package/commands/docs/list.md +61 -0
- package/commands/docs/sync.md +64 -0
- package/commands/docs/update.md +49 -0
- package/commands/plans/clear.md +23 -0
- package/commands/plans/create.md +71 -0
- package/commands/plans/list.md +21 -0
- package/commands/plans/sync.md +38 -0
- package/commands/reinstall.md +20 -0
- package/commands/replicate.md +303 -0
- package/commands/warp.md +0 -0
- package/doc/README.md +35 -0
- package/doc/claude-code/capabilities.md +202 -0
- package/doc/claude-code/directory-structure.md +246 -0
- package/doc/claude-code/hooks.md +348 -0
- package/doc/claude-code/overview.md +109 -0
- package/doc/claude-code/plugins.md +273 -0
- package/doc/claude-code/sdk-protocols.md +202 -0
- package/document-manifest.toml +29 -0
- package/justfile +39 -0
- package/package.json +33 -0
- package/plans/compiled-watching-feather.md +217 -0
- package/plans/crispy-crafting-pnueli.md +103 -0
- package/plans/greedy-booping-coral.md +146 -0
- package/plans/imperative-sleeping-flamingo.md +192 -0
- package/plans/jaunty-sprouting-marble.md +171 -0
- package/plans/jiggly-discovering-lake.md +68 -0
- package/plans/magical-nibbling-spark.md +144 -0
- package/plans/mellow-kindling-acorn.md +110 -0
- package/plans/recursive-questing-engelbart.md +65 -0
- package/plans/serialized-roaming-kernighan.md +227 -0
- package/plans/structured-wondering-wirth.md +230 -0
- package/plans/vectorized-dreaming-iverson.md +191 -0
- package/plans/velvety-enchanting-ocean.md +92 -0
- package/plans/wiggly-sparking-pixel.md +48 -0
- package/plans/zippy-shimmying-fox.md +188 -0
- package/plugins/installed_plugins.json +4 -0
- package/sample-hooks.json +298 -0
- package/settings.json +24 -0
- package/settings.local.json +7 -0
- package/src/commands/bump.ts +130 -0
- package/src/commands/disk.ts +419 -0
- package/src/commands/docs.ts +729 -0
- package/src/commands/plans.ts +259 -0
- package/src/commands/utils.ts +299 -0
- package/src/index.ts +26 -0
- package/src/lib/colors.ts +87 -0
- package/src/lib/exec.ts +25 -0
- package/src/lib/fs.ts +119 -0
- package/src/lib/html.ts +205 -0
- package/src/lib/spinner.ts +42 -0
- package/src/types/index.ts +61 -0
- package/tests/lib/colors.test.ts +69 -0
- package/tests/lib/fs.test.ts +65 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* Version bumping script
|
|
4
|
+
* Usage: npx tsx src/commands/bump.ts [patch|minor|major]
|
|
5
|
+
* - No args: bump pre-release number (1.0.0-pre1 -> 1.0.0-pre2)
|
|
6
|
+
* - patch: bump patch version (1.0.0-pre1 -> 1.0.1-pre1, or 1.0.0 -> 1.0.1)
|
|
7
|
+
* - minor: bump minor version (1.0.0 -> 1.1.0, or 1.0.0-pre1 -> 1.1.0-pre1)
|
|
8
|
+
* - major: bump major version (1.0.0 -> 2.0.0)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { execSync } from 'node:child_process';
|
|
12
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
|
|
15
|
+
const ROOT_DIR = join(import.meta.dirname, '../..');
|
|
16
|
+
|
|
17
|
+
interface VersionParts {
|
|
18
|
+
major: number;
|
|
19
|
+
minor: number;
|
|
20
|
+
patch: number;
|
|
21
|
+
pre: number | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function parseVersion(version: string): VersionParts {
|
|
25
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)(-pre(\d+))?$/);
|
|
26
|
+
if (!match) {
|
|
27
|
+
throw new Error(`Cannot parse version '${version}'`);
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
major: Number.parseInt(match[1], 10),
|
|
31
|
+
minor: Number.parseInt(match[2], 10),
|
|
32
|
+
patch: Number.parseInt(match[3], 10),
|
|
33
|
+
pre: match[5] ? Number.parseInt(match[5], 10) : null,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function formatVersion(parts: VersionParts): string {
|
|
38
|
+
const base = `${parts.major}.${parts.minor}.${parts.patch}`;
|
|
39
|
+
return parts.pre !== null ? `${base}-pre${parts.pre}` : base;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function bumpVersion(current: string, type?: string): string {
|
|
43
|
+
const parts = parseVersion(current);
|
|
44
|
+
|
|
45
|
+
switch (type) {
|
|
46
|
+
case 'major':
|
|
47
|
+
case '--major':
|
|
48
|
+
parts.major++;
|
|
49
|
+
parts.minor = 0;
|
|
50
|
+
parts.patch = 0;
|
|
51
|
+
if (parts.pre !== null) parts.pre = 1;
|
|
52
|
+
break;
|
|
53
|
+
|
|
54
|
+
case 'minor':
|
|
55
|
+
case '--minor':
|
|
56
|
+
parts.minor++;
|
|
57
|
+
parts.patch = 0;
|
|
58
|
+
if (parts.pre !== null) parts.pre = 1;
|
|
59
|
+
break;
|
|
60
|
+
|
|
61
|
+
case 'patch':
|
|
62
|
+
case '--patch':
|
|
63
|
+
parts.patch++;
|
|
64
|
+
if (parts.pre !== null) parts.pre = 1;
|
|
65
|
+
break;
|
|
66
|
+
|
|
67
|
+
default:
|
|
68
|
+
// Bump pre-release number
|
|
69
|
+
if (parts.pre !== null) {
|
|
70
|
+
parts.pre++;
|
|
71
|
+
} else {
|
|
72
|
+
parts.pre = 1;
|
|
73
|
+
}
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return formatVersion(parts);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function exec(cmd: string): string {
|
|
81
|
+
return execSync(cmd, { cwd: ROOT_DIR, encoding: 'utf-8' }).trim();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function run(cmd: string): void {
|
|
85
|
+
execSync(cmd, { cwd: ROOT_DIR, stdio: 'inherit' });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function main() {
|
|
89
|
+
const arg = process.argv[2];
|
|
90
|
+
|
|
91
|
+
// Read current version from package.json
|
|
92
|
+
const pkgPath = join(ROOT_DIR, 'package.json');
|
|
93
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
94
|
+
const currentVersion = pkg.version;
|
|
95
|
+
|
|
96
|
+
console.log(`Current version: ${currentVersion}`);
|
|
97
|
+
|
|
98
|
+
// Calculate new version
|
|
99
|
+
const newVersion = bumpVersion(currentVersion, arg);
|
|
100
|
+
console.log(`New version: ${newVersion}`);
|
|
101
|
+
|
|
102
|
+
// Update package.json
|
|
103
|
+
pkg.version = newVersion;
|
|
104
|
+
writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
105
|
+
|
|
106
|
+
// Update version in index.ts
|
|
107
|
+
const indexPath = join(ROOT_DIR, 'src/index.ts');
|
|
108
|
+
let indexContent = readFileSync(indexPath, 'utf-8');
|
|
109
|
+
indexContent = indexContent.replace(/version: '[^']+'/, `version: '${newVersion}'`);
|
|
110
|
+
writeFileSync(indexPath, indexContent);
|
|
111
|
+
|
|
112
|
+
console.log('Updated package.json and src/index.ts');
|
|
113
|
+
|
|
114
|
+
// Reinstall
|
|
115
|
+
console.log('Reinstalling...');
|
|
116
|
+
run('npm install && npm link');
|
|
117
|
+
|
|
118
|
+
// Git commit, tag, and push
|
|
119
|
+
run('git add package.json package-lock.json src/index.ts');
|
|
120
|
+
run(`git commit -m "chore: Bump version to ${newVersion}"`);
|
|
121
|
+
run(`git tag "v${newVersion}"`);
|
|
122
|
+
run('git push origin HEAD --tags');
|
|
123
|
+
|
|
124
|
+
console.log(`\nDone! Version ${newVersion} installed and pushed.`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
main().catch((err) => {
|
|
128
|
+
console.error(err.message);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
});
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disk space analysis commands
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { defineCommand } from 'citty';
|
|
6
|
+
import {
|
|
7
|
+
c,
|
|
8
|
+
colorizeSize,
|
|
9
|
+
formatSize,
|
|
10
|
+
parseSize,
|
|
11
|
+
printHeader,
|
|
12
|
+
printSubHeader,
|
|
13
|
+
progressBar,
|
|
14
|
+
} from '../lib/colors.ts';
|
|
15
|
+
import { exec } from '../lib/exec.ts';
|
|
16
|
+
import { startSpinner, stopSpinner } from '../lib/spinner.ts';
|
|
17
|
+
import type { CleanupSuggestion, SpaceHogCheck } from '../types/index.ts';
|
|
18
|
+
|
|
19
|
+
// ─────────────────────────────────────────────────────────────
|
|
20
|
+
// Analysis Functions
|
|
21
|
+
// ─────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
async function getDiskOverview(): Promise<void> {
|
|
24
|
+
startSpinner('Analyzing APFS volumes...');
|
|
25
|
+
|
|
26
|
+
const dfOutput = exec('df -h /System/Volumes/Data');
|
|
27
|
+
const lines = dfOutput.split('\n');
|
|
28
|
+
|
|
29
|
+
let used = '0G';
|
|
30
|
+
let avail = '0G';
|
|
31
|
+
let capacity = '0%';
|
|
32
|
+
let total = '0G';
|
|
33
|
+
if (lines[1]) {
|
|
34
|
+
const parts = lines[1].split(/\s+/);
|
|
35
|
+
total = parts[1];
|
|
36
|
+
used = parts[2];
|
|
37
|
+
avail = parts[3];
|
|
38
|
+
capacity = parts[4];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
stopSpinner(true, 'APFS volumes analyzed');
|
|
42
|
+
|
|
43
|
+
printHeader('💾 DISK OVERVIEW');
|
|
44
|
+
|
|
45
|
+
const pct = Number.parseInt(capacity) || 0;
|
|
46
|
+
console.log(` ${c.bold}Data Volume${c.reset} (/System/Volumes/Data)`);
|
|
47
|
+
console.log();
|
|
48
|
+
console.log(` ${progressBar(pct, 40)} ${c.bold}${capacity}${c.reset}`);
|
|
49
|
+
console.log();
|
|
50
|
+
console.log(
|
|
51
|
+
` ${c.green}●${c.reset} Used: ${c.bold}${used}${c.reset} ${c.blue}●${c.reset} Free: ${c.bold}${avail}${c.reset} ${c.dim}Total: ${total}${c.reset}`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function getHomeBreakdown(): Promise<void> {
|
|
56
|
+
startSpinner('Scanning home directory...');
|
|
57
|
+
|
|
58
|
+
const output = exec('sudo du -sh ~/* ~/.* 2>/dev/null | sort -hr | head -20');
|
|
59
|
+
const lines = output.split('\n').filter((l) => l.trim());
|
|
60
|
+
|
|
61
|
+
stopSpinner(true, 'Home directory scanned');
|
|
62
|
+
|
|
63
|
+
printHeader('🏠 HOME DIRECTORY BREAKDOWN');
|
|
64
|
+
|
|
65
|
+
const rows = lines
|
|
66
|
+
.map((line) => {
|
|
67
|
+
const match = line.match(/^([\d.]+[KMGTP]?)\s+(.+)$/i);
|
|
68
|
+
if (!match) return null;
|
|
69
|
+
const [, size, path] = match;
|
|
70
|
+
const shortPath = path.replace(process.env.HOME || '', '~');
|
|
71
|
+
return `${colorizeSize(size.padStart(8))} ${shortPath}`;
|
|
72
|
+
})
|
|
73
|
+
.filter((row): row is string => row !== null);
|
|
74
|
+
|
|
75
|
+
for (const row of rows) {
|
|
76
|
+
console.log(` ${row}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function getLibraryBreakdown(): Promise<void> {
|
|
81
|
+
startSpinner('Analyzing ~/Library...');
|
|
82
|
+
|
|
83
|
+
const output = exec('sudo du -sh ~/Library/*/ 2>/dev/null | sort -hr | head -20');
|
|
84
|
+
const lines = output.split('\n').filter((l) => l.trim());
|
|
85
|
+
|
|
86
|
+
stopSpinner(true, 'Library analyzed');
|
|
87
|
+
|
|
88
|
+
printHeader('📚 LIBRARY BREAKDOWN');
|
|
89
|
+
|
|
90
|
+
const rows = lines
|
|
91
|
+
.map((line) => {
|
|
92
|
+
const match = line.match(/^([\d.]+[KMGTP]?)\s+(.+)$/i);
|
|
93
|
+
if (!match) return null;
|
|
94
|
+
const [, size, path] = match;
|
|
95
|
+
const shortPath = path.replace(`${process.env.HOME || ''}/Library/`, '').replace(/\/$/, '');
|
|
96
|
+
return `${colorizeSize(size.padStart(8))} ${shortPath}`;
|
|
97
|
+
})
|
|
98
|
+
.filter((row): row is string => row !== null);
|
|
99
|
+
|
|
100
|
+
for (const row of rows) {
|
|
101
|
+
console.log(` ${row}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function getCommonHogs(): Promise<void> {
|
|
106
|
+
printHeader('🐷 COMMON SPACE HOGS');
|
|
107
|
+
|
|
108
|
+
const checks: SpaceHogCheck[] = [
|
|
109
|
+
{ name: 'Ollama models', cmd: 'sudo du -sh ~/.ollama/models/ 2>/dev/null' },
|
|
110
|
+
{ name: 'LM Studio models', cmd: 'sudo du -sh ~/.lmstudio/models 2>/dev/null' },
|
|
111
|
+
{ name: 'asdf versions', cmd: 'sudo du -sh ~/.asdf/installs 2>/dev/null' },
|
|
112
|
+
{
|
|
113
|
+
name: 'Homebrew',
|
|
114
|
+
cmd: 'sudo du -sh /opt/homebrew/Cellar 2>/dev/null || sudo du -sh /usr/local/Cellar 2>/dev/null',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'Docker VM',
|
|
118
|
+
cmd: 'sudo du -sh ~/Library/Containers/com.docker.docker/Data/vms/ 2>/dev/null',
|
|
119
|
+
},
|
|
120
|
+
{ name: 'Trash', cmd: 'sudo du -sh ~/.Trash/ 2>/dev/null' },
|
|
121
|
+
{
|
|
122
|
+
name: 'StabilityMatrix',
|
|
123
|
+
cmd: 'sudo du -sh ~/Library/Application\\ Support/StabilityMatrix/Models 2>/dev/null',
|
|
124
|
+
},
|
|
125
|
+
{ name: 'Downloads', cmd: 'sudo du -sh ~/Downloads 2>/dev/null' },
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
for (const check of checks) {
|
|
129
|
+
startSpinner(`Checking ${check.name}...`);
|
|
130
|
+
const output = exec(check.cmd);
|
|
131
|
+
const match = output.match(/^([\d.]+[KMGTP]?)/i);
|
|
132
|
+
const size = match ? match[1] : 'N/A';
|
|
133
|
+
stopSpinner(size !== 'N/A', `${check.name}: ${colorizeSize(size)}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
interface ModelInfo {
|
|
138
|
+
name: string;
|
|
139
|
+
size: string;
|
|
140
|
+
sizeBytes: number;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function getModelsBreakdown(): Promise<void> {
|
|
144
|
+
printHeader('🤖 AI MODELS');
|
|
145
|
+
|
|
146
|
+
// StabilityMatrix models
|
|
147
|
+
printSubHeader('StabilityMatrix');
|
|
148
|
+
startSpinner('Scanning StabilityMatrix models...');
|
|
149
|
+
|
|
150
|
+
const smPath = '~/Library/Application\\ Support/StabilityMatrix/Models';
|
|
151
|
+
const smOutput = exec(
|
|
152
|
+
`find ${smPath} -type f \\( -name "*.safetensors" -o -name "*.ckpt" -o -name "*.pt" \\) -exec du -sh {} \\; 2>/dev/null | sort -hr`,
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const smModels: ModelInfo[] = smOutput
|
|
156
|
+
.split('\n')
|
|
157
|
+
.filter((l) => l.trim())
|
|
158
|
+
.map((line) => {
|
|
159
|
+
const match = line.match(/^([\d.]+[KMGTP]?)\s+(.+)$/i);
|
|
160
|
+
if (!match) return null;
|
|
161
|
+
const [, size, path] = match;
|
|
162
|
+
const name =
|
|
163
|
+
path
|
|
164
|
+
.split('/')
|
|
165
|
+
.pop()
|
|
166
|
+
?.replace(/\.(safetensors|ckpt|pt)$/, '') || path;
|
|
167
|
+
return { name, size, sizeBytes: parseSize(size) };
|
|
168
|
+
})
|
|
169
|
+
.filter((m): m is ModelInfo => m !== null);
|
|
170
|
+
|
|
171
|
+
const smTotal = smModels.reduce((acc, m) => acc + m.sizeBytes, 0);
|
|
172
|
+
const smTotalStr = formatSize(smTotal);
|
|
173
|
+
|
|
174
|
+
stopSpinner(true, `Found ${smModels.length} models (${smTotalStr})`);
|
|
175
|
+
|
|
176
|
+
for (const model of smModels) {
|
|
177
|
+
console.log(
|
|
178
|
+
` ${c.dim}•${c.reset} ${model.name} ${c.dim}—${c.reset} ${colorizeSize(model.size)}`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
console.log();
|
|
183
|
+
|
|
184
|
+
// LM Studio models
|
|
185
|
+
printSubHeader('LM Studio');
|
|
186
|
+
startSpinner('Scanning LM Studio models...');
|
|
187
|
+
|
|
188
|
+
const lmOutput = exec(
|
|
189
|
+
'find ~/.lmstudio/models -type f -name "*.safetensors" -exec du -sh {} \\; 2>/dev/null | sort -hr',
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
// Group by model directory
|
|
193
|
+
const lmFiles = lmOutput
|
|
194
|
+
.split('\n')
|
|
195
|
+
.filter((l) => l.trim())
|
|
196
|
+
.map((line) => {
|
|
197
|
+
const match = line.match(/^([\d.]+[KMGTP]?)\s+(.+)$/i);
|
|
198
|
+
if (!match) return null;
|
|
199
|
+
const [, size, path] = match;
|
|
200
|
+
// Extract model name from path (parent directory)
|
|
201
|
+
const parts = path.split('/');
|
|
202
|
+
const modelName = parts[parts.length - 2] || 'unknown';
|
|
203
|
+
return { modelName, size, sizeBytes: parseSize(size) };
|
|
204
|
+
})
|
|
205
|
+
.filter((m): m is { modelName: string; size: string; sizeBytes: number } => m !== null);
|
|
206
|
+
|
|
207
|
+
// Aggregate by model name
|
|
208
|
+
const lmModels = new Map<string, number>();
|
|
209
|
+
for (const file of lmFiles) {
|
|
210
|
+
const existing = lmModels.get(file.modelName) || 0;
|
|
211
|
+
lmModels.set(file.modelName, existing + file.sizeBytes);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const lmModelList: ModelInfo[] = Array.from(lmModels.entries())
|
|
215
|
+
.map(([name, sizeBytes]) => ({ name, size: formatSize(sizeBytes), sizeBytes }))
|
|
216
|
+
.sort((a, b) => b.sizeBytes - a.sizeBytes);
|
|
217
|
+
|
|
218
|
+
const lmTotal = lmModelList.reduce((acc, m) => acc + m.sizeBytes, 0);
|
|
219
|
+
const lmTotalStr = formatSize(lmTotal);
|
|
220
|
+
|
|
221
|
+
stopSpinner(true, `Found ${lmModelList.length} models (${lmTotalStr})`);
|
|
222
|
+
|
|
223
|
+
for (const model of lmModelList) {
|
|
224
|
+
console.log(
|
|
225
|
+
` ${c.dim}•${c.reset} ${model.name} ${c.dim}—${c.reset} ${colorizeSize(model.size)}`,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function getCleanupSuggestions(): Promise<void> {
|
|
231
|
+
printHeader('🧹 CLEANUP SUGGESTIONS');
|
|
232
|
+
|
|
233
|
+
const suggestions: CleanupSuggestion[] = [];
|
|
234
|
+
|
|
235
|
+
// Check Trash
|
|
236
|
+
const trashSize = exec('sudo du -sh ~/.Trash/ 2>/dev/null').match(/^([\d.]+[KMGTP]?)/i);
|
|
237
|
+
if (trashSize && parseSize(trashSize[1]) > 100 * 1024 * 1024) {
|
|
238
|
+
suggestions.push({
|
|
239
|
+
item: 'Trash',
|
|
240
|
+
size: trashSize[1],
|
|
241
|
+
cmd: 'rm -rf ~/.Trash/*',
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Check Homebrew
|
|
246
|
+
const brewCleanable = exec('brew cleanup -n 2>/dev/null | tail -1');
|
|
247
|
+
if (brewCleanable.includes('would be removed')) {
|
|
248
|
+
suggestions.push({
|
|
249
|
+
item: 'Homebrew cache',
|
|
250
|
+
size: 'varies',
|
|
251
|
+
cmd: 'brew cleanup && brew autoremove',
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Check npm cache
|
|
256
|
+
const npmCache = exec('npm cache ls 2>/dev/null | wc -l').trim();
|
|
257
|
+
if (Number.parseInt(npmCache) > 100) {
|
|
258
|
+
suggestions.push({
|
|
259
|
+
item: 'npm cache',
|
|
260
|
+
size: 'varies',
|
|
261
|
+
cmd: 'npm cache clean --force',
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Check pip cache
|
|
266
|
+
const pipCache = exec('pip cache info 2>/dev/null | grep "Package index"');
|
|
267
|
+
if (pipCache) {
|
|
268
|
+
suggestions.push({
|
|
269
|
+
item: 'pip cache',
|
|
270
|
+
size: 'varies',
|
|
271
|
+
cmd: 'pip cache purge',
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (suggestions.length === 0) {
|
|
276
|
+
console.log(` ${c.green}✓${c.reset} ${c.dim}No obvious cleanup targets found${c.reset}`);
|
|
277
|
+
} else {
|
|
278
|
+
for (const s of suggestions) {
|
|
279
|
+
console.log(
|
|
280
|
+
` ${c.yellow}●${c.reset} ${c.bold}${s.item}${c.reset} (${colorizeSize(s.size)})`,
|
|
281
|
+
);
|
|
282
|
+
console.log(` ${c.dim}$ ${s.cmd}${c.reset}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ─────────────────────────────────────────────────────────────
|
|
288
|
+
// Subcommands
|
|
289
|
+
// ─────────────────────────────────────────────────────────────
|
|
290
|
+
|
|
291
|
+
const overviewCommand = defineCommand({
|
|
292
|
+
meta: {
|
|
293
|
+
name: 'overview',
|
|
294
|
+
description: 'Show APFS volume disk usage overview',
|
|
295
|
+
},
|
|
296
|
+
run: async () => {
|
|
297
|
+
printBanner();
|
|
298
|
+
await getDiskOverview();
|
|
299
|
+
printFooter();
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const homeCommand = defineCommand({
|
|
304
|
+
meta: {
|
|
305
|
+
name: 'home',
|
|
306
|
+
description: 'Show home directory breakdown',
|
|
307
|
+
},
|
|
308
|
+
run: async () => {
|
|
309
|
+
printBanner();
|
|
310
|
+
await getHomeBreakdown();
|
|
311
|
+
printFooter();
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
const libraryCommand = defineCommand({
|
|
316
|
+
meta: {
|
|
317
|
+
name: 'library',
|
|
318
|
+
description: 'Show ~/Library breakdown',
|
|
319
|
+
},
|
|
320
|
+
run: async () => {
|
|
321
|
+
printBanner();
|
|
322
|
+
await getLibraryBreakdown();
|
|
323
|
+
printFooter();
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const hogsCommand = defineCommand({
|
|
328
|
+
meta: {
|
|
329
|
+
name: 'hogs',
|
|
330
|
+
description: 'Check common space hogs',
|
|
331
|
+
},
|
|
332
|
+
run: async () => {
|
|
333
|
+
printBanner();
|
|
334
|
+
await getCommonHogs();
|
|
335
|
+
printFooter();
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const cleanupCommand = defineCommand({
|
|
340
|
+
meta: {
|
|
341
|
+
name: 'cleanup',
|
|
342
|
+
description: 'Show cleanup suggestions',
|
|
343
|
+
},
|
|
344
|
+
run: async () => {
|
|
345
|
+
printBanner();
|
|
346
|
+
await getCleanupSuggestions();
|
|
347
|
+
printFooter();
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const modelsCommand = defineCommand({
|
|
352
|
+
meta: {
|
|
353
|
+
name: 'models',
|
|
354
|
+
description: 'List AI models (StabilityMatrix, LM Studio)',
|
|
355
|
+
},
|
|
356
|
+
run: async () => {
|
|
357
|
+
printBanner();
|
|
358
|
+
await getModelsBreakdown();
|
|
359
|
+
printFooter();
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
const inventoryCommand = defineCommand({
|
|
364
|
+
meta: {
|
|
365
|
+
name: 'inventory',
|
|
366
|
+
description: 'Full disk space analysis (default)',
|
|
367
|
+
},
|
|
368
|
+
run: async () => {
|
|
369
|
+
printBanner();
|
|
370
|
+
await getDiskOverview();
|
|
371
|
+
await getHomeBreakdown();
|
|
372
|
+
await getLibraryBreakdown();
|
|
373
|
+
await getCommonHogs();
|
|
374
|
+
await getCleanupSuggestions();
|
|
375
|
+
printFooter();
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// ─────────────────────────────────────────────────────────────
|
|
380
|
+
// UI Helpers
|
|
381
|
+
// ─────────────────────────────────────────────────────────────
|
|
382
|
+
|
|
383
|
+
function printBanner(): void {
|
|
384
|
+
console.log();
|
|
385
|
+
console.log(`${c.bold}${c.magenta}╔════════════════════════════════════════╗${c.reset}`);
|
|
386
|
+
console.log(
|
|
387
|
+
`${c.bold}${c.magenta}║${c.reset} ${c.bold}💾 Disk Space Inventory${c.reset} ${c.bold}${c.magenta}║${c.reset}`,
|
|
388
|
+
);
|
|
389
|
+
console.log(`${c.bold}${c.magenta}╚════════════════════════════════════════╝${c.reset}`);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function printFooter(): void {
|
|
393
|
+
console.log();
|
|
394
|
+
console.log(`${c.dim}─────────────────────────────────────────${c.reset}`);
|
|
395
|
+
console.log(
|
|
396
|
+
`${c.dim}Run: hu disk [overview|home|library|hogs|models|cleanup|inventory]${c.reset}`,
|
|
397
|
+
);
|
|
398
|
+
console.log();
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ─────────────────────────────────────────────────────────────
|
|
402
|
+
// Main Command
|
|
403
|
+
// ─────────────────────────────────────────────────────────────
|
|
404
|
+
|
|
405
|
+
export const diskCommand = defineCommand({
|
|
406
|
+
meta: {
|
|
407
|
+
name: 'disk',
|
|
408
|
+
description: 'Disk space analysis tools',
|
|
409
|
+
},
|
|
410
|
+
subCommands: {
|
|
411
|
+
inventory: inventoryCommand,
|
|
412
|
+
overview: overviewCommand,
|
|
413
|
+
home: homeCommand,
|
|
414
|
+
library: libraryCommand,
|
|
415
|
+
hogs: hogsCommand,
|
|
416
|
+
models: modelsCommand,
|
|
417
|
+
cleanup: cleanupCommand,
|
|
418
|
+
},
|
|
419
|
+
});
|