@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/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
- return encoded.replace(/-/g, "/");
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 (!existsSync(backupDir)) {
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} ${size}
858
- ${path}`;
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 autoCleanEntries = [
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 selectableSessions) {
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 (selectableSessions.length === 0) {
925
- if (autoCleanEntries.length > 0) {
926
- sessionsToClean = autoCleanEntries;
927
- logger.info(
928
- `Only ${autoCleanEntries.length} auto-cleanup target(s) found`
929
- );
930
- } else {
931
- logger.info("No selectable sessions found.");
932
- return;
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
- } else {
935
- console.log();
936
- const { selected } = await inquirer.prompt([
937
- {
938
- type: "checkbox",
939
- name: "selected",
940
- message: "Select sessions to delete:",
941
- choices: selectableSessions.map((session) => ({
942
- name: formatSessionChoice(session),
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
- sessionsToClean = [...selected, ...autoCleanEntries];
955
- const selectedSize = selected.reduce((sum, s) => sum + s.size, 0);
956
- console.log();
957
- if (selected.length > 0) {
958
- logger.info(
959
- `Selected: ${selected.length} session(s) (${formatSize(selectedSize)})`
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
- if (autoCleanEntries.length > 0) {
963
- const autoSize = autoCleanEntries.reduce((sum, s) => sum + s.size, 0);
964
- logger.info(
965
- `+ ${autoCleanEntries.length} auto-cleanup target(s) (${formatSize(autoSize)})`
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
- autoCleanParts.push(`${dryRunEnvs.length} session-env`);
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
- autoCleanParts.push(`${dryRunTodos.length} todos`);
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.dim(`Would auto-delete: ${autoCleanParts.join(" + ")} (${formatSize(autoSize)})`)}`
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 existsSync4 } from "fs";
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 existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
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 (existsSync2(packagePath)) {
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 (!existsSync2(configPath)) {
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 (!existsSync2(configDir)) {
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 existsSync3 } from "fs";
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 && existsSync3(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 (!existsSync3(launchAgentsDir)) {
1563
+ if (!existsSync4(launchAgentsDir)) {
1448
1564
  await mkdir2(launchAgentsDir, { recursive: true });
1449
1565
  }
1450
1566
  const logDir = join7(homedir4(), ".ai-session-tidy");
1451
- if (!existsSync3(logDir)) {
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 (existsSync3(this.plistPath)) {
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 (!existsSync3(this.plistPath)) {
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 (!existsSync3(this.plistPath)) {
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 (!existsSync3(this.plistPath)) {
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 (existsSync3(stdoutPath)) {
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 (existsSync3(stderrPath)) {
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) => existsSync4(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) => !existsSync4(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",