@sooink/ai-session-tidy 0.1.2 → 0.1.4
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/.github/workflows/release.yml +57 -0
- package/CHANGELOG.md +32 -0
- package/README.md +50 -11
- package/assets/demo-interactive.gif +0 -0
- package/assets/demo.gif +0 -0
- package/dist/index.js +369 -116
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/commands/clean.ts +187 -78
- package/src/commands/config.ts +7 -2
- package/src/commands/scan.ts +74 -2
- package/src/commands/watch.ts +12 -7
- package/src/core/cleaner.ts +5 -0
- package/src/core/constants.ts +10 -10
- package/src/core/service.ts +64 -10
- package/src/scanners/claude-code.ts +55 -0
- package/src/scanners/types.ts +1 -1
- package/src/utils/paths.ts +43 -3
- package/README.ko.md +0 -176
package/dist/index.js
CHANGED
|
@@ -67,19 +67,29 @@ async function getDirectorySize(dirPath) {
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
// src/scanners/claude-code.ts
|
|
71
|
-
import { readdir as readdir2, readFile, stat as stat2, access } from "fs/promises";
|
|
72
|
-
import { join as join3 } from "path";
|
|
73
|
-
import { createReadStream } from "fs";
|
|
74
|
-
import { createInterface } from "readline";
|
|
75
|
-
|
|
76
70
|
// src/utils/paths.ts
|
|
71
|
+
import { existsSync } from "fs";
|
|
77
72
|
import { homedir } from "os";
|
|
78
73
|
import { join as join2 } from "path";
|
|
79
74
|
function decodePath(encoded) {
|
|
80
75
|
if (encoded === "") return "";
|
|
81
76
|
if (!encoded.startsWith("-")) return encoded;
|
|
82
|
-
|
|
77
|
+
const parts = encoded.slice(1).split("-");
|
|
78
|
+
return reconstructPath(parts, "");
|
|
79
|
+
}
|
|
80
|
+
function reconstructPath(parts, currentPath) {
|
|
81
|
+
if (parts.length === 0) return currentPath;
|
|
82
|
+
for (let len = parts.length; len >= 1; len--) {
|
|
83
|
+
const segment = parts.slice(0, len).join("-");
|
|
84
|
+
const testPath = currentPath + "/" + segment;
|
|
85
|
+
if (existsSync(testPath)) {
|
|
86
|
+
const result = reconstructPath(parts.slice(len), testPath);
|
|
87
|
+
if (existsSync(result) || parts.slice(len).length === 0) {
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return currentPath + "/" + parts.join("/");
|
|
83
93
|
}
|
|
84
94
|
function getConfigDir(appName) {
|
|
85
95
|
switch (process.platform) {
|
|
@@ -109,6 +119,9 @@ function getClaudeTodosDir() {
|
|
|
109
119
|
function getClaudeFileHistoryDir() {
|
|
110
120
|
return join2(homedir(), ".claude", "file-history");
|
|
111
121
|
}
|
|
122
|
+
function getClaudeTasksDir() {
|
|
123
|
+
return join2(homedir(), ".claude", "tasks");
|
|
124
|
+
}
|
|
112
125
|
function tildify(path) {
|
|
113
126
|
const home = homedir();
|
|
114
127
|
if (path.startsWith(home)) {
|
|
@@ -118,6 +131,10 @@ function tildify(path) {
|
|
|
118
131
|
}
|
|
119
132
|
|
|
120
133
|
// src/scanners/claude-code.ts
|
|
134
|
+
import { readdir as readdir2, readFile, stat as stat2, access } from "fs/promises";
|
|
135
|
+
import { join as join3 } from "path";
|
|
136
|
+
import { createReadStream } from "fs";
|
|
137
|
+
import { createInterface } from "readline";
|
|
121
138
|
var ClaudeCodeScanner = class {
|
|
122
139
|
name = "claude-code";
|
|
123
140
|
projectsDir;
|
|
@@ -125,6 +142,7 @@ var ClaudeCodeScanner = class {
|
|
|
125
142
|
sessionEnvDir;
|
|
126
143
|
todosDir;
|
|
127
144
|
fileHistoryDir;
|
|
145
|
+
tasksDir;
|
|
128
146
|
constructor(projectsDirOrOptions) {
|
|
129
147
|
if (typeof projectsDirOrOptions === "string") {
|
|
130
148
|
this.projectsDir = projectsDirOrOptions;
|
|
@@ -132,18 +150,21 @@ var ClaudeCodeScanner = class {
|
|
|
132
150
|
this.sessionEnvDir = null;
|
|
133
151
|
this.todosDir = null;
|
|
134
152
|
this.fileHistoryDir = null;
|
|
153
|
+
this.tasksDir = null;
|
|
135
154
|
} else if (projectsDirOrOptions) {
|
|
136
155
|
this.projectsDir = projectsDirOrOptions.projectsDir ?? getClaudeProjectsDir();
|
|
137
156
|
this.configPath = projectsDirOrOptions.configPath === void 0 ? getClaudeConfigPath() : projectsDirOrOptions.configPath;
|
|
138
157
|
this.sessionEnvDir = projectsDirOrOptions.sessionEnvDir === void 0 ? getClaudeSessionEnvDir() : projectsDirOrOptions.sessionEnvDir;
|
|
139
158
|
this.todosDir = projectsDirOrOptions.todosDir === void 0 ? getClaudeTodosDir() : projectsDirOrOptions.todosDir;
|
|
140
159
|
this.fileHistoryDir = projectsDirOrOptions.fileHistoryDir === void 0 ? getClaudeFileHistoryDir() : projectsDirOrOptions.fileHistoryDir;
|
|
160
|
+
this.tasksDir = projectsDirOrOptions.tasksDir === void 0 ? getClaudeTasksDir() : projectsDirOrOptions.tasksDir;
|
|
141
161
|
} else {
|
|
142
162
|
this.projectsDir = getClaudeProjectsDir();
|
|
143
163
|
this.configPath = getClaudeConfigPath();
|
|
144
164
|
this.sessionEnvDir = getClaudeSessionEnvDir();
|
|
145
165
|
this.todosDir = getClaudeTodosDir();
|
|
146
166
|
this.fileHistoryDir = getClaudeFileHistoryDir();
|
|
167
|
+
this.tasksDir = getClaudeTasksDir();
|
|
147
168
|
}
|
|
148
169
|
}
|
|
149
170
|
async isAvailable() {
|
|
@@ -190,6 +211,8 @@ var ClaudeCodeScanner = class {
|
|
|
190
211
|
sessions.push(...todosSessions);
|
|
191
212
|
const fileHistorySessions = await this.scanFileHistoryDir(validSessionIds);
|
|
192
213
|
sessions.push(...fileHistorySessions);
|
|
214
|
+
const tasksSessions = await this.scanTasksDir(validSessionIds);
|
|
215
|
+
sessions.push(...tasksSessions);
|
|
193
216
|
const totalSize = sessions.reduce((sum, s) => sum + s.size, 0);
|
|
194
217
|
return {
|
|
195
218
|
toolName: this.name,
|
|
@@ -359,6 +382,40 @@ var ClaudeCodeScanner = class {
|
|
|
359
382
|
}
|
|
360
383
|
return orphanedHistories;
|
|
361
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* Detect orphaned folders from ~/.claude/tasks
|
|
387
|
+
* Folder name is the session UUID, contains .lock file
|
|
388
|
+
*/
|
|
389
|
+
async scanTasksDir(validSessionIds) {
|
|
390
|
+
if (!this.tasksDir) {
|
|
391
|
+
return [];
|
|
392
|
+
}
|
|
393
|
+
const orphanedTasks = [];
|
|
394
|
+
try {
|
|
395
|
+
await access(this.tasksDir);
|
|
396
|
+
const entries = await readdir2(this.tasksDir, { withFileTypes: true });
|
|
397
|
+
for (const entry of entries) {
|
|
398
|
+
if (!entry.isDirectory()) continue;
|
|
399
|
+
const sessionId = entry.name;
|
|
400
|
+
if (!validSessionIds.has(sessionId)) {
|
|
401
|
+
const taskPath = join3(this.tasksDir, entry.name);
|
|
402
|
+
const size = await getDirectorySize(taskPath);
|
|
403
|
+
const taskStat = await stat2(taskPath);
|
|
404
|
+
orphanedTasks.push({
|
|
405
|
+
toolName: this.name,
|
|
406
|
+
sessionPath: taskPath,
|
|
407
|
+
projectPath: sessionId,
|
|
408
|
+
// Session UUID
|
|
409
|
+
size,
|
|
410
|
+
lastModified: taskStat.mtime,
|
|
411
|
+
type: "tasks"
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
} catch {
|
|
416
|
+
}
|
|
417
|
+
return orphanedTasks;
|
|
418
|
+
}
|
|
362
419
|
/**
|
|
363
420
|
* Extract project path (cwd) from JSONL file
|
|
364
421
|
*/
|
|
@@ -602,6 +659,7 @@ function outputTable(results, verbose) {
|
|
|
602
659
|
const sessionEnvEntries = allSessions.filter((s) => s.type === "session-env");
|
|
603
660
|
const todosEntries = allSessions.filter((s) => s.type === "todos");
|
|
604
661
|
const fileHistoryEntries = allSessions.filter((s) => s.type === "file-history");
|
|
662
|
+
const tasksEntries = allSessions.filter((s) => s.type === "tasks");
|
|
605
663
|
const totalSize = results.reduce((sum, r) => sum + r.totalSize, 0);
|
|
606
664
|
if (allSessions.length === 0) {
|
|
607
665
|
logger.success("No orphaned sessions found.");
|
|
@@ -624,6 +682,9 @@ function outputTable(results, verbose) {
|
|
|
624
682
|
if (fileHistoryEntries.length > 0) {
|
|
625
683
|
parts.push(`${fileHistoryEntries.length} file-history folder(s)`);
|
|
626
684
|
}
|
|
685
|
+
if (tasksEntries.length > 0) {
|
|
686
|
+
parts.push(`${tasksEntries.length} tasks folder(s)`);
|
|
687
|
+
}
|
|
627
688
|
logger.warn(`Found ${parts.join(" + ")} (${formatSize(totalSize)})`);
|
|
628
689
|
console.log();
|
|
629
690
|
const summaryTable = new Table({
|
|
@@ -634,6 +695,7 @@ function outputTable(results, verbose) {
|
|
|
634
695
|
chalk2.cyan("Env"),
|
|
635
696
|
chalk2.cyan("Todos"),
|
|
636
697
|
chalk2.cyan("History"),
|
|
698
|
+
chalk2.cyan("Tasks"),
|
|
637
699
|
chalk2.cyan("Size"),
|
|
638
700
|
chalk2.cyan("Scan Time")
|
|
639
701
|
],
|
|
@@ -646,6 +708,7 @@ function outputTable(results, verbose) {
|
|
|
646
708
|
const envs = result.sessions.filter((s) => s.type === "session-env").length;
|
|
647
709
|
const todos = result.sessions.filter((s) => s.type === "todos").length;
|
|
648
710
|
const histories = result.sessions.filter((s) => s.type === "file-history").length;
|
|
711
|
+
const tasks = result.sessions.filter((s) => s.type === "tasks").length;
|
|
649
712
|
summaryTable.push([
|
|
650
713
|
result.toolName,
|
|
651
714
|
folders > 0 ? String(folders) : "-",
|
|
@@ -653,6 +716,7 @@ function outputTable(results, verbose) {
|
|
|
653
716
|
envs > 0 ? String(envs) : "-",
|
|
654
717
|
todos > 0 ? String(todos) : "-",
|
|
655
718
|
histories > 0 ? String(histories) : "-",
|
|
719
|
+
tasks > 0 ? String(tasks) : "-",
|
|
656
720
|
formatSize(result.totalSize),
|
|
657
721
|
`${result.scanDuration.toFixed(0)}ms`
|
|
658
722
|
]);
|
|
@@ -669,7 +733,7 @@ function outputTable(results, verbose) {
|
|
|
669
733
|
console.log(
|
|
670
734
|
` ${chalk2.cyan(`[${session.toolName}]`)} ${chalk2.white(projectName)} ${chalk2.dim(`(${formatSize(session.size)})`)}`
|
|
671
735
|
);
|
|
672
|
-
console.log(` ${chalk2.dim("\u2192")} ${session.projectPath}`);
|
|
736
|
+
console.log(` ${chalk2.dim("\u2192")} ${tildify(session.projectPath)}`);
|
|
673
737
|
console.log(` ${chalk2.dim("Modified:")} ${session.lastModified.toLocaleDateString()}`);
|
|
674
738
|
console.log();
|
|
675
739
|
}
|
|
@@ -683,7 +747,7 @@ function outputTable(results, verbose) {
|
|
|
683
747
|
console.log(
|
|
684
748
|
` ${chalk2.yellow("[config]")} ${chalk2.white(projectName)}`
|
|
685
749
|
);
|
|
686
|
-
console.log(` ${chalk2.dim("\u2192")} ${entry.projectPath}`);
|
|
750
|
+
console.log(` ${chalk2.dim("\u2192")} ${tildify(entry.projectPath)}`);
|
|
687
751
|
if (entry.configStats?.lastCost) {
|
|
688
752
|
const cost = `$${entry.configStats.lastCost.toFixed(2)}`;
|
|
689
753
|
const inTokens = formatTokens(entry.configStats.lastTotalInputTokens);
|
|
@@ -693,6 +757,58 @@ function outputTable(results, verbose) {
|
|
|
693
757
|
console.log();
|
|
694
758
|
}
|
|
695
759
|
}
|
|
760
|
+
if (sessionEnvEntries.length > 0) {
|
|
761
|
+
console.log();
|
|
762
|
+
console.log(chalk2.bold("Empty Session Env Folders:"));
|
|
763
|
+
console.log();
|
|
764
|
+
for (const entry of sessionEnvEntries) {
|
|
765
|
+
const folderName = entry.sessionPath.split("/").pop() || entry.sessionPath;
|
|
766
|
+
console.log(
|
|
767
|
+
` ${chalk2.green("[session-env]")} ${chalk2.white(folderName)} ${chalk2.dim("(empty)")}`
|
|
768
|
+
);
|
|
769
|
+
console.log(` ${chalk2.dim("\u2192")} ${tildify(entry.sessionPath)}`);
|
|
770
|
+
console.log();
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
if (todosEntries.length > 0) {
|
|
774
|
+
console.log();
|
|
775
|
+
console.log(chalk2.bold("Orphaned Todos:"));
|
|
776
|
+
console.log();
|
|
777
|
+
for (const entry of todosEntries) {
|
|
778
|
+
const fileName = entry.sessionPath.split("/").pop() || entry.sessionPath;
|
|
779
|
+
console.log(
|
|
780
|
+
` ${chalk2.magenta("[todos]")} ${chalk2.white(fileName)} ${chalk2.dim(`(${formatSize(entry.size)})`)}`
|
|
781
|
+
);
|
|
782
|
+
console.log(` ${chalk2.dim("\u2192")} ${tildify(entry.sessionPath)}`);
|
|
783
|
+
console.log();
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
if (fileHistoryEntries.length > 0) {
|
|
787
|
+
console.log();
|
|
788
|
+
console.log(chalk2.bold("Orphaned File History:"));
|
|
789
|
+
console.log();
|
|
790
|
+
for (const entry of fileHistoryEntries) {
|
|
791
|
+
const folderName = entry.sessionPath.split("/").pop() || entry.sessionPath;
|
|
792
|
+
console.log(
|
|
793
|
+
` ${chalk2.blue("[file-history]")} ${chalk2.white(folderName)} ${chalk2.dim(`(${formatSize(entry.size)})`)}`
|
|
794
|
+
);
|
|
795
|
+
console.log(` ${chalk2.dim("\u2192")} ${tildify(entry.sessionPath)}`);
|
|
796
|
+
console.log();
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
if (tasksEntries.length > 0) {
|
|
800
|
+
console.log();
|
|
801
|
+
console.log(chalk2.bold("Orphaned Tasks:"));
|
|
802
|
+
console.log();
|
|
803
|
+
for (const entry of tasksEntries) {
|
|
804
|
+
const folderName = entry.sessionPath.split("/").pop() || entry.sessionPath;
|
|
805
|
+
console.log(
|
|
806
|
+
` ${chalk2.cyan("[tasks]")} ${chalk2.white(folderName)} ${chalk2.dim(`(${formatSize(entry.size)})`)}`
|
|
807
|
+
);
|
|
808
|
+
console.log(` ${chalk2.dim("\u2192")} ${tildify(entry.sessionPath)}`);
|
|
809
|
+
console.log();
|
|
810
|
+
}
|
|
811
|
+
}
|
|
696
812
|
}
|
|
697
813
|
console.log();
|
|
698
814
|
console.log(
|
|
@@ -709,7 +825,7 @@ import inquirer from "inquirer";
|
|
|
709
825
|
|
|
710
826
|
// src/core/cleaner.ts
|
|
711
827
|
import { readFile as readFile3, writeFile, mkdir, copyFile } from "fs/promises";
|
|
712
|
-
import { existsSync } from "fs";
|
|
828
|
+
import { existsSync as existsSync2 } from "fs";
|
|
713
829
|
import { join as join5 } from "path";
|
|
714
830
|
import { homedir as homedir2 } from "os";
|
|
715
831
|
|
|
@@ -751,7 +867,8 @@ var Cleaner = class {
|
|
|
751
867
|
session: 0,
|
|
752
868
|
sessionEnv: 0,
|
|
753
869
|
todos: 0,
|
|
754
|
-
fileHistory: 0
|
|
870
|
+
fileHistory: 0,
|
|
871
|
+
tasks: 0
|
|
755
872
|
},
|
|
756
873
|
skippedCount: 0,
|
|
757
874
|
alreadyGoneCount: 0,
|
|
@@ -782,6 +899,9 @@ var Cleaner = class {
|
|
|
782
899
|
case "file-history":
|
|
783
900
|
result.deletedByType.fileHistory++;
|
|
784
901
|
break;
|
|
902
|
+
case "tasks":
|
|
903
|
+
result.deletedByType.tasks++;
|
|
904
|
+
break;
|
|
785
905
|
default:
|
|
786
906
|
result.deletedByType.session++;
|
|
787
907
|
}
|
|
@@ -821,7 +941,7 @@ var Cleaner = class {
|
|
|
821
941
|
const configPath = entries[0].sessionPath;
|
|
822
942
|
const projectPathsToRemove = new Set(entries.map((e) => e.projectPath));
|
|
823
943
|
const backupDir = getBackupDir();
|
|
824
|
-
if (!
|
|
944
|
+
if (!existsSync2(backupDir)) {
|
|
825
945
|
await mkdir(backupDir, { recursive: true });
|
|
826
946
|
}
|
|
827
947
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -852,10 +972,29 @@ function formatSessionChoice(session) {
|
|
|
852
972
|
const isConfig = session.type === "config";
|
|
853
973
|
const toolTag = isConfig ? chalk3.yellow("[config]") : chalk3.cyan(`[${session.toolName}]`);
|
|
854
974
|
const name = chalk3.white(projectName);
|
|
855
|
-
const size = isConfig ? "" : chalk3.dim(`(${formatSize(session.size)})`)
|
|
856
|
-
const path = chalk3.dim(`\u2192 ${session.projectPath}`);
|
|
857
|
-
return `${toolTag} ${name}
|
|
858
|
-
|
|
975
|
+
const size = isConfig ? "" : ` ${chalk3.dim(`(${formatSize(session.size)})`)}`;
|
|
976
|
+
const path = chalk3.dim(`\u2192 ${tildify(session.projectPath)}`);
|
|
977
|
+
return `${toolTag} ${name}${size}
|
|
978
|
+
${path}`;
|
|
979
|
+
}
|
|
980
|
+
function formatGroupChoice(group) {
|
|
981
|
+
const labels = {
|
|
982
|
+
"session-env": "empty session-env folder",
|
|
983
|
+
"todos": "orphaned todos file",
|
|
984
|
+
"file-history": "orphaned file-history folder",
|
|
985
|
+
"tasks": "orphaned tasks folder"
|
|
986
|
+
};
|
|
987
|
+
const colors = {
|
|
988
|
+
"session-env": chalk3.green,
|
|
989
|
+
"todos": chalk3.magenta,
|
|
990
|
+
"file-history": chalk3.blue,
|
|
991
|
+
"tasks": chalk3.cyan
|
|
992
|
+
};
|
|
993
|
+
const label = labels[group.type] || group.type;
|
|
994
|
+
const count = group.sessions.length;
|
|
995
|
+
const plural = count > 1 ? "s" : "";
|
|
996
|
+
const color = colors[group.type] || chalk3.white;
|
|
997
|
+
return `${color(`[${group.type}]`)} ${chalk3.white(`${count} ${label}${plural}`)} ${chalk3.dim(`(${formatSize(group.totalSize)})`)}`;
|
|
859
998
|
}
|
|
860
999
|
var cleanCommand = new Command2("clean").description("Remove orphaned session data").option("-f, --force", "Skip confirmation prompt").option("-n, --dry-run", "Show what would be deleted without deleting").option("-i, --interactive", "Select sessions to delete interactively").option("--no-trash", "Permanently delete instead of moving to trash").option("-v, --verbose", "Enable verbose output").action(async (options) => {
|
|
861
1000
|
const spinner = ora2("Scanning for orphaned sessions...").start();
|
|
@@ -885,14 +1024,39 @@ var cleanCommand = new Command2("clean").description("Remove orphaned session da
|
|
|
885
1024
|
const fileHistoryEntries = allSessions.filter(
|
|
886
1025
|
(s) => s.type === "file-history"
|
|
887
1026
|
);
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
...fileHistoryEntries
|
|
892
|
-
];
|
|
893
|
-
const selectableSessions = allSessions.filter(
|
|
894
|
-
(s) => s.type !== "session-env" && s.type !== "todos" && s.type !== "file-history"
|
|
1027
|
+
const tasksEntries = allSessions.filter((s) => s.type === "tasks");
|
|
1028
|
+
const individualSessions = allSessions.filter(
|
|
1029
|
+
(s) => s.type !== "session-env" && s.type !== "todos" && s.type !== "file-history" && s.type !== "tasks"
|
|
895
1030
|
);
|
|
1031
|
+
const groupChoices = [];
|
|
1032
|
+
if (sessionEnvEntries.length > 0) {
|
|
1033
|
+
groupChoices.push({
|
|
1034
|
+
type: "session-env",
|
|
1035
|
+
sessions: sessionEnvEntries,
|
|
1036
|
+
totalSize: sessionEnvEntries.reduce((sum, s) => sum + s.size, 0)
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
if (todosEntries.length > 0) {
|
|
1040
|
+
groupChoices.push({
|
|
1041
|
+
type: "todos",
|
|
1042
|
+
sessions: todosEntries,
|
|
1043
|
+
totalSize: todosEntries.reduce((sum, s) => sum + s.size, 0)
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
if (fileHistoryEntries.length > 0) {
|
|
1047
|
+
groupChoices.push({
|
|
1048
|
+
type: "file-history",
|
|
1049
|
+
sessions: fileHistoryEntries,
|
|
1050
|
+
totalSize: fileHistoryEntries.reduce((sum, s) => sum + s.size, 0)
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
if (tasksEntries.length > 0) {
|
|
1054
|
+
groupChoices.push({
|
|
1055
|
+
type: "tasks",
|
|
1056
|
+
sessions: tasksEntries,
|
|
1057
|
+
totalSize: tasksEntries.reduce((sum, s) => sum + s.size, 0)
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
896
1060
|
console.log();
|
|
897
1061
|
const parts = [];
|
|
898
1062
|
if (folderSessions.length > 0) {
|
|
@@ -910,62 +1074,89 @@ var cleanCommand = new Command2("clean").description("Remove orphaned session da
|
|
|
910
1074
|
if (fileHistoryEntries.length > 0) {
|
|
911
1075
|
parts.push(`${fileHistoryEntries.length} file-history folder(s)`);
|
|
912
1076
|
}
|
|
1077
|
+
if (tasksEntries.length > 0) {
|
|
1078
|
+
parts.push(`${tasksEntries.length} tasks folder(s)`);
|
|
1079
|
+
}
|
|
913
1080
|
logger.warn(`Found ${parts.join(" + ")} (${formatSize(totalSize)})`);
|
|
914
1081
|
if (options.verbose && !options.interactive) {
|
|
915
1082
|
console.log();
|
|
916
|
-
for (const session of
|
|
1083
|
+
for (const session of individualSessions) {
|
|
917
1084
|
console.log(
|
|
918
|
-
chalk3.dim(` ${session.toolName}: ${session.projectPath}`)
|
|
1085
|
+
chalk3.dim(` ${session.toolName}: ${tildify(session.projectPath)}`)
|
|
919
1086
|
);
|
|
920
1087
|
}
|
|
921
1088
|
}
|
|
922
1089
|
let sessionsToClean = allSessions;
|
|
923
1090
|
if (options.interactive) {
|
|
924
|
-
if (
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1091
|
+
if (!process.stdout.isTTY) {
|
|
1092
|
+
logger.error("Interactive mode requires a TTY. Omit -i or use -f to skip confirmation.");
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
const choices = [];
|
|
1096
|
+
for (const session of individualSessions) {
|
|
1097
|
+
choices.push({
|
|
1098
|
+
name: formatSessionChoice(session),
|
|
1099
|
+
value: { type: "individual", session },
|
|
1100
|
+
checked: false
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
for (const group of groupChoices) {
|
|
1104
|
+
choices.push({
|
|
1105
|
+
name: formatGroupChoice(group),
|
|
1106
|
+
value: { type: "group", group },
|
|
1107
|
+
checked: false
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
if (choices.length === 0) {
|
|
1111
|
+
logger.info("No selectable sessions found.");
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
console.log();
|
|
1115
|
+
const { selected } = await inquirer.prompt([
|
|
1116
|
+
{
|
|
1117
|
+
type: "checkbox",
|
|
1118
|
+
name: "selected",
|
|
1119
|
+
message: "Select items to delete:",
|
|
1120
|
+
choices,
|
|
1121
|
+
pageSize: 15,
|
|
1122
|
+
loop: false
|
|
933
1123
|
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
value: session,
|
|
944
|
-
checked: false
|
|
945
|
-
})),
|
|
946
|
-
pageSize: 15,
|
|
947
|
-
loop: false
|
|
948
|
-
}
|
|
949
|
-
]);
|
|
950
|
-
if (selected.length === 0) {
|
|
951
|
-
logger.info("No sessions selected. Cancelled.");
|
|
952
|
-
return;
|
|
1124
|
+
]);
|
|
1125
|
+
if (selected.length === 0) {
|
|
1126
|
+
logger.info("No items selected. Cancelled.");
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
if (process.stdout.isTTY) {
|
|
1130
|
+
const linesToClear = selected.length + 1;
|
|
1131
|
+
for (let i = 0; i < linesToClear; i++) {
|
|
1132
|
+
process.stdout.write("\x1B[1A\x1B[2K");
|
|
953
1133
|
}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
if (
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
)
|
|
1134
|
+
}
|
|
1135
|
+
console.log(chalk3.green("\u2714") + " " + chalk3.bold("Selected items:"));
|
|
1136
|
+
for (const item of selected) {
|
|
1137
|
+
if (item.type === "individual") {
|
|
1138
|
+
const s = item.session;
|
|
1139
|
+
const tag = s.type === "config" ? chalk3.yellow("[config]") : chalk3.cyan(`[${s.toolName}]`);
|
|
1140
|
+
const size = s.type === "config" ? "" : ` ${chalk3.dim(`(${formatSize(s.size)})`)}`;
|
|
1141
|
+
console.log(` ${tag} ${basename(s.projectPath)}${size}`);
|
|
1142
|
+
console.log(` ${chalk3.dim(`\u2192 ${tildify(s.projectPath)}`)}`);
|
|
1143
|
+
} else {
|
|
1144
|
+
console.log(` ${formatGroupChoice(item.group)}`);
|
|
961
1145
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
);
|
|
1146
|
+
}
|
|
1147
|
+
sessionsToClean = [];
|
|
1148
|
+
for (const item of selected) {
|
|
1149
|
+
if (item.type === "individual") {
|
|
1150
|
+
sessionsToClean.push(item.session);
|
|
1151
|
+
} else {
|
|
1152
|
+
sessionsToClean.push(...item.group.sessions);
|
|
967
1153
|
}
|
|
968
1154
|
}
|
|
1155
|
+
const selectedSize = sessionsToClean.reduce((sum, s) => sum + s.size, 0);
|
|
1156
|
+
console.log();
|
|
1157
|
+
logger.info(
|
|
1158
|
+
`Selected: ${sessionsToClean.length} item(s) (${formatSize(selectedSize)})`
|
|
1159
|
+
);
|
|
969
1160
|
}
|
|
970
1161
|
const cleanSize = sessionsToClean.reduce((sum, s) => sum + s.size, 0);
|
|
971
1162
|
if (options.dryRun) {
|
|
@@ -985,9 +1176,12 @@ var cleanCommand = new Command2("clean").description("Remove orphaned session da
|
|
|
985
1176
|
const dryRunHistories = sessionsToClean.filter(
|
|
986
1177
|
(s) => s.type === "file-history"
|
|
987
1178
|
);
|
|
1179
|
+
const dryRunTasks = sessionsToClean.filter(
|
|
1180
|
+
(s) => s.type === "tasks"
|
|
1181
|
+
);
|
|
988
1182
|
for (const session of dryRunFolders) {
|
|
989
1183
|
console.log(
|
|
990
|
-
` ${chalk3.red("Would delete:")} ${session.sessionPath} (${formatSize(session.size)})`
|
|
1184
|
+
` ${chalk3.red("Would delete:")} ${tildify(session.sessionPath)} (${formatSize(session.size)})`
|
|
991
1185
|
);
|
|
992
1186
|
}
|
|
993
1187
|
if (dryRunConfigs.length > 0) {
|
|
@@ -996,29 +1190,40 @@ var cleanCommand = new Command2("clean").description("Remove orphaned session da
|
|
|
996
1190
|
` ${chalk3.yellow("Would remove from ~/.claude.json:")}`
|
|
997
1191
|
);
|
|
998
1192
|
for (const config of dryRunConfigs) {
|
|
999
|
-
console.log(` - ${config.projectPath}`);
|
|
1193
|
+
console.log(` - ${tildify(config.projectPath)}`);
|
|
1000
1194
|
}
|
|
1001
1195
|
}
|
|
1002
|
-
const autoCleanParts = [];
|
|
1003
1196
|
if (dryRunEnvs.length > 0) {
|
|
1004
|
-
|
|
1197
|
+
console.log();
|
|
1198
|
+
console.log(
|
|
1199
|
+
` ${chalk3.green("Would delete:")} ${dryRunEnvs.length} session-env folder(s)`
|
|
1200
|
+
);
|
|
1005
1201
|
}
|
|
1006
1202
|
if (dryRunTodos.length > 0) {
|
|
1007
|
-
|
|
1203
|
+
console.log();
|
|
1204
|
+
console.log(
|
|
1205
|
+
` ${chalk3.magenta("Would delete:")} ${dryRunTodos.length} todos file(s) (${formatSize(dryRunTodos.reduce((sum, s) => sum + s.size, 0))})`
|
|
1206
|
+
);
|
|
1008
1207
|
}
|
|
1009
1208
|
if (dryRunHistories.length > 0) {
|
|
1010
|
-
|
|
1209
|
+
console.log();
|
|
1210
|
+
console.log(
|
|
1211
|
+
` ${chalk3.blue("Would delete:")} ${dryRunHistories.length} file-history folder(s) (${formatSize(dryRunHistories.reduce((sum, s) => sum + s.size, 0))})`
|
|
1212
|
+
);
|
|
1011
1213
|
}
|
|
1012
|
-
if (
|
|
1013
|
-
const autoSize = dryRunEnvs.reduce((sum, s) => sum + s.size, 0) + dryRunTodos.reduce((sum, s) => sum + s.size, 0) + dryRunHistories.reduce((sum, s) => sum + s.size, 0);
|
|
1214
|
+
if (dryRunTasks.length > 0) {
|
|
1014
1215
|
console.log();
|
|
1015
1216
|
console.log(
|
|
1016
|
-
` ${chalk3.
|
|
1217
|
+
` ${chalk3.cyan("Would delete:")} ${dryRunTasks.length} tasks folder(s) (${formatSize(dryRunTasks.reduce((sum, s) => sum + s.size, 0))})`
|
|
1017
1218
|
);
|
|
1018
1219
|
}
|
|
1019
1220
|
return;
|
|
1020
1221
|
}
|
|
1021
1222
|
if (!options.force) {
|
|
1223
|
+
if (!process.stdout.isTTY) {
|
|
1224
|
+
logger.error("Confirmation requires a TTY. Use -f to skip confirmation.");
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1022
1227
|
console.log();
|
|
1023
1228
|
const action = options.noTrash ? "permanently delete" : "move to trash";
|
|
1024
1229
|
const { confirmed } = await inquirer.prompt([
|
|
@@ -1058,6 +1263,9 @@ var cleanCommand = new Command2("clean").description("Remove orphaned session da
|
|
|
1058
1263
|
if (deletedByType.fileHistory > 0) {
|
|
1059
1264
|
parts2.push(`${deletedByType.fileHistory} file-history`);
|
|
1060
1265
|
}
|
|
1266
|
+
if (deletedByType.tasks > 0) {
|
|
1267
|
+
parts2.push(`${deletedByType.tasks} tasks`);
|
|
1268
|
+
}
|
|
1061
1269
|
const summary = parts2.length > 0 ? parts2.join(" + ") : `${cleanResult.deletedCount} item(s)`;
|
|
1062
1270
|
logger.success(
|
|
1063
1271
|
`${action}: ${summary} (${formatSize(cleanResult.totalSizeDeleted)})`
|
|
@@ -1080,7 +1288,7 @@ var cleanCommand = new Command2("clean").description("Remove orphaned session da
|
|
|
1080
1288
|
logger.error(`Failed to delete ${cleanResult.errors.length} item(s)`);
|
|
1081
1289
|
if (options.verbose) {
|
|
1082
1290
|
for (const err of cleanResult.errors) {
|
|
1083
|
-
console.log(chalk3.red(` ${err.sessionPath}: ${err.error.message}`));
|
|
1291
|
+
console.log(chalk3.red(` ${tildify(err.sessionPath)}: ${err.error.message}`));
|
|
1084
1292
|
}
|
|
1085
1293
|
}
|
|
1086
1294
|
}
|
|
@@ -1096,12 +1304,12 @@ var cleanCommand = new Command2("clean").description("Remove orphaned session da
|
|
|
1096
1304
|
// src/commands/watch.ts
|
|
1097
1305
|
import { Command as Command3 } from "commander";
|
|
1098
1306
|
import chalk4 from "chalk";
|
|
1099
|
-
import { existsSync as
|
|
1307
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1100
1308
|
import { homedir as homedir5 } from "os";
|
|
1101
1309
|
import { join as join8, resolve as resolve2 } from "path";
|
|
1102
1310
|
|
|
1103
1311
|
// src/utils/config.ts
|
|
1104
|
-
import { existsSync as
|
|
1312
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
1105
1313
|
import { homedir as homedir3 } from "os";
|
|
1106
1314
|
import { dirname as dirname2, join as join6, resolve } from "path";
|
|
1107
1315
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -1116,7 +1324,7 @@ function getAppVersion() {
|
|
|
1116
1324
|
];
|
|
1117
1325
|
for (const packagePath of paths) {
|
|
1118
1326
|
try {
|
|
1119
|
-
if (
|
|
1327
|
+
if (existsSync3(packagePath)) {
|
|
1120
1328
|
const content = readFileSync(packagePath, "utf-8");
|
|
1121
1329
|
const pkg = JSON.parse(content);
|
|
1122
1330
|
return pkg.version;
|
|
@@ -1132,7 +1340,7 @@ function getConfigPath() {
|
|
|
1132
1340
|
}
|
|
1133
1341
|
function loadConfig() {
|
|
1134
1342
|
const configPath = getConfigPath();
|
|
1135
|
-
if (!
|
|
1343
|
+
if (!existsSync3(configPath)) {
|
|
1136
1344
|
return {};
|
|
1137
1345
|
}
|
|
1138
1346
|
try {
|
|
@@ -1145,7 +1353,7 @@ function loadConfig() {
|
|
|
1145
1353
|
function saveConfig(config) {
|
|
1146
1354
|
const configPath = getConfigPath();
|
|
1147
1355
|
const configDir = dirname2(configPath);
|
|
1148
|
-
if (!
|
|
1356
|
+
if (!existsSync3(configDir)) {
|
|
1149
1357
|
mkdirSync(configDir, { recursive: true });
|
|
1150
1358
|
}
|
|
1151
1359
|
const { version: _removed, ...cleanConfig } = config;
|
|
@@ -1241,17 +1449,17 @@ var IGNORED_SYSTEM_PATTERNS = [
|
|
|
1241
1449
|
// All hidden folders (starting with .)
|
|
1242
1450
|
/(^|[/\\])\../,
|
|
1243
1451
|
// macOS user folders (not project directories)
|
|
1244
|
-
/\/Library
|
|
1245
|
-
/\/Applications
|
|
1246
|
-
/\/Music
|
|
1247
|
-
/\/Movies
|
|
1248
|
-
/\/Pictures
|
|
1249
|
-
/\/Downloads
|
|
1250
|
-
/\/Documents
|
|
1251
|
-
/\/Desktop
|
|
1252
|
-
/\/Public
|
|
1452
|
+
/\/Library(\/|$)/,
|
|
1453
|
+
/\/Applications(\/|$)/,
|
|
1454
|
+
/\/Music(\/|$)/,
|
|
1455
|
+
/\/Movies(\/|$)/,
|
|
1456
|
+
/\/Pictures(\/|$)/,
|
|
1457
|
+
/\/Downloads(\/|$)/,
|
|
1458
|
+
/\/Documents(\/|$)/,
|
|
1459
|
+
/\/Desktop(\/|$)/,
|
|
1460
|
+
/\/Public(\/|$)/,
|
|
1253
1461
|
// Development folders
|
|
1254
|
-
/node_modules/
|
|
1462
|
+
/node_modules(\/|$)/
|
|
1255
1463
|
];
|
|
1256
1464
|
|
|
1257
1465
|
// src/core/watcher.ts
|
|
@@ -1384,37 +1592,67 @@ var Watcher = class {
|
|
|
1384
1592
|
import { homedir as homedir4 } from "os";
|
|
1385
1593
|
import { join as join7, dirname as dirname3 } from "path";
|
|
1386
1594
|
import { readFile as readFile4, writeFile as writeFile2, unlink, mkdir as mkdir2 } from "fs/promises";
|
|
1387
|
-
import { existsSync as
|
|
1595
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1388
1596
|
import { execSync } from "child_process";
|
|
1389
1597
|
var SERVICE_LABEL = "sooink.ai-session-tidy.watcher";
|
|
1390
1598
|
var PLIST_FILENAME = `${SERVICE_LABEL}.plist`;
|
|
1599
|
+
var BIN_NAME = "ai-session-tidy";
|
|
1600
|
+
var BUNDLE_IDENTIFIER = "io.github.sooink.ai-session-tidy";
|
|
1391
1601
|
function getPlistPath() {
|
|
1392
1602
|
return join7(homedir4(), "Library", "LaunchAgents", PLIST_FILENAME);
|
|
1393
1603
|
}
|
|
1604
|
+
function getBinPath() {
|
|
1605
|
+
try {
|
|
1606
|
+
const binPath = execSync(`which ${BIN_NAME}`, {
|
|
1607
|
+
encoding: "utf-8",
|
|
1608
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1609
|
+
}).trim();
|
|
1610
|
+
if (binPath && existsSync4(binPath)) {
|
|
1611
|
+
return binPath;
|
|
1612
|
+
}
|
|
1613
|
+
} catch {
|
|
1614
|
+
}
|
|
1615
|
+
return null;
|
|
1616
|
+
}
|
|
1394
1617
|
function getNodePath() {
|
|
1395
1618
|
return process.execPath;
|
|
1396
1619
|
}
|
|
1397
1620
|
function getScriptPath() {
|
|
1398
1621
|
const scriptPath = process.argv[1];
|
|
1399
|
-
if (scriptPath &&
|
|
1622
|
+
if (scriptPath && existsSync4(scriptPath)) {
|
|
1400
1623
|
return scriptPath;
|
|
1401
1624
|
}
|
|
1402
1625
|
throw new Error("Could not determine script path");
|
|
1403
1626
|
}
|
|
1627
|
+
function getProgramArgs(args) {
|
|
1628
|
+
const binPath = getBinPath();
|
|
1629
|
+
if (binPath) {
|
|
1630
|
+
return [binPath, ...args];
|
|
1631
|
+
}
|
|
1632
|
+
return [getNodePath(), getScriptPath(), ...args];
|
|
1633
|
+
}
|
|
1404
1634
|
function generatePlist(options) {
|
|
1405
|
-
const
|
|
1406
|
-
const argsXml = allArgs.map((arg) => ` <string>${arg}</string>`).join("\n");
|
|
1635
|
+
const argsXml = options.programArgs.map((arg) => ` <string>${arg}</string>`).join("\n");
|
|
1407
1636
|
const home = homedir4();
|
|
1637
|
+
const nodeBinDir = dirname3(process.execPath);
|
|
1638
|
+
const systemPath = process.env["PATH"] ?? "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
|
|
1639
|
+
const envPath = systemPath.includes(nodeBinDir) ? systemPath : `${nodeBinDir}:${systemPath}`;
|
|
1408
1640
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
1409
1641
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1410
1642
|
<plist version="1.0">
|
|
1411
1643
|
<dict>
|
|
1412
1644
|
<key>Label</key>
|
|
1413
1645
|
<string>${options.label}</string>
|
|
1646
|
+
<key>AssociatedBundleIdentifiers</key>
|
|
1647
|
+
<array>
|
|
1648
|
+
<string>${BUNDLE_IDENTIFIER}</string>
|
|
1649
|
+
</array>
|
|
1414
1650
|
<key>EnvironmentVariables</key>
|
|
1415
1651
|
<dict>
|
|
1416
1652
|
<key>HOME</key>
|
|
1417
1653
|
<string>${home}</string>
|
|
1654
|
+
<key>PATH</key>
|
|
1655
|
+
<string>${envPath}</string>
|
|
1418
1656
|
</dict>
|
|
1419
1657
|
<key>ProgramArguments</key>
|
|
1420
1658
|
<array>
|
|
@@ -1444,11 +1682,11 @@ var ServiceManager = class {
|
|
|
1444
1682
|
throw new Error("Service management is only supported on macOS");
|
|
1445
1683
|
}
|
|
1446
1684
|
const launchAgentsDir = dirname3(this.plistPath);
|
|
1447
|
-
if (!
|
|
1685
|
+
if (!existsSync4(launchAgentsDir)) {
|
|
1448
1686
|
await mkdir2(launchAgentsDir, { recursive: true });
|
|
1449
1687
|
}
|
|
1450
1688
|
const logDir = join7(homedir4(), ".ai-session-tidy");
|
|
1451
|
-
if (!
|
|
1689
|
+
if (!existsSync4(logDir)) {
|
|
1452
1690
|
await mkdir2(logDir, { recursive: true });
|
|
1453
1691
|
}
|
|
1454
1692
|
const stdoutPath = join7(logDir, "watcher.log");
|
|
@@ -1457,14 +1695,12 @@ var ServiceManager = class {
|
|
|
1457
1695
|
await writeFile2(stderrPath, "", "utf-8");
|
|
1458
1696
|
const plistContent = generatePlist({
|
|
1459
1697
|
label: SERVICE_LABEL,
|
|
1460
|
-
|
|
1461
|
-
scriptPath: getScriptPath(),
|
|
1462
|
-
args: ["watch", "run"]
|
|
1698
|
+
programArgs: getProgramArgs(["watch", "run"])
|
|
1463
1699
|
});
|
|
1464
1700
|
await writeFile2(this.plistPath, plistContent, "utf-8");
|
|
1465
1701
|
}
|
|
1466
1702
|
async uninstall() {
|
|
1467
|
-
if (
|
|
1703
|
+
if (existsSync4(this.plistPath)) {
|
|
1468
1704
|
await unlink(this.plistPath);
|
|
1469
1705
|
}
|
|
1470
1706
|
}
|
|
@@ -1472,9 +1708,18 @@ var ServiceManager = class {
|
|
|
1472
1708
|
if (!this.isSupported()) {
|
|
1473
1709
|
throw new Error("Service management is only supported on macOS");
|
|
1474
1710
|
}
|
|
1475
|
-
if (!
|
|
1711
|
+
if (!existsSync4(this.plistPath)) {
|
|
1476
1712
|
throw new Error('Service not installed. Run "watch start" to install and start.');
|
|
1477
1713
|
}
|
|
1714
|
+
const logDir = join7(homedir4(), ".ai-session-tidy");
|
|
1715
|
+
const stdoutPath = join7(logDir, "watcher.log");
|
|
1716
|
+
const stderrPath = join7(logDir, "watcher.error.log");
|
|
1717
|
+
if (existsSync4(stdoutPath)) {
|
|
1718
|
+
await writeFile2(stdoutPath, "", "utf-8");
|
|
1719
|
+
}
|
|
1720
|
+
if (existsSync4(stderrPath)) {
|
|
1721
|
+
await writeFile2(stderrPath, "", "utf-8");
|
|
1722
|
+
}
|
|
1478
1723
|
try {
|
|
1479
1724
|
execSync(`launchctl load "${this.plistPath}"`, { stdio: "pipe" });
|
|
1480
1725
|
} catch (error) {
|
|
@@ -1488,7 +1733,7 @@ var ServiceManager = class {
|
|
|
1488
1733
|
if (!this.isSupported()) {
|
|
1489
1734
|
throw new Error("Service management is only supported on macOS");
|
|
1490
1735
|
}
|
|
1491
|
-
if (!
|
|
1736
|
+
if (!existsSync4(this.plistPath)) {
|
|
1492
1737
|
return;
|
|
1493
1738
|
}
|
|
1494
1739
|
try {
|
|
@@ -1505,7 +1750,7 @@ var ServiceManager = class {
|
|
|
1505
1750
|
if (!this.isSupported()) {
|
|
1506
1751
|
return info;
|
|
1507
1752
|
}
|
|
1508
|
-
if (!
|
|
1753
|
+
if (!existsSync4(this.plistPath)) {
|
|
1509
1754
|
return info;
|
|
1510
1755
|
}
|
|
1511
1756
|
try {
|
|
@@ -1538,7 +1783,7 @@ var ServiceManager = class {
|
|
|
1538
1783
|
let stdout = "";
|
|
1539
1784
|
let stderr = "";
|
|
1540
1785
|
try {
|
|
1541
|
-
if (
|
|
1786
|
+
if (existsSync4(stdoutPath)) {
|
|
1542
1787
|
const content = await readFile4(stdoutPath, "utf-8");
|
|
1543
1788
|
const logLines = content.split("\n");
|
|
1544
1789
|
stdout = logLines.slice(-lines).join("\n");
|
|
@@ -1546,7 +1791,7 @@ var ServiceManager = class {
|
|
|
1546
1791
|
} catch {
|
|
1547
1792
|
}
|
|
1548
1793
|
try {
|
|
1549
|
-
if (
|
|
1794
|
+
if (existsSync4(stderrPath)) {
|
|
1550
1795
|
const content = await readFile4(stderrPath, "utf-8");
|
|
1551
1796
|
const logLines = content.split("\n");
|
|
1552
1797
|
stderr = logLines.slice(-lines).join("\n");
|
|
@@ -1638,7 +1883,7 @@ var statusCommand = new Command3("status").description("Show watcher service sta
|
|
|
1638
1883
|
if (status.pid) {
|
|
1639
1884
|
console.log(`PID: ${status.pid}`);
|
|
1640
1885
|
}
|
|
1641
|
-
console.log(`Plist: ${status.plistPath}`);
|
|
1886
|
+
console.log(`Plist: ${tildify(status.plistPath)}`);
|
|
1642
1887
|
console.log();
|
|
1643
1888
|
if (options.logs) {
|
|
1644
1889
|
const lines = parseInt(options.logs, 10) || 20;
|
|
@@ -1701,14 +1946,14 @@ async function runWatcher(options) {
|
|
|
1701
1946
|
logger.info(`Using default watch paths.`);
|
|
1702
1947
|
}
|
|
1703
1948
|
}
|
|
1704
|
-
const validPaths = watchPaths.filter((p) =>
|
|
1949
|
+
const validPaths = watchPaths.filter((p) => existsSync5(p));
|
|
1705
1950
|
if (validPaths.length === 0) {
|
|
1706
1951
|
logger.error("No valid watch paths found. Use -p to specify paths.");
|
|
1707
1952
|
return;
|
|
1708
1953
|
}
|
|
1709
1954
|
if (validPaths.length < watchPaths.length) {
|
|
1710
|
-
const invalidPaths = watchPaths.filter((p) => !
|
|
1711
|
-
logger.warn(`Skipping non-existent paths: ${invalidPaths.join(", ")}`);
|
|
1955
|
+
const invalidPaths = watchPaths.filter((p) => !existsSync5(p));
|
|
1956
|
+
logger.warn(`Skipping non-existent paths: ${invalidPaths.map(tildify).join(", ")}`);
|
|
1712
1957
|
}
|
|
1713
1958
|
const allScanners = createAllScanners();
|
|
1714
1959
|
const availableScanners = await getAvailableScanners(allScanners);
|
|
@@ -1720,7 +1965,7 @@ async function runWatcher(options) {
|
|
|
1720
1965
|
logger.info(
|
|
1721
1966
|
`Watching for project deletions (${availableScanners.map((s) => s.name).join(", ")})`
|
|
1722
1967
|
);
|
|
1723
|
-
logger.info(`Watch paths: ${validPaths.join(", ")}`);
|
|
1968
|
+
logger.info(`Watch paths: ${validPaths.map(tildify).join(", ")}`);
|
|
1724
1969
|
logger.info(`Cleanup delay: ${String(delayMinutes)} minute(s)`);
|
|
1725
1970
|
logger.info(`Watch depth: ${String(depth)}`);
|
|
1726
1971
|
if (process.stdout.isTTY) {
|
|
@@ -1732,15 +1977,16 @@ async function runWatcher(options) {
|
|
|
1732
1977
|
watchPaths: validPaths,
|
|
1733
1978
|
delayMs,
|
|
1734
1979
|
depth,
|
|
1735
|
-
ignorePaths: getIgnorePaths(),
|
|
1980
|
+
ignorePaths: getIgnorePaths() ?? [],
|
|
1736
1981
|
onDelete: async (events) => {
|
|
1737
|
-
|
|
1738
|
-
|
|
1982
|
+
const firstEvent = events[0];
|
|
1983
|
+
if (events.length === 1 && firstEvent) {
|
|
1984
|
+
logger.info(`Detected deletion: ${tildify(firstEvent.path)}`);
|
|
1739
1985
|
} else {
|
|
1740
1986
|
logger.info(`Detected ${events.length} deletions (debounced)`);
|
|
1741
1987
|
if (options.verbose) {
|
|
1742
1988
|
for (const event of events) {
|
|
1743
|
-
logger.debug(` - ${event.path}`);
|
|
1989
|
+
logger.debug(` - ${tildify(event.path)}`);
|
|
1744
1990
|
}
|
|
1745
1991
|
}
|
|
1746
1992
|
}
|
|
@@ -1772,6 +2018,9 @@ async function runWatcher(options) {
|
|
|
1772
2018
|
if (deletedByType.fileHistory > 0) {
|
|
1773
2019
|
parts.push(`${deletedByType.fileHistory} file-history`);
|
|
1774
2020
|
}
|
|
2021
|
+
if (deletedByType.tasks > 0) {
|
|
2022
|
+
parts.push(`${deletedByType.tasks} tasks`);
|
|
2023
|
+
}
|
|
1775
2024
|
const summary = parts.length > 0 ? parts.join(" + ") : `${cleanResult.deletedCount} item(s)`;
|
|
1776
2025
|
logger.success(
|
|
1777
2026
|
`${action}: ${summary} (${formatSize(cleanResult.totalSizeDeleted)})`
|
|
@@ -1904,7 +2153,7 @@ pathCommand.command("list").description("List watch paths").action(() => {
|
|
|
1904
2153
|
}
|
|
1905
2154
|
console.log();
|
|
1906
2155
|
for (const p of paths) {
|
|
1907
|
-
console.log(` ${p}`);
|
|
2156
|
+
console.log(` ${tildify(p)}`);
|
|
1908
2157
|
}
|
|
1909
2158
|
});
|
|
1910
2159
|
configCommand.addCommand(pathCommand);
|
|
@@ -1929,7 +2178,7 @@ ignoreCommand.command("list").description("List ignored paths").action(() => {
|
|
|
1929
2178
|
}
|
|
1930
2179
|
console.log();
|
|
1931
2180
|
for (const p of paths) {
|
|
1932
|
-
console.log(` ${p}`);
|
|
2181
|
+
console.log(` ${tildify(p)}`);
|
|
1933
2182
|
}
|
|
1934
2183
|
});
|
|
1935
2184
|
configCommand.addCommand(ignoreCommand);
|
|
@@ -1980,6 +2229,10 @@ configCommand.command("show").description("Show all configuration").action(() =>
|
|
|
1980
2229
|
});
|
|
1981
2230
|
configCommand.command("reset").description("Reset configuration to defaults").option("-f, --force", "Skip confirmation prompt").action(async (options) => {
|
|
1982
2231
|
if (!options.force) {
|
|
2232
|
+
if (!process.stdout.isTTY) {
|
|
2233
|
+
logger.error("Confirmation requires a TTY. Use -f to skip confirmation.");
|
|
2234
|
+
return;
|
|
2235
|
+
}
|
|
1983
2236
|
const { confirmed } = await inquirer2.prompt([
|
|
1984
2237
|
{
|
|
1985
2238
|
type: "confirm",
|