@sooink/ai-session-tidy 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +56 -14
- package/README.md +56 -14
- package/assets/demo-interactive.gif +0 -0
- package/assets/demo.gif +0 -0
- package/dist/index.js +333 -109
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/commands/clean.ts +161 -78
- package/src/commands/config.ts +66 -6
- package/src/commands/scan.ts +51 -2
- package/src/commands/watch.ts +8 -6
- package/src/core/constants.ts +22 -0
- package/src/core/service.ts +11 -0
- package/src/core/watcher.ts +22 -0
- package/src/utils/config.ts +34 -4
- package/src/utils/paths.ts +36 -3
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
|
+
}
|
package/src/utils/paths.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
1
2
|
import { homedir } from 'os';
|
|
2
3
|
import { join } from 'path';
|
|
3
4
|
|
|
@@ -14,13 +15,45 @@ export function encodePath(path: string): string {
|
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Decode Claude Code encoded path to Unix path
|
|
17
|
-
*
|
|
18
|
+
* Handles hyphenated folder names by checking filesystem
|
|
19
|
+
*
|
|
20
|
+
* -Users-kory-my-project → /Users/kory/my-project (if exists)
|
|
21
|
+
* -Users-kory-my-project → /Users/kory/my/project (fallback)
|
|
18
22
|
*/
|
|
19
23
|
export function decodePath(encoded: string): string {
|
|
20
24
|
if (encoded === '') return '';
|
|
21
|
-
// Treat as Unix encoding if it starts with a dash
|
|
22
25
|
if (!encoded.startsWith('-')) return encoded;
|
|
23
|
-
|
|
26
|
+
|
|
27
|
+
// Split by dash (remove leading dash first)
|
|
28
|
+
const parts = encoded.slice(1).split('-');
|
|
29
|
+
|
|
30
|
+
// Try to reconstruct path by checking filesystem
|
|
31
|
+
return reconstructPath(parts, '');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Recursively reconstruct path by trying different combinations
|
|
36
|
+
*/
|
|
37
|
+
function reconstructPath(parts: string[], currentPath: string): string {
|
|
38
|
+
if (parts.length === 0) return currentPath;
|
|
39
|
+
|
|
40
|
+
// Try combining segments (longest first to prefer hyphenated names)
|
|
41
|
+
for (let len = parts.length; len >= 1; len--) {
|
|
42
|
+
const segment = parts.slice(0, len).join('-');
|
|
43
|
+
const testPath = currentPath + '/' + segment;
|
|
44
|
+
|
|
45
|
+
if (existsSync(testPath)) {
|
|
46
|
+
// Found existing path, continue with remaining parts
|
|
47
|
+
const result = reconstructPath(parts.slice(len), testPath);
|
|
48
|
+
// Verify the final path exists (or is the best we can do)
|
|
49
|
+
if (existsSync(result) || parts.slice(len).length === 0) {
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// No existing path found, use simple decode for remaining parts
|
|
56
|
+
return currentPath + '/' + parts.join('/');
|
|
24
57
|
}
|
|
25
58
|
|
|
26
59
|
/**
|