@sooink/ai-session-tidy 0.1.1 → 0.1.2

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 CHANGED
@@ -124,14 +124,19 @@ ai-session-tidy watch status -l # 최근 로그 표시
124
124
  설정을 관리합니다.
125
125
 
126
126
  ```bash
127
- ai-session-tidy config show # 전체 설정 보기
128
- ai-session-tidy config paths add ~/projects # 감시 경로 추가
129
- ai-session-tidy config paths list # 감시 경로 목록
130
- ai-session-tidy config delay 1 # 정리 딜레이 설정 (분)
131
- ai-session-tidy config depth 5 # 감시 깊이 설정
132
- ai-session-tidy config reset # 기본값으로 초기화
127
+ ai-session-tidy config show # 전체 설정 보기
128
+ ai-session-tidy config path add ~/projects # 감시 경로 추가
129
+ ai-session-tidy config path list # 감시 경로 목록
130
+ ai-session-tidy config ignore add ~/backup # 제외 경로 추가
131
+ ai-session-tidy config ignore list # 제외 경로 목록
132
+ ai-session-tidy config delay 1 # 정리 딜레이 설정 (분)
133
+ ai-session-tidy config depth 5 # 감시 깊이 설정
134
+ ai-session-tidy config reset # 기본값으로 초기화
133
135
  ```
134
136
 
137
+ > [!TIP]
138
+ > 숨김 폴더 (`.git`, `.cache` 등)와 macOS 시스템 폴더 (`Library`, `Music` 등)는 자동으로 제외됩니다.
139
+
135
140
  ## 정리 대상
136
141
 
137
142
  ### Claude Code
package/README.md CHANGED
@@ -122,14 +122,19 @@ ai-session-tidy watch status -l # Show recent logs
122
122
  Manage settings.
123
123
 
124
124
  ```bash
125
- ai-session-tidy config show # Show all settings
126
- ai-session-tidy config paths add ~/projects # Add watch path
127
- ai-session-tidy config paths list # List watch paths
128
- ai-session-tidy config delay 1 # Set cleanup delay (minutes)
129
- ai-session-tidy config depth 5 # Set watch depth
130
- ai-session-tidy config reset # Reset to defaults
125
+ ai-session-tidy config show # Show all settings
126
+ ai-session-tidy config path add ~/projects # Add watch path
127
+ ai-session-tidy config path list # List watch paths
128
+ ai-session-tidy config ignore add ~/backup # Add ignore path
129
+ ai-session-tidy config ignore list # List ignore paths
130
+ ai-session-tidy config delay 1 # Set cleanup delay (minutes)
131
+ ai-session-tidy config depth 5 # Set watch depth
132
+ ai-session-tidy config reset # Reset to defaults
131
133
  ```
132
134
 
135
+ > [!TIP]
136
+ > Hidden folders (`.git`, `.cache`, etc.) and macOS system folders (`Library`, `Music`, etc.) are automatically excluded from watching.
137
+
133
138
  ## What Gets Cleaned
134
139
 
135
140
  ### Claude Code
package/dist/index.js CHANGED
@@ -375,7 +375,7 @@ var ClaudeCodeScanner = class {
375
375
  }
376
376
  }
377
377
  async findCwdInJsonl(jsonlPath) {
378
- return new Promise((resolve3) => {
378
+ return new Promise((resolve4) => {
379
379
  const stream = createReadStream(jsonlPath, { encoding: "utf-8" });
380
380
  const rl = createInterface({ input: stream, crlfDelay: Infinity });
381
381
  let found = false;
@@ -393,16 +393,16 @@ var ClaudeCodeScanner = class {
393
393
  found = true;
394
394
  rl.close();
395
395
  stream.destroy();
396
- resolve3(entry.cwd);
396
+ resolve4(entry.cwd);
397
397
  }
398
398
  } catch {
399
399
  }
400
400
  });
401
401
  rl.on("close", () => {
402
- if (!found) resolve3(null);
402
+ if (!found) resolve4(null);
403
403
  });
404
404
  rl.on("error", () => {
405
- resolve3(null);
405
+ resolve4(null);
406
406
  });
407
407
  });
408
408
  }
@@ -1148,10 +1148,10 @@ function saveConfig(config) {
1148
1148
  if (!existsSync2(configDir)) {
1149
1149
  mkdirSync(configDir, { recursive: true });
1150
1150
  }
1151
+ const { version: _removed, ...cleanConfig } = config;
1151
1152
  const configWithVersion = {
1152
- version: getAppVersion(),
1153
1153
  configVersion: CONFIG_VERSION,
1154
- ...config
1154
+ ...cleanConfig
1155
1155
  };
1156
1156
  writeFileSync(configPath, JSON.stringify(configWithVersion, null, 2), "utf-8");
1157
1157
  }
@@ -1206,10 +1206,55 @@ function setWatchDepth(depth) {
1206
1206
  function resetConfig() {
1207
1207
  saveConfig({});
1208
1208
  }
1209
+ function getIgnorePaths() {
1210
+ const config = loadConfig();
1211
+ return config.ignorePaths;
1212
+ }
1213
+ function addIgnorePath(path) {
1214
+ const config = loadConfig();
1215
+ const resolved = resolve(path.replace(/^~/, homedir3()));
1216
+ const paths = config.ignorePaths ?? [];
1217
+ if (!paths.includes(resolved)) {
1218
+ paths.push(resolved);
1219
+ config.ignorePaths = paths;
1220
+ saveConfig(config);
1221
+ }
1222
+ }
1223
+ function removeIgnorePath(path) {
1224
+ const config = loadConfig();
1225
+ const resolved = resolve(path.replace(/^~/, homedir3()));
1226
+ const paths = config.ignorePaths ?? [];
1227
+ const index = paths.indexOf(resolved);
1228
+ if (index === -1) return false;
1229
+ paths.splice(index, 1);
1230
+ config.ignorePaths = paths;
1231
+ saveConfig(config);
1232
+ return true;
1233
+ }
1209
1234
 
1210
1235
  // src/core/watcher.ts
1211
1236
  import { watch } from "chokidar";
1212
1237
  import { access as access4 } from "fs/promises";
1238
+
1239
+ // src/core/constants.ts
1240
+ var IGNORED_SYSTEM_PATTERNS = [
1241
+ // All hidden folders (starting with .)
1242
+ /(^|[/\\])\../,
1243
+ // macOS user folders (not project directories)
1244
+ /\/Library\//,
1245
+ /\/Applications\//,
1246
+ /\/Music\//,
1247
+ /\/Movies\//,
1248
+ /\/Pictures\//,
1249
+ /\/Downloads\//,
1250
+ /\/Documents\//,
1251
+ /\/Desktop\//,
1252
+ /\/Public\//,
1253
+ // Development folders
1254
+ /node_modules/
1255
+ ];
1256
+
1257
+ // src/core/watcher.ts
1213
1258
  var Watcher = class {
1214
1259
  options;
1215
1260
  debounceMs;
@@ -1220,16 +1265,23 @@ var Watcher = class {
1220
1265
  batchedEvents = [];
1221
1266
  /** Debounce timer */
1222
1267
  debounceTimer = null;
1268
+ /** Paths that errored (to avoid duplicate logs) */
1269
+ erroredPaths = /* @__PURE__ */ new Set();
1223
1270
  constructor(options) {
1224
1271
  this.options = options;
1225
1272
  this.debounceMs = options.debounceMs ?? 1e4;
1226
1273
  }
1227
1274
  start() {
1228
1275
  if (this.watcher) return;
1276
+ const ignored = [...IGNORED_SYSTEM_PATTERNS];
1277
+ if (this.options.ignorePaths) {
1278
+ ignored.push(...this.options.ignorePaths);
1279
+ }
1229
1280
  this.watcher = watch(this.options.watchPaths, {
1230
1281
  ignoreInitial: true,
1231
1282
  persistent: true,
1232
- depth: this.options.depth ?? 3
1283
+ depth: this.options.depth ?? 3,
1284
+ ignored
1233
1285
  });
1234
1286
  this.watcher.on("unlinkDir", (path) => {
1235
1287
  this.handleDelete(path);
@@ -1237,6 +1289,13 @@ var Watcher = class {
1237
1289
  this.watcher.on("addDir", (path) => {
1238
1290
  this.handleRecovery(path);
1239
1291
  });
1292
+ this.watcher.on("error", (error) => {
1293
+ const pathInfo = error.path;
1294
+ if (pathInfo && !this.erroredPaths.has(pathInfo)) {
1295
+ this.erroredPaths.add(pathInfo);
1296
+ console.error(`[watch] Cannot watch: ${pathInfo} (${error.code})`);
1297
+ }
1298
+ });
1240
1299
  }
1241
1300
  stop() {
1242
1301
  if (!this.watcher) return;
@@ -1673,6 +1732,7 @@ async function runWatcher(options) {
1673
1732
  watchPaths: validPaths,
1674
1733
  delayMs,
1675
1734
  depth,
1735
+ ignorePaths: getIgnorePaths(),
1676
1736
  onDelete: async (events) => {
1677
1737
  if (events.length === 1) {
1678
1738
  logger.info(`Detected deletion: ${events[0].path}`);
@@ -1810,15 +1870,25 @@ var listCommand = new Command4("list").description("List detected AI coding tool
1810
1870
  // src/commands/config.ts
1811
1871
  import { Command as Command5 } from "commander";
1812
1872
  import inquirer2 from "inquirer";
1873
+ import { homedir as homedir6 } from "os";
1874
+ import { resolve as resolve3 } from "path";
1813
1875
  var configCommand = new Command5("config").description(
1814
1876
  "Manage configuration"
1815
1877
  );
1816
- var pathsCommand = new Command5("paths").description("Manage watch paths");
1817
- pathsCommand.command("add <path>").description("Add a watch path").action((path) => {
1878
+ var pathCommand = new Command5("path").description("Manage watch paths");
1879
+ pathCommand.command("add <path>").description("Add a watch path").action((path) => {
1880
+ const resolved = resolve3(path.replace(/^~/, homedir6()));
1881
+ const home = homedir6();
1882
+ if (resolved === home || home.startsWith(resolved + "/")) {
1883
+ logger.warn("Warning: Watching home directory is not recommended.");
1884
+ logger.warn("Many system folders will be excluded automatically.");
1885
+ logger.warn("Consider adding specific project folders instead.");
1886
+ console.log();
1887
+ }
1818
1888
  addWatchPath(path);
1819
1889
  logger.success(`Added: ${path}`);
1820
1890
  });
1821
- pathsCommand.command("remove <path>").description("Remove a watch path").action((path) => {
1891
+ pathCommand.command("remove <path>").description("Remove a watch path").action((path) => {
1822
1892
  const removed = removeWatchPath(path);
1823
1893
  if (removed) {
1824
1894
  logger.success(`Removed: ${path}`);
@@ -1826,7 +1896,7 @@ pathsCommand.command("remove <path>").description("Remove a watch path").action(
1826
1896
  logger.warn(`Path not found: ${path}`);
1827
1897
  }
1828
1898
  });
1829
- pathsCommand.command("list").description("List watch paths").action(() => {
1899
+ pathCommand.command("list").description("List watch paths").action(() => {
1830
1900
  const paths = getWatchPaths();
1831
1901
  if (!paths || paths.length === 0) {
1832
1902
  logger.info("No watch paths configured.");
@@ -1837,7 +1907,32 @@ pathsCommand.command("list").description("List watch paths").action(() => {
1837
1907
  console.log(` ${p}`);
1838
1908
  }
1839
1909
  });
1840
- configCommand.addCommand(pathsCommand);
1910
+ configCommand.addCommand(pathCommand);
1911
+ var ignoreCommand = new Command5("ignore").description("Manage ignore paths");
1912
+ ignoreCommand.command("add <path>").description("Add a path to ignore from watching").action((path) => {
1913
+ addIgnorePath(path);
1914
+ logger.success(`Added to ignore: ${path}`);
1915
+ });
1916
+ ignoreCommand.command("remove <path>").description("Remove a path from ignore list").action((path) => {
1917
+ const removed = removeIgnorePath(path);
1918
+ if (removed) {
1919
+ logger.success(`Removed from ignore: ${path}`);
1920
+ } else {
1921
+ logger.warn(`Path not found: ${path}`);
1922
+ }
1923
+ });
1924
+ ignoreCommand.command("list").description("List ignored paths").action(() => {
1925
+ const paths = getIgnorePaths();
1926
+ if (!paths || paths.length === 0) {
1927
+ logger.info("No ignore paths configured.");
1928
+ return;
1929
+ }
1930
+ console.log();
1931
+ for (const p of paths) {
1932
+ console.log(` ${p}`);
1933
+ }
1934
+ });
1935
+ configCommand.addCommand(ignoreCommand);
1841
1936
  var DEFAULT_DELAY_MINUTES2 = 5;
1842
1937
  var MAX_DELAY_MINUTES2 = 10;
1843
1938
  configCommand.command("delay [minutes]").description(`Get or set watch delay in minutes (default: ${DEFAULT_DELAY_MINUTES2}, max: ${MAX_DELAY_MINUTES2})`).action((minutes) => {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/commands/scan.ts","../src/utils/logger.ts","../src/utils/size.ts","../src/scanners/claude-code.ts","../src/utils/paths.ts","../src/scanners/cursor.ts","../src/scanners/index.ts","../src/commands/clean.ts","../src/core/cleaner.ts","../src/core/trash.ts","../src/commands/watch.ts","../src/utils/config.ts","../src/core/watcher.ts","../src/core/service.ts","../src/commands/list.ts","../src/commands/config.ts","../src/index.ts"],"sourcesContent":["import { Command } from 'commander';\n\nimport { scanCommand } from './commands/scan.js';\nimport { cleanCommand } from './commands/clean.js';\nimport { watchCommand } from './commands/watch.js';\nimport { listCommand } from './commands/list.js';\nimport { configCommand } from './commands/config.js';\nimport { getAppVersion } from './utils/config.js';\n\nexport const cli = new Command()\n .name('ai-session-tidy')\n .description(\n 'CLI tool that detects and cleans orphaned session data from AI coding tools'\n )\n .version(getAppVersion());\n\ncli.addCommand(scanCommand, { isDefault: true });\ncli.addCommand(cleanCommand);\ncli.addCommand(watchCommand);\ncli.addCommand(listCommand);\ncli.addCommand(configCommand);\n","import { Command } from 'commander';\nimport Table from 'cli-table3';\nimport ora from 'ora';\nimport chalk from 'chalk';\n\nimport { logger } from '../utils/logger.js';\nimport { formatSize } from '../utils/size.js';\nimport {\n createAllScanners,\n getAvailableScanners,\n runAllScanners,\n} from '../scanners/index.js';\nimport type { ScanResult } from '../scanners/types.js';\n\ninterface ScanOptions {\n verbose?: boolean;\n json?: boolean;\n}\n\nfunction formatTokens(count?: number): string {\n if (!count) return '0';\n if (count >= 1000000) return `${(count / 1000000).toFixed(1)}M`;\n if (count >= 1000) return `${(count / 1000).toFixed(0)}K`;\n return count.toString();\n}\n\nexport const scanCommand = new Command('scan')\n .description('Scan for orphaned session data from AI coding tools')\n .option('-v, --verbose', 'Enable verbose output')\n .option('--json', 'Output results as JSON')\n .action(async (options: ScanOptions) => {\n const spinner = ora('Scanning for orphaned sessions...').start();\n\n try {\n const allScanners = createAllScanners();\n const availableScanners = await getAvailableScanners(allScanners);\n\n if (availableScanners.length === 0) {\n spinner.warn('No AI coding tools detected on this system.');\n return;\n }\n\n if (options.verbose) {\n spinner.text = `Found ${availableScanners.length} tools: ${availableScanners.map((s) => s.name).join(', ')}`;\n }\n\n const results = await runAllScanners(availableScanners);\n spinner.stop();\n\n if (options.json) {\n outputJson(results);\n } else {\n outputTable(results, options.verbose);\n }\n } catch (error) {\n spinner.fail('Scan failed');\n logger.error(\n error instanceof Error ? error.message : 'Unknown error occurred'\n );\n process.exit(1);\n }\n });\n\nfunction outputJson(results: ScanResult[]): void {\n const allSessions = results.flatMap((r) => r.sessions);\n const output = {\n totalSessions: allSessions.length,\n totalSize: results.reduce((sum, r) => sum + r.totalSize, 0),\n results: results.map((r) => ({\n tool: r.toolName,\n sessionCount: r.sessions.length,\n totalSize: r.totalSize,\n scanDuration: r.scanDuration,\n sessions: r.sessions,\n })),\n };\n console.log(JSON.stringify(output, null, 2));\n}\n\nfunction outputTable(results: ScanResult[], verbose?: boolean): void {\n const allSessions = results.flatMap((r) => r.sessions);\n const folderSessions = allSessions.filter((s) => s.type === 'session' || s.type === undefined);\n const configEntries = allSessions.filter((s) => s.type === 'config');\n const sessionEnvEntries = allSessions.filter((s) => s.type === 'session-env');\n const todosEntries = allSessions.filter((s) => s.type === 'todos');\n const fileHistoryEntries = allSessions.filter((s) => s.type === 'file-history');\n const totalSize = results.reduce((sum, r) => sum + r.totalSize, 0);\n\n if (allSessions.length === 0) {\n logger.success('No orphaned sessions found.');\n return;\n }\n\n console.log();\n\n // Summary message\n const parts: string[] = [];\n if (folderSessions.length > 0) {\n parts.push(`${folderSessions.length} session folder(s)`);\n }\n if (configEntries.length > 0) {\n parts.push(`${configEntries.length} config entry(ies)`);\n }\n if (sessionEnvEntries.length > 0) {\n parts.push(`${sessionEnvEntries.length} session-env folder(s)`);\n }\n if (todosEntries.length > 0) {\n parts.push(`${todosEntries.length} todos file(s)`);\n }\n if (fileHistoryEntries.length > 0) {\n parts.push(`${fileHistoryEntries.length} file-history folder(s)`);\n }\n logger.warn(`Found ${parts.join(' + ')} (${formatSize(totalSize)})`);\n console.log();\n\n // Summary by tool\n const summaryTable = new Table({\n head: [\n chalk.cyan('Tool'),\n chalk.cyan('Sessions'),\n chalk.cyan('Config'),\n chalk.cyan('Env'),\n chalk.cyan('Todos'),\n chalk.cyan('History'),\n chalk.cyan('Size'),\n chalk.cyan('Scan Time'),\n ],\n style: { head: [] },\n });\n\n for (const result of results) {\n if (result.sessions.length > 0) {\n const folders = result.sessions.filter((s) => s.type === 'session' || s.type === undefined).length;\n const configs = result.sessions.filter((s) => s.type === 'config').length;\n const envs = result.sessions.filter((s) => s.type === 'session-env').length;\n const todos = result.sessions.filter((s) => s.type === 'todos').length;\n const histories = result.sessions.filter((s) => s.type === 'file-history').length;\n summaryTable.push([\n result.toolName,\n folders > 0 ? String(folders) : '-',\n configs > 0 ? String(configs) : '-',\n envs > 0 ? String(envs) : '-',\n todos > 0 ? String(todos) : '-',\n histories > 0 ? String(histories) : '-',\n formatSize(result.totalSize),\n `${result.scanDuration.toFixed(0)}ms`,\n ]);\n }\n }\n\n console.log(summaryTable.toString());\n\n // Detailed info (verbose)\n if (verbose && allSessions.length > 0) {\n // Session folders\n if (folderSessions.length > 0) {\n console.log();\n console.log(chalk.bold('Session Folders:'));\n console.log();\n\n for (const session of folderSessions) {\n const projectName = session.projectPath.split('/').pop() || session.projectPath;\n console.log(\n ` ${chalk.cyan(`[${session.toolName}]`)} ${chalk.white(projectName)} ${chalk.dim(`(${formatSize(session.size)})`)}`\n );\n console.log(` ${chalk.dim('→')} ${session.projectPath}`);\n console.log(` ${chalk.dim('Modified:')} ${session.lastModified.toLocaleDateString()}`);\n console.log();\n }\n }\n\n // Config entries\n if (configEntries.length > 0) {\n console.log();\n console.log(chalk.bold('Config Entries (~/.claude.json):'));\n console.log();\n\n for (const entry of configEntries) {\n const projectName = entry.projectPath.split('/').pop() || entry.projectPath;\n console.log(\n ` ${chalk.yellow('[config]')} ${chalk.white(projectName)}`\n );\n console.log(` ${chalk.dim('→')} ${entry.projectPath}`);\n if (entry.configStats?.lastCost) {\n const cost = `$${entry.configStats.lastCost.toFixed(2)}`;\n const inTokens = formatTokens(entry.configStats.lastTotalInputTokens);\n const outTokens = formatTokens(entry.configStats.lastTotalOutputTokens);\n console.log(` ${chalk.dim(`Cost: ${cost} | Tokens: ${inTokens} in / ${outTokens} out`)}`);\n }\n console.log();\n }\n }\n\n }\n\n console.log();\n console.log(\n chalk.dim('Run \"ai-session-tidy clean\" to remove orphaned sessions.')\n );\n}\n","import chalk from 'chalk';\n\nexport interface Logger {\n info(message: string): void;\n warn(message: string): void;\n error(message: string): void;\n success(message: string): void;\n debug(message: string): void;\n}\n\nexport const logger: Logger = {\n info(message: string): void {\n console.log(chalk.blue('ℹ'), message);\n },\n warn(message: string): void {\n console.log(chalk.yellow('⚠'), message);\n },\n error(message: string): void {\n console.log(chalk.red('✖'), message);\n },\n success(message: string): void {\n console.log(chalk.green('✔'), message);\n },\n debug(message: string): void {\n if (process.env['DEBUG']) {\n console.log(chalk.gray('🐛'), message);\n }\n },\n};\n","import { readdir, stat } from 'fs/promises';\nimport { join } from 'path';\n\nconst UNITS = ['B', 'KB', 'MB', 'GB', 'TB'] as const;\n\n/**\n * Format byte size to human-readable format\n */\nexport function formatSize(bytes: number): string {\n if (bytes <= 0) return '0 B';\n\n let size = bytes;\n let unitIndex = 0;\n\n while (size >= 1024 && unitIndex < UNITS.length - 1) {\n size /= 1024;\n unitIndex++;\n }\n\n if (unitIndex === 0) {\n return `${Math.floor(size)} ${UNITS[unitIndex]}`;\n }\n\n return `${size.toFixed(1)} ${UNITS[unitIndex]}`;\n}\n\n/**\n * Recursively calculate total size of a directory\n */\nexport async function getDirectorySize(dirPath: string): Promise<number> {\n try {\n const entries = await readdir(dirPath, { withFileTypes: true });\n let totalSize = 0;\n\n for (const entry of entries) {\n const fullPath = join(dirPath, entry.name);\n\n if (entry.isDirectory()) {\n totalSize += await getDirectorySize(fullPath);\n } else if (entry.isFile()) {\n const fileStat = await stat(fullPath);\n totalSize += fileStat.size;\n }\n }\n\n return totalSize;\n } catch {\n return 0;\n }\n}\n","import { readdir, readFile, stat, access } from 'fs/promises';\nimport { join } from 'path';\nimport { createReadStream } from 'fs';\nimport { createInterface } from 'readline';\n\nimport type { Scanner, ScanResult, OrphanedSession } from './types.js';\nimport {\n decodePath,\n getClaudeProjectsDir,\n getClaudeConfigPath,\n getClaudeSessionEnvDir,\n getClaudeTodosDir,\n getClaudeFileHistoryDir,\n} from '../utils/paths.js';\nimport { getDirectorySize } from '../utils/size.js';\n\ninterface ClaudeProjectData {\n lastCost?: number;\n lastTotalInputTokens?: number;\n lastTotalOutputTokens?: number;\n [key: string]: unknown;\n}\n\ninterface ClaudeConfig {\n projects?: Record<string, ClaudeProjectData>;\n [key: string]: unknown;\n}\n\ninterface SessionEntry {\n cwd?: string;\n}\n\nexport interface ClaudeCodeScannerOptions {\n projectsDir?: string;\n configPath?: string | null; // null to disable config scanning\n sessionEnvDir?: string | null; // null to disable session-env scanning\n todosDir?: string | null; // null to disable todos scanning\n fileHistoryDir?: string | null; // null to disable file-history scanning\n}\n\nexport class ClaudeCodeScanner implements Scanner {\n readonly name = 'claude-code' as const;\n private readonly projectsDir: string;\n private readonly configPath: string | null;\n private readonly sessionEnvDir: string | null;\n private readonly todosDir: string | null;\n private readonly fileHistoryDir: string | null;\n\n constructor(projectsDirOrOptions?: string | ClaudeCodeScannerOptions) {\n if (typeof projectsDirOrOptions === 'string') {\n // Backward compatibility: treat string as projectsDir, disable other scans\n this.projectsDir = projectsDirOrOptions;\n this.configPath = null;\n this.sessionEnvDir = null;\n this.todosDir = null;\n this.fileHistoryDir = null;\n } else if (projectsDirOrOptions) {\n this.projectsDir = projectsDirOrOptions.projectsDir ?? getClaudeProjectsDir();\n this.configPath = projectsDirOrOptions.configPath === undefined\n ? getClaudeConfigPath()\n : projectsDirOrOptions.configPath;\n this.sessionEnvDir = projectsDirOrOptions.sessionEnvDir === undefined\n ? getClaudeSessionEnvDir()\n : projectsDirOrOptions.sessionEnvDir;\n this.todosDir = projectsDirOrOptions.todosDir === undefined\n ? getClaudeTodosDir()\n : projectsDirOrOptions.todosDir;\n this.fileHistoryDir = projectsDirOrOptions.fileHistoryDir === undefined\n ? getClaudeFileHistoryDir()\n : projectsDirOrOptions.fileHistoryDir;\n } else {\n this.projectsDir = getClaudeProjectsDir();\n this.configPath = getClaudeConfigPath();\n this.sessionEnvDir = getClaudeSessionEnvDir();\n this.todosDir = getClaudeTodosDir();\n this.fileHistoryDir = getClaudeFileHistoryDir();\n }\n }\n\n async isAvailable(): Promise<boolean> {\n try {\n await access(this.projectsDir);\n return true;\n } catch {\n return false;\n }\n }\n\n async scan(): Promise<ScanResult> {\n const startTime = performance.now();\n const sessions: OrphanedSession[] = [];\n\n // 1. Scan session folders\n if (await this.isAvailable()) {\n const entries = await readdir(this.projectsDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n const sessionPath = join(this.projectsDir, entry.name);\n\n // Extract actual project path from JSONL file\n let projectPath = await this.extractProjectPath(sessionPath);\n\n // Fallback to decoding if path not found in JSONL\n if (!projectPath) {\n projectPath = decodePath(entry.name);\n }\n\n // Check if original project exists\n const projectExists = await this.pathExists(projectPath);\n if (projectExists) continue;\n\n // Exclude empty directories\n const size = await getDirectorySize(sessionPath);\n if (size === 0) continue;\n\n // Get last modified time\n const lastModified = await this.getLastModified(sessionPath);\n\n sessions.push({\n toolName: this.name,\n sessionPath,\n projectPath,\n size,\n lastModified,\n type: 'session',\n });\n }\n }\n\n // 2. Scan ~/.claude.json config file\n const configSessions = await this.scanConfigFile();\n sessions.push(...configSessions);\n\n // 3. Scan ~/.claude/session-env empty folders\n const sessionEnvSessions = await this.scanSessionEnvDir();\n sessions.push(...sessionEnvSessions);\n\n // 4. Collect valid session UUIDs then scan todos/file-history\n const validSessionIds = await this.collectValidSessionIds();\n\n // 5. Scan ~/.claude/todos for orphaned files\n const todosSessions = await this.scanTodosDir(validSessionIds);\n sessions.push(...todosSessions);\n\n // 6. Scan ~/.claude/file-history for orphaned folders\n const fileHistorySessions = await this.scanFileHistoryDir(validSessionIds);\n sessions.push(...fileHistorySessions);\n\n const totalSize = sessions.reduce((sum, s) => sum + s.size, 0);\n\n return {\n toolName: this.name,\n sessions,\n totalSize,\n scanDuration: performance.now() - startTime,\n };\n }\n\n /**\n * Detect orphaned projects from ~/.claude.json projects entries\n */\n private async scanConfigFile(): Promise<OrphanedSession[]> {\n if (!this.configPath) {\n return [];\n }\n\n const configPath = this.configPath;\n const orphanedConfigs: OrphanedSession[] = [];\n\n try {\n const content = await readFile(configPath, 'utf-8');\n const config: ClaudeConfig = JSON.parse(content);\n const configStat = await stat(configPath);\n\n if (!config.projects || typeof config.projects !== 'object') {\n return [];\n }\n\n for (const [projectPath, projectData] of Object.entries(config.projects)) {\n const projectExists = await this.pathExists(projectPath);\n if (!projectExists) {\n orphanedConfigs.push({\n toolName: this.name,\n sessionPath: configPath,\n projectPath,\n size: 0, // config entries have negligible size\n lastModified: configStat.mtime,\n type: 'config',\n configStats: {\n lastCost: projectData.lastCost,\n lastTotalInputTokens: projectData.lastTotalInputTokens,\n lastTotalOutputTokens: projectData.lastTotalOutputTokens,\n },\n });\n }\n }\n } catch {\n // Ignore if config file doesn't exist or read fails\n }\n\n return orphanedConfigs;\n }\n\n /**\n * Detect empty session environment folders from ~/.claude/session-env\n */\n private async scanSessionEnvDir(): Promise<OrphanedSession[]> {\n if (!this.sessionEnvDir) {\n return [];\n }\n\n const orphanedEnvs: OrphanedSession[] = [];\n\n try {\n await access(this.sessionEnvDir);\n const entries = await readdir(this.sessionEnvDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n const envPath = join(this.sessionEnvDir, entry.name);\n const files = await readdir(envPath);\n\n // Only treat empty folders as orphaned\n if (files.length === 0) {\n const envStat = await stat(envPath);\n orphanedEnvs.push({\n toolName: this.name,\n sessionPath: envPath,\n projectPath: entry.name, // UUID\n size: 0,\n lastModified: envStat.mtime,\n type: 'session-env',\n });\n }\n }\n } catch {\n // Ignore if directory doesn't exist or access fails\n }\n\n return orphanedEnvs;\n }\n\n /**\n * Collect valid session UUIDs from all projects\n */\n private async collectValidSessionIds(): Promise<Set<string>> {\n const sessionIds = new Set<string>();\n\n try {\n const projectDirs = await readdir(this.projectsDir, { withFileTypes: true });\n\n for (const projectDir of projectDirs) {\n if (!projectDir.isDirectory()) continue;\n\n const projectPath = join(this.projectsDir, projectDir.name);\n const files = await readdir(projectPath);\n\n for (const file of files) {\n if (file.endsWith('.jsonl')) {\n // Extract UUID from UUID.jsonl\n const sessionId = file.replace('.jsonl', '');\n sessionIds.add(sessionId);\n }\n }\n }\n } catch {\n // Return empty Set if directory access fails\n }\n\n return sessionIds;\n }\n\n /**\n * Detect orphaned todo files from ~/.claude/todos\n * Filename pattern: {session-uuid}-agent-{agent-uuid}.json\n */\n private async scanTodosDir(validSessionIds: Set<string>): Promise<OrphanedSession[]> {\n if (!this.todosDir) {\n return [];\n }\n\n const orphanedTodos: OrphanedSession[] = [];\n\n try {\n await access(this.todosDir);\n const entries = await readdir(this.todosDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith('.json')) continue;\n\n // Extract session UUID from filename (first UUID)\n const sessionId = entry.name.split('-agent-')[0];\n if (!sessionId) continue;\n\n // Orphan if not in valid session IDs\n if (!validSessionIds.has(sessionId)) {\n const todoPath = join(this.todosDir, entry.name);\n const todoStat = await stat(todoPath);\n\n orphanedTodos.push({\n toolName: this.name,\n sessionPath: todoPath,\n projectPath: sessionId, // Session UUID\n size: todoStat.size,\n lastModified: todoStat.mtime,\n type: 'todos',\n });\n }\n }\n } catch {\n // Ignore if directory doesn't exist or access fails\n }\n\n return orphanedTodos;\n }\n\n /**\n * Detect orphaned folders from ~/.claude/file-history\n * Folder name is the session UUID\n */\n private async scanFileHistoryDir(validSessionIds: Set<string>): Promise<OrphanedSession[]> {\n if (!this.fileHistoryDir) {\n return [];\n }\n\n const orphanedHistories: OrphanedSession[] = [];\n\n try {\n await access(this.fileHistoryDir);\n const entries = await readdir(this.fileHistoryDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n const sessionId = entry.name;\n\n // Orphan if not in valid session IDs\n if (!validSessionIds.has(sessionId)) {\n const historyPath = join(this.fileHistoryDir, entry.name);\n const size = await getDirectorySize(historyPath);\n const historyStat = await stat(historyPath);\n\n orphanedHistories.push({\n toolName: this.name,\n sessionPath: historyPath,\n projectPath: sessionId, // Session UUID\n size,\n lastModified: historyStat.mtime,\n type: 'file-history',\n });\n }\n }\n } catch {\n // Ignore if directory doesn't exist or access fails\n }\n\n return orphanedHistories;\n }\n\n /**\n * Extract project path (cwd) from JSONL file\n */\n private async extractProjectPath(sessionDir: string): Promise<string | null> {\n try {\n const files = await readdir(sessionDir);\n const jsonlFile = files.find((f) => f.endsWith('.jsonl'));\n\n if (!jsonlFile) return null;\n\n const jsonlPath = join(sessionDir, jsonlFile);\n\n // Read only first few lines to find cwd\n const cwd = await this.findCwdInJsonl(jsonlPath);\n return cwd;\n } catch {\n return null;\n }\n }\n\n private async findCwdInJsonl(jsonlPath: string): Promise<string | null> {\n return new Promise((resolve) => {\n const stream = createReadStream(jsonlPath, { encoding: 'utf-8' });\n const rl = createInterface({ input: stream, crlfDelay: Infinity });\n\n let found = false;\n let lineCount = 0;\n const maxLines = 10; // Check only first 10 lines\n\n rl.on('line', (line) => {\n if (found || lineCount >= maxLines) {\n rl.close();\n return;\n }\n\n lineCount++;\n\n try {\n const entry: SessionEntry = JSON.parse(line);\n if (entry.cwd) {\n found = true;\n rl.close();\n stream.destroy();\n resolve(entry.cwd);\n }\n } catch {\n // Ignore JSON parse failures\n }\n });\n\n rl.on('close', () => {\n if (!found) resolve(null);\n });\n\n rl.on('error', () => {\n resolve(null);\n });\n });\n }\n\n private async pathExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n }\n\n private async getLastModified(dirPath: string): Promise<Date> {\n try {\n const entries = await readdir(dirPath, { withFileTypes: true });\n let latestTime = 0;\n\n for (const entry of entries) {\n const fullPath = join(dirPath, entry.name);\n const fileStat = await stat(fullPath);\n const mtime = fileStat.mtimeMs;\n\n if (mtime > latestTime) {\n latestTime = mtime;\n }\n }\n\n return latestTime > 0 ? new Date(latestTime) : new Date();\n } catch {\n return new Date();\n }\n }\n}\n","import { homedir } from 'os';\nimport { join } from 'path';\n\n/**\n * Encode Unix path to Claude Code style\n * /home/user/project → -home-user-project\n */\nexport function encodePath(path: string): string {\n if (path === '') return '';\n // Only convert Unix slashes, leave Windows paths as-is\n if (!path.includes('/')) return path;\n return path.replace(/\\//g, '-');\n}\n\n/**\n * Decode Claude Code encoded path to Unix path\n * -home-user-project → /home/user/project\n */\nexport function decodePath(encoded: string): string {\n if (encoded === '') return '';\n // Treat as Unix encoding if it starts with a dash\n if (!encoded.startsWith('-')) return encoded;\n return encoded.replace(/-/g, '/');\n}\n\n/**\n * Return platform-specific application config directory\n */\nexport function getConfigDir(appName: string): string {\n switch (process.platform) {\n case 'darwin':\n return join(homedir(), 'Library/Application Support', appName);\n case 'win32':\n return join(process.env['APPDATA'] || '', appName);\n default:\n return join(homedir(), '.config', appName);\n }\n}\n\n/**\n * Claude Code projects directory path\n */\nexport function getClaudeProjectsDir(): string {\n return join(homedir(), '.claude', 'projects');\n}\n\n/**\n * Claude Code global config file path (~/.claude.json)\n */\nexport function getClaudeConfigPath(): string {\n return join(homedir(), '.claude.json');\n}\n\n/**\n * Cursor workspaceStorage directory path\n */\nexport function getCursorWorkspaceDir(): string {\n return join(getConfigDir('Cursor'), 'User', 'workspaceStorage');\n}\n\n/**\n * Claude Code session environment directory path (~/.claude/session-env)\n */\nexport function getClaudeSessionEnvDir(): string {\n return join(homedir(), '.claude', 'session-env');\n}\n\n/**\n * Claude Code todos directory path (~/.claude/todos)\n */\nexport function getClaudeTodosDir(): string {\n return join(homedir(), '.claude', 'todos');\n}\n\n/**\n * Claude Code file-history directory path (~/.claude/file-history)\n */\nexport function getClaudeFileHistoryDir(): string {\n return join(homedir(), '.claude', 'file-history');\n}\n\n/**\n * Replace home directory with ~ for display\n * /Users/user/.ai-session-tidy → ~/.ai-session-tidy\n */\nexport function tildify(path: string): string {\n const home = homedir();\n if (path.startsWith(home)) {\n return path.replace(home, '~');\n }\n return path;\n}\n","import { readdir, readFile, stat, access } from 'fs/promises';\nimport { join } from 'path';\nimport { fileURLToPath } from 'url';\n\nimport type { Scanner, ScanResult, OrphanedSession } from './types.js';\nimport { getCursorWorkspaceDir } from '../utils/paths.js';\nimport { getDirectorySize } from '../utils/size.js';\n\ninterface WorkspaceJson {\n folder?: string;\n}\n\nexport class CursorScanner implements Scanner {\n readonly name = 'cursor' as const;\n private readonly workspaceDir: string;\n\n constructor(workspaceDir?: string) {\n this.workspaceDir = workspaceDir ?? getCursorWorkspaceDir();\n }\n\n async isAvailable(): Promise<boolean> {\n try {\n await access(this.workspaceDir);\n return true;\n } catch {\n return false;\n }\n }\n\n async scan(): Promise<ScanResult> {\n const startTime = performance.now();\n const sessions: OrphanedSession[] = [];\n\n if (!(await this.isAvailable())) {\n return {\n toolName: this.name,\n sessions: [],\n totalSize: 0,\n scanDuration: performance.now() - startTime,\n };\n }\n\n const entries = await readdir(this.workspaceDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n const sessionPath = join(this.workspaceDir, entry.name);\n const workspaceJsonPath = join(sessionPath, 'workspace.json');\n\n // Read workspace.json\n const projectPath = await this.parseWorkspaceJson(workspaceJsonPath);\n if (!projectPath) continue;\n\n // Check if original project exists\n const projectExists = await this.pathExists(projectPath);\n if (projectExists) continue;\n\n // Calculate session size\n const size = await getDirectorySize(sessionPath);\n\n // Get last modified time\n const lastModified = await this.getLastModified(sessionPath);\n\n sessions.push({\n toolName: this.name,\n sessionPath,\n projectPath,\n size,\n lastModified,\n });\n }\n\n const totalSize = sessions.reduce((sum, s) => sum + s.size, 0);\n\n return {\n toolName: this.name,\n sessions,\n totalSize,\n scanDuration: performance.now() - startTime,\n };\n }\n\n private async parseWorkspaceJson(\n workspaceJsonPath: string\n ): Promise<string | null> {\n try {\n const content = await readFile(workspaceJsonPath, 'utf-8');\n const data: WorkspaceJson = JSON.parse(content);\n\n if (!data.folder) return null;\n\n // Convert file:// URL to regular path\n if (data.folder.startsWith('file://')) {\n return fileURLToPath(data.folder);\n }\n\n return data.folder;\n } catch {\n return null;\n }\n }\n\n private async pathExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n }\n\n private async getLastModified(dirPath: string): Promise<Date> {\n try {\n const entries = await readdir(dirPath, { withFileTypes: true });\n let latestTime = 0;\n\n for (const entry of entries) {\n const fullPath = join(dirPath, entry.name);\n const fileStat = await stat(fullPath);\n const mtime = fileStat.mtimeMs;\n\n if (mtime > latestTime) {\n latestTime = mtime;\n }\n }\n\n return latestTime > 0 ? new Date(latestTime) : new Date();\n } catch {\n return new Date();\n }\n }\n}\n","import type { Scanner, ScanResult } from './types.js';\nimport { ClaudeCodeScanner } from './claude-code.js';\nimport { CursorScanner } from './cursor.js';\n\nexport { ClaudeCodeScanner } from './claude-code.js';\nexport { CursorScanner } from './cursor.js';\nexport * from './types.js';\n\n/**\n * Create all scanner instances\n */\nexport function createAllScanners(): Scanner[] {\n return [new ClaudeCodeScanner(), new CursorScanner()];\n}\n\n/**\n * Filter to available scanners only\n */\nexport async function getAvailableScanners(\n scanners: Scanner[]\n): Promise<Scanner[]> {\n const results = await Promise.all(\n scanners.map(async (scanner) => ({\n scanner,\n available: await scanner.isAvailable(),\n }))\n );\n\n return results.filter((r) => r.available).map((r) => r.scanner);\n}\n\n/**\n * Run all scanners and merge results\n */\nexport async function runAllScanners(\n scanners: Scanner[]\n): Promise<ScanResult[]> {\n return Promise.all(scanners.map((scanner) => scanner.scan()));\n}\n","import { basename } from 'path';\n\nimport { Command } from 'commander';\nimport ora from 'ora';\nimport chalk from 'chalk';\nimport inquirer from 'inquirer';\n\nimport { logger } from '../utils/logger.js';\nimport { formatSize } from '../utils/size.js';\nimport { tildify } from '../utils/paths.js';\nimport {\n createAllScanners,\n getAvailableScanners,\n runAllScanners,\n} from '../scanners/index.js';\nimport { Cleaner } from '../core/cleaner.js';\nimport type { OrphanedSession } from '../scanners/types.js';\n\ninterface CleanOptions {\n force?: boolean;\n dryRun?: boolean;\n noTrash?: boolean;\n verbose?: boolean;\n interactive?: boolean;\n}\n\nfunction formatSessionChoice(session: OrphanedSession): string {\n const projectName = basename(session.projectPath);\n const isConfig = session.type === 'config';\n const toolTag = isConfig\n ? chalk.yellow('[config]')\n : chalk.cyan(`[${session.toolName}]`);\n const name = chalk.white(projectName);\n const size = isConfig ? '' : chalk.dim(`(${formatSize(session.size)})`);\n const path = chalk.dim(`→ ${session.projectPath}`);\n return `${toolTag} ${name} ${size}\\n ${path}`;\n}\n\nexport const cleanCommand = new Command('clean')\n .description('Remove orphaned session data')\n .option('-f, --force', 'Skip confirmation prompt')\n .option('-n, --dry-run', 'Show what would be deleted without deleting')\n .option('-i, --interactive', 'Select sessions to delete interactively')\n .option('--no-trash', 'Permanently delete instead of moving to trash')\n .option('-v, --verbose', 'Enable verbose output')\n .action(async (options: CleanOptions) => {\n const spinner = ora('Scanning for orphaned sessions...').start();\n\n try {\n // Scan\n const allScanners = createAllScanners();\n const availableScanners = await getAvailableScanners(allScanners);\n\n if (availableScanners.length === 0) {\n spinner.warn('No AI coding tools detected on this system.');\n return;\n }\n\n const results = await runAllScanners(availableScanners);\n const allSessions = results.flatMap((r) => r.sessions);\n const totalSize = results.reduce((sum, r) => sum + r.totalSize, 0);\n\n spinner.stop();\n\n if (allSessions.length === 0) {\n logger.success('No orphaned sessions found.');\n return;\n }\n\n // Separate session folders, config entries, and auto-cleanup targets\n const folderSessions = allSessions.filter(\n (s) => s.type === 'session' || s.type === undefined\n );\n const configEntries = allSessions.filter((s) => s.type === 'config');\n const sessionEnvEntries = allSessions.filter(\n (s) => s.type === 'session-env'\n );\n const todosEntries = allSessions.filter((s) => s.type === 'todos');\n const fileHistoryEntries = allSessions.filter(\n (s) => s.type === 'file-history'\n );\n\n // Auto-cleanup targets (session-env, todos, file-history)\n const autoCleanEntries = [\n ...sessionEnvEntries,\n ...todosEntries,\n ...fileHistoryEntries,\n ];\n\n // Interactive selection targets (excluding auto-cleanup targets)\n const selectableSessions = allSessions.filter(\n (s) =>\n s.type !== 'session-env' &&\n s.type !== 'todos' &&\n s.type !== 'file-history'\n );\n\n // Output summary\n console.log();\n const parts: string[] = [];\n if (folderSessions.length > 0) {\n parts.push(`${folderSessions.length} session folder(s)`);\n }\n if (configEntries.length > 0) {\n parts.push(`${configEntries.length} config entry(ies)`);\n }\n if (sessionEnvEntries.length > 0) {\n parts.push(`${sessionEnvEntries.length} session-env folder(s)`);\n }\n if (todosEntries.length > 0) {\n parts.push(`${todosEntries.length} todos file(s)`);\n }\n if (fileHistoryEntries.length > 0) {\n parts.push(`${fileHistoryEntries.length} file-history folder(s)`);\n }\n logger.warn(`Found ${parts.join(' + ')} (${formatSize(totalSize)})`);\n\n if (options.verbose && !options.interactive) {\n console.log();\n // Exclude auto-cleanup targets from detailed list\n for (const session of selectableSessions) {\n console.log(\n chalk.dim(` ${session.toolName}: ${session.projectPath}`)\n );\n }\n }\n\n // Interactive mode: session selection (excluding auto-cleanup targets)\n let sessionsToClean = allSessions;\n if (options.interactive) {\n if (selectableSessions.length === 0) {\n // Only auto-cleanup targets exist\n if (autoCleanEntries.length > 0) {\n sessionsToClean = autoCleanEntries;\n logger.info(\n `Only ${autoCleanEntries.length} auto-cleanup target(s) found`\n );\n } else {\n logger.info('No selectable sessions found.');\n return;\n }\n } else {\n console.log();\n const { selected } = await inquirer.prompt<{\n selected: OrphanedSession[];\n }>([\n {\n type: 'checkbox',\n name: 'selected',\n message: 'Select sessions to delete:',\n choices: selectableSessions.map((session) => ({\n name: formatSessionChoice(session),\n value: session,\n checked: false,\n })),\n pageSize: 15,\n loop: false,\n },\n ]);\n\n if (selected.length === 0) {\n logger.info('No sessions selected. Cancelled.');\n return;\n }\n\n // Include selected sessions + auto-cleanup targets\n sessionsToClean = [...selected, ...autoCleanEntries];\n const selectedSize = selected.reduce((sum, s) => sum + s.size, 0);\n console.log();\n if (selected.length > 0) {\n logger.info(\n `Selected: ${selected.length} session(s) (${formatSize(selectedSize)})`\n );\n }\n if (autoCleanEntries.length > 0) {\n const autoSize = autoCleanEntries.reduce((sum, s) => sum + s.size, 0);\n logger.info(\n `+ ${autoCleanEntries.length} auto-cleanup target(s) (${formatSize(autoSize)})`\n );\n }\n }\n }\n\n const cleanSize = sessionsToClean.reduce((sum, s) => sum + s.size, 0);\n\n // Dry-run mode\n if (options.dryRun) {\n console.log();\n logger.info(\n chalk.yellow('Dry-run mode: No files will be deleted.')\n );\n console.log();\n\n const dryRunFolders = sessionsToClean.filter(\n (s) => s.type === 'session' || s.type === undefined\n );\n const dryRunConfigs = sessionsToClean.filter((s) => s.type === 'config');\n const dryRunEnvs = sessionsToClean.filter(\n (s) => s.type === 'session-env'\n );\n const dryRunTodos = sessionsToClean.filter((s) => s.type === 'todos');\n const dryRunHistories = sessionsToClean.filter(\n (s) => s.type === 'file-history'\n );\n\n for (const session of dryRunFolders) {\n console.log(\n ` ${chalk.red('Would delete:')} ${session.sessionPath} (${formatSize(session.size)})`\n );\n }\n\n if (dryRunConfigs.length > 0) {\n console.log();\n console.log(\n ` ${chalk.yellow('Would remove from ~/.claude.json:')}`\n );\n for (const config of dryRunConfigs) {\n console.log(` - ${config.projectPath}`);\n }\n }\n\n // Auto-cleanup targets summary\n const autoCleanParts: string[] = [];\n if (dryRunEnvs.length > 0) {\n autoCleanParts.push(`${dryRunEnvs.length} session-env`);\n }\n if (dryRunTodos.length > 0) {\n autoCleanParts.push(`${dryRunTodos.length} todos`);\n }\n if (dryRunHistories.length > 0) {\n autoCleanParts.push(`${dryRunHistories.length} file-history`);\n }\n if (autoCleanParts.length > 0) {\n const autoSize =\n dryRunEnvs.reduce((sum, s) => sum + s.size, 0) +\n dryRunTodos.reduce((sum, s) => sum + s.size, 0) +\n dryRunHistories.reduce((sum, s) => sum + s.size, 0);\n console.log();\n console.log(\n ` ${chalk.dim(`Would auto-delete: ${autoCleanParts.join(' + ')} (${formatSize(autoSize)})`)}`\n );\n }\n return;\n }\n\n // Confirmation prompt (also in interactive mode)\n if (!options.force) {\n console.log();\n const action = options.noTrash ? 'permanently delete' : 'move to trash';\n const { confirmed } = await inquirer.prompt<{ confirmed: boolean }>([\n {\n type: 'confirm',\n name: 'confirmed',\n message: `${action} ${sessionsToClean.length} session(s) (${formatSize(cleanSize)})?`,\n default: false,\n },\n ]);\n\n if (!confirmed) {\n logger.info('Cancelled.');\n return;\n }\n }\n\n // Execute cleanup\n const cleanSpinner = ora('Cleaning orphaned sessions...').start();\n\n const cleaner = new Cleaner();\n const cleanResult = await cleaner.clean(sessionsToClean, {\n dryRun: false,\n useTrash: !options.noTrash,\n });\n\n cleanSpinner.stop();\n\n // Output results\n console.log();\n if (cleanResult.deletedCount > 0) {\n const action = options.noTrash ? 'Deleted' : 'Moved to trash';\n const parts: string[] = [];\n const { deletedByType } = cleanResult;\n\n if (deletedByType.session > 0) {\n parts.push(`${deletedByType.session} session`);\n }\n if (deletedByType.sessionEnv > 0) {\n parts.push(`${deletedByType.sessionEnv} session-env`);\n }\n if (deletedByType.todos > 0) {\n parts.push(`${deletedByType.todos} todos`);\n }\n if (deletedByType.fileHistory > 0) {\n parts.push(`${deletedByType.fileHistory} file-history`);\n }\n\n const summary =\n parts.length > 0\n ? parts.join(' + ')\n : `${cleanResult.deletedCount} item(s)`;\n logger.success(\n `${action}: ${summary} (${formatSize(cleanResult.totalSizeDeleted)})`\n );\n }\n\n if (cleanResult.alreadyGoneCount > 0 && options.verbose) {\n logger.info(\n `Skipped ${cleanResult.alreadyGoneCount} already-deleted item(s)`\n );\n }\n\n if (cleanResult.configEntriesRemoved > 0) {\n logger.success(\n `Removed ${cleanResult.configEntriesRemoved} config entry(ies) from ~/.claude.json`\n );\n if (cleanResult.backupPath) {\n logger.info(`Backup saved to: ${tildify(cleanResult.backupPath)}`);\n }\n }\n\n if (cleanResult.errors.length > 0) {\n logger.error(`Failed to delete ${cleanResult.errors.length} item(s)`);\n if (options.verbose) {\n for (const err of cleanResult.errors) {\n console.log(chalk.red(` ${err.sessionPath}: ${err.error.message}`));\n }\n }\n }\n } catch (error) {\n spinner.fail('Clean failed');\n logger.error(\n error instanceof Error ? error.message : 'Unknown error occurred'\n );\n process.exit(1);\n }\n });\n","import { readFile, writeFile, mkdir, copyFile } from 'fs/promises';\nimport { existsSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { homedir } from 'os';\n\nimport type { OrphanedSession } from '../scanners/types.js';\nimport { moveToTrash, permanentDelete } from './trash.js';\n\nexport interface CleanOptions {\n dryRun: boolean;\n useTrash?: boolean;\n}\n\nexport interface CleanError {\n sessionPath: string;\n error: Error;\n}\n\nexport interface CleanCountByType {\n session: number;\n sessionEnv: number;\n todos: number;\n fileHistory: number;\n}\n\nexport interface CleanResult {\n deletedCount: number;\n deletedByType: CleanCountByType;\n skippedCount: number;\n alreadyGoneCount: number;\n configEntriesRemoved: number;\n totalSizeDeleted: number;\n errors: CleanError[];\n backupPath?: string;\n}\n\ninterface ClaudeConfig {\n projects?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nfunction getBackupDir(): string {\n return join(homedir(), '.ai-session-tidy', 'backups');\n}\n\nexport class Cleaner {\n async clean(\n sessions: OrphanedSession[],\n options: CleanOptions\n ): Promise<CleanResult> {\n const result: CleanResult = {\n deletedCount: 0,\n deletedByType: {\n session: 0,\n sessionEnv: 0,\n todos: 0,\n fileHistory: 0,\n },\n skippedCount: 0,\n alreadyGoneCount: 0,\n configEntriesRemoved: 0,\n totalSizeDeleted: 0,\n errors: [],\n };\n\n const useTrash = options.useTrash ?? true;\n\n // Separate session folders and config entries\n const folderSessions = sessions.filter((s) => s.type !== 'config');\n const configEntries = sessions.filter((s) => s.type === 'config');\n\n // 1. Clean session folders/files\n for (const session of folderSessions) {\n if (options.dryRun) {\n result.skippedCount++;\n continue;\n }\n\n try {\n const deleted = useTrash\n ? await moveToTrash(session.sessionPath)\n : await permanentDelete(session.sessionPath);\n\n if (deleted) {\n result.deletedCount++;\n result.totalSizeDeleted += session.size;\n\n // Count by type\n switch (session.type) {\n case 'session-env':\n result.deletedByType.sessionEnv++;\n break;\n case 'todos':\n result.deletedByType.todos++;\n break;\n case 'file-history':\n result.deletedByType.fileHistory++;\n break;\n default:\n result.deletedByType.session++;\n }\n } else {\n // Already deleted path - not an error\n result.alreadyGoneCount++;\n }\n } catch (error) {\n result.errors.push({\n sessionPath: session.sessionPath,\n error: error instanceof Error ? error : new Error(String(error)),\n });\n }\n }\n\n // 2. Clean config entries\n if (configEntries.length > 0 && !options.dryRun) {\n try {\n const configResult = await this.cleanConfigEntries(configEntries);\n result.configEntriesRemoved = configResult.removed;\n result.backupPath = configResult.backupPath;\n } catch (error) {\n result.errors.push({\n sessionPath: configEntries[0].sessionPath,\n error: error instanceof Error ? error : new Error(String(error)),\n });\n }\n } else if (options.dryRun) {\n result.skippedCount += configEntries.length;\n }\n\n return result;\n }\n\n /**\n * Remove orphaned project entries from ~/.claude.json\n */\n private async cleanConfigEntries(\n entries: OrphanedSession[]\n ): Promise<{ removed: number; backupPath: string }> {\n if (entries.length === 0) {\n return { removed: 0, backupPath: '' };\n }\n\n const configPath = entries[0].sessionPath;\n const projectPathsToRemove = new Set(entries.map((e) => e.projectPath));\n\n // Create backup directory\n const backupDir = getBackupDir();\n if (!existsSync(backupDir)) {\n await mkdir(backupDir, { recursive: true });\n }\n\n // Create backup file (with timestamp)\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-');\n const backupPath = join(backupDir, `claude.json.${timestamp}.bak`);\n await copyFile(configPath, backupPath);\n\n // Read config file\n const content = await readFile(configPath, 'utf-8');\n const config: ClaudeConfig = JSON.parse(content);\n\n if (!config.projects) {\n return { removed: 0, backupPath };\n }\n\n // Remove orphaned project entries\n let removedCount = 0;\n for (const projectPath of projectPathsToRemove) {\n if (projectPath in config.projects) {\n delete config.projects[projectPath];\n removedCount++;\n }\n }\n\n // Save modified config\n if (removedCount > 0) {\n await writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');\n }\n\n return { removed: removedCount, backupPath };\n }\n}\n","import { rm, access } from 'fs/promises';\nimport trash from 'trash';\n\n/**\n * Check if path exists\n */\nasync function pathExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Move file or directory to trash\n * @returns true if deleted, false if already gone\n */\nexport async function moveToTrash(path: string): Promise<boolean> {\n if (!(await pathExists(path))) {\n return false; // Already deleted - silent skip\n }\n\n await trash(path);\n return true;\n}\n\n/**\n * Permanently delete file or directory\n * @returns true if deleted, false if already gone\n */\nexport async function permanentDelete(path: string): Promise<boolean> {\n if (!(await pathExists(path))) {\n return false; // Already deleted - silent skip\n }\n\n await rm(path, { recursive: true, force: true });\n return true;\n}\n","import { Command } from 'commander';\nimport chalk from 'chalk';\nimport { existsSync } from 'fs';\nimport { homedir } from 'os';\nimport { join, resolve } from 'path';\n\nimport { logger } from '../utils/logger.js';\nimport { formatSize } from '../utils/size.js';\nimport { getWatchPaths as getConfigWatchPaths, setWatchPaths, getWatchDelay, getWatchDepth } from '../utils/config.js';\nimport {\n createAllScanners,\n getAvailableScanners,\n runAllScanners,\n} from '../scanners/index.js';\nimport { Watcher } from '../core/watcher.js';\nimport { Cleaner } from '../core/cleaner.js';\nimport { serviceManager } from '../core/service.js';\n\nconst DEFAULT_DELAY_MINUTES = 5;\nconst MAX_DELAY_MINUTES = 10;\n\ninterface RunOptions {\n delay?: string;\n path?: string[];\n noSave?: boolean;\n noTrash?: boolean;\n verbose?: boolean;\n}\n\nexport const watchCommand = new Command('watch')\n .description('Watch for project deletions and auto-clean orphaned sessions');\n\n// Run subcommand (default) - foreground execution\nconst runCommand = new Command('run')\n .description('Run watcher in foreground (default)')\n .option(\n '-p, --path <path>',\n 'Path to watch (can be used multiple times, saves to config)',\n (value: string, previous: string[]) => previous.concat([value]),\n [] as string[]\n )\n .option('--no-save', 'Do not save paths to config')\n .option(\n '-d, --delay <minutes>',\n `Delay before cleanup (default: ${DEFAULT_DELAY_MINUTES}, max: ${MAX_DELAY_MINUTES} minutes)`\n )\n .option('--no-trash', 'Permanently delete instead of moving to trash')\n .option('-v, --verbose', 'Enable verbose output')\n .action(runWatcher);\n\n// Start subcommand - install and start as OS service\nconst startCommand = new Command('start')\n .description('Start watcher as OS service (background + auto-start at login)')\n .action(async () => {\n const supported = serviceManager.isSupported();\n if (!supported) {\n logger.error('Service management is only supported on macOS.');\n logger.info('Use \"watch run\" to run the watcher in foreground.');\n return;\n }\n\n try {\n // Check current status\n const currentStatus = await serviceManager.status();\n if (currentStatus.status === 'running') {\n logger.info('Watcher service is already running.');\n return;\n }\n\n // Install and start\n logger.info('Installing watcher service...');\n await serviceManager.install();\n\n logger.info('Starting watcher service...');\n await serviceManager.start();\n\n // Verify\n const status = await serviceManager.status();\n if (status.status === 'running') {\n logger.success(`Watcher service started (PID: ${status.pid})`);\n logger.info('The watcher will automatically start at login.');\n logger.info(`Logs: ~/.ai-session-tidy/watcher.log`);\n } else {\n logger.warn('Service installed but may not be running yet.');\n logger.info('Check status with: ai-session-tidy watch status');\n }\n } catch (error) {\n logger.error(`Failed to start service: ${error instanceof Error ? error.message : String(error)}`);\n }\n });\n\n// Stop subcommand - stop and uninstall OS service\nconst stopCommand = new Command('stop')\n .description('Stop watcher service and disable auto-start')\n .action(async () => {\n const supported = serviceManager.isSupported();\n if (!supported) {\n logger.error('Service management is only supported on macOS.');\n return;\n }\n\n try {\n const currentStatus = await serviceManager.status();\n if (currentStatus.status === 'not_installed') {\n logger.info('Watcher service is not installed.');\n return;\n }\n\n logger.info('Stopping watcher service...');\n await serviceManager.stop();\n\n logger.info('Removing service configuration...');\n await serviceManager.uninstall();\n\n logger.success('Watcher service stopped and removed.');\n } catch (error) {\n logger.error(`Failed to stop service: ${error instanceof Error ? error.message : String(error)}`);\n }\n });\n\n// Status subcommand - show service status\nconst statusCommand = new Command('status')\n .description('Show watcher service status')\n .option('-l, --logs [lines]', 'Show recent logs', '20')\n .action(async (options: { logs?: string }) => {\n const supported = serviceManager.isSupported();\n if (!supported) {\n logger.error('Service management is only supported on macOS.');\n return;\n }\n\n try {\n const status = await serviceManager.status();\n\n console.log();\n console.log(chalk.bold('Watcher Service Status'));\n console.log('─'.repeat(40));\n console.log(`Label: ${status.label}`);\n console.log(`Status: ${formatStatus(status.status)}`);\n if (status.pid) {\n console.log(`PID: ${status.pid}`);\n }\n console.log(`Plist: ${status.plistPath}`);\n console.log();\n\n if (options.logs) {\n const lines = parseInt(options.logs, 10) || 20;\n const logs = await serviceManager.getLogs(lines);\n\n if (logs.stdout) {\n console.log(chalk.bold('Recent Logs:'));\n console.log('─'.repeat(40));\n console.log(logs.stdout);\n console.log();\n }\n\n if (logs.stderr) {\n console.log(chalk.bold.red('Error Logs:'));\n console.log('─'.repeat(40));\n console.log(logs.stderr);\n console.log();\n }\n }\n } catch (error) {\n logger.error(`Failed to get status: ${error instanceof Error ? error.message : String(error)}`);\n }\n });\n\nfunction formatStatus(status: string): string {\n switch (status) {\n case 'running':\n return chalk.green('running');\n case 'stopped':\n return chalk.yellow('stopped');\n case 'not_installed':\n return chalk.dim('not installed');\n default:\n return status;\n }\n}\n\n// Add subcommands\nwatchCommand.addCommand(runCommand, { isDefault: true });\nwatchCommand.addCommand(startCommand);\nwatchCommand.addCommand(stopCommand);\nwatchCommand.addCommand(statusCommand);\n\n// The main watcher logic (foreground mode)\nasync function runWatcher(options: RunOptions): Promise<void> {\n // Priority: CLI option > config > default\n const configDelay = getWatchDelay();\n let delayMinutes = options.delay\n ? parseInt(options.delay, 10)\n : (configDelay ?? DEFAULT_DELAY_MINUTES);\n\n // Enforce max delay\n if (delayMinutes > MAX_DELAY_MINUTES) {\n logger.warn(`Maximum delay is ${MAX_DELAY_MINUTES} minutes. Using ${MAX_DELAY_MINUTES}.`);\n delayMinutes = MAX_DELAY_MINUTES;\n }\n\n const delayMs = delayMinutes * 60 * 1000;\n\n // Determine watch paths\n let watchPaths: string[];\n if (options.path && options.path.length > 0) {\n // Use paths from -p option\n watchPaths = options.path.map((p) => resolve(p.replace(/^~/, homedir())));\n\n // Save to config (unless --no-save)\n if (!options.noSave) {\n setWatchPaths(watchPaths);\n logger.info(`Saved watch paths to config.`);\n }\n } else {\n // Read from config or use defaults\n const configPaths = getConfigWatchPaths();\n if (configPaths && configPaths.length > 0) {\n watchPaths = configPaths;\n logger.info(`Using saved watch paths from config.`);\n } else {\n watchPaths = getDefaultWatchPaths();\n logger.info(`Using default watch paths.`);\n }\n }\n\n // Filter to existing paths\n const validPaths = watchPaths.filter((p) => existsSync(p));\n if (validPaths.length === 0) {\n logger.error('No valid watch paths found. Use -p to specify paths.');\n return;\n }\n\n if (validPaths.length < watchPaths.length) {\n const invalidPaths = watchPaths.filter((p) => !existsSync(p));\n logger.warn(`Skipping non-existent paths: ${invalidPaths.join(', ')}`);\n }\n\n // Check scanners\n const allScanners = createAllScanners();\n const availableScanners = await getAvailableScanners(allScanners);\n\n if (availableScanners.length === 0) {\n logger.warn('No AI coding tools detected on this system.');\n return;\n }\n\n const depth = getWatchDepth() ?? 3;\n\n logger.info(\n `Watching for project deletions (${availableScanners.map((s) => s.name).join(', ')})`\n );\n logger.info(`Watch paths: ${validPaths.join(', ')}`);\n logger.info(`Cleanup delay: ${String(delayMinutes)} minute(s)`);\n logger.info(`Watch depth: ${String(depth)}`);\n if (process.stdout.isTTY) {\n logger.info(chalk.dim('Press Ctrl+C to stop watching.'));\n }\n console.log();\n\n const cleaner = new Cleaner();\n\n const watcher = new Watcher({\n watchPaths: validPaths,\n delayMs,\n depth,\n onDelete: async (events) => {\n // Log batch events\n if (events.length === 1) {\n logger.info(`Detected deletion: ${events[0].path}`);\n } else {\n logger.info(`Detected ${events.length} deletions (debounced)`);\n if (options.verbose) {\n for (const event of events) {\n logger.debug(` - ${event.path}`);\n }\n }\n }\n\n // Scan for orphaned sessions (executed only once)\n const results = await runAllScanners(availableScanners);\n const allSessions = results.flatMap((r) => r.sessions);\n\n if (allSessions.length === 0) {\n if (options.verbose) {\n logger.debug('No orphaned sessions found after deletion.');\n }\n return;\n }\n\n // Clean up\n const cleanResult = await cleaner.clean(allSessions, {\n dryRun: false,\n useTrash: !options.noTrash,\n });\n\n if (cleanResult.deletedCount > 0) {\n const action = options.noTrash ? 'Deleted' : 'Moved to trash';\n const parts: string[] = [];\n const { deletedByType } = cleanResult;\n\n if (deletedByType.session > 0) {\n parts.push(`${deletedByType.session} session`);\n }\n if (deletedByType.sessionEnv > 0) {\n parts.push(`${deletedByType.sessionEnv} session-env`);\n }\n if (deletedByType.todos > 0) {\n parts.push(`${deletedByType.todos} todos`);\n }\n if (deletedByType.fileHistory > 0) {\n parts.push(`${deletedByType.fileHistory} file-history`);\n }\n\n const summary = parts.length > 0 ? parts.join(' + ') : `${cleanResult.deletedCount} item(s)`;\n logger.success(\n `${action}: ${summary} (${formatSize(cleanResult.totalSizeDeleted)})`\n );\n }\n\n if (cleanResult.configEntriesRemoved > 0) {\n logger.success(\n `Removed ${cleanResult.configEntriesRemoved} config entry(ies) from ~/.claude.json`\n );\n }\n\n if (cleanResult.errors.length > 0) {\n logger.error(\n `Failed to clean ${cleanResult.errors.length} item(s)`\n );\n }\n },\n });\n\n watcher.start();\n\n // Handle Ctrl+C\n process.on('SIGINT', () => {\n console.log();\n logger.info('Stopping watcher...');\n watcher.stop();\n process.exit(0);\n });\n\n // Keep process alive\n await new Promise(() => {});\n}\n\nfunction getDefaultWatchPaths(): string[] {\n const home = homedir();\n return [\n join(home, 'dev'),\n join(home, 'code'),\n join(home, 'projects'),\n join(home, 'Development'),\n join(home, 'Developer'), // macOS Xcode\n join(home, 'Documents'),\n ];\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';\nimport { homedir } from 'os';\nimport { dirname, join, resolve } from 'path';\nimport { fileURLToPath } from 'url';\n\nconst CONFIG_VERSION = '0.1';\n\nexport function getAppVersion(): string {\n const __dirname = dirname(fileURLToPath(import.meta.url));\n // Try multiple paths (source vs bundled)\n const paths = [\n join(__dirname, '..', '..', 'package.json'), // from src/utils/\n join(__dirname, '..', 'package.json'), // from dist/\n ];\n\n for (const packagePath of paths) {\n try {\n if (existsSync(packagePath)) {\n const content = readFileSync(packagePath, 'utf-8');\n const pkg = JSON.parse(content) as { version: string };\n return pkg.version;\n }\n } catch {\n continue;\n }\n }\n return 'unknown';\n}\n\nexport interface Config {\n version?: string; // app version that last saved this config\n configVersion?: string; // config schema version\n watchPaths?: string[];\n watchDelay?: number; // minutes\n watchDepth?: number; // folder depth (default: 3, max: 5)\n}\n\nfunction getConfigPath(): string {\n return join(homedir(), '.config', 'ai-session-tidy', 'config.json');\n}\n\nexport function loadConfig(): Config {\n const configPath = getConfigPath();\n\n if (!existsSync(configPath)) {\n return {};\n }\n\n try {\n const content = readFileSync(configPath, 'utf-8');\n return JSON.parse(content) as Config;\n } catch {\n return {};\n }\n}\n\nexport function saveConfig(config: Config): void {\n const configPath = getConfigPath();\n const configDir = dirname(configPath);\n\n if (!existsSync(configDir)) {\n mkdirSync(configDir, { recursive: true });\n }\n\n const configWithVersion: Config = {\n version: getAppVersion(),\n configVersion: CONFIG_VERSION,\n ...config,\n };\n\n writeFileSync(configPath, JSON.stringify(configWithVersion, null, 2), 'utf-8');\n}\n\nexport function getWatchPaths(): string[] | undefined {\n const config = loadConfig();\n return config.watchPaths;\n}\n\nexport function setWatchPaths(paths: string[]): void {\n const config = loadConfig();\n config.watchPaths = paths;\n saveConfig(config);\n}\n\nexport function addWatchPath(path: string): void {\n const config = loadConfig();\n const resolved = resolve(path.replace(/^~/, homedir()));\n const paths = config.watchPaths ?? [];\n if (!paths.includes(resolved)) {\n paths.push(resolved);\n config.watchPaths = paths;\n saveConfig(config);\n }\n}\n\nexport function removeWatchPath(path: string): boolean {\n const config = loadConfig();\n const resolved = resolve(path.replace(/^~/, homedir()));\n const paths = config.watchPaths ?? [];\n const index = paths.indexOf(resolved);\n if (index === -1) return false;\n paths.splice(index, 1);\n config.watchPaths = paths;\n saveConfig(config);\n return true;\n}\n\nexport function getWatchDelay(): number | undefined {\n const config = loadConfig();\n return config.watchDelay;\n}\n\nexport function setWatchDelay(minutes: number): void {\n const config = loadConfig();\n config.watchDelay = minutes;\n saveConfig(config);\n}\n\nexport function getWatchDepth(): number | undefined {\n const config = loadConfig();\n return config.watchDepth;\n}\n\nexport function setWatchDepth(depth: number): void {\n const config = loadConfig();\n config.watchDepth = Math.min(depth, 5); // max 5\n saveConfig(config);\n}\n\nexport function resetConfig(): void {\n saveConfig({});\n}\n","import { watch, FSWatcher } from 'chokidar';\nimport { access } from 'fs/promises';\n\nexport interface WatchEvent {\n path: string;\n timestamp: Date;\n}\n\nexport interface WatcherOptions {\n watchPaths: string[];\n /** Delay before cleanup after deletion detected (allows recovery) */\n delayMs: number;\n /** Debounce time to batch multiple delete events (default: 10 seconds) */\n debounceMs?: number;\n depth?: number;\n /** Callback to handle batched delete events */\n onDelete: (events: WatchEvent[]) => void;\n}\n\n/**\n * Watcher that monitors project folder deletions and invokes cleanup callback\n *\n * ## Event Processing Flow\n *\n * When a folder is deleted, the OS generates individual events for each subfolder:\n * ```\n * rm -rf /project\n * → unlinkDir: /project/frontend\n * → unlinkDir: /project/backend\n * → unlinkDir: /project\n * ```\n *\n * Running scan/cleanup for each event would be inefficient,\n * so we use a two-stage delay mechanism:\n *\n * 1. **Per-path delay (delayMs)**: Provides recovery opportunity (default 5 min)\n * - If folder is restored during delay, cleanup is cancelled\n *\n * 2. **Debounce (debounceMs)**: Batches multiple events together (default 10 sec)\n * - After 10 seconds with no new events, batch is executed\n * - Scan/cleanup runs only once\n *\n * ## Timeline Example\n *\n * ```\n * T+0s: /project/frontend deletion detected → 5min timer starts\n * T+0.1s: /project/backend deletion detected → 5min timer starts\n * T+0.2s: /project deletion detected → 5min timer starts\n * T+5m: /project/frontend timer complete → add to batch, 10sec debounce starts\n * T+5m0.1s: /project/backend timer complete → add to batch, debounce reset\n * T+5m0.2s: /project timer complete → add to batch, debounce reset\n * T+5m10.2s: debounce complete → onDelete([3 events]) → single scan execution\n * ```\n */\nexport class Watcher {\n private readonly options: WatcherOptions;\n private readonly debounceMs: number;\n private watcher: FSWatcher | null = null;\n /** Per-path delay timers */\n private pendingDeletes: Map<string, NodeJS.Timeout> = new Map();\n /** Events waiting for debounce */\n private batchedEvents: WatchEvent[] = [];\n /** Debounce timer */\n private debounceTimer: NodeJS.Timeout | null = null;\n\n constructor(options: WatcherOptions) {\n this.options = options;\n this.debounceMs = options.debounceMs ?? 10000; // default 10 seconds\n }\n\n start(): void {\n if (this.watcher) return;\n\n this.watcher = watch(this.options.watchPaths, {\n ignoreInitial: true,\n persistent: true,\n depth: this.options.depth ?? 3,\n });\n\n this.watcher.on('unlinkDir', (path) => {\n this.handleDelete(path);\n });\n\n this.watcher.on('addDir', (path) => {\n this.handleRecovery(path);\n });\n }\n\n stop(): void {\n if (!this.watcher) return;\n\n this.watcher.close();\n this.watcher = null;\n\n // Cancel all pending timers\n for (const timeout of this.pendingDeletes.values()) {\n clearTimeout(timeout);\n }\n this.pendingDeletes.clear();\n\n // Cancel debounce timer\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n this.batchedEvents = [];\n }\n\n isWatching(): boolean {\n return this.watcher !== null;\n }\n\n /**\n * Handle folder deletion event\n *\n * Stage 1: Set per-path delay timer\n * - Don't process immediately to allow recovery\n * - Add to batch if still deleted after delay\n */\n private handleDelete(path: string): void {\n // Ignore if already pending (prevent duplicate events)\n if (this.pendingDeletes.has(path)) return;\n\n const timeout = setTimeout(async () => {\n // Verify path is still deleted after delay\n const stillDeleted = !(await this.pathExists(path));\n\n if (stillDeleted) {\n // Move to stage 2: add to batch\n this.addToBatch({\n path,\n timestamp: new Date(),\n });\n }\n\n this.pendingDeletes.delete(path);\n }, this.options.delayMs);\n\n this.pendingDeletes.set(path, timeout);\n }\n\n /**\n * Add event to batch and reset debounce timer\n *\n * Stage 2: Debounce\n * - Reset timer on each new event\n * - Execute batch when no new events for debounce period\n * - This combines multiple subfolder deletions into single cleanup\n */\n private addToBatch(event: WatchEvent): void {\n this.batchedEvents.push(event);\n\n // Reset debounce timer (extend wait time on new event)\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n }\n\n this.debounceTimer = setTimeout(() => {\n this.flushBatch();\n }, this.debounceMs);\n }\n\n /**\n * Deliver batched events to callback\n * - Scan/cleanup runs only once\n */\n private flushBatch(): void {\n if (this.batchedEvents.length === 0) return;\n\n const events = [...this.batchedEvents];\n this.batchedEvents = [];\n this.debounceTimer = null;\n\n this.options.onDelete(events);\n }\n\n private handleRecovery(path: string): void {\n const timeout = this.pendingDeletes.get(path);\n\n if (timeout) {\n clearTimeout(timeout);\n this.pendingDeletes.delete(path);\n }\n }\n\n private async pathExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n }\n}\n","import { homedir } from 'os';\nimport { join, dirname } from 'path';\nimport { readFile, writeFile, unlink, mkdir } from 'fs/promises';\nimport { existsSync } from 'fs';\nimport { execSync } from 'child_process';\n\nconst SERVICE_LABEL = 'sooink.ai-session-tidy.watcher';\nconst PLIST_FILENAME = `${SERVICE_LABEL}.plist`;\n\nexport type ServiceStatus = 'running' | 'stopped' | 'not_installed';\n\nexport interface ServiceInfo {\n status: ServiceStatus;\n pid?: number;\n label: string;\n plistPath: string;\n}\n\nfunction getPlistPath(): string {\n return join(homedir(), 'Library', 'LaunchAgents', PLIST_FILENAME);\n}\n\nfunction getNodePath(): string {\n // Get absolute path to node executable\n return process.execPath;\n}\n\nfunction getScriptPath(): string {\n // Get absolute path to the CLI script\n const scriptPath = process.argv[1];\n if (scriptPath && existsSync(scriptPath)) {\n return scriptPath;\n }\n throw new Error('Could not determine script path');\n}\n\nfunction generatePlist(options: {\n label: string;\n nodePath: string;\n scriptPath: string;\n args: string[];\n}): string {\n const allArgs = [options.nodePath, options.scriptPath, ...options.args];\n const argsXml = allArgs.map((arg) => ` <string>${arg}</string>`).join('\\n');\n\n const home = homedir();\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${options.label}</string>\n <key>EnvironmentVariables</key>\n <dict>\n <key>HOME</key>\n <string>${home}</string>\n </dict>\n <key>ProgramArguments</key>\n <array>\n${argsXml}\n </array>\n <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <true/>\n <key>StandardOutPath</key>\n <string>${join(home, '.ai-session-tidy', 'watcher.log')}</string>\n <key>StandardErrorPath</key>\n <string>${join(home, '.ai-session-tidy', 'watcher.error.log')}</string>\n</dict>\n</plist>`;\n}\n\nexport class ServiceManager {\n private readonly plistPath: string;\n\n constructor() {\n this.plistPath = getPlistPath();\n }\n\n isSupported(): boolean {\n return process.platform === 'darwin';\n }\n\n async install(): Promise<void> {\n if (!this.isSupported()) {\n throw new Error('Service management is only supported on macOS');\n }\n\n // Ensure LaunchAgents directory exists\n const launchAgentsDir = dirname(this.plistPath);\n if (!existsSync(launchAgentsDir)) {\n await mkdir(launchAgentsDir, { recursive: true });\n }\n\n // Ensure log directory exists and clear old logs\n const logDir = join(homedir(), '.ai-session-tidy');\n if (!existsSync(logDir)) {\n await mkdir(logDir, { recursive: true });\n }\n\n // Clear old log files\n const stdoutPath = join(logDir, 'watcher.log');\n const stderrPath = join(logDir, 'watcher.error.log');\n await writeFile(stdoutPath, '', 'utf-8');\n await writeFile(stderrPath, '', 'utf-8');\n\n const plistContent = generatePlist({\n label: SERVICE_LABEL,\n nodePath: getNodePath(),\n scriptPath: getScriptPath(),\n args: ['watch', 'run'],\n });\n\n await writeFile(this.plistPath, plistContent, 'utf-8');\n }\n\n async uninstall(): Promise<void> {\n if (existsSync(this.plistPath)) {\n await unlink(this.plistPath);\n }\n }\n\n async start(): Promise<void> {\n if (!this.isSupported()) {\n throw new Error('Service management is only supported on macOS');\n }\n\n if (!existsSync(this.plistPath)) {\n throw new Error('Service not installed. Run \"watch start\" to install and start.');\n }\n\n try {\n execSync(`launchctl load \"${this.plistPath}\"`, { stdio: 'pipe' });\n } catch (error) {\n // Already loaded is not an error\n const message = error instanceof Error ? error.message : String(error);\n if (!message.includes('already loaded')) {\n throw error;\n }\n }\n }\n\n async stop(): Promise<void> {\n if (!this.isSupported()) {\n throw new Error('Service management is only supported on macOS');\n }\n\n if (!existsSync(this.plistPath)) {\n return; // Nothing to stop\n }\n\n try {\n execSync(`launchctl unload \"${this.plistPath}\"`, { stdio: 'pipe' });\n } catch {\n // Ignore errors when stopping (might not be running)\n }\n }\n\n async status(): Promise<ServiceInfo> {\n const info: ServiceInfo = {\n status: 'not_installed',\n label: SERVICE_LABEL,\n plistPath: this.plistPath,\n };\n\n if (!this.isSupported()) {\n return info;\n }\n\n if (!existsSync(this.plistPath)) {\n return info;\n }\n\n try {\n const output = execSync('launchctl list', { encoding: 'utf-8' });\n const lines = output.split('\\n');\n\n for (const line of lines) {\n if (line.includes(SERVICE_LABEL)) {\n const parts = line.split(/\\s+/);\n const pid = parseInt(parts[0] ?? '', 10);\n\n if (!isNaN(pid) && pid > 0) {\n info.status = 'running';\n info.pid = pid;\n } else {\n info.status = 'stopped';\n }\n return info;\n }\n }\n\n // plist exists but not loaded\n info.status = 'stopped';\n return info;\n } catch {\n info.status = 'stopped';\n return info;\n }\n }\n\n async getLogs(lines: number = 50): Promise<{ stdout: string; stderr: string }> {\n const logDir = join(homedir(), '.ai-session-tidy');\n const stdoutPath = join(logDir, 'watcher.log');\n const stderrPath = join(logDir, 'watcher.error.log');\n\n let stdout = '';\n let stderr = '';\n\n try {\n if (existsSync(stdoutPath)) {\n const content = await readFile(stdoutPath, 'utf-8');\n const logLines = content.split('\\n');\n stdout = logLines.slice(-lines).join('\\n');\n }\n } catch {\n // Ignore read errors\n }\n\n try {\n if (existsSync(stderrPath)) {\n const content = await readFile(stderrPath, 'utf-8');\n const logLines = content.split('\\n');\n stderr = logLines.slice(-lines).join('\\n');\n }\n } catch {\n // Ignore read errors\n }\n\n return { stdout, stderr };\n }\n}\n\nexport const serviceManager = new ServiceManager();\n","import { Command } from 'commander';\nimport Table from 'cli-table3';\nimport chalk from 'chalk';\n\nimport { logger } from '../utils/logger.js';\nimport { formatSize } from '../utils/size.js';\nimport {\n createAllScanners,\n getAvailableScanners,\n} from '../scanners/index.js';\nimport {\n getClaudeProjectsDir,\n getCursorWorkspaceDir,\n} from '../utils/paths.js';\n\ninterface ListOptions {\n verbose?: boolean;\n}\n\nexport const listCommand = new Command('list')\n .description('List detected AI coding tools and their data locations')\n .option('-v, --verbose', 'Show detailed information')\n .action(async (options: ListOptions) => {\n const allScanners = createAllScanners();\n const availableScanners = await getAvailableScanners(allScanners);\n\n console.log();\n console.log(chalk.bold('AI Coding Tools Status:'));\n console.log();\n\n const table = new Table({\n head: [\n chalk.cyan('Tool'),\n chalk.cyan('Status'),\n chalk.cyan('Data Location'),\n ],\n style: { head: [] },\n });\n\n const toolLocations: Record<string, string> = {\n 'claude-code': getClaudeProjectsDir(),\n cursor: getCursorWorkspaceDir(),\n };\n\n for (const scanner of allScanners) {\n const isAvailable = availableScanners.some((s) => s.name === scanner.name);\n const status = isAvailable\n ? chalk.green('Available')\n : chalk.dim('Not found');\n const location = toolLocations[scanner.name] || 'Unknown';\n\n table.push([scanner.name, status, isAvailable ? location : chalk.dim(location)]);\n }\n\n console.log(table.toString());\n\n // Additional info (verbose)\n if (options.verbose && availableScanners.length > 0) {\n console.log();\n console.log(chalk.bold('Tool Details:'));\n console.log();\n\n for (const scanner of availableScanners) {\n console.log(chalk.cyan(`${scanner.name}:`));\n\n switch (scanner.name) {\n case 'claude-code':\n console.log(' Session format: Encoded project path directories');\n console.log(' Path encoding: /path/to/project → -path-to-project');\n break;\n case 'cursor':\n console.log(' Session format: Hash-named directories with workspace.json');\n console.log(' Project info: Stored in workspace.json \"folder\" field');\n break;\n }\n console.log();\n }\n }\n\n // Summary\n console.log(\n chalk.dim(\n `${availableScanners.length} of ${allScanners.length} tools detected on this system.`\n )\n );\n });\n","import { Command } from 'commander';\nimport inquirer from 'inquirer';\n\nimport { logger } from '../utils/logger.js';\nimport {\n loadConfig,\n addWatchPath,\n removeWatchPath,\n getWatchPaths,\n getWatchDelay,\n setWatchDelay,\n getWatchDepth,\n setWatchDepth,\n resetConfig,\n} from '../utils/config.js';\n\nexport const configCommand = new Command('config').description(\n 'Manage configuration'\n);\n\nconst pathsCommand = new Command('paths').description('Manage watch paths');\n\npathsCommand\n .command('add <path>')\n .description('Add a watch path')\n .action((path: string) => {\n addWatchPath(path);\n logger.success(`Added: ${path}`);\n });\n\npathsCommand\n .command('remove <path>')\n .description('Remove a watch path')\n .action((path: string) => {\n const removed = removeWatchPath(path);\n if (removed) {\n logger.success(`Removed: ${path}`);\n } else {\n logger.warn(`Path not found: ${path}`);\n }\n });\n\npathsCommand\n .command('list')\n .description('List watch paths')\n .action(() => {\n const paths = getWatchPaths();\n if (!paths || paths.length === 0) {\n logger.info('No watch paths configured.');\n return;\n }\n console.log();\n for (const p of paths) {\n console.log(` ${p}`);\n }\n });\n\nconfigCommand.addCommand(pathsCommand);\n\nconst DEFAULT_DELAY_MINUTES = 5;\nconst MAX_DELAY_MINUTES = 10;\n\nconfigCommand\n .command('delay [minutes]')\n .description(`Get or set watch delay in minutes (default: ${DEFAULT_DELAY_MINUTES}, max: ${MAX_DELAY_MINUTES})`)\n .action((minutes?: string) => {\n if (minutes === undefined) {\n // Get current delay\n const delay = getWatchDelay() ?? DEFAULT_DELAY_MINUTES;\n console.log(`Watch delay: ${String(delay)} minute(s)`);\n } else {\n // Set delay\n const value = parseInt(minutes, 10);\n if (isNaN(value) || value < 1) {\n logger.error('Invalid delay value. Must be a positive number.');\n return;\n }\n if (value > MAX_DELAY_MINUTES) {\n logger.warn(`Maximum delay is ${String(MAX_DELAY_MINUTES)} minutes. Setting to ${String(MAX_DELAY_MINUTES)}.`);\n setWatchDelay(MAX_DELAY_MINUTES);\n return;\n }\n setWatchDelay(value);\n logger.success(`Watch delay set to ${String(value)} minute(s)`);\n }\n });\n\nconst DEFAULT_DEPTH = 3;\nconst MAX_DEPTH = 5;\n\nconfigCommand\n .command('depth [level]')\n .description('Get or set watch depth (default: 3, max: 5)')\n .action((level?: string) => {\n if (level === undefined) {\n const depth = getWatchDepth() ?? DEFAULT_DEPTH;\n console.log(`Watch depth: ${String(depth)}`);\n } else {\n const value = parseInt(level, 10);\n if (isNaN(value) || value < 1) {\n logger.error('Invalid depth value. Must be a positive number.');\n return;\n }\n if (value > MAX_DEPTH) {\n logger.warn(`Maximum depth is ${String(MAX_DEPTH)}. Setting to ${String(MAX_DEPTH)}.`);\n }\n setWatchDepth(value);\n const actualValue = Math.min(value, MAX_DEPTH);\n logger.success(`Watch depth set to ${String(actualValue)}`);\n }\n });\n\nconfigCommand\n .command('show')\n .description('Show all configuration')\n .action(() => {\n const config = loadConfig();\n console.log(JSON.stringify(config, null, 2));\n });\n\nconfigCommand\n .command('reset')\n .description('Reset configuration to defaults')\n .option('-f, --force', 'Skip confirmation prompt')\n .action(async (options: { force?: boolean }) => {\n if (!options.force) {\n const { confirmed } = await inquirer.prompt<{ confirmed: boolean }>([\n {\n type: 'confirm',\n name: 'confirmed',\n message: 'Reset all configuration to defaults?',\n default: false,\n },\n ]);\n\n if (!confirmed) {\n logger.info('Cancelled.');\n return;\n }\n }\n\n resetConfig();\n logger.success('Configuration reset to defaults.');\n });\n","#!/usr/bin/env node\n\nimport { cli } from './cli.js';\n\ncli.parse(process.argv);\n"],"mappings":"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","names":["Command","chalk","readdir","stat","join","join","join","readdir","join","stat","resolve","readdir","readFile","stat","access","join","access","readdir","join","readFile","stat","chalk","Command","ora","chalk","readFile","join","homedir","access","join","homedir","readFile","chalk","Command","ora","parts","Command","chalk","existsSync","homedir","join","resolve","existsSync","homedir","dirname","join","fileURLToPath","access","homedir","join","dirname","readFile","writeFile","mkdir","existsSync","Command","chalk","resolve","homedir","existsSync","join","Command","Table","chalk","Command","chalk","Table","Command","inquirer","Command","DEFAULT_DELAY_MINUTES","MAX_DELAY_MINUTES","inquirer","Command"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/commands/scan.ts","../src/utils/logger.ts","../src/utils/size.ts","../src/scanners/claude-code.ts","../src/utils/paths.ts","../src/scanners/cursor.ts","../src/scanners/index.ts","../src/commands/clean.ts","../src/core/cleaner.ts","../src/core/trash.ts","../src/commands/watch.ts","../src/utils/config.ts","../src/core/watcher.ts","../src/core/constants.ts","../src/core/service.ts","../src/commands/list.ts","../src/commands/config.ts","../src/index.ts"],"sourcesContent":["import { Command } from 'commander';\n\nimport { scanCommand } from './commands/scan.js';\nimport { cleanCommand } from './commands/clean.js';\nimport { watchCommand } from './commands/watch.js';\nimport { listCommand } from './commands/list.js';\nimport { configCommand } from './commands/config.js';\nimport { getAppVersion } from './utils/config.js';\n\nexport const cli = new Command()\n .name('ai-session-tidy')\n .description(\n 'CLI tool that detects and cleans orphaned session data from AI coding tools'\n )\n .version(getAppVersion());\n\ncli.addCommand(scanCommand, { isDefault: true });\ncli.addCommand(cleanCommand);\ncli.addCommand(watchCommand);\ncli.addCommand(listCommand);\ncli.addCommand(configCommand);\n","import { Command } from 'commander';\nimport Table from 'cli-table3';\nimport ora from 'ora';\nimport chalk from 'chalk';\n\nimport { logger } from '../utils/logger.js';\nimport { formatSize } from '../utils/size.js';\nimport {\n createAllScanners,\n getAvailableScanners,\n runAllScanners,\n} from '../scanners/index.js';\nimport type { ScanResult } from '../scanners/types.js';\n\ninterface ScanOptions {\n verbose?: boolean;\n json?: boolean;\n}\n\nfunction formatTokens(count?: number): string {\n if (!count) return '0';\n if (count >= 1000000) return `${(count / 1000000).toFixed(1)}M`;\n if (count >= 1000) return `${(count / 1000).toFixed(0)}K`;\n return count.toString();\n}\n\nexport const scanCommand = new Command('scan')\n .description('Scan for orphaned session data from AI coding tools')\n .option('-v, --verbose', 'Enable verbose output')\n .option('--json', 'Output results as JSON')\n .action(async (options: ScanOptions) => {\n const spinner = ora('Scanning for orphaned sessions...').start();\n\n try {\n const allScanners = createAllScanners();\n const availableScanners = await getAvailableScanners(allScanners);\n\n if (availableScanners.length === 0) {\n spinner.warn('No AI coding tools detected on this system.');\n return;\n }\n\n if (options.verbose) {\n spinner.text = `Found ${availableScanners.length} tools: ${availableScanners.map((s) => s.name).join(', ')}`;\n }\n\n const results = await runAllScanners(availableScanners);\n spinner.stop();\n\n if (options.json) {\n outputJson(results);\n } else {\n outputTable(results, options.verbose);\n }\n } catch (error) {\n spinner.fail('Scan failed');\n logger.error(\n error instanceof Error ? error.message : 'Unknown error occurred'\n );\n process.exit(1);\n }\n });\n\nfunction outputJson(results: ScanResult[]): void {\n const allSessions = results.flatMap((r) => r.sessions);\n const output = {\n totalSessions: allSessions.length,\n totalSize: results.reduce((sum, r) => sum + r.totalSize, 0),\n results: results.map((r) => ({\n tool: r.toolName,\n sessionCount: r.sessions.length,\n totalSize: r.totalSize,\n scanDuration: r.scanDuration,\n sessions: r.sessions,\n })),\n };\n console.log(JSON.stringify(output, null, 2));\n}\n\nfunction outputTable(results: ScanResult[], verbose?: boolean): void {\n const allSessions = results.flatMap((r) => r.sessions);\n const folderSessions = allSessions.filter((s) => s.type === 'session' || s.type === undefined);\n const configEntries = allSessions.filter((s) => s.type === 'config');\n const sessionEnvEntries = allSessions.filter((s) => s.type === 'session-env');\n const todosEntries = allSessions.filter((s) => s.type === 'todos');\n const fileHistoryEntries = allSessions.filter((s) => s.type === 'file-history');\n const totalSize = results.reduce((sum, r) => sum + r.totalSize, 0);\n\n if (allSessions.length === 0) {\n logger.success('No orphaned sessions found.');\n return;\n }\n\n console.log();\n\n // Summary message\n const parts: string[] = [];\n if (folderSessions.length > 0) {\n parts.push(`${folderSessions.length} session folder(s)`);\n }\n if (configEntries.length > 0) {\n parts.push(`${configEntries.length} config entry(ies)`);\n }\n if (sessionEnvEntries.length > 0) {\n parts.push(`${sessionEnvEntries.length} session-env folder(s)`);\n }\n if (todosEntries.length > 0) {\n parts.push(`${todosEntries.length} todos file(s)`);\n }\n if (fileHistoryEntries.length > 0) {\n parts.push(`${fileHistoryEntries.length} file-history folder(s)`);\n }\n logger.warn(`Found ${parts.join(' + ')} (${formatSize(totalSize)})`);\n console.log();\n\n // Summary by tool\n const summaryTable = new Table({\n head: [\n chalk.cyan('Tool'),\n chalk.cyan('Sessions'),\n chalk.cyan('Config'),\n chalk.cyan('Env'),\n chalk.cyan('Todos'),\n chalk.cyan('History'),\n chalk.cyan('Size'),\n chalk.cyan('Scan Time'),\n ],\n style: { head: [] },\n });\n\n for (const result of results) {\n if (result.sessions.length > 0) {\n const folders = result.sessions.filter((s) => s.type === 'session' || s.type === undefined).length;\n const configs = result.sessions.filter((s) => s.type === 'config').length;\n const envs = result.sessions.filter((s) => s.type === 'session-env').length;\n const todos = result.sessions.filter((s) => s.type === 'todos').length;\n const histories = result.sessions.filter((s) => s.type === 'file-history').length;\n summaryTable.push([\n result.toolName,\n folders > 0 ? String(folders) : '-',\n configs > 0 ? String(configs) : '-',\n envs > 0 ? String(envs) : '-',\n todos > 0 ? String(todos) : '-',\n histories > 0 ? String(histories) : '-',\n formatSize(result.totalSize),\n `${result.scanDuration.toFixed(0)}ms`,\n ]);\n }\n }\n\n console.log(summaryTable.toString());\n\n // Detailed info (verbose)\n if (verbose && allSessions.length > 0) {\n // Session folders\n if (folderSessions.length > 0) {\n console.log();\n console.log(chalk.bold('Session Folders:'));\n console.log();\n\n for (const session of folderSessions) {\n const projectName = session.projectPath.split('/').pop() || session.projectPath;\n console.log(\n ` ${chalk.cyan(`[${session.toolName}]`)} ${chalk.white(projectName)} ${chalk.dim(`(${formatSize(session.size)})`)}`\n );\n console.log(` ${chalk.dim('→')} ${session.projectPath}`);\n console.log(` ${chalk.dim('Modified:')} ${session.lastModified.toLocaleDateString()}`);\n console.log();\n }\n }\n\n // Config entries\n if (configEntries.length > 0) {\n console.log();\n console.log(chalk.bold('Config Entries (~/.claude.json):'));\n console.log();\n\n for (const entry of configEntries) {\n const projectName = entry.projectPath.split('/').pop() || entry.projectPath;\n console.log(\n ` ${chalk.yellow('[config]')} ${chalk.white(projectName)}`\n );\n console.log(` ${chalk.dim('→')} ${entry.projectPath}`);\n if (entry.configStats?.lastCost) {\n const cost = `$${entry.configStats.lastCost.toFixed(2)}`;\n const inTokens = formatTokens(entry.configStats.lastTotalInputTokens);\n const outTokens = formatTokens(entry.configStats.lastTotalOutputTokens);\n console.log(` ${chalk.dim(`Cost: ${cost} | Tokens: ${inTokens} in / ${outTokens} out`)}`);\n }\n console.log();\n }\n }\n\n }\n\n console.log();\n console.log(\n chalk.dim('Run \"ai-session-tidy clean\" to remove orphaned sessions.')\n );\n}\n","import chalk from 'chalk';\n\nexport interface Logger {\n info(message: string): void;\n warn(message: string): void;\n error(message: string): void;\n success(message: string): void;\n debug(message: string): void;\n}\n\nexport const logger: Logger = {\n info(message: string): void {\n console.log(chalk.blue('ℹ'), message);\n },\n warn(message: string): void {\n console.log(chalk.yellow('⚠'), message);\n },\n error(message: string): void {\n console.log(chalk.red('✖'), message);\n },\n success(message: string): void {\n console.log(chalk.green('✔'), message);\n },\n debug(message: string): void {\n if (process.env['DEBUG']) {\n console.log(chalk.gray('🐛'), message);\n }\n },\n};\n","import { readdir, stat } from 'fs/promises';\nimport { join } from 'path';\n\nconst UNITS = ['B', 'KB', 'MB', 'GB', 'TB'] as const;\n\n/**\n * Format byte size to human-readable format\n */\nexport function formatSize(bytes: number): string {\n if (bytes <= 0) return '0 B';\n\n let size = bytes;\n let unitIndex = 0;\n\n while (size >= 1024 && unitIndex < UNITS.length - 1) {\n size /= 1024;\n unitIndex++;\n }\n\n if (unitIndex === 0) {\n return `${Math.floor(size)} ${UNITS[unitIndex]}`;\n }\n\n return `${size.toFixed(1)} ${UNITS[unitIndex]}`;\n}\n\n/**\n * Recursively calculate total size of a directory\n */\nexport async function getDirectorySize(dirPath: string): Promise<number> {\n try {\n const entries = await readdir(dirPath, { withFileTypes: true });\n let totalSize = 0;\n\n for (const entry of entries) {\n const fullPath = join(dirPath, entry.name);\n\n if (entry.isDirectory()) {\n totalSize += await getDirectorySize(fullPath);\n } else if (entry.isFile()) {\n const fileStat = await stat(fullPath);\n totalSize += fileStat.size;\n }\n }\n\n return totalSize;\n } catch {\n return 0;\n }\n}\n","import { readdir, readFile, stat, access } from 'fs/promises';\nimport { join } from 'path';\nimport { createReadStream } from 'fs';\nimport { createInterface } from 'readline';\n\nimport type { Scanner, ScanResult, OrphanedSession } from './types.js';\nimport {\n decodePath,\n getClaudeProjectsDir,\n getClaudeConfigPath,\n getClaudeSessionEnvDir,\n getClaudeTodosDir,\n getClaudeFileHistoryDir,\n} from '../utils/paths.js';\nimport { getDirectorySize } from '../utils/size.js';\n\ninterface ClaudeProjectData {\n lastCost?: number;\n lastTotalInputTokens?: number;\n lastTotalOutputTokens?: number;\n [key: string]: unknown;\n}\n\ninterface ClaudeConfig {\n projects?: Record<string, ClaudeProjectData>;\n [key: string]: unknown;\n}\n\ninterface SessionEntry {\n cwd?: string;\n}\n\nexport interface ClaudeCodeScannerOptions {\n projectsDir?: string;\n configPath?: string | null; // null to disable config scanning\n sessionEnvDir?: string | null; // null to disable session-env scanning\n todosDir?: string | null; // null to disable todos scanning\n fileHistoryDir?: string | null; // null to disable file-history scanning\n}\n\nexport class ClaudeCodeScanner implements Scanner {\n readonly name = 'claude-code' as const;\n private readonly projectsDir: string;\n private readonly configPath: string | null;\n private readonly sessionEnvDir: string | null;\n private readonly todosDir: string | null;\n private readonly fileHistoryDir: string | null;\n\n constructor(projectsDirOrOptions?: string | ClaudeCodeScannerOptions) {\n if (typeof projectsDirOrOptions === 'string') {\n // Backward compatibility: treat string as projectsDir, disable other scans\n this.projectsDir = projectsDirOrOptions;\n this.configPath = null;\n this.sessionEnvDir = null;\n this.todosDir = null;\n this.fileHistoryDir = null;\n } else if (projectsDirOrOptions) {\n this.projectsDir = projectsDirOrOptions.projectsDir ?? getClaudeProjectsDir();\n this.configPath = projectsDirOrOptions.configPath === undefined\n ? getClaudeConfigPath()\n : projectsDirOrOptions.configPath;\n this.sessionEnvDir = projectsDirOrOptions.sessionEnvDir === undefined\n ? getClaudeSessionEnvDir()\n : projectsDirOrOptions.sessionEnvDir;\n this.todosDir = projectsDirOrOptions.todosDir === undefined\n ? getClaudeTodosDir()\n : projectsDirOrOptions.todosDir;\n this.fileHistoryDir = projectsDirOrOptions.fileHistoryDir === undefined\n ? getClaudeFileHistoryDir()\n : projectsDirOrOptions.fileHistoryDir;\n } else {\n this.projectsDir = getClaudeProjectsDir();\n this.configPath = getClaudeConfigPath();\n this.sessionEnvDir = getClaudeSessionEnvDir();\n this.todosDir = getClaudeTodosDir();\n this.fileHistoryDir = getClaudeFileHistoryDir();\n }\n }\n\n async isAvailable(): Promise<boolean> {\n try {\n await access(this.projectsDir);\n return true;\n } catch {\n return false;\n }\n }\n\n async scan(): Promise<ScanResult> {\n const startTime = performance.now();\n const sessions: OrphanedSession[] = [];\n\n // 1. Scan session folders\n if (await this.isAvailable()) {\n const entries = await readdir(this.projectsDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n const sessionPath = join(this.projectsDir, entry.name);\n\n // Extract actual project path from JSONL file\n let projectPath = await this.extractProjectPath(sessionPath);\n\n // Fallback to decoding if path not found in JSONL\n if (!projectPath) {\n projectPath = decodePath(entry.name);\n }\n\n // Check if original project exists\n const projectExists = await this.pathExists(projectPath);\n if (projectExists) continue;\n\n // Exclude empty directories\n const size = await getDirectorySize(sessionPath);\n if (size === 0) continue;\n\n // Get last modified time\n const lastModified = await this.getLastModified(sessionPath);\n\n sessions.push({\n toolName: this.name,\n sessionPath,\n projectPath,\n size,\n lastModified,\n type: 'session',\n });\n }\n }\n\n // 2. Scan ~/.claude.json config file\n const configSessions = await this.scanConfigFile();\n sessions.push(...configSessions);\n\n // 3. Scan ~/.claude/session-env empty folders\n const sessionEnvSessions = await this.scanSessionEnvDir();\n sessions.push(...sessionEnvSessions);\n\n // 4. Collect valid session UUIDs then scan todos/file-history\n const validSessionIds = await this.collectValidSessionIds();\n\n // 5. Scan ~/.claude/todos for orphaned files\n const todosSessions = await this.scanTodosDir(validSessionIds);\n sessions.push(...todosSessions);\n\n // 6. Scan ~/.claude/file-history for orphaned folders\n const fileHistorySessions = await this.scanFileHistoryDir(validSessionIds);\n sessions.push(...fileHistorySessions);\n\n const totalSize = sessions.reduce((sum, s) => sum + s.size, 0);\n\n return {\n toolName: this.name,\n sessions,\n totalSize,\n scanDuration: performance.now() - startTime,\n };\n }\n\n /**\n * Detect orphaned projects from ~/.claude.json projects entries\n */\n private async scanConfigFile(): Promise<OrphanedSession[]> {\n if (!this.configPath) {\n return [];\n }\n\n const configPath = this.configPath;\n const orphanedConfigs: OrphanedSession[] = [];\n\n try {\n const content = await readFile(configPath, 'utf-8');\n const config: ClaudeConfig = JSON.parse(content);\n const configStat = await stat(configPath);\n\n if (!config.projects || typeof config.projects !== 'object') {\n return [];\n }\n\n for (const [projectPath, projectData] of Object.entries(config.projects)) {\n const projectExists = await this.pathExists(projectPath);\n if (!projectExists) {\n orphanedConfigs.push({\n toolName: this.name,\n sessionPath: configPath,\n projectPath,\n size: 0, // config entries have negligible size\n lastModified: configStat.mtime,\n type: 'config',\n configStats: {\n lastCost: projectData.lastCost,\n lastTotalInputTokens: projectData.lastTotalInputTokens,\n lastTotalOutputTokens: projectData.lastTotalOutputTokens,\n },\n });\n }\n }\n } catch {\n // Ignore if config file doesn't exist or read fails\n }\n\n return orphanedConfigs;\n }\n\n /**\n * Detect empty session environment folders from ~/.claude/session-env\n */\n private async scanSessionEnvDir(): Promise<OrphanedSession[]> {\n if (!this.sessionEnvDir) {\n return [];\n }\n\n const orphanedEnvs: OrphanedSession[] = [];\n\n try {\n await access(this.sessionEnvDir);\n const entries = await readdir(this.sessionEnvDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n const envPath = join(this.sessionEnvDir, entry.name);\n const files = await readdir(envPath);\n\n // Only treat empty folders as orphaned\n if (files.length === 0) {\n const envStat = await stat(envPath);\n orphanedEnvs.push({\n toolName: this.name,\n sessionPath: envPath,\n projectPath: entry.name, // UUID\n size: 0,\n lastModified: envStat.mtime,\n type: 'session-env',\n });\n }\n }\n } catch {\n // Ignore if directory doesn't exist or access fails\n }\n\n return orphanedEnvs;\n }\n\n /**\n * Collect valid session UUIDs from all projects\n */\n private async collectValidSessionIds(): Promise<Set<string>> {\n const sessionIds = new Set<string>();\n\n try {\n const projectDirs = await readdir(this.projectsDir, { withFileTypes: true });\n\n for (const projectDir of projectDirs) {\n if (!projectDir.isDirectory()) continue;\n\n const projectPath = join(this.projectsDir, projectDir.name);\n const files = await readdir(projectPath);\n\n for (const file of files) {\n if (file.endsWith('.jsonl')) {\n // Extract UUID from UUID.jsonl\n const sessionId = file.replace('.jsonl', '');\n sessionIds.add(sessionId);\n }\n }\n }\n } catch {\n // Return empty Set if directory access fails\n }\n\n return sessionIds;\n }\n\n /**\n * Detect orphaned todo files from ~/.claude/todos\n * Filename pattern: {session-uuid}-agent-{agent-uuid}.json\n */\n private async scanTodosDir(validSessionIds: Set<string>): Promise<OrphanedSession[]> {\n if (!this.todosDir) {\n return [];\n }\n\n const orphanedTodos: OrphanedSession[] = [];\n\n try {\n await access(this.todosDir);\n const entries = await readdir(this.todosDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith('.json')) continue;\n\n // Extract session UUID from filename (first UUID)\n const sessionId = entry.name.split('-agent-')[0];\n if (!sessionId) continue;\n\n // Orphan if not in valid session IDs\n if (!validSessionIds.has(sessionId)) {\n const todoPath = join(this.todosDir, entry.name);\n const todoStat = await stat(todoPath);\n\n orphanedTodos.push({\n toolName: this.name,\n sessionPath: todoPath,\n projectPath: sessionId, // Session UUID\n size: todoStat.size,\n lastModified: todoStat.mtime,\n type: 'todos',\n });\n }\n }\n } catch {\n // Ignore if directory doesn't exist or access fails\n }\n\n return orphanedTodos;\n }\n\n /**\n * Detect orphaned folders from ~/.claude/file-history\n * Folder name is the session UUID\n */\n private async scanFileHistoryDir(validSessionIds: Set<string>): Promise<OrphanedSession[]> {\n if (!this.fileHistoryDir) {\n return [];\n }\n\n const orphanedHistories: OrphanedSession[] = [];\n\n try {\n await access(this.fileHistoryDir);\n const entries = await readdir(this.fileHistoryDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n const sessionId = entry.name;\n\n // Orphan if not in valid session IDs\n if (!validSessionIds.has(sessionId)) {\n const historyPath = join(this.fileHistoryDir, entry.name);\n const size = await getDirectorySize(historyPath);\n const historyStat = await stat(historyPath);\n\n orphanedHistories.push({\n toolName: this.name,\n sessionPath: historyPath,\n projectPath: sessionId, // Session UUID\n size,\n lastModified: historyStat.mtime,\n type: 'file-history',\n });\n }\n }\n } catch {\n // Ignore if directory doesn't exist or access fails\n }\n\n return orphanedHistories;\n }\n\n /**\n * Extract project path (cwd) from JSONL file\n */\n private async extractProjectPath(sessionDir: string): Promise<string | null> {\n try {\n const files = await readdir(sessionDir);\n const jsonlFile = files.find((f) => f.endsWith('.jsonl'));\n\n if (!jsonlFile) return null;\n\n const jsonlPath = join(sessionDir, jsonlFile);\n\n // Read only first few lines to find cwd\n const cwd = await this.findCwdInJsonl(jsonlPath);\n return cwd;\n } catch {\n return null;\n }\n }\n\n private async findCwdInJsonl(jsonlPath: string): Promise<string | null> {\n return new Promise((resolve) => {\n const stream = createReadStream(jsonlPath, { encoding: 'utf-8' });\n const rl = createInterface({ input: stream, crlfDelay: Infinity });\n\n let found = false;\n let lineCount = 0;\n const maxLines = 10; // Check only first 10 lines\n\n rl.on('line', (line) => {\n if (found || lineCount >= maxLines) {\n rl.close();\n return;\n }\n\n lineCount++;\n\n try {\n const entry: SessionEntry = JSON.parse(line);\n if (entry.cwd) {\n found = true;\n rl.close();\n stream.destroy();\n resolve(entry.cwd);\n }\n } catch {\n // Ignore JSON parse failures\n }\n });\n\n rl.on('close', () => {\n if (!found) resolve(null);\n });\n\n rl.on('error', () => {\n resolve(null);\n });\n });\n }\n\n private async pathExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n }\n\n private async getLastModified(dirPath: string): Promise<Date> {\n try {\n const entries = await readdir(dirPath, { withFileTypes: true });\n let latestTime = 0;\n\n for (const entry of entries) {\n const fullPath = join(dirPath, entry.name);\n const fileStat = await stat(fullPath);\n const mtime = fileStat.mtimeMs;\n\n if (mtime > latestTime) {\n latestTime = mtime;\n }\n }\n\n return latestTime > 0 ? new Date(latestTime) : new Date();\n } catch {\n return new Date();\n }\n }\n}\n","import { homedir } from 'os';\nimport { join } from 'path';\n\n/**\n * Encode Unix path to Claude Code style\n * /home/user/project → -home-user-project\n */\nexport function encodePath(path: string): string {\n if (path === '') return '';\n // Only convert Unix slashes, leave Windows paths as-is\n if (!path.includes('/')) return path;\n return path.replace(/\\//g, '-');\n}\n\n/**\n * Decode Claude Code encoded path to Unix path\n * -home-user-project → /home/user/project\n */\nexport function decodePath(encoded: string): string {\n if (encoded === '') return '';\n // Treat as Unix encoding if it starts with a dash\n if (!encoded.startsWith('-')) return encoded;\n return encoded.replace(/-/g, '/');\n}\n\n/**\n * Return platform-specific application config directory\n */\nexport function getConfigDir(appName: string): string {\n switch (process.platform) {\n case 'darwin':\n return join(homedir(), 'Library/Application Support', appName);\n case 'win32':\n return join(process.env['APPDATA'] || '', appName);\n default:\n return join(homedir(), '.config', appName);\n }\n}\n\n/**\n * Claude Code projects directory path\n */\nexport function getClaudeProjectsDir(): string {\n return join(homedir(), '.claude', 'projects');\n}\n\n/**\n * Claude Code global config file path (~/.claude.json)\n */\nexport function getClaudeConfigPath(): string {\n return join(homedir(), '.claude.json');\n}\n\n/**\n * Cursor workspaceStorage directory path\n */\nexport function getCursorWorkspaceDir(): string {\n return join(getConfigDir('Cursor'), 'User', 'workspaceStorage');\n}\n\n/**\n * Claude Code session environment directory path (~/.claude/session-env)\n */\nexport function getClaudeSessionEnvDir(): string {\n return join(homedir(), '.claude', 'session-env');\n}\n\n/**\n * Claude Code todos directory path (~/.claude/todos)\n */\nexport function getClaudeTodosDir(): string {\n return join(homedir(), '.claude', 'todos');\n}\n\n/**\n * Claude Code file-history directory path (~/.claude/file-history)\n */\nexport function getClaudeFileHistoryDir(): string {\n return join(homedir(), '.claude', 'file-history');\n}\n\n/**\n * Replace home directory with ~ for display\n * /Users/user/.ai-session-tidy → ~/.ai-session-tidy\n */\nexport function tildify(path: string): string {\n const home = homedir();\n if (path.startsWith(home)) {\n return path.replace(home, '~');\n }\n return path;\n}\n","import { readdir, readFile, stat, access } from 'fs/promises';\nimport { join } from 'path';\nimport { fileURLToPath } from 'url';\n\nimport type { Scanner, ScanResult, OrphanedSession } from './types.js';\nimport { getCursorWorkspaceDir } from '../utils/paths.js';\nimport { getDirectorySize } from '../utils/size.js';\n\ninterface WorkspaceJson {\n folder?: string;\n}\n\nexport class CursorScanner implements Scanner {\n readonly name = 'cursor' as const;\n private readonly workspaceDir: string;\n\n constructor(workspaceDir?: string) {\n this.workspaceDir = workspaceDir ?? getCursorWorkspaceDir();\n }\n\n async isAvailable(): Promise<boolean> {\n try {\n await access(this.workspaceDir);\n return true;\n } catch {\n return false;\n }\n }\n\n async scan(): Promise<ScanResult> {\n const startTime = performance.now();\n const sessions: OrphanedSession[] = [];\n\n if (!(await this.isAvailable())) {\n return {\n toolName: this.name,\n sessions: [],\n totalSize: 0,\n scanDuration: performance.now() - startTime,\n };\n }\n\n const entries = await readdir(this.workspaceDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n const sessionPath = join(this.workspaceDir, entry.name);\n const workspaceJsonPath = join(sessionPath, 'workspace.json');\n\n // Read workspace.json\n const projectPath = await this.parseWorkspaceJson(workspaceJsonPath);\n if (!projectPath) continue;\n\n // Check if original project exists\n const projectExists = await this.pathExists(projectPath);\n if (projectExists) continue;\n\n // Calculate session size\n const size = await getDirectorySize(sessionPath);\n\n // Get last modified time\n const lastModified = await this.getLastModified(sessionPath);\n\n sessions.push({\n toolName: this.name,\n sessionPath,\n projectPath,\n size,\n lastModified,\n });\n }\n\n const totalSize = sessions.reduce((sum, s) => sum + s.size, 0);\n\n return {\n toolName: this.name,\n sessions,\n totalSize,\n scanDuration: performance.now() - startTime,\n };\n }\n\n private async parseWorkspaceJson(\n workspaceJsonPath: string\n ): Promise<string | null> {\n try {\n const content = await readFile(workspaceJsonPath, 'utf-8');\n const data: WorkspaceJson = JSON.parse(content);\n\n if (!data.folder) return null;\n\n // Convert file:// URL to regular path\n if (data.folder.startsWith('file://')) {\n return fileURLToPath(data.folder);\n }\n\n return data.folder;\n } catch {\n return null;\n }\n }\n\n private async pathExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n }\n\n private async getLastModified(dirPath: string): Promise<Date> {\n try {\n const entries = await readdir(dirPath, { withFileTypes: true });\n let latestTime = 0;\n\n for (const entry of entries) {\n const fullPath = join(dirPath, entry.name);\n const fileStat = await stat(fullPath);\n const mtime = fileStat.mtimeMs;\n\n if (mtime > latestTime) {\n latestTime = mtime;\n }\n }\n\n return latestTime > 0 ? new Date(latestTime) : new Date();\n } catch {\n return new Date();\n }\n }\n}\n","import type { Scanner, ScanResult } from './types.js';\nimport { ClaudeCodeScanner } from './claude-code.js';\nimport { CursorScanner } from './cursor.js';\n\nexport { ClaudeCodeScanner } from './claude-code.js';\nexport { CursorScanner } from './cursor.js';\nexport * from './types.js';\n\n/**\n * Create all scanner instances\n */\nexport function createAllScanners(): Scanner[] {\n return [new ClaudeCodeScanner(), new CursorScanner()];\n}\n\n/**\n * Filter to available scanners only\n */\nexport async function getAvailableScanners(\n scanners: Scanner[]\n): Promise<Scanner[]> {\n const results = await Promise.all(\n scanners.map(async (scanner) => ({\n scanner,\n available: await scanner.isAvailable(),\n }))\n );\n\n return results.filter((r) => r.available).map((r) => r.scanner);\n}\n\n/**\n * Run all scanners and merge results\n */\nexport async function runAllScanners(\n scanners: Scanner[]\n): Promise<ScanResult[]> {\n return Promise.all(scanners.map((scanner) => scanner.scan()));\n}\n","import { basename } from 'path';\n\nimport { Command } from 'commander';\nimport ora from 'ora';\nimport chalk from 'chalk';\nimport inquirer from 'inquirer';\n\nimport { logger } from '../utils/logger.js';\nimport { formatSize } from '../utils/size.js';\nimport { tildify } from '../utils/paths.js';\nimport {\n createAllScanners,\n getAvailableScanners,\n runAllScanners,\n} from '../scanners/index.js';\nimport { Cleaner } from '../core/cleaner.js';\nimport type { OrphanedSession } from '../scanners/types.js';\n\ninterface CleanOptions {\n force?: boolean;\n dryRun?: boolean;\n noTrash?: boolean;\n verbose?: boolean;\n interactive?: boolean;\n}\n\nfunction formatSessionChoice(session: OrphanedSession): string {\n const projectName = basename(session.projectPath);\n const isConfig = session.type === 'config';\n const toolTag = isConfig\n ? chalk.yellow('[config]')\n : chalk.cyan(`[${session.toolName}]`);\n const name = chalk.white(projectName);\n const size = isConfig ? '' : chalk.dim(`(${formatSize(session.size)})`);\n const path = chalk.dim(`→ ${session.projectPath}`);\n return `${toolTag} ${name} ${size}\\n ${path}`;\n}\n\nexport const cleanCommand = new Command('clean')\n .description('Remove orphaned session data')\n .option('-f, --force', 'Skip confirmation prompt')\n .option('-n, --dry-run', 'Show what would be deleted without deleting')\n .option('-i, --interactive', 'Select sessions to delete interactively')\n .option('--no-trash', 'Permanently delete instead of moving to trash')\n .option('-v, --verbose', 'Enable verbose output')\n .action(async (options: CleanOptions) => {\n const spinner = ora('Scanning for orphaned sessions...').start();\n\n try {\n // Scan\n const allScanners = createAllScanners();\n const availableScanners = await getAvailableScanners(allScanners);\n\n if (availableScanners.length === 0) {\n spinner.warn('No AI coding tools detected on this system.');\n return;\n }\n\n const results = await runAllScanners(availableScanners);\n const allSessions = results.flatMap((r) => r.sessions);\n const totalSize = results.reduce((sum, r) => sum + r.totalSize, 0);\n\n spinner.stop();\n\n if (allSessions.length === 0) {\n logger.success('No orphaned sessions found.');\n return;\n }\n\n // Separate session folders, config entries, and auto-cleanup targets\n const folderSessions = allSessions.filter(\n (s) => s.type === 'session' || s.type === undefined\n );\n const configEntries = allSessions.filter((s) => s.type === 'config');\n const sessionEnvEntries = allSessions.filter(\n (s) => s.type === 'session-env'\n );\n const todosEntries = allSessions.filter((s) => s.type === 'todos');\n const fileHistoryEntries = allSessions.filter(\n (s) => s.type === 'file-history'\n );\n\n // Auto-cleanup targets (session-env, todos, file-history)\n const autoCleanEntries = [\n ...sessionEnvEntries,\n ...todosEntries,\n ...fileHistoryEntries,\n ];\n\n // Interactive selection targets (excluding auto-cleanup targets)\n const selectableSessions = allSessions.filter(\n (s) =>\n s.type !== 'session-env' &&\n s.type !== 'todos' &&\n s.type !== 'file-history'\n );\n\n // Output summary\n console.log();\n const parts: string[] = [];\n if (folderSessions.length > 0) {\n parts.push(`${folderSessions.length} session folder(s)`);\n }\n if (configEntries.length > 0) {\n parts.push(`${configEntries.length} config entry(ies)`);\n }\n if (sessionEnvEntries.length > 0) {\n parts.push(`${sessionEnvEntries.length} session-env folder(s)`);\n }\n if (todosEntries.length > 0) {\n parts.push(`${todosEntries.length} todos file(s)`);\n }\n if (fileHistoryEntries.length > 0) {\n parts.push(`${fileHistoryEntries.length} file-history folder(s)`);\n }\n logger.warn(`Found ${parts.join(' + ')} (${formatSize(totalSize)})`);\n\n if (options.verbose && !options.interactive) {\n console.log();\n // Exclude auto-cleanup targets from detailed list\n for (const session of selectableSessions) {\n console.log(\n chalk.dim(` ${session.toolName}: ${session.projectPath}`)\n );\n }\n }\n\n // Interactive mode: session selection (excluding auto-cleanup targets)\n let sessionsToClean = allSessions;\n if (options.interactive) {\n if (selectableSessions.length === 0) {\n // Only auto-cleanup targets exist\n if (autoCleanEntries.length > 0) {\n sessionsToClean = autoCleanEntries;\n logger.info(\n `Only ${autoCleanEntries.length} auto-cleanup target(s) found`\n );\n } else {\n logger.info('No selectable sessions found.');\n return;\n }\n } else {\n console.log();\n const { selected } = await inquirer.prompt<{\n selected: OrphanedSession[];\n }>([\n {\n type: 'checkbox',\n name: 'selected',\n message: 'Select sessions to delete:',\n choices: selectableSessions.map((session) => ({\n name: formatSessionChoice(session),\n value: session,\n checked: false,\n })),\n pageSize: 15,\n loop: false,\n },\n ]);\n\n if (selected.length === 0) {\n logger.info('No sessions selected. Cancelled.');\n return;\n }\n\n // Include selected sessions + auto-cleanup targets\n sessionsToClean = [...selected, ...autoCleanEntries];\n const selectedSize = selected.reduce((sum, s) => sum + s.size, 0);\n console.log();\n if (selected.length > 0) {\n logger.info(\n `Selected: ${selected.length} session(s) (${formatSize(selectedSize)})`\n );\n }\n if (autoCleanEntries.length > 0) {\n const autoSize = autoCleanEntries.reduce((sum, s) => sum + s.size, 0);\n logger.info(\n `+ ${autoCleanEntries.length} auto-cleanup target(s) (${formatSize(autoSize)})`\n );\n }\n }\n }\n\n const cleanSize = sessionsToClean.reduce((sum, s) => sum + s.size, 0);\n\n // Dry-run mode\n if (options.dryRun) {\n console.log();\n logger.info(\n chalk.yellow('Dry-run mode: No files will be deleted.')\n );\n console.log();\n\n const dryRunFolders = sessionsToClean.filter(\n (s) => s.type === 'session' || s.type === undefined\n );\n const dryRunConfigs = sessionsToClean.filter((s) => s.type === 'config');\n const dryRunEnvs = sessionsToClean.filter(\n (s) => s.type === 'session-env'\n );\n const dryRunTodos = sessionsToClean.filter((s) => s.type === 'todos');\n const dryRunHistories = sessionsToClean.filter(\n (s) => s.type === 'file-history'\n );\n\n for (const session of dryRunFolders) {\n console.log(\n ` ${chalk.red('Would delete:')} ${session.sessionPath} (${formatSize(session.size)})`\n );\n }\n\n if (dryRunConfigs.length > 0) {\n console.log();\n console.log(\n ` ${chalk.yellow('Would remove from ~/.claude.json:')}`\n );\n for (const config of dryRunConfigs) {\n console.log(` - ${config.projectPath}`);\n }\n }\n\n // Auto-cleanup targets summary\n const autoCleanParts: string[] = [];\n if (dryRunEnvs.length > 0) {\n autoCleanParts.push(`${dryRunEnvs.length} session-env`);\n }\n if (dryRunTodos.length > 0) {\n autoCleanParts.push(`${dryRunTodos.length} todos`);\n }\n if (dryRunHistories.length > 0) {\n autoCleanParts.push(`${dryRunHistories.length} file-history`);\n }\n if (autoCleanParts.length > 0) {\n const autoSize =\n dryRunEnvs.reduce((sum, s) => sum + s.size, 0) +\n dryRunTodos.reduce((sum, s) => sum + s.size, 0) +\n dryRunHistories.reduce((sum, s) => sum + s.size, 0);\n console.log();\n console.log(\n ` ${chalk.dim(`Would auto-delete: ${autoCleanParts.join(' + ')} (${formatSize(autoSize)})`)}`\n );\n }\n return;\n }\n\n // Confirmation prompt (also in interactive mode)\n if (!options.force) {\n console.log();\n const action = options.noTrash ? 'permanently delete' : 'move to trash';\n const { confirmed } = await inquirer.prompt<{ confirmed: boolean }>([\n {\n type: 'confirm',\n name: 'confirmed',\n message: `${action} ${sessionsToClean.length} session(s) (${formatSize(cleanSize)})?`,\n default: false,\n },\n ]);\n\n if (!confirmed) {\n logger.info('Cancelled.');\n return;\n }\n }\n\n // Execute cleanup\n const cleanSpinner = ora('Cleaning orphaned sessions...').start();\n\n const cleaner = new Cleaner();\n const cleanResult = await cleaner.clean(sessionsToClean, {\n dryRun: false,\n useTrash: !options.noTrash,\n });\n\n cleanSpinner.stop();\n\n // Output results\n console.log();\n if (cleanResult.deletedCount > 0) {\n const action = options.noTrash ? 'Deleted' : 'Moved to trash';\n const parts: string[] = [];\n const { deletedByType } = cleanResult;\n\n if (deletedByType.session > 0) {\n parts.push(`${deletedByType.session} session`);\n }\n if (deletedByType.sessionEnv > 0) {\n parts.push(`${deletedByType.sessionEnv} session-env`);\n }\n if (deletedByType.todos > 0) {\n parts.push(`${deletedByType.todos} todos`);\n }\n if (deletedByType.fileHistory > 0) {\n parts.push(`${deletedByType.fileHistory} file-history`);\n }\n\n const summary =\n parts.length > 0\n ? parts.join(' + ')\n : `${cleanResult.deletedCount} item(s)`;\n logger.success(\n `${action}: ${summary} (${formatSize(cleanResult.totalSizeDeleted)})`\n );\n }\n\n if (cleanResult.alreadyGoneCount > 0 && options.verbose) {\n logger.info(\n `Skipped ${cleanResult.alreadyGoneCount} already-deleted item(s)`\n );\n }\n\n if (cleanResult.configEntriesRemoved > 0) {\n logger.success(\n `Removed ${cleanResult.configEntriesRemoved} config entry(ies) from ~/.claude.json`\n );\n if (cleanResult.backupPath) {\n logger.info(`Backup saved to: ${tildify(cleanResult.backupPath)}`);\n }\n }\n\n if (cleanResult.errors.length > 0) {\n logger.error(`Failed to delete ${cleanResult.errors.length} item(s)`);\n if (options.verbose) {\n for (const err of cleanResult.errors) {\n console.log(chalk.red(` ${err.sessionPath}: ${err.error.message}`));\n }\n }\n }\n } catch (error) {\n spinner.fail('Clean failed');\n logger.error(\n error instanceof Error ? error.message : 'Unknown error occurred'\n );\n process.exit(1);\n }\n });\n","import { readFile, writeFile, mkdir, copyFile } from 'fs/promises';\nimport { existsSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { homedir } from 'os';\n\nimport type { OrphanedSession } from '../scanners/types.js';\nimport { moveToTrash, permanentDelete } from './trash.js';\n\nexport interface CleanOptions {\n dryRun: boolean;\n useTrash?: boolean;\n}\n\nexport interface CleanError {\n sessionPath: string;\n error: Error;\n}\n\nexport interface CleanCountByType {\n session: number;\n sessionEnv: number;\n todos: number;\n fileHistory: number;\n}\n\nexport interface CleanResult {\n deletedCount: number;\n deletedByType: CleanCountByType;\n skippedCount: number;\n alreadyGoneCount: number;\n configEntriesRemoved: number;\n totalSizeDeleted: number;\n errors: CleanError[];\n backupPath?: string;\n}\n\ninterface ClaudeConfig {\n projects?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nfunction getBackupDir(): string {\n return join(homedir(), '.ai-session-tidy', 'backups');\n}\n\nexport class Cleaner {\n async clean(\n sessions: OrphanedSession[],\n options: CleanOptions\n ): Promise<CleanResult> {\n const result: CleanResult = {\n deletedCount: 0,\n deletedByType: {\n session: 0,\n sessionEnv: 0,\n todos: 0,\n fileHistory: 0,\n },\n skippedCount: 0,\n alreadyGoneCount: 0,\n configEntriesRemoved: 0,\n totalSizeDeleted: 0,\n errors: [],\n };\n\n const useTrash = options.useTrash ?? true;\n\n // Separate session folders and config entries\n const folderSessions = sessions.filter((s) => s.type !== 'config');\n const configEntries = sessions.filter((s) => s.type === 'config');\n\n // 1. Clean session folders/files\n for (const session of folderSessions) {\n if (options.dryRun) {\n result.skippedCount++;\n continue;\n }\n\n try {\n const deleted = useTrash\n ? await moveToTrash(session.sessionPath)\n : await permanentDelete(session.sessionPath);\n\n if (deleted) {\n result.deletedCount++;\n result.totalSizeDeleted += session.size;\n\n // Count by type\n switch (session.type) {\n case 'session-env':\n result.deletedByType.sessionEnv++;\n break;\n case 'todos':\n result.deletedByType.todos++;\n break;\n case 'file-history':\n result.deletedByType.fileHistory++;\n break;\n default:\n result.deletedByType.session++;\n }\n } else {\n // Already deleted path - not an error\n result.alreadyGoneCount++;\n }\n } catch (error) {\n result.errors.push({\n sessionPath: session.sessionPath,\n error: error instanceof Error ? error : new Error(String(error)),\n });\n }\n }\n\n // 2. Clean config entries\n if (configEntries.length > 0 && !options.dryRun) {\n try {\n const configResult = await this.cleanConfigEntries(configEntries);\n result.configEntriesRemoved = configResult.removed;\n result.backupPath = configResult.backupPath;\n } catch (error) {\n result.errors.push({\n sessionPath: configEntries[0].sessionPath,\n error: error instanceof Error ? error : new Error(String(error)),\n });\n }\n } else if (options.dryRun) {\n result.skippedCount += configEntries.length;\n }\n\n return result;\n }\n\n /**\n * Remove orphaned project entries from ~/.claude.json\n */\n private async cleanConfigEntries(\n entries: OrphanedSession[]\n ): Promise<{ removed: number; backupPath: string }> {\n if (entries.length === 0) {\n return { removed: 0, backupPath: '' };\n }\n\n const configPath = entries[0].sessionPath;\n const projectPathsToRemove = new Set(entries.map((e) => e.projectPath));\n\n // Create backup directory\n const backupDir = getBackupDir();\n if (!existsSync(backupDir)) {\n await mkdir(backupDir, { recursive: true });\n }\n\n // Create backup file (with timestamp)\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-');\n const backupPath = join(backupDir, `claude.json.${timestamp}.bak`);\n await copyFile(configPath, backupPath);\n\n // Read config file\n const content = await readFile(configPath, 'utf-8');\n const config: ClaudeConfig = JSON.parse(content);\n\n if (!config.projects) {\n return { removed: 0, backupPath };\n }\n\n // Remove orphaned project entries\n let removedCount = 0;\n for (const projectPath of projectPathsToRemove) {\n if (projectPath in config.projects) {\n delete config.projects[projectPath];\n removedCount++;\n }\n }\n\n // Save modified config\n if (removedCount > 0) {\n await writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');\n }\n\n return { removed: removedCount, backupPath };\n }\n}\n","import { rm, access } from 'fs/promises';\nimport trash from 'trash';\n\n/**\n * Check if path exists\n */\nasync function pathExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Move file or directory to trash\n * @returns true if deleted, false if already gone\n */\nexport async function moveToTrash(path: string): Promise<boolean> {\n if (!(await pathExists(path))) {\n return false; // Already deleted - silent skip\n }\n\n await trash(path);\n return true;\n}\n\n/**\n * Permanently delete file or directory\n * @returns true if deleted, false if already gone\n */\nexport async function permanentDelete(path: string): Promise<boolean> {\n if (!(await pathExists(path))) {\n return false; // Already deleted - silent skip\n }\n\n await rm(path, { recursive: true, force: true });\n return true;\n}\n","import { Command } from 'commander';\nimport chalk from 'chalk';\nimport { existsSync } from 'fs';\nimport { homedir } from 'os';\nimport { join, resolve } from 'path';\n\nimport { logger } from '../utils/logger.js';\nimport { formatSize } from '../utils/size.js';\nimport { getWatchPaths as getConfigWatchPaths, setWatchPaths, getWatchDelay, getWatchDepth, getIgnorePaths } from '../utils/config.js';\nimport {\n createAllScanners,\n getAvailableScanners,\n runAllScanners,\n} from '../scanners/index.js';\nimport { Watcher } from '../core/watcher.js';\nimport { Cleaner } from '../core/cleaner.js';\nimport { serviceManager } from '../core/service.js';\n\nconst DEFAULT_DELAY_MINUTES = 5;\nconst MAX_DELAY_MINUTES = 10;\n\ninterface RunOptions {\n delay?: string;\n path?: string[];\n noSave?: boolean;\n noTrash?: boolean;\n verbose?: boolean;\n}\n\nexport const watchCommand = new Command('watch')\n .description('Watch for project deletions and auto-clean orphaned sessions');\n\n// Run subcommand (default) - foreground execution\nconst runCommand = new Command('run')\n .description('Run watcher in foreground (default)')\n .option(\n '-p, --path <path>',\n 'Path to watch (can be used multiple times, saves to config)',\n (value: string, previous: string[]) => previous.concat([value]),\n [] as string[]\n )\n .option('--no-save', 'Do not save paths to config')\n .option(\n '-d, --delay <minutes>',\n `Delay before cleanup (default: ${DEFAULT_DELAY_MINUTES}, max: ${MAX_DELAY_MINUTES} minutes)`\n )\n .option('--no-trash', 'Permanently delete instead of moving to trash')\n .option('-v, --verbose', 'Enable verbose output')\n .action(runWatcher);\n\n// Start subcommand - install and start as OS service\nconst startCommand = new Command('start')\n .description('Start watcher as OS service (background + auto-start at login)')\n .action(async () => {\n const supported = serviceManager.isSupported();\n if (!supported) {\n logger.error('Service management is only supported on macOS.');\n logger.info('Use \"watch run\" to run the watcher in foreground.');\n return;\n }\n\n try {\n // Check current status\n const currentStatus = await serviceManager.status();\n if (currentStatus.status === 'running') {\n logger.info('Watcher service is already running.');\n return;\n }\n\n // Install and start\n logger.info('Installing watcher service...');\n await serviceManager.install();\n\n logger.info('Starting watcher service...');\n await serviceManager.start();\n\n // Verify\n const status = await serviceManager.status();\n if (status.status === 'running') {\n logger.success(`Watcher service started (PID: ${status.pid})`);\n logger.info('The watcher will automatically start at login.');\n logger.info(`Logs: ~/.ai-session-tidy/watcher.log`);\n } else {\n logger.warn('Service installed but may not be running yet.');\n logger.info('Check status with: ai-session-tidy watch status');\n }\n } catch (error) {\n logger.error(`Failed to start service: ${error instanceof Error ? error.message : String(error)}`);\n }\n });\n\n// Stop subcommand - stop and uninstall OS service\nconst stopCommand = new Command('stop')\n .description('Stop watcher service and disable auto-start')\n .action(async () => {\n const supported = serviceManager.isSupported();\n if (!supported) {\n logger.error('Service management is only supported on macOS.');\n return;\n }\n\n try {\n const currentStatus = await serviceManager.status();\n if (currentStatus.status === 'not_installed') {\n logger.info('Watcher service is not installed.');\n return;\n }\n\n logger.info('Stopping watcher service...');\n await serviceManager.stop();\n\n logger.info('Removing service configuration...');\n await serviceManager.uninstall();\n\n logger.success('Watcher service stopped and removed.');\n } catch (error) {\n logger.error(`Failed to stop service: ${error instanceof Error ? error.message : String(error)}`);\n }\n });\n\n// Status subcommand - show service status\nconst statusCommand = new Command('status')\n .description('Show watcher service status')\n .option('-l, --logs [lines]', 'Show recent logs', '20')\n .action(async (options: { logs?: string }) => {\n const supported = serviceManager.isSupported();\n if (!supported) {\n logger.error('Service management is only supported on macOS.');\n return;\n }\n\n try {\n const status = await serviceManager.status();\n\n console.log();\n console.log(chalk.bold('Watcher Service Status'));\n console.log('─'.repeat(40));\n console.log(`Label: ${status.label}`);\n console.log(`Status: ${formatStatus(status.status)}`);\n if (status.pid) {\n console.log(`PID: ${status.pid}`);\n }\n console.log(`Plist: ${status.plistPath}`);\n console.log();\n\n if (options.logs) {\n const lines = parseInt(options.logs, 10) || 20;\n const logs = await serviceManager.getLogs(lines);\n\n if (logs.stdout) {\n console.log(chalk.bold('Recent Logs:'));\n console.log('─'.repeat(40));\n console.log(logs.stdout);\n console.log();\n }\n\n if (logs.stderr) {\n console.log(chalk.bold.red('Error Logs:'));\n console.log('─'.repeat(40));\n console.log(logs.stderr);\n console.log();\n }\n }\n } catch (error) {\n logger.error(`Failed to get status: ${error instanceof Error ? error.message : String(error)}`);\n }\n });\n\nfunction formatStatus(status: string): string {\n switch (status) {\n case 'running':\n return chalk.green('running');\n case 'stopped':\n return chalk.yellow('stopped');\n case 'not_installed':\n return chalk.dim('not installed');\n default:\n return status;\n }\n}\n\n// Add subcommands\nwatchCommand.addCommand(runCommand, { isDefault: true });\nwatchCommand.addCommand(startCommand);\nwatchCommand.addCommand(stopCommand);\nwatchCommand.addCommand(statusCommand);\n\n// The main watcher logic (foreground mode)\nasync function runWatcher(options: RunOptions): Promise<void> {\n // Priority: CLI option > config > default\n const configDelay = getWatchDelay();\n let delayMinutes = options.delay\n ? parseInt(options.delay, 10)\n : (configDelay ?? DEFAULT_DELAY_MINUTES);\n\n // Enforce max delay\n if (delayMinutes > MAX_DELAY_MINUTES) {\n logger.warn(`Maximum delay is ${MAX_DELAY_MINUTES} minutes. Using ${MAX_DELAY_MINUTES}.`);\n delayMinutes = MAX_DELAY_MINUTES;\n }\n\n const delayMs = delayMinutes * 60 * 1000;\n\n // Determine watch paths\n let watchPaths: string[];\n if (options.path && options.path.length > 0) {\n // Use paths from -p option\n watchPaths = options.path.map((p) => resolve(p.replace(/^~/, homedir())));\n\n // Save to config (unless --no-save)\n if (!options.noSave) {\n setWatchPaths(watchPaths);\n logger.info(`Saved watch paths to config.`);\n }\n } else {\n // Read from config or use defaults\n const configPaths = getConfigWatchPaths();\n if (configPaths && configPaths.length > 0) {\n watchPaths = configPaths;\n logger.info(`Using saved watch paths from config.`);\n } else {\n watchPaths = getDefaultWatchPaths();\n logger.info(`Using default watch paths.`);\n }\n }\n\n // Filter to existing paths\n const validPaths = watchPaths.filter((p) => existsSync(p));\n if (validPaths.length === 0) {\n logger.error('No valid watch paths found. Use -p to specify paths.');\n return;\n }\n\n if (validPaths.length < watchPaths.length) {\n const invalidPaths = watchPaths.filter((p) => !existsSync(p));\n logger.warn(`Skipping non-existent paths: ${invalidPaths.join(', ')}`);\n }\n\n // Check scanners\n const allScanners = createAllScanners();\n const availableScanners = await getAvailableScanners(allScanners);\n\n if (availableScanners.length === 0) {\n logger.warn('No AI coding tools detected on this system.');\n return;\n }\n\n const depth = getWatchDepth() ?? 3;\n\n logger.info(\n `Watching for project deletions (${availableScanners.map((s) => s.name).join(', ')})`\n );\n logger.info(`Watch paths: ${validPaths.join(', ')}`);\n logger.info(`Cleanup delay: ${String(delayMinutes)} minute(s)`);\n logger.info(`Watch depth: ${String(depth)}`);\n if (process.stdout.isTTY) {\n logger.info(chalk.dim('Press Ctrl+C to stop watching.'));\n }\n console.log();\n\n const cleaner = new Cleaner();\n\n const watcher = new Watcher({\n watchPaths: validPaths,\n delayMs,\n depth,\n ignorePaths: getIgnorePaths(),\n onDelete: async (events) => {\n // Log batch events\n if (events.length === 1) {\n logger.info(`Detected deletion: ${events[0].path}`);\n } else {\n logger.info(`Detected ${events.length} deletions (debounced)`);\n if (options.verbose) {\n for (const event of events) {\n logger.debug(` - ${event.path}`);\n }\n }\n }\n\n // Scan for orphaned sessions (executed only once)\n const results = await runAllScanners(availableScanners);\n const allSessions = results.flatMap((r) => r.sessions);\n\n if (allSessions.length === 0) {\n if (options.verbose) {\n logger.debug('No orphaned sessions found after deletion.');\n }\n return;\n }\n\n // Clean up\n const cleanResult = await cleaner.clean(allSessions, {\n dryRun: false,\n useTrash: !options.noTrash,\n });\n\n if (cleanResult.deletedCount > 0) {\n const action = options.noTrash ? 'Deleted' : 'Moved to trash';\n const parts: string[] = [];\n const { deletedByType } = cleanResult;\n\n if (deletedByType.session > 0) {\n parts.push(`${deletedByType.session} session`);\n }\n if (deletedByType.sessionEnv > 0) {\n parts.push(`${deletedByType.sessionEnv} session-env`);\n }\n if (deletedByType.todos > 0) {\n parts.push(`${deletedByType.todos} todos`);\n }\n if (deletedByType.fileHistory > 0) {\n parts.push(`${deletedByType.fileHistory} file-history`);\n }\n\n const summary = parts.length > 0 ? parts.join(' + ') : `${cleanResult.deletedCount} item(s)`;\n logger.success(\n `${action}: ${summary} (${formatSize(cleanResult.totalSizeDeleted)})`\n );\n }\n\n if (cleanResult.configEntriesRemoved > 0) {\n logger.success(\n `Removed ${cleanResult.configEntriesRemoved} config entry(ies) from ~/.claude.json`\n );\n }\n\n if (cleanResult.errors.length > 0) {\n logger.error(\n `Failed to clean ${cleanResult.errors.length} item(s)`\n );\n }\n },\n });\n\n watcher.start();\n\n // Handle Ctrl+C\n process.on('SIGINT', () => {\n console.log();\n logger.info('Stopping watcher...');\n watcher.stop();\n process.exit(0);\n });\n\n // Keep process alive\n await new Promise(() => {});\n}\n\nfunction getDefaultWatchPaths(): string[] {\n const home = homedir();\n return [\n join(home, 'dev'),\n join(home, 'code'),\n join(home, 'projects'),\n join(home, 'Development'),\n join(home, 'Developer'), // macOS Xcode\n join(home, 'Documents'),\n ];\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';\nimport { homedir } from 'os';\nimport { dirname, join, resolve } from 'path';\nimport { fileURLToPath } from 'url';\n\nconst CONFIG_VERSION = '0.1';\n\nexport function getAppVersion(): string {\n const __dirname = dirname(fileURLToPath(import.meta.url));\n // Try multiple paths (source vs bundled)\n const paths = [\n join(__dirname, '..', '..', 'package.json'), // from src/utils/\n join(__dirname, '..', 'package.json'), // from dist/\n ];\n\n for (const packagePath of paths) {\n try {\n if (existsSync(packagePath)) {\n const content = readFileSync(packagePath, 'utf-8');\n const pkg = JSON.parse(content) as { version: string };\n return pkg.version;\n }\n } catch {\n continue;\n }\n }\n return 'unknown';\n}\n\nexport interface Config {\n configVersion?: string; // config schema version for migration\n watchPaths?: string[];\n watchDelay?: number; // minutes\n watchDepth?: number; // folder depth (default: 3, max: 5)\n ignorePaths?: string[]; // paths to ignore from watching\n}\n\nfunction getConfigPath(): string {\n return join(homedir(), '.config', 'ai-session-tidy', 'config.json');\n}\n\nexport function loadConfig(): Config {\n const configPath = getConfigPath();\n\n if (!existsSync(configPath)) {\n return {};\n }\n\n try {\n const content = readFileSync(configPath, 'utf-8');\n return JSON.parse(content) as Config;\n } catch {\n return {};\n }\n}\n\nexport function saveConfig(config: Config): void {\n const configPath = getConfigPath();\n const configDir = dirname(configPath);\n\n if (!existsSync(configDir)) {\n mkdirSync(configDir, { recursive: true });\n }\n\n // Remove deprecated 'version' field if present\n const { version: _removed, ...cleanConfig } = config as Config & { version?: string };\n\n const configWithVersion: Config = {\n configVersion: CONFIG_VERSION,\n ...cleanConfig,\n };\n\n writeFileSync(configPath, JSON.stringify(configWithVersion, null, 2), 'utf-8');\n}\n\nexport function getWatchPaths(): string[] | undefined {\n const config = loadConfig();\n return config.watchPaths;\n}\n\nexport function setWatchPaths(paths: string[]): void {\n const config = loadConfig();\n config.watchPaths = paths;\n saveConfig(config);\n}\n\nexport function addWatchPath(path: string): void {\n const config = loadConfig();\n const resolved = resolve(path.replace(/^~/, homedir()));\n const paths = config.watchPaths ?? [];\n if (!paths.includes(resolved)) {\n paths.push(resolved);\n config.watchPaths = paths;\n saveConfig(config);\n }\n}\n\nexport function removeWatchPath(path: string): boolean {\n const config = loadConfig();\n const resolved = resolve(path.replace(/^~/, homedir()));\n const paths = config.watchPaths ?? [];\n const index = paths.indexOf(resolved);\n if (index === -1) return false;\n paths.splice(index, 1);\n config.watchPaths = paths;\n saveConfig(config);\n return true;\n}\n\nexport function getWatchDelay(): number | undefined {\n const config = loadConfig();\n return config.watchDelay;\n}\n\nexport function setWatchDelay(minutes: number): void {\n const config = loadConfig();\n config.watchDelay = minutes;\n saveConfig(config);\n}\n\nexport function getWatchDepth(): number | undefined {\n const config = loadConfig();\n return config.watchDepth;\n}\n\nexport function setWatchDepth(depth: number): void {\n const config = loadConfig();\n config.watchDepth = Math.min(depth, 5); // max 5\n saveConfig(config);\n}\n\nexport function resetConfig(): void {\n saveConfig({});\n}\n\nexport function getIgnorePaths(): string[] | undefined {\n const config = loadConfig();\n return config.ignorePaths;\n}\n\nexport function addIgnorePath(path: string): void {\n const config = loadConfig();\n const resolved = resolve(path.replace(/^~/, homedir()));\n const paths = config.ignorePaths ?? [];\n if (!paths.includes(resolved)) {\n paths.push(resolved);\n config.ignorePaths = paths;\n saveConfig(config);\n }\n}\n\nexport function removeIgnorePath(path: string): boolean {\n const config = loadConfig();\n const resolved = resolve(path.replace(/^~/, homedir()));\n const paths = config.ignorePaths ?? [];\n const index = paths.indexOf(resolved);\n if (index === -1) return false;\n paths.splice(index, 1);\n config.ignorePaths = paths;\n saveConfig(config);\n return true;\n}\n","import { watch, FSWatcher } from 'chokidar';\nimport { access } from 'fs/promises';\n\nimport { IGNORED_SYSTEM_PATTERNS } from './constants.js';\n\nexport interface WatchEvent {\n path: string;\n timestamp: Date;\n}\n\nexport interface WatcherOptions {\n watchPaths: string[];\n /** Delay before cleanup after deletion detected (allows recovery) */\n delayMs: number;\n /** Debounce time to batch multiple delete events (default: 10 seconds) */\n debounceMs?: number;\n depth?: number;\n /** User-defined paths to ignore */\n ignorePaths?: string[];\n /** Callback to handle batched delete events */\n onDelete: (events: WatchEvent[]) => void;\n}\n\n/**\n * Watcher that monitors project folder deletions and invokes cleanup callback\n *\n * ## Event Processing Flow\n *\n * When a folder is deleted, the OS generates individual events for each subfolder:\n * ```\n * rm -rf /project\n * → unlinkDir: /project/frontend\n * → unlinkDir: /project/backend\n * → unlinkDir: /project\n * ```\n *\n * Running scan/cleanup for each event would be inefficient,\n * so we use a two-stage delay mechanism:\n *\n * 1. **Per-path delay (delayMs)**: Provides recovery opportunity (default 5 min)\n * - If folder is restored during delay, cleanup is cancelled\n *\n * 2. **Debounce (debounceMs)**: Batches multiple events together (default 10 sec)\n * - After 10 seconds with no new events, batch is executed\n * - Scan/cleanup runs only once\n *\n * ## Timeline Example\n *\n * ```\n * T+0s: /project/frontend deletion detected → 5min timer starts\n * T+0.1s: /project/backend deletion detected → 5min timer starts\n * T+0.2s: /project deletion detected → 5min timer starts\n * T+5m: /project/frontend timer complete → add to batch, 10sec debounce starts\n * T+5m0.1s: /project/backend timer complete → add to batch, debounce reset\n * T+5m0.2s: /project timer complete → add to batch, debounce reset\n * T+5m10.2s: debounce complete → onDelete([3 events]) → single scan execution\n * ```\n */\nexport class Watcher {\n private readonly options: WatcherOptions;\n private readonly debounceMs: number;\n private watcher: FSWatcher | null = null;\n /** Per-path delay timers */\n private pendingDeletes: Map<string, NodeJS.Timeout> = new Map();\n /** Events waiting for debounce */\n private batchedEvents: WatchEvent[] = [];\n /** Debounce timer */\n private debounceTimer: NodeJS.Timeout | null = null;\n /** Paths that errored (to avoid duplicate logs) */\n private erroredPaths: Set<string> = new Set();\n\n constructor(options: WatcherOptions) {\n this.options = options;\n this.debounceMs = options.debounceMs ?? 10000; // default 10 seconds\n }\n\n start(): void {\n if (this.watcher) return;\n\n // Combine hardcoded patterns with user-defined ignore paths\n const ignored: (RegExp | string)[] = [...IGNORED_SYSTEM_PATTERNS];\n if (this.options.ignorePaths) {\n ignored.push(...this.options.ignorePaths);\n }\n\n this.watcher = watch(this.options.watchPaths, {\n ignoreInitial: true,\n persistent: true,\n depth: this.options.depth ?? 3,\n ignored,\n });\n\n this.watcher.on('unlinkDir', (path) => {\n this.handleDelete(path);\n });\n\n this.watcher.on('addDir', (path) => {\n this.handleRecovery(path);\n });\n\n // Handle errors gracefully - log and continue watching\n this.watcher.on('error', (error) => {\n const pathInfo = (error as NodeJS.ErrnoException & { path?: string }).path;\n if (pathInfo && !this.erroredPaths.has(pathInfo)) {\n this.erroredPaths.add(pathInfo);\n console.error(`[watch] Cannot watch: ${pathInfo} (${(error as NodeJS.ErrnoException).code})`);\n }\n });\n }\n\n stop(): void {\n if (!this.watcher) return;\n\n this.watcher.close();\n this.watcher = null;\n\n // Cancel all pending timers\n for (const timeout of this.pendingDeletes.values()) {\n clearTimeout(timeout);\n }\n this.pendingDeletes.clear();\n\n // Cancel debounce timer\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n this.batchedEvents = [];\n }\n\n isWatching(): boolean {\n return this.watcher !== null;\n }\n\n /**\n * Handle folder deletion event\n *\n * Stage 1: Set per-path delay timer\n * - Don't process immediately to allow recovery\n * - Add to batch if still deleted after delay\n */\n private handleDelete(path: string): void {\n // Ignore if already pending (prevent duplicate events)\n if (this.pendingDeletes.has(path)) return;\n\n const timeout = setTimeout(async () => {\n // Verify path is still deleted after delay\n const stillDeleted = !(await this.pathExists(path));\n\n if (stillDeleted) {\n // Move to stage 2: add to batch\n this.addToBatch({\n path,\n timestamp: new Date(),\n });\n }\n\n this.pendingDeletes.delete(path);\n }, this.options.delayMs);\n\n this.pendingDeletes.set(path, timeout);\n }\n\n /**\n * Add event to batch and reset debounce timer\n *\n * Stage 2: Debounce\n * - Reset timer on each new event\n * - Execute batch when no new events for debounce period\n * - This combines multiple subfolder deletions into single cleanup\n */\n private addToBatch(event: WatchEvent): void {\n this.batchedEvents.push(event);\n\n // Reset debounce timer (extend wait time on new event)\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n }\n\n this.debounceTimer = setTimeout(() => {\n this.flushBatch();\n }, this.debounceMs);\n }\n\n /**\n * Deliver batched events to callback\n * - Scan/cleanup runs only once\n */\n private flushBatch(): void {\n if (this.batchedEvents.length === 0) return;\n\n const events = [...this.batchedEvents];\n this.batchedEvents = [];\n this.debounceTimer = null;\n\n this.options.onDelete(events);\n }\n\n private handleRecovery(path: string): void {\n const timeout = this.pendingDeletes.get(path);\n\n if (timeout) {\n clearTimeout(timeout);\n this.pendingDeletes.delete(path);\n }\n }\n\n private async pathExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n }\n}\n","/**\n * System folders to ignore when watching (macOS)\n * These are hidden folders that should never be monitored\n */\nexport const IGNORED_SYSTEM_PATTERNS: RegExp[] = [\n // All hidden folders (starting with .)\n /(^|[/\\\\])\\../,\n\n // macOS user folders (not project directories)\n /\\/Library\\//,\n /\\/Applications\\//,\n /\\/Music\\//,\n /\\/Movies\\//,\n /\\/Pictures\\//,\n /\\/Downloads\\//,\n /\\/Documents\\//,\n /\\/Desktop\\//,\n /\\/Public\\//,\n\n // Development folders\n /node_modules/,\n];\n","import { homedir } from 'os';\nimport { join, dirname } from 'path';\nimport { readFile, writeFile, unlink, mkdir } from 'fs/promises';\nimport { existsSync } from 'fs';\nimport { execSync } from 'child_process';\n\nconst SERVICE_LABEL = 'sooink.ai-session-tidy.watcher';\nconst PLIST_FILENAME = `${SERVICE_LABEL}.plist`;\n\nexport type ServiceStatus = 'running' | 'stopped' | 'not_installed';\n\nexport interface ServiceInfo {\n status: ServiceStatus;\n pid?: number;\n label: string;\n plistPath: string;\n}\n\nfunction getPlistPath(): string {\n return join(homedir(), 'Library', 'LaunchAgents', PLIST_FILENAME);\n}\n\nfunction getNodePath(): string {\n // Get absolute path to node executable\n return process.execPath;\n}\n\nfunction getScriptPath(): string {\n // Get absolute path to the CLI script\n const scriptPath = process.argv[1];\n if (scriptPath && existsSync(scriptPath)) {\n return scriptPath;\n }\n throw new Error('Could not determine script path');\n}\n\nfunction generatePlist(options: {\n label: string;\n nodePath: string;\n scriptPath: string;\n args: string[];\n}): string {\n const allArgs = [options.nodePath, options.scriptPath, ...options.args];\n const argsXml = allArgs.map((arg) => ` <string>${arg}</string>`).join('\\n');\n\n const home = homedir();\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${options.label}</string>\n <key>EnvironmentVariables</key>\n <dict>\n <key>HOME</key>\n <string>${home}</string>\n </dict>\n <key>ProgramArguments</key>\n <array>\n${argsXml}\n </array>\n <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <true/>\n <key>StandardOutPath</key>\n <string>${join(home, '.ai-session-tidy', 'watcher.log')}</string>\n <key>StandardErrorPath</key>\n <string>${join(home, '.ai-session-tidy', 'watcher.error.log')}</string>\n</dict>\n</plist>`;\n}\n\nexport class ServiceManager {\n private readonly plistPath: string;\n\n constructor() {\n this.plistPath = getPlistPath();\n }\n\n isSupported(): boolean {\n return process.platform === 'darwin';\n }\n\n async install(): Promise<void> {\n if (!this.isSupported()) {\n throw new Error('Service management is only supported on macOS');\n }\n\n // Ensure LaunchAgents directory exists\n const launchAgentsDir = dirname(this.plistPath);\n if (!existsSync(launchAgentsDir)) {\n await mkdir(launchAgentsDir, { recursive: true });\n }\n\n // Ensure log directory exists and clear old logs\n const logDir = join(homedir(), '.ai-session-tidy');\n if (!existsSync(logDir)) {\n await mkdir(logDir, { recursive: true });\n }\n\n // Clear old log files\n const stdoutPath = join(logDir, 'watcher.log');\n const stderrPath = join(logDir, 'watcher.error.log');\n await writeFile(stdoutPath, '', 'utf-8');\n await writeFile(stderrPath, '', 'utf-8');\n\n const plistContent = generatePlist({\n label: SERVICE_LABEL,\n nodePath: getNodePath(),\n scriptPath: getScriptPath(),\n args: ['watch', 'run'],\n });\n\n await writeFile(this.plistPath, plistContent, 'utf-8');\n }\n\n async uninstall(): Promise<void> {\n if (existsSync(this.plistPath)) {\n await unlink(this.plistPath);\n }\n }\n\n async start(): Promise<void> {\n if (!this.isSupported()) {\n throw new Error('Service management is only supported on macOS');\n }\n\n if (!existsSync(this.plistPath)) {\n throw new Error('Service not installed. Run \"watch start\" to install and start.');\n }\n\n try {\n execSync(`launchctl load \"${this.plistPath}\"`, { stdio: 'pipe' });\n } catch (error) {\n // Already loaded is not an error\n const message = error instanceof Error ? error.message : String(error);\n if (!message.includes('already loaded')) {\n throw error;\n }\n }\n }\n\n async stop(): Promise<void> {\n if (!this.isSupported()) {\n throw new Error('Service management is only supported on macOS');\n }\n\n if (!existsSync(this.plistPath)) {\n return; // Nothing to stop\n }\n\n try {\n execSync(`launchctl unload \"${this.plistPath}\"`, { stdio: 'pipe' });\n } catch {\n // Ignore errors when stopping (might not be running)\n }\n }\n\n async status(): Promise<ServiceInfo> {\n const info: ServiceInfo = {\n status: 'not_installed',\n label: SERVICE_LABEL,\n plistPath: this.plistPath,\n };\n\n if (!this.isSupported()) {\n return info;\n }\n\n if (!existsSync(this.plistPath)) {\n return info;\n }\n\n try {\n const output = execSync('launchctl list', { encoding: 'utf-8' });\n const lines = output.split('\\n');\n\n for (const line of lines) {\n if (line.includes(SERVICE_LABEL)) {\n const parts = line.split(/\\s+/);\n const pid = parseInt(parts[0] ?? '', 10);\n\n if (!isNaN(pid) && pid > 0) {\n info.status = 'running';\n info.pid = pid;\n } else {\n info.status = 'stopped';\n }\n return info;\n }\n }\n\n // plist exists but not loaded\n info.status = 'stopped';\n return info;\n } catch {\n info.status = 'stopped';\n return info;\n }\n }\n\n async getLogs(lines: number = 50): Promise<{ stdout: string; stderr: string }> {\n const logDir = join(homedir(), '.ai-session-tidy');\n const stdoutPath = join(logDir, 'watcher.log');\n const stderrPath = join(logDir, 'watcher.error.log');\n\n let stdout = '';\n let stderr = '';\n\n try {\n if (existsSync(stdoutPath)) {\n const content = await readFile(stdoutPath, 'utf-8');\n const logLines = content.split('\\n');\n stdout = logLines.slice(-lines).join('\\n');\n }\n } catch {\n // Ignore read errors\n }\n\n try {\n if (existsSync(stderrPath)) {\n const content = await readFile(stderrPath, 'utf-8');\n const logLines = content.split('\\n');\n stderr = logLines.slice(-lines).join('\\n');\n }\n } catch {\n // Ignore read errors\n }\n\n return { stdout, stderr };\n }\n}\n\nexport const serviceManager = new ServiceManager();\n","import { Command } from 'commander';\nimport Table from 'cli-table3';\nimport chalk from 'chalk';\n\nimport { logger } from '../utils/logger.js';\nimport { formatSize } from '../utils/size.js';\nimport {\n createAllScanners,\n getAvailableScanners,\n} from '../scanners/index.js';\nimport {\n getClaudeProjectsDir,\n getCursorWorkspaceDir,\n} from '../utils/paths.js';\n\ninterface ListOptions {\n verbose?: boolean;\n}\n\nexport const listCommand = new Command('list')\n .description('List detected AI coding tools and their data locations')\n .option('-v, --verbose', 'Show detailed information')\n .action(async (options: ListOptions) => {\n const allScanners = createAllScanners();\n const availableScanners = await getAvailableScanners(allScanners);\n\n console.log();\n console.log(chalk.bold('AI Coding Tools Status:'));\n console.log();\n\n const table = new Table({\n head: [\n chalk.cyan('Tool'),\n chalk.cyan('Status'),\n chalk.cyan('Data Location'),\n ],\n style: { head: [] },\n });\n\n const toolLocations: Record<string, string> = {\n 'claude-code': getClaudeProjectsDir(),\n cursor: getCursorWorkspaceDir(),\n };\n\n for (const scanner of allScanners) {\n const isAvailable = availableScanners.some((s) => s.name === scanner.name);\n const status = isAvailable\n ? chalk.green('Available')\n : chalk.dim('Not found');\n const location = toolLocations[scanner.name] || 'Unknown';\n\n table.push([scanner.name, status, isAvailable ? location : chalk.dim(location)]);\n }\n\n console.log(table.toString());\n\n // Additional info (verbose)\n if (options.verbose && availableScanners.length > 0) {\n console.log();\n console.log(chalk.bold('Tool Details:'));\n console.log();\n\n for (const scanner of availableScanners) {\n console.log(chalk.cyan(`${scanner.name}:`));\n\n switch (scanner.name) {\n case 'claude-code':\n console.log(' Session format: Encoded project path directories');\n console.log(' Path encoding: /path/to/project → -path-to-project');\n break;\n case 'cursor':\n console.log(' Session format: Hash-named directories with workspace.json');\n console.log(' Project info: Stored in workspace.json \"folder\" field');\n break;\n }\n console.log();\n }\n }\n\n // Summary\n console.log(\n chalk.dim(\n `${availableScanners.length} of ${allScanners.length} tools detected on this system.`\n )\n );\n });\n","import { Command } from 'commander';\nimport inquirer from 'inquirer';\nimport { homedir } from 'os';\nimport { resolve } from 'path';\n\nimport { logger } from '../utils/logger.js';\nimport {\n loadConfig,\n addWatchPath,\n removeWatchPath,\n getWatchPaths,\n getWatchDelay,\n setWatchDelay,\n getWatchDepth,\n setWatchDepth,\n resetConfig,\n addIgnorePath,\n removeIgnorePath,\n getIgnorePaths,\n} from '../utils/config.js';\n\nexport const configCommand = new Command('config').description(\n 'Manage configuration'\n);\n\nconst pathCommand = new Command('path').description('Manage watch paths');\n\npathCommand\n .command('add <path>')\n .description('Add a watch path')\n .action((path: string) => {\n const resolved = resolve(path.replace(/^~/, homedir()));\n const home = homedir();\n\n // Warn if adding home directory or its parent\n if (resolved === home || home.startsWith(resolved + '/')) {\n logger.warn('Warning: Watching home directory is not recommended.');\n logger.warn('Many system folders will be excluded automatically.');\n logger.warn('Consider adding specific project folders instead.');\n console.log();\n }\n\n addWatchPath(path);\n logger.success(`Added: ${path}`);\n });\n\npathCommand\n .command('remove <path>')\n .description('Remove a watch path')\n .action((path: string) => {\n const removed = removeWatchPath(path);\n if (removed) {\n logger.success(`Removed: ${path}`);\n } else {\n logger.warn(`Path not found: ${path}`);\n }\n });\n\npathCommand\n .command('list')\n .description('List watch paths')\n .action(() => {\n const paths = getWatchPaths();\n if (!paths || paths.length === 0) {\n logger.info('No watch paths configured.');\n return;\n }\n console.log();\n for (const p of paths) {\n console.log(` ${p}`);\n }\n });\n\nconfigCommand.addCommand(pathCommand);\n\nconst ignoreCommand = new Command('ignore').description('Manage ignore paths');\n\nignoreCommand\n .command('add <path>')\n .description('Add a path to ignore from watching')\n .action((path: string) => {\n addIgnorePath(path);\n logger.success(`Added to ignore: ${path}`);\n });\n\nignoreCommand\n .command('remove <path>')\n .description('Remove a path from ignore list')\n .action((path: string) => {\n const removed = removeIgnorePath(path);\n if (removed) {\n logger.success(`Removed from ignore: ${path}`);\n } else {\n logger.warn(`Path not found: ${path}`);\n }\n });\n\nignoreCommand\n .command('list')\n .description('List ignored paths')\n .action(() => {\n const paths = getIgnorePaths();\n if (!paths || paths.length === 0) {\n logger.info('No ignore paths configured.');\n return;\n }\n console.log();\n for (const p of paths) {\n console.log(` ${p}`);\n }\n });\n\nconfigCommand.addCommand(ignoreCommand);\n\nconst DEFAULT_DELAY_MINUTES = 5;\nconst MAX_DELAY_MINUTES = 10;\n\nconfigCommand\n .command('delay [minutes]')\n .description(`Get or set watch delay in minutes (default: ${DEFAULT_DELAY_MINUTES}, max: ${MAX_DELAY_MINUTES})`)\n .action((minutes?: string) => {\n if (minutes === undefined) {\n // Get current delay\n const delay = getWatchDelay() ?? DEFAULT_DELAY_MINUTES;\n console.log(`Watch delay: ${String(delay)} minute(s)`);\n } else {\n // Set delay\n const value = parseInt(minutes, 10);\n if (isNaN(value) || value < 1) {\n logger.error('Invalid delay value. Must be a positive number.');\n return;\n }\n if (value > MAX_DELAY_MINUTES) {\n logger.warn(`Maximum delay is ${String(MAX_DELAY_MINUTES)} minutes. Setting to ${String(MAX_DELAY_MINUTES)}.`);\n setWatchDelay(MAX_DELAY_MINUTES);\n return;\n }\n setWatchDelay(value);\n logger.success(`Watch delay set to ${String(value)} minute(s)`);\n }\n });\n\nconst DEFAULT_DEPTH = 3;\nconst MAX_DEPTH = 5;\n\nconfigCommand\n .command('depth [level]')\n .description('Get or set watch depth (default: 3, max: 5)')\n .action((level?: string) => {\n if (level === undefined) {\n const depth = getWatchDepth() ?? DEFAULT_DEPTH;\n console.log(`Watch depth: ${String(depth)}`);\n } else {\n const value = parseInt(level, 10);\n if (isNaN(value) || value < 1) {\n logger.error('Invalid depth value. Must be a positive number.');\n return;\n }\n if (value > MAX_DEPTH) {\n logger.warn(`Maximum depth is ${String(MAX_DEPTH)}. Setting to ${String(MAX_DEPTH)}.`);\n }\n setWatchDepth(value);\n const actualValue = Math.min(value, MAX_DEPTH);\n logger.success(`Watch depth set to ${String(actualValue)}`);\n }\n });\n\nconfigCommand\n .command('show')\n .description('Show all configuration')\n .action(() => {\n const config = loadConfig();\n console.log(JSON.stringify(config, null, 2));\n });\n\nconfigCommand\n .command('reset')\n .description('Reset configuration to defaults')\n .option('-f, --force', 'Skip confirmation prompt')\n .action(async (options: { force?: boolean }) => {\n if (!options.force) {\n const { confirmed } = await inquirer.prompt<{ confirmed: boolean }>([\n {\n type: 'confirm',\n name: 'confirmed',\n message: 'Reset all configuration to defaults?',\n default: false,\n },\n ]);\n\n if (!confirmed) {\n logger.info('Cancelled.');\n return;\n }\n }\n\n resetConfig();\n logger.success('Configuration reset to defaults.');\n });\n","#!/usr/bin/env node\n\nimport { cli } from './cli.js';\n\ncli.parse(process.argv);\n"],"mappings":"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","names":["Command","chalk","readdir","stat","join","join","join","readdir","join","stat","resolve","readdir","readFile","stat","access","join","access","readdir","join","readFile","stat","chalk","Command","ora","chalk","readFile","join","homedir","access","join","homedir","readFile","chalk","Command","ora","parts","Command","chalk","existsSync","homedir","join","resolve","existsSync","homedir","dirname","join","fileURLToPath","access","access","homedir","join","dirname","readFile","writeFile","mkdir","existsSync","Command","chalk","resolve","homedir","existsSync","join","Command","Table","chalk","Command","chalk","Table","Command","inquirer","homedir","resolve","Command","resolve","homedir","DEFAULT_DELAY_MINUTES","MAX_DELAY_MINUTES","inquirer","Command"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sooink/ai-session-tidy",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "CLI tool that detects and cleans orphaned session data from AI coding tools",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,5 +1,7 @@
1
1
  import { Command } from 'commander';
2
2
  import inquirer from 'inquirer';
3
+ import { homedir } from 'os';
4
+ import { resolve } from 'path';
3
5
 
4
6
  import { logger } from '../utils/logger.js';
5
7
  import {
@@ -12,23 +14,37 @@ import {
12
14
  getWatchDepth,
13
15
  setWatchDepth,
14
16
  resetConfig,
17
+ addIgnorePath,
18
+ removeIgnorePath,
19
+ getIgnorePaths,
15
20
  } from '../utils/config.js';
16
21
 
17
22
  export const configCommand = new Command('config').description(
18
23
  'Manage configuration'
19
24
  );
20
25
 
21
- const pathsCommand = new Command('paths').description('Manage watch paths');
26
+ const pathCommand = new Command('path').description('Manage watch paths');
22
27
 
23
- pathsCommand
28
+ pathCommand
24
29
  .command('add <path>')
25
30
  .description('Add a watch path')
26
31
  .action((path: string) => {
32
+ const resolved = resolve(path.replace(/^~/, homedir()));
33
+ const home = homedir();
34
+
35
+ // Warn if adding home directory or its parent
36
+ if (resolved === home || home.startsWith(resolved + '/')) {
37
+ logger.warn('Warning: Watching home directory is not recommended.');
38
+ logger.warn('Many system folders will be excluded automatically.');
39
+ logger.warn('Consider adding specific project folders instead.');
40
+ console.log();
41
+ }
42
+
27
43
  addWatchPath(path);
28
44
  logger.success(`Added: ${path}`);
29
45
  });
30
46
 
31
- pathsCommand
47
+ pathCommand
32
48
  .command('remove <path>')
33
49
  .description('Remove a watch path')
34
50
  .action((path: string) => {
@@ -40,7 +56,7 @@ pathsCommand
40
56
  }
41
57
  });
42
58
 
43
- pathsCommand
59
+ pathCommand
44
60
  .command('list')
45
61
  .description('List watch paths')
46
62
  .action(() => {
@@ -55,7 +71,46 @@ pathsCommand
55
71
  }
56
72
  });
57
73
 
58
- configCommand.addCommand(pathsCommand);
74
+ configCommand.addCommand(pathCommand);
75
+
76
+ const ignoreCommand = new Command('ignore').description('Manage ignore paths');
77
+
78
+ ignoreCommand
79
+ .command('add <path>')
80
+ .description('Add a path to ignore from watching')
81
+ .action((path: string) => {
82
+ addIgnorePath(path);
83
+ logger.success(`Added to ignore: ${path}`);
84
+ });
85
+
86
+ ignoreCommand
87
+ .command('remove <path>')
88
+ .description('Remove a path from ignore list')
89
+ .action((path: string) => {
90
+ const removed = removeIgnorePath(path);
91
+ if (removed) {
92
+ logger.success(`Removed from ignore: ${path}`);
93
+ } else {
94
+ logger.warn(`Path not found: ${path}`);
95
+ }
96
+ });
97
+
98
+ ignoreCommand
99
+ .command('list')
100
+ .description('List ignored paths')
101
+ .action(() => {
102
+ const paths = getIgnorePaths();
103
+ if (!paths || paths.length === 0) {
104
+ logger.info('No ignore paths configured.');
105
+ return;
106
+ }
107
+ console.log();
108
+ for (const p of paths) {
109
+ console.log(` ${p}`);
110
+ }
111
+ });
112
+
113
+ configCommand.addCommand(ignoreCommand);
59
114
 
60
115
  const DEFAULT_DELAY_MINUTES = 5;
61
116
  const MAX_DELAY_MINUTES = 10;
@@ -6,7 +6,7 @@ import { join, resolve } from 'path';
6
6
 
7
7
  import { logger } from '../utils/logger.js';
8
8
  import { formatSize } from '../utils/size.js';
9
- import { getWatchPaths as getConfigWatchPaths, setWatchPaths, getWatchDelay, getWatchDepth } from '../utils/config.js';
9
+ import { getWatchPaths as getConfigWatchPaths, setWatchPaths, getWatchDelay, getWatchDepth, getIgnorePaths } from '../utils/config.js';
10
10
  import {
11
11
  createAllScanners,
12
12
  getAvailableScanners,
@@ -264,6 +264,7 @@ async function runWatcher(options: RunOptions): Promise<void> {
264
264
  watchPaths: validPaths,
265
265
  delayMs,
266
266
  depth,
267
+ ignorePaths: getIgnorePaths(),
267
268
  onDelete: async (events) => {
268
269
  // Log batch events
269
270
  if (events.length === 1) {
@@ -0,0 +1,22 @@
1
+ /**
2
+ * System folders to ignore when watching (macOS)
3
+ * These are hidden folders that should never be monitored
4
+ */
5
+ export const IGNORED_SYSTEM_PATTERNS: RegExp[] = [
6
+ // All hidden folders (starting with .)
7
+ /(^|[/\\])\../,
8
+
9
+ // macOS user folders (not project directories)
10
+ /\/Library\//,
11
+ /\/Applications\//,
12
+ /\/Music\//,
13
+ /\/Movies\//,
14
+ /\/Pictures\//,
15
+ /\/Downloads\//,
16
+ /\/Documents\//,
17
+ /\/Desktop\//,
18
+ /\/Public\//,
19
+
20
+ // Development folders
21
+ /node_modules/,
22
+ ];
@@ -1,6 +1,8 @@
1
1
  import { watch, FSWatcher } from 'chokidar';
2
2
  import { access } from 'fs/promises';
3
3
 
4
+ import { IGNORED_SYSTEM_PATTERNS } from './constants.js';
5
+
4
6
  export interface WatchEvent {
5
7
  path: string;
6
8
  timestamp: Date;
@@ -13,6 +15,8 @@ export interface WatcherOptions {
13
15
  /** Debounce time to batch multiple delete events (default: 10 seconds) */
14
16
  debounceMs?: number;
15
17
  depth?: number;
18
+ /** User-defined paths to ignore */
19
+ ignorePaths?: string[];
16
20
  /** Callback to handle batched delete events */
17
21
  onDelete: (events: WatchEvent[]) => void;
18
22
  }
@@ -62,6 +66,8 @@ export class Watcher {
62
66
  private batchedEvents: WatchEvent[] = [];
63
67
  /** Debounce timer */
64
68
  private debounceTimer: NodeJS.Timeout | null = null;
69
+ /** Paths that errored (to avoid duplicate logs) */
70
+ private erroredPaths: Set<string> = new Set();
65
71
 
66
72
  constructor(options: WatcherOptions) {
67
73
  this.options = options;
@@ -71,10 +77,17 @@ export class Watcher {
71
77
  start(): void {
72
78
  if (this.watcher) return;
73
79
 
80
+ // Combine hardcoded patterns with user-defined ignore paths
81
+ const ignored: (RegExp | string)[] = [...IGNORED_SYSTEM_PATTERNS];
82
+ if (this.options.ignorePaths) {
83
+ ignored.push(...this.options.ignorePaths);
84
+ }
85
+
74
86
  this.watcher = watch(this.options.watchPaths, {
75
87
  ignoreInitial: true,
76
88
  persistent: true,
77
89
  depth: this.options.depth ?? 3,
90
+ ignored,
78
91
  });
79
92
 
80
93
  this.watcher.on('unlinkDir', (path) => {
@@ -84,6 +97,15 @@ export class Watcher {
84
97
  this.watcher.on('addDir', (path) => {
85
98
  this.handleRecovery(path);
86
99
  });
100
+
101
+ // Handle errors gracefully - log and continue watching
102
+ this.watcher.on('error', (error) => {
103
+ const pathInfo = (error as NodeJS.ErrnoException & { path?: string }).path;
104
+ if (pathInfo && !this.erroredPaths.has(pathInfo)) {
105
+ this.erroredPaths.add(pathInfo);
106
+ console.error(`[watch] Cannot watch: ${pathInfo} (${(error as NodeJS.ErrnoException).code})`);
107
+ }
108
+ });
87
109
  }
88
110
 
89
111
  stop(): void {
@@ -28,11 +28,11 @@ export function getAppVersion(): string {
28
28
  }
29
29
 
30
30
  export interface Config {
31
- version?: string; // app version that last saved this config
32
- configVersion?: string; // config schema version
31
+ configVersion?: string; // config schema version for migration
33
32
  watchPaths?: string[];
34
33
  watchDelay?: number; // minutes
35
34
  watchDepth?: number; // folder depth (default: 3, max: 5)
35
+ ignorePaths?: string[]; // paths to ignore from watching
36
36
  }
37
37
 
38
38
  function getConfigPath(): string {
@@ -62,10 +62,12 @@ export function saveConfig(config: Config): void {
62
62
  mkdirSync(configDir, { recursive: true });
63
63
  }
64
64
 
65
+ // Remove deprecated 'version' field if present
66
+ const { version: _removed, ...cleanConfig } = config as Config & { version?: string };
67
+
65
68
  const configWithVersion: Config = {
66
- version: getAppVersion(),
67
69
  configVersion: CONFIG_VERSION,
68
- ...config,
70
+ ...cleanConfig,
69
71
  };
70
72
 
71
73
  writeFileSync(configPath, JSON.stringify(configWithVersion, null, 2), 'utf-8');
@@ -130,3 +132,31 @@ export function setWatchDepth(depth: number): void {
130
132
  export function resetConfig(): void {
131
133
  saveConfig({});
132
134
  }
135
+
136
+ export function getIgnorePaths(): string[] | undefined {
137
+ const config = loadConfig();
138
+ return config.ignorePaths;
139
+ }
140
+
141
+ export function addIgnorePath(path: string): void {
142
+ const config = loadConfig();
143
+ const resolved = resolve(path.replace(/^~/, homedir()));
144
+ const paths = config.ignorePaths ?? [];
145
+ if (!paths.includes(resolved)) {
146
+ paths.push(resolved);
147
+ config.ignorePaths = paths;
148
+ saveConfig(config);
149
+ }
150
+ }
151
+
152
+ export function removeIgnorePath(path: string): boolean {
153
+ const config = loadConfig();
154
+ const resolved = resolve(path.replace(/^~/, homedir()));
155
+ const paths = config.ignorePaths ?? [];
156
+ const index = paths.indexOf(resolved);
157
+ if (index === -1) return false;
158
+ paths.splice(index, 1);
159
+ config.ignorePaths = paths;
160
+ saveConfig(config);
161
+ return true;
162
+ }