@sooink/ai-session-tidy 0.1.2 → 0.1.3
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/README.ko.md +45 -8
- package/README.md +45 -8
- package/assets/demo-interactive.gif +0 -0
- package/assets/demo.gif +0 -0
- package/dist/index.js +237 -108
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/commands/clean.ts +161 -78
- package/src/commands/config.ts +7 -2
- package/src/commands/scan.ts +51 -2
- package/src/commands/watch.ts +6 -5
- package/src/core/constants.ts +10 -10
- package/src/core/service.ts +11 -0
- package/src/utils/paths.ts +36 -3
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) {
|
|
@@ -118,6 +128,10 @@ function tildify(path) {
|
|
|
118
128
|
}
|
|
119
129
|
|
|
120
130
|
// src/scanners/claude-code.ts
|
|
131
|
+
import { readdir as readdir2, readFile, stat as stat2, access } from "fs/promises";
|
|
132
|
+
import { join as join3 } from "path";
|
|
133
|
+
import { createReadStream } from "fs";
|
|
134
|
+
import { createInterface } from "readline";
|
|
121
135
|
var ClaudeCodeScanner = class {
|
|
122
136
|
name = "claude-code";
|
|
123
137
|
projectsDir;
|
|
@@ -669,7 +683,7 @@ function outputTable(results, verbose) {
|
|
|
669
683
|
console.log(
|
|
670
684
|
` ${chalk2.cyan(`[${session.toolName}]`)} ${chalk2.white(projectName)} ${chalk2.dim(`(${formatSize(session.size)})`)}`
|
|
671
685
|
);
|
|
672
|
-
console.log(` ${chalk2.dim("\u2192")} ${session.projectPath}`);
|
|
686
|
+
console.log(` ${chalk2.dim("\u2192")} ${tildify(session.projectPath)}`);
|
|
673
687
|
console.log(` ${chalk2.dim("Modified:")} ${session.lastModified.toLocaleDateString()}`);
|
|
674
688
|
console.log();
|
|
675
689
|
}
|
|
@@ -683,7 +697,7 @@ function outputTable(results, verbose) {
|
|
|
683
697
|
console.log(
|
|
684
698
|
` ${chalk2.yellow("[config]")} ${chalk2.white(projectName)}`
|
|
685
699
|
);
|
|
686
|
-
console.log(` ${chalk2.dim("\u2192")} ${entry.projectPath}`);
|
|
700
|
+
console.log(` ${chalk2.dim("\u2192")} ${tildify(entry.projectPath)}`);
|
|
687
701
|
if (entry.configStats?.lastCost) {
|
|
688
702
|
const cost = `$${entry.configStats.lastCost.toFixed(2)}`;
|
|
689
703
|
const inTokens = formatTokens(entry.configStats.lastTotalInputTokens);
|
|
@@ -693,6 +707,45 @@ function outputTable(results, verbose) {
|
|
|
693
707
|
console.log();
|
|
694
708
|
}
|
|
695
709
|
}
|
|
710
|
+
if (sessionEnvEntries.length > 0) {
|
|
711
|
+
console.log();
|
|
712
|
+
console.log(chalk2.bold("Empty Session Env Folders:"));
|
|
713
|
+
console.log();
|
|
714
|
+
for (const entry of sessionEnvEntries) {
|
|
715
|
+
const folderName = entry.sessionPath.split("/").pop() || entry.sessionPath;
|
|
716
|
+
console.log(
|
|
717
|
+
` ${chalk2.green("[session-env]")} ${chalk2.white(folderName)} ${chalk2.dim("(empty)")}`
|
|
718
|
+
);
|
|
719
|
+
console.log(` ${chalk2.dim("\u2192")} ${tildify(entry.sessionPath)}`);
|
|
720
|
+
console.log();
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
if (todosEntries.length > 0) {
|
|
724
|
+
console.log();
|
|
725
|
+
console.log(chalk2.bold("Orphaned Todos:"));
|
|
726
|
+
console.log();
|
|
727
|
+
for (const entry of todosEntries) {
|
|
728
|
+
const fileName = entry.sessionPath.split("/").pop() || entry.sessionPath;
|
|
729
|
+
console.log(
|
|
730
|
+
` ${chalk2.magenta("[todos]")} ${chalk2.white(fileName)} ${chalk2.dim(`(${formatSize(entry.size)})`)}`
|
|
731
|
+
);
|
|
732
|
+
console.log(` ${chalk2.dim("\u2192")} ${tildify(entry.sessionPath)}`);
|
|
733
|
+
console.log();
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
if (fileHistoryEntries.length > 0) {
|
|
737
|
+
console.log();
|
|
738
|
+
console.log(chalk2.bold("Orphaned File History:"));
|
|
739
|
+
console.log();
|
|
740
|
+
for (const entry of fileHistoryEntries) {
|
|
741
|
+
const folderName = entry.sessionPath.split("/").pop() || entry.sessionPath;
|
|
742
|
+
console.log(
|
|
743
|
+
` ${chalk2.blue("[file-history]")} ${chalk2.white(folderName)} ${chalk2.dim(`(${formatSize(entry.size)})`)}`
|
|
744
|
+
);
|
|
745
|
+
console.log(` ${chalk2.dim("\u2192")} ${tildify(entry.sessionPath)}`);
|
|
746
|
+
console.log();
|
|
747
|
+
}
|
|
748
|
+
}
|
|
696
749
|
}
|
|
697
750
|
console.log();
|
|
698
751
|
console.log(
|
|
@@ -709,7 +762,7 @@ import inquirer from "inquirer";
|
|
|
709
762
|
|
|
710
763
|
// src/core/cleaner.ts
|
|
711
764
|
import { readFile as readFile3, writeFile, mkdir, copyFile } from "fs/promises";
|
|
712
|
-
import { existsSync } from "fs";
|
|
765
|
+
import { existsSync as existsSync2 } from "fs";
|
|
713
766
|
import { join as join5 } from "path";
|
|
714
767
|
import { homedir as homedir2 } from "os";
|
|
715
768
|
|
|
@@ -821,7 +874,7 @@ var Cleaner = class {
|
|
|
821
874
|
const configPath = entries[0].sessionPath;
|
|
822
875
|
const projectPathsToRemove = new Set(entries.map((e) => e.projectPath));
|
|
823
876
|
const backupDir = getBackupDir();
|
|
824
|
-
if (!
|
|
877
|
+
if (!existsSync2(backupDir)) {
|
|
825
878
|
await mkdir(backupDir, { recursive: true });
|
|
826
879
|
}
|
|
827
880
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -852,10 +905,27 @@ function formatSessionChoice(session) {
|
|
|
852
905
|
const isConfig = session.type === "config";
|
|
853
906
|
const toolTag = isConfig ? chalk3.yellow("[config]") : chalk3.cyan(`[${session.toolName}]`);
|
|
854
907
|
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
|
-
|
|
908
|
+
const size = isConfig ? "" : ` ${chalk3.dim(`(${formatSize(session.size)})`)}`;
|
|
909
|
+
const path = chalk3.dim(`\u2192 ${tildify(session.projectPath)}`);
|
|
910
|
+
return `${toolTag} ${name}${size}
|
|
911
|
+
${path}`;
|
|
912
|
+
}
|
|
913
|
+
function formatGroupChoice(group) {
|
|
914
|
+
const labels = {
|
|
915
|
+
"session-env": "empty session-env folder",
|
|
916
|
+
"todos": "orphaned todos file",
|
|
917
|
+
"file-history": "orphaned file-history folder"
|
|
918
|
+
};
|
|
919
|
+
const colors = {
|
|
920
|
+
"session-env": chalk3.green,
|
|
921
|
+
"todos": chalk3.magenta,
|
|
922
|
+
"file-history": chalk3.blue
|
|
923
|
+
};
|
|
924
|
+
const label = labels[group.type] || group.type;
|
|
925
|
+
const count = group.sessions.length;
|
|
926
|
+
const plural = count > 1 ? "s" : "";
|
|
927
|
+
const color = colors[group.type] || chalk3.white;
|
|
928
|
+
return `${color(`[${group.type}]`)} ${chalk3.white(`${count} ${label}${plural}`)} ${chalk3.dim(`(${formatSize(group.totalSize)})`)}`;
|
|
859
929
|
}
|
|
860
930
|
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
931
|
const spinner = ora2("Scanning for orphaned sessions...").start();
|
|
@@ -885,14 +955,31 @@ var cleanCommand = new Command2("clean").description("Remove orphaned session da
|
|
|
885
955
|
const fileHistoryEntries = allSessions.filter(
|
|
886
956
|
(s) => s.type === "file-history"
|
|
887
957
|
);
|
|
888
|
-
const
|
|
889
|
-
...sessionEnvEntries,
|
|
890
|
-
...todosEntries,
|
|
891
|
-
...fileHistoryEntries
|
|
892
|
-
];
|
|
893
|
-
const selectableSessions = allSessions.filter(
|
|
958
|
+
const individualSessions = allSessions.filter(
|
|
894
959
|
(s) => s.type !== "session-env" && s.type !== "todos" && s.type !== "file-history"
|
|
895
960
|
);
|
|
961
|
+
const groupChoices = [];
|
|
962
|
+
if (sessionEnvEntries.length > 0) {
|
|
963
|
+
groupChoices.push({
|
|
964
|
+
type: "session-env",
|
|
965
|
+
sessions: sessionEnvEntries,
|
|
966
|
+
totalSize: sessionEnvEntries.reduce((sum, s) => sum + s.size, 0)
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
if (todosEntries.length > 0) {
|
|
970
|
+
groupChoices.push({
|
|
971
|
+
type: "todos",
|
|
972
|
+
sessions: todosEntries,
|
|
973
|
+
totalSize: todosEntries.reduce((sum, s) => sum + s.size, 0)
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
if (fileHistoryEntries.length > 0) {
|
|
977
|
+
groupChoices.push({
|
|
978
|
+
type: "file-history",
|
|
979
|
+
sessions: fileHistoryEntries,
|
|
980
|
+
totalSize: fileHistoryEntries.reduce((sum, s) => sum + s.size, 0)
|
|
981
|
+
});
|
|
982
|
+
}
|
|
896
983
|
console.log();
|
|
897
984
|
const parts = [];
|
|
898
985
|
if (folderSessions.length > 0) {
|
|
@@ -913,59 +1000,83 @@ var cleanCommand = new Command2("clean").description("Remove orphaned session da
|
|
|
913
1000
|
logger.warn(`Found ${parts.join(" + ")} (${formatSize(totalSize)})`);
|
|
914
1001
|
if (options.verbose && !options.interactive) {
|
|
915
1002
|
console.log();
|
|
916
|
-
for (const session of
|
|
1003
|
+
for (const session of individualSessions) {
|
|
917
1004
|
console.log(
|
|
918
|
-
chalk3.dim(` ${session.toolName}: ${session.projectPath}`)
|
|
1005
|
+
chalk3.dim(` ${session.toolName}: ${tildify(session.projectPath)}`)
|
|
919
1006
|
);
|
|
920
1007
|
}
|
|
921
1008
|
}
|
|
922
1009
|
let sessionsToClean = allSessions;
|
|
923
1010
|
if (options.interactive) {
|
|
924
|
-
if (
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1011
|
+
if (!process.stdout.isTTY) {
|
|
1012
|
+
logger.error("Interactive mode requires a TTY. Omit -i or use -f to skip confirmation.");
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
const choices = [];
|
|
1016
|
+
for (const session of individualSessions) {
|
|
1017
|
+
choices.push({
|
|
1018
|
+
name: formatSessionChoice(session),
|
|
1019
|
+
value: { type: "individual", session },
|
|
1020
|
+
checked: false
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
for (const group of groupChoices) {
|
|
1024
|
+
choices.push({
|
|
1025
|
+
name: formatGroupChoice(group),
|
|
1026
|
+
value: { type: "group", group },
|
|
1027
|
+
checked: false
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
if (choices.length === 0) {
|
|
1031
|
+
logger.info("No selectable sessions found.");
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
console.log();
|
|
1035
|
+
const { selected } = await inquirer.prompt([
|
|
1036
|
+
{
|
|
1037
|
+
type: "checkbox",
|
|
1038
|
+
name: "selected",
|
|
1039
|
+
message: "Select items to delete:",
|
|
1040
|
+
choices,
|
|
1041
|
+
pageSize: 15,
|
|
1042
|
+
loop: false
|
|
933
1043
|
}
|
|
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;
|
|
1044
|
+
]);
|
|
1045
|
+
if (selected.length === 0) {
|
|
1046
|
+
logger.info("No items selected. Cancelled.");
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
if (process.stdout.isTTY) {
|
|
1050
|
+
const linesToClear = selected.length + 1;
|
|
1051
|
+
for (let i = 0; i < linesToClear; i++) {
|
|
1052
|
+
process.stdout.write("\x1B[1A\x1B[2K");
|
|
953
1053
|
}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
if (
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
)
|
|
1054
|
+
}
|
|
1055
|
+
console.log(chalk3.green("\u2714") + " " + chalk3.bold("Selected items:"));
|
|
1056
|
+
for (const item of selected) {
|
|
1057
|
+
if (item.type === "individual") {
|
|
1058
|
+
const s = item.session;
|
|
1059
|
+
const tag = s.type === "config" ? chalk3.yellow("[config]") : chalk3.cyan(`[${s.toolName}]`);
|
|
1060
|
+
const size = s.type === "config" ? "" : ` ${chalk3.dim(`(${formatSize(s.size)})`)}`;
|
|
1061
|
+
console.log(` ${tag} ${basename(s.projectPath)}${size}`);
|
|
1062
|
+
console.log(` ${chalk3.dim(`\u2192 ${tildify(s.projectPath)}`)}`);
|
|
1063
|
+
} else {
|
|
1064
|
+
console.log(` ${formatGroupChoice(item.group)}`);
|
|
961
1065
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
);
|
|
1066
|
+
}
|
|
1067
|
+
sessionsToClean = [];
|
|
1068
|
+
for (const item of selected) {
|
|
1069
|
+
if (item.type === "individual") {
|
|
1070
|
+
sessionsToClean.push(item.session);
|
|
1071
|
+
} else {
|
|
1072
|
+
sessionsToClean.push(...item.group.sessions);
|
|
967
1073
|
}
|
|
968
1074
|
}
|
|
1075
|
+
const selectedSize = sessionsToClean.reduce((sum, s) => sum + s.size, 0);
|
|
1076
|
+
console.log();
|
|
1077
|
+
logger.info(
|
|
1078
|
+
`Selected: ${sessionsToClean.length} item(s) (${formatSize(selectedSize)})`
|
|
1079
|
+
);
|
|
969
1080
|
}
|
|
970
1081
|
const cleanSize = sessionsToClean.reduce((sum, s) => sum + s.size, 0);
|
|
971
1082
|
if (options.dryRun) {
|
|
@@ -987,7 +1098,7 @@ var cleanCommand = new Command2("clean").description("Remove orphaned session da
|
|
|
987
1098
|
);
|
|
988
1099
|
for (const session of dryRunFolders) {
|
|
989
1100
|
console.log(
|
|
990
|
-
` ${chalk3.red("Would delete:")} ${session.sessionPath} (${formatSize(session.size)})`
|
|
1101
|
+
` ${chalk3.red("Would delete:")} ${tildify(session.sessionPath)} (${formatSize(session.size)})`
|
|
991
1102
|
);
|
|
992
1103
|
}
|
|
993
1104
|
if (dryRunConfigs.length > 0) {
|
|
@@ -996,29 +1107,34 @@ var cleanCommand = new Command2("clean").description("Remove orphaned session da
|
|
|
996
1107
|
` ${chalk3.yellow("Would remove from ~/.claude.json:")}`
|
|
997
1108
|
);
|
|
998
1109
|
for (const config of dryRunConfigs) {
|
|
999
|
-
console.log(` - ${config.projectPath}`);
|
|
1110
|
+
console.log(` - ${tildify(config.projectPath)}`);
|
|
1000
1111
|
}
|
|
1001
1112
|
}
|
|
1002
|
-
const autoCleanParts = [];
|
|
1003
1113
|
if (dryRunEnvs.length > 0) {
|
|
1004
|
-
|
|
1114
|
+
console.log();
|
|
1115
|
+
console.log(
|
|
1116
|
+
` ${chalk3.green("Would delete:")} ${dryRunEnvs.length} session-env folder(s)`
|
|
1117
|
+
);
|
|
1005
1118
|
}
|
|
1006
1119
|
if (dryRunTodos.length > 0) {
|
|
1007
|
-
|
|
1120
|
+
console.log();
|
|
1121
|
+
console.log(
|
|
1122
|
+
` ${chalk3.magenta("Would delete:")} ${dryRunTodos.length} todos file(s) (${formatSize(dryRunTodos.reduce((sum, s) => sum + s.size, 0))})`
|
|
1123
|
+
);
|
|
1008
1124
|
}
|
|
1009
1125
|
if (dryRunHistories.length > 0) {
|
|
1010
|
-
autoCleanParts.push(`${dryRunHistories.length} file-history`);
|
|
1011
|
-
}
|
|
1012
|
-
if (autoCleanParts.length > 0) {
|
|
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);
|
|
1014
1126
|
console.log();
|
|
1015
1127
|
console.log(
|
|
1016
|
-
` ${chalk3.
|
|
1128
|
+
` ${chalk3.blue("Would delete:")} ${dryRunHistories.length} file-history folder(s) (${formatSize(dryRunHistories.reduce((sum, s) => sum + s.size, 0))})`
|
|
1017
1129
|
);
|
|
1018
1130
|
}
|
|
1019
1131
|
return;
|
|
1020
1132
|
}
|
|
1021
1133
|
if (!options.force) {
|
|
1134
|
+
if (!process.stdout.isTTY) {
|
|
1135
|
+
logger.error("Confirmation requires a TTY. Use -f to skip confirmation.");
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1022
1138
|
console.log();
|
|
1023
1139
|
const action = options.noTrash ? "permanently delete" : "move to trash";
|
|
1024
1140
|
const { confirmed } = await inquirer.prompt([
|
|
@@ -1080,7 +1196,7 @@ var cleanCommand = new Command2("clean").description("Remove orphaned session da
|
|
|
1080
1196
|
logger.error(`Failed to delete ${cleanResult.errors.length} item(s)`);
|
|
1081
1197
|
if (options.verbose) {
|
|
1082
1198
|
for (const err of cleanResult.errors) {
|
|
1083
|
-
console.log(chalk3.red(` ${err.sessionPath}: ${err.error.message}`));
|
|
1199
|
+
console.log(chalk3.red(` ${tildify(err.sessionPath)}: ${err.error.message}`));
|
|
1084
1200
|
}
|
|
1085
1201
|
}
|
|
1086
1202
|
}
|
|
@@ -1096,12 +1212,12 @@ var cleanCommand = new Command2("clean").description("Remove orphaned session da
|
|
|
1096
1212
|
// src/commands/watch.ts
|
|
1097
1213
|
import { Command as Command3 } from "commander";
|
|
1098
1214
|
import chalk4 from "chalk";
|
|
1099
|
-
import { existsSync as
|
|
1215
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1100
1216
|
import { homedir as homedir5 } from "os";
|
|
1101
1217
|
import { join as join8, resolve as resolve2 } from "path";
|
|
1102
1218
|
|
|
1103
1219
|
// src/utils/config.ts
|
|
1104
|
-
import { existsSync as
|
|
1220
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
1105
1221
|
import { homedir as homedir3 } from "os";
|
|
1106
1222
|
import { dirname as dirname2, join as join6, resolve } from "path";
|
|
1107
1223
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -1116,7 +1232,7 @@ function getAppVersion() {
|
|
|
1116
1232
|
];
|
|
1117
1233
|
for (const packagePath of paths) {
|
|
1118
1234
|
try {
|
|
1119
|
-
if (
|
|
1235
|
+
if (existsSync3(packagePath)) {
|
|
1120
1236
|
const content = readFileSync(packagePath, "utf-8");
|
|
1121
1237
|
const pkg = JSON.parse(content);
|
|
1122
1238
|
return pkg.version;
|
|
@@ -1132,7 +1248,7 @@ function getConfigPath() {
|
|
|
1132
1248
|
}
|
|
1133
1249
|
function loadConfig() {
|
|
1134
1250
|
const configPath = getConfigPath();
|
|
1135
|
-
if (!
|
|
1251
|
+
if (!existsSync3(configPath)) {
|
|
1136
1252
|
return {};
|
|
1137
1253
|
}
|
|
1138
1254
|
try {
|
|
@@ -1145,7 +1261,7 @@ function loadConfig() {
|
|
|
1145
1261
|
function saveConfig(config) {
|
|
1146
1262
|
const configPath = getConfigPath();
|
|
1147
1263
|
const configDir = dirname2(configPath);
|
|
1148
|
-
if (!
|
|
1264
|
+
if (!existsSync3(configDir)) {
|
|
1149
1265
|
mkdirSync(configDir, { recursive: true });
|
|
1150
1266
|
}
|
|
1151
1267
|
const { version: _removed, ...cleanConfig } = config;
|
|
@@ -1241,17 +1357,17 @@ var IGNORED_SYSTEM_PATTERNS = [
|
|
|
1241
1357
|
// All hidden folders (starting with .)
|
|
1242
1358
|
/(^|[/\\])\../,
|
|
1243
1359
|
// macOS user folders (not project directories)
|
|
1244
|
-
/\/Library
|
|
1245
|
-
/\/Applications
|
|
1246
|
-
/\/Music
|
|
1247
|
-
/\/Movies
|
|
1248
|
-
/\/Pictures
|
|
1249
|
-
/\/Downloads
|
|
1250
|
-
/\/Documents
|
|
1251
|
-
/\/Desktop
|
|
1252
|
-
/\/Public
|
|
1360
|
+
/\/Library(\/|$)/,
|
|
1361
|
+
/\/Applications(\/|$)/,
|
|
1362
|
+
/\/Music(\/|$)/,
|
|
1363
|
+
/\/Movies(\/|$)/,
|
|
1364
|
+
/\/Pictures(\/|$)/,
|
|
1365
|
+
/\/Downloads(\/|$)/,
|
|
1366
|
+
/\/Documents(\/|$)/,
|
|
1367
|
+
/\/Desktop(\/|$)/,
|
|
1368
|
+
/\/Public(\/|$)/,
|
|
1253
1369
|
// Development folders
|
|
1254
|
-
/node_modules/
|
|
1370
|
+
/node_modules(\/|$)/
|
|
1255
1371
|
];
|
|
1256
1372
|
|
|
1257
1373
|
// src/core/watcher.ts
|
|
@@ -1384,7 +1500,7 @@ var Watcher = class {
|
|
|
1384
1500
|
import { homedir as homedir4 } from "os";
|
|
1385
1501
|
import { join as join7, dirname as dirname3 } from "path";
|
|
1386
1502
|
import { readFile as readFile4, writeFile as writeFile2, unlink, mkdir as mkdir2 } from "fs/promises";
|
|
1387
|
-
import { existsSync as
|
|
1503
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1388
1504
|
import { execSync } from "child_process";
|
|
1389
1505
|
var SERVICE_LABEL = "sooink.ai-session-tidy.watcher";
|
|
1390
1506
|
var PLIST_FILENAME = `${SERVICE_LABEL}.plist`;
|
|
@@ -1396,7 +1512,7 @@ function getNodePath() {
|
|
|
1396
1512
|
}
|
|
1397
1513
|
function getScriptPath() {
|
|
1398
1514
|
const scriptPath = process.argv[1];
|
|
1399
|
-
if (scriptPath &&
|
|
1515
|
+
if (scriptPath && existsSync4(scriptPath)) {
|
|
1400
1516
|
return scriptPath;
|
|
1401
1517
|
}
|
|
1402
1518
|
throw new Error("Could not determine script path");
|
|
@@ -1444,11 +1560,11 @@ var ServiceManager = class {
|
|
|
1444
1560
|
throw new Error("Service management is only supported on macOS");
|
|
1445
1561
|
}
|
|
1446
1562
|
const launchAgentsDir = dirname3(this.plistPath);
|
|
1447
|
-
if (!
|
|
1563
|
+
if (!existsSync4(launchAgentsDir)) {
|
|
1448
1564
|
await mkdir2(launchAgentsDir, { recursive: true });
|
|
1449
1565
|
}
|
|
1450
1566
|
const logDir = join7(homedir4(), ".ai-session-tidy");
|
|
1451
|
-
if (!
|
|
1567
|
+
if (!existsSync4(logDir)) {
|
|
1452
1568
|
await mkdir2(logDir, { recursive: true });
|
|
1453
1569
|
}
|
|
1454
1570
|
const stdoutPath = join7(logDir, "watcher.log");
|
|
@@ -1464,7 +1580,7 @@ var ServiceManager = class {
|
|
|
1464
1580
|
await writeFile2(this.plistPath, plistContent, "utf-8");
|
|
1465
1581
|
}
|
|
1466
1582
|
async uninstall() {
|
|
1467
|
-
if (
|
|
1583
|
+
if (existsSync4(this.plistPath)) {
|
|
1468
1584
|
await unlink(this.plistPath);
|
|
1469
1585
|
}
|
|
1470
1586
|
}
|
|
@@ -1472,9 +1588,18 @@ var ServiceManager = class {
|
|
|
1472
1588
|
if (!this.isSupported()) {
|
|
1473
1589
|
throw new Error("Service management is only supported on macOS");
|
|
1474
1590
|
}
|
|
1475
|
-
if (!
|
|
1591
|
+
if (!existsSync4(this.plistPath)) {
|
|
1476
1592
|
throw new Error('Service not installed. Run "watch start" to install and start.');
|
|
1477
1593
|
}
|
|
1594
|
+
const logDir = join7(homedir4(), ".ai-session-tidy");
|
|
1595
|
+
const stdoutPath = join7(logDir, "watcher.log");
|
|
1596
|
+
const stderrPath = join7(logDir, "watcher.error.log");
|
|
1597
|
+
if (existsSync4(stdoutPath)) {
|
|
1598
|
+
await writeFile2(stdoutPath, "", "utf-8");
|
|
1599
|
+
}
|
|
1600
|
+
if (existsSync4(stderrPath)) {
|
|
1601
|
+
await writeFile2(stderrPath, "", "utf-8");
|
|
1602
|
+
}
|
|
1478
1603
|
try {
|
|
1479
1604
|
execSync(`launchctl load "${this.plistPath}"`, { stdio: "pipe" });
|
|
1480
1605
|
} catch (error) {
|
|
@@ -1488,7 +1613,7 @@ var ServiceManager = class {
|
|
|
1488
1613
|
if (!this.isSupported()) {
|
|
1489
1614
|
throw new Error("Service management is only supported on macOS");
|
|
1490
1615
|
}
|
|
1491
|
-
if (!
|
|
1616
|
+
if (!existsSync4(this.plistPath)) {
|
|
1492
1617
|
return;
|
|
1493
1618
|
}
|
|
1494
1619
|
try {
|
|
@@ -1505,7 +1630,7 @@ var ServiceManager = class {
|
|
|
1505
1630
|
if (!this.isSupported()) {
|
|
1506
1631
|
return info;
|
|
1507
1632
|
}
|
|
1508
|
-
if (!
|
|
1633
|
+
if (!existsSync4(this.plistPath)) {
|
|
1509
1634
|
return info;
|
|
1510
1635
|
}
|
|
1511
1636
|
try {
|
|
@@ -1538,7 +1663,7 @@ var ServiceManager = class {
|
|
|
1538
1663
|
let stdout = "";
|
|
1539
1664
|
let stderr = "";
|
|
1540
1665
|
try {
|
|
1541
|
-
if (
|
|
1666
|
+
if (existsSync4(stdoutPath)) {
|
|
1542
1667
|
const content = await readFile4(stdoutPath, "utf-8");
|
|
1543
1668
|
const logLines = content.split("\n");
|
|
1544
1669
|
stdout = logLines.slice(-lines).join("\n");
|
|
@@ -1546,7 +1671,7 @@ var ServiceManager = class {
|
|
|
1546
1671
|
} catch {
|
|
1547
1672
|
}
|
|
1548
1673
|
try {
|
|
1549
|
-
if (
|
|
1674
|
+
if (existsSync4(stderrPath)) {
|
|
1550
1675
|
const content = await readFile4(stderrPath, "utf-8");
|
|
1551
1676
|
const logLines = content.split("\n");
|
|
1552
1677
|
stderr = logLines.slice(-lines).join("\n");
|
|
@@ -1638,7 +1763,7 @@ var statusCommand = new Command3("status").description("Show watcher service sta
|
|
|
1638
1763
|
if (status.pid) {
|
|
1639
1764
|
console.log(`PID: ${status.pid}`);
|
|
1640
1765
|
}
|
|
1641
|
-
console.log(`Plist: ${status.plistPath}`);
|
|
1766
|
+
console.log(`Plist: ${tildify(status.plistPath)}`);
|
|
1642
1767
|
console.log();
|
|
1643
1768
|
if (options.logs) {
|
|
1644
1769
|
const lines = parseInt(options.logs, 10) || 20;
|
|
@@ -1701,14 +1826,14 @@ async function runWatcher(options) {
|
|
|
1701
1826
|
logger.info(`Using default watch paths.`);
|
|
1702
1827
|
}
|
|
1703
1828
|
}
|
|
1704
|
-
const validPaths = watchPaths.filter((p) =>
|
|
1829
|
+
const validPaths = watchPaths.filter((p) => existsSync5(p));
|
|
1705
1830
|
if (validPaths.length === 0) {
|
|
1706
1831
|
logger.error("No valid watch paths found. Use -p to specify paths.");
|
|
1707
1832
|
return;
|
|
1708
1833
|
}
|
|
1709
1834
|
if (validPaths.length < watchPaths.length) {
|
|
1710
|
-
const invalidPaths = watchPaths.filter((p) => !
|
|
1711
|
-
logger.warn(`Skipping non-existent paths: ${invalidPaths.join(", ")}`);
|
|
1835
|
+
const invalidPaths = watchPaths.filter((p) => !existsSync5(p));
|
|
1836
|
+
logger.warn(`Skipping non-existent paths: ${invalidPaths.map(tildify).join(", ")}`);
|
|
1712
1837
|
}
|
|
1713
1838
|
const allScanners = createAllScanners();
|
|
1714
1839
|
const availableScanners = await getAvailableScanners(allScanners);
|
|
@@ -1720,7 +1845,7 @@ async function runWatcher(options) {
|
|
|
1720
1845
|
logger.info(
|
|
1721
1846
|
`Watching for project deletions (${availableScanners.map((s) => s.name).join(", ")})`
|
|
1722
1847
|
);
|
|
1723
|
-
logger.info(`Watch paths: ${validPaths.join(", ")}`);
|
|
1848
|
+
logger.info(`Watch paths: ${validPaths.map(tildify).join(", ")}`);
|
|
1724
1849
|
logger.info(`Cleanup delay: ${String(delayMinutes)} minute(s)`);
|
|
1725
1850
|
logger.info(`Watch depth: ${String(depth)}`);
|
|
1726
1851
|
if (process.stdout.isTTY) {
|
|
@@ -1735,12 +1860,12 @@ async function runWatcher(options) {
|
|
|
1735
1860
|
ignorePaths: getIgnorePaths(),
|
|
1736
1861
|
onDelete: async (events) => {
|
|
1737
1862
|
if (events.length === 1) {
|
|
1738
|
-
logger.info(`Detected deletion: ${events[0].path}`);
|
|
1863
|
+
logger.info(`Detected deletion: ${tildify(events[0].path)}`);
|
|
1739
1864
|
} else {
|
|
1740
1865
|
logger.info(`Detected ${events.length} deletions (debounced)`);
|
|
1741
1866
|
if (options.verbose) {
|
|
1742
1867
|
for (const event of events) {
|
|
1743
|
-
logger.debug(` - ${event.path}`);
|
|
1868
|
+
logger.debug(` - ${tildify(event.path)}`);
|
|
1744
1869
|
}
|
|
1745
1870
|
}
|
|
1746
1871
|
}
|
|
@@ -1904,7 +2029,7 @@ pathCommand.command("list").description("List watch paths").action(() => {
|
|
|
1904
2029
|
}
|
|
1905
2030
|
console.log();
|
|
1906
2031
|
for (const p of paths) {
|
|
1907
|
-
console.log(` ${p}`);
|
|
2032
|
+
console.log(` ${tildify(p)}`);
|
|
1908
2033
|
}
|
|
1909
2034
|
});
|
|
1910
2035
|
configCommand.addCommand(pathCommand);
|
|
@@ -1929,7 +2054,7 @@ ignoreCommand.command("list").description("List ignored paths").action(() => {
|
|
|
1929
2054
|
}
|
|
1930
2055
|
console.log();
|
|
1931
2056
|
for (const p of paths) {
|
|
1932
|
-
console.log(` ${p}`);
|
|
2057
|
+
console.log(` ${tildify(p)}`);
|
|
1933
2058
|
}
|
|
1934
2059
|
});
|
|
1935
2060
|
configCommand.addCommand(ignoreCommand);
|
|
@@ -1980,6 +2105,10 @@ configCommand.command("show").description("Show all configuration").action(() =>
|
|
|
1980
2105
|
});
|
|
1981
2106
|
configCommand.command("reset").description("Reset configuration to defaults").option("-f, --force", "Skip confirmation prompt").action(async (options) => {
|
|
1982
2107
|
if (!options.force) {
|
|
2108
|
+
if (!process.stdout.isTTY) {
|
|
2109
|
+
logger.error("Confirmation requires a TTY. Use -f to skip confirmation.");
|
|
2110
|
+
return;
|
|
2111
|
+
}
|
|
1983
2112
|
const { confirmed } = await inquirer2.prompt([
|
|
1984
2113
|
{
|
|
1985
2114
|
type: "confirm",
|