@lexmanh/shed-cli 0.2.0-beta.4 → 0.2.0-beta.6
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/dist/cli.js +120 -6
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -421,9 +421,82 @@ import {
|
|
|
421
421
|
XcodeDetector as XcodeDetector2
|
|
422
422
|
} from "@lexmanh/shed-core";
|
|
423
423
|
import pc4 from "picocolors";
|
|
424
|
+
|
|
425
|
+
// src/commands/scan-aggregate.ts
|
|
426
|
+
import { dirname } from "path";
|
|
427
|
+
var AGGREGATE_THRESHOLD = 3;
|
|
428
|
+
function aggregateForDisplay(items) {
|
|
429
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
430
|
+
const singles = [];
|
|
431
|
+
for (const item of items) {
|
|
432
|
+
const kind = item.metadata?.kind ?? null;
|
|
433
|
+
if (!kind) {
|
|
434
|
+
singles.push(item);
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
const key = `${dirname(item.path)}::${item.detector}::${kind}`;
|
|
438
|
+
const arr = buckets.get(key) ?? [];
|
|
439
|
+
arr.push(item);
|
|
440
|
+
buckets.set(key, arr);
|
|
441
|
+
}
|
|
442
|
+
const result = [];
|
|
443
|
+
for (const arr of buckets.values()) {
|
|
444
|
+
if (arr.length >= AGGREGATE_THRESHOLD) {
|
|
445
|
+
const first = arr[0];
|
|
446
|
+
if (!first) continue;
|
|
447
|
+
const totalBytes = arr.reduce((s, i) => s + i.sizeBytes, 0);
|
|
448
|
+
const kind = first.metadata?.kind;
|
|
449
|
+
const parentDir = dirname(first.path);
|
|
450
|
+
const displayPath = parentDir === "." ? kind : parentDir;
|
|
451
|
+
result.push({
|
|
452
|
+
type: "aggregate",
|
|
453
|
+
risk: first.risk,
|
|
454
|
+
displayPath,
|
|
455
|
+
description: `${arr.length} ${kind} files`,
|
|
456
|
+
totalBytes,
|
|
457
|
+
detector: first.detector,
|
|
458
|
+
itemCount: arr.length,
|
|
459
|
+
items: arr
|
|
460
|
+
});
|
|
461
|
+
} else {
|
|
462
|
+
for (const item of arr) result.push(toSingle(item));
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
for (const item of singles) result.push(toSingle(item));
|
|
466
|
+
return result;
|
|
467
|
+
}
|
|
468
|
+
function toSingle(item) {
|
|
469
|
+
return {
|
|
470
|
+
type: "single",
|
|
471
|
+
risk: item.risk,
|
|
472
|
+
displayPath: item.path,
|
|
473
|
+
description: item.description,
|
|
474
|
+
totalBytes: item.sizeBytes,
|
|
475
|
+
detector: item.detector,
|
|
476
|
+
itemCount: 1,
|
|
477
|
+
items: [item]
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
function selectTopGroups(groups, topN) {
|
|
481
|
+
const sorted = [...groups].sort((a, b) => b.totalBytes - a.totalBytes);
|
|
482
|
+
const shown = sorted.slice(0, topN);
|
|
483
|
+
const rest = sorted.slice(topN);
|
|
484
|
+
return {
|
|
485
|
+
shown,
|
|
486
|
+
hidden: {
|
|
487
|
+
groupCount: rest.length,
|
|
488
|
+
itemCount: rest.reduce((s, g) => s + g.itemCount, 0),
|
|
489
|
+
totalBytes: rest.reduce((s, g) => s + g.totalBytes, 0)
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// src/commands/scan.ts
|
|
424
495
|
var require2 = createRequire(import.meta.url);
|
|
425
496
|
var { version: SHED_VERSION } = require2("../package.json");
|
|
426
497
|
var JSON_SCHEMA_VERSION = 1;
|
|
498
|
+
var COMPACT_TOP_N = 15;
|
|
499
|
+
var DETECTOR_BREAKDOWN_TOP_N = 6;
|
|
427
500
|
var RISK_LABEL = {
|
|
428
501
|
[RiskTier2.Green]: pc4.green("\u25CF Green"),
|
|
429
502
|
[RiskTier2.Yellow]: pc4.yellow("\u25CF Yellow"),
|
|
@@ -535,6 +608,52 @@ async function scanCommand(path = ".", options = {}) {
|
|
|
535
608
|
p4.outro(pc4.dim("All clear!"));
|
|
536
609
|
return;
|
|
537
610
|
}
|
|
611
|
+
if (options.all) {
|
|
612
|
+
renderFull(allItems);
|
|
613
|
+
} else {
|
|
614
|
+
renderCompact(allItems);
|
|
615
|
+
}
|
|
616
|
+
console.log();
|
|
617
|
+
p4.outro(
|
|
618
|
+
`Total recoverable: ${pc4.bold(pc4.green(formatBytes2(totalBytes)))} \u2014 run ${pc4.cyan("shed clean")} to proceed.`
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
function renderCompact(allItems) {
|
|
622
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
623
|
+
const byRisk = { green: 0, yellow: 0, red: 0 };
|
|
624
|
+
let detectOnly = 0;
|
|
625
|
+
const byDetector = /* @__PURE__ */ new Map();
|
|
626
|
+
for (const item of allItems) {
|
|
627
|
+
byRisk[item.risk]++;
|
|
628
|
+
if (item.metadata?.detectOnly === true) detectOnly++;
|
|
629
|
+
byDetector.set(item.detector, (byDetector.get(item.detector) ?? 0) + item.sizeBytes);
|
|
630
|
+
}
|
|
631
|
+
const riskLine = `${pc4.green(`\u25CF ${byRisk.green} Green`)} ${pc4.yellow(`\u25CF ${byRisk.yellow} Yellow`)} ${pc4.red(`\u25CF ${byRisk.red} Red`)}${detectOnly > 0 ? pc4.dim(` (${detectOnly} detect-only)`) : ""}`;
|
|
632
|
+
const detectorLine = [...byDetector.entries()].sort(([, a], [, b]) => b - a).slice(0, DETECTOR_BREAKDOWN_TOP_N).map(([d, b]) => `${d} ${formatBytes2(b)}`).join(" \xB7 ");
|
|
633
|
+
console.log();
|
|
634
|
+
console.log(` By risk: ${riskLine}`);
|
|
635
|
+
console.log(` By detector: ${pc4.dim(detectorLine)}`);
|
|
636
|
+
const groups = aggregateForDisplay(allItems);
|
|
637
|
+
const { shown, hidden } = selectTopGroups(groups, COMPACT_TOP_N);
|
|
638
|
+
console.log();
|
|
639
|
+
console.log(` ${pc4.bold(`Top ${shown.length} items:`)}`);
|
|
640
|
+
for (const g of shown) {
|
|
641
|
+
const path = home ? g.displayPath.replace(home, "~") : g.displayPath;
|
|
642
|
+
const tag = g.type === "aggregate" ? pc4.dim(` (${g.itemCount} ${g.detector} items)`) : "";
|
|
643
|
+
const size = g.totalBytes > 0 ? pc4.dim(` ${formatBytes2(g.totalBytes)}`) : "";
|
|
644
|
+
console.log(` ${RISK_LABEL[g.risk]} ${path}${tag}${size}`);
|
|
645
|
+
}
|
|
646
|
+
if (hidden.groupCount > 0) {
|
|
647
|
+
console.log();
|
|
648
|
+
console.log(
|
|
649
|
+
pc4.dim(
|
|
650
|
+
` \u2026 ${hidden.groupCount} more groups (${hidden.itemCount} items, ${formatBytes2(hidden.totalBytes)}) \u2014 use --all to see everything`
|
|
651
|
+
)
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function renderFull(allItems) {
|
|
656
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
538
657
|
const byProject = /* @__PURE__ */ new Map();
|
|
539
658
|
for (const item of allItems) {
|
|
540
659
|
const key = item.projectRoot ?? "(global)";
|
|
@@ -543,7 +662,6 @@ async function scanCommand(path = ".", options = {}) {
|
|
|
543
662
|
byProject.set(key, group);
|
|
544
663
|
}
|
|
545
664
|
for (const [projectRoot, items] of byProject.entries()) {
|
|
546
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
547
665
|
const projectLabel = projectRoot === "(global)" ? pc4.dim("global caches") : pc4.cyan(home ? projectRoot.replace(home, "~") : projectRoot);
|
|
548
666
|
const groupTotal = items.reduce((s, i) => s + i.sizeBytes, 0);
|
|
549
667
|
console.log(`
|
|
@@ -556,10 +674,6 @@ async function scanCommand(path = ".", options = {}) {
|
|
|
556
674
|
console.log(` ${pc4.dim(` ${item.description}`)}`);
|
|
557
675
|
}
|
|
558
676
|
}
|
|
559
|
-
console.log();
|
|
560
|
-
p4.outro(
|
|
561
|
-
`Total recoverable: ${pc4.bold(pc4.green(formatBytes2(totalBytes)))} \u2014 run ${pc4.cyan("shed clean")} to proceed.`
|
|
562
|
-
);
|
|
563
677
|
}
|
|
564
678
|
|
|
565
679
|
// src/commands/undo.ts
|
|
@@ -627,7 +741,7 @@ var require3 = createRequire2(import.meta.url);
|
|
|
627
741
|
var { version } = require3("../package.json");
|
|
628
742
|
var program = new Command();
|
|
629
743
|
program.name("shed").description("Safe disk cleanup for dev machines and Linux servers").version(version).option("-v, --verbose", "Enable verbose logging");
|
|
630
|
-
program.command("scan [path]").description("Scan for cleanable items without modifying anything").option("--json", "Output machine-readable JSON").option("--max-age <days>", "Only include items older than N days", "30").action(scanCommand);
|
|
744
|
+
program.command("scan [path]").description("Scan for cleanable items without modifying anything").option("--json", "Output machine-readable JSON").option("--max-age <days>", "Only include items older than N days", "30").option("--all", "Show every item (default: compact summary with top 15)").action(scanCommand);
|
|
631
745
|
program.command("clean [path]").description("Interactive cleanup of detected items").option("--dry-run", "Preview operations without executing", true).option("--execute", "Actually perform the cleanup (overrides --dry-run)").option("--hard-delete", "Skip Trash, delete permanently").option("--include-red", "Include Red-tier (high-risk) items").option("--yes", "Skip interactive confirmations (CI mode)").action(cleanCommand);
|
|
632
746
|
program.command("undo").description("List and restore items from previous cleanups").action(undoCommand);
|
|
633
747
|
program.command("doctor").description("Check environment and configuration").action(doctorCommand);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lexmanh/shed-cli",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.6",
|
|
4
4
|
"description": "Safe disk cleanup CLI for dev machines and Linux servers — git-aware, cross-stack, trash-by-default",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"conf": "^13.1.0",
|
|
24
24
|
"execa": "^9.5.0",
|
|
25
25
|
"picocolors": "^1.1.1",
|
|
26
|
-
"@lexmanh/shed-core": "0.2.0-beta.
|
|
26
|
+
"@lexmanh/shed-core": "0.2.0-beta.6"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/node": "^22.10.0",
|