@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 +11 -6
- package/README.md +11 -6
- package/dist/index.js +107 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/config.ts +60 -5
- package/src/commands/watch.ts +2 -1
- package/src/core/constants.ts +22 -0
- package/src/core/watcher.ts +22 -0
- package/src/utils/config.ts +34 -4
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
|
|
129
|
-
ai-session-tidy config
|
|
130
|
-
ai-session-tidy config
|
|
131
|
-
ai-session-tidy config
|
|
132
|
-
ai-session-tidy config
|
|
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
|
|
126
|
-
ai-session-tidy config
|
|
127
|
-
ai-session-tidy config
|
|
128
|
-
ai-session-tidy config
|
|
129
|
-
ai-session-tidy config
|
|
130
|
-
ai-session-tidy config
|
|
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((
|
|
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
|
-
|
|
396
|
+
resolve4(entry.cwd);
|
|
397
397
|
}
|
|
398
398
|
} catch {
|
|
399
399
|
}
|
|
400
400
|
});
|
|
401
401
|
rl.on("close", () => {
|
|
402
|
-
if (!found)
|
|
402
|
+
if (!found) resolve4(null);
|
|
403
403
|
});
|
|
404
404
|
rl.on("error", () => {
|
|
405
|
-
|
|
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
|
-
...
|
|
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
|
|
1817
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
package/src/commands/config.ts
CHANGED
|
@@ -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
|
|
26
|
+
const pathCommand = new Command('path').description('Manage watch paths');
|
|
22
27
|
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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;
|
package/src/commands/watch.ts
CHANGED
|
@@ -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
|
+
];
|
package/src/core/watcher.ts
CHANGED
|
@@ -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 {
|
package/src/utils/config.ts
CHANGED
|
@@ -28,11 +28,11 @@ export function getAppVersion(): string {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export interface Config {
|
|
31
|
-
|
|
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
|
-
...
|
|
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
|
+
}
|