@sooink/ai-session-tidy 0.1.3 → 0.1.4
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/.github/workflows/release.yml +57 -0
- package/CHANGELOG.md +32 -0
- package/README.md +10 -8
- package/assets/demo-interactive.gif +0 -0
- package/assets/demo.gif +0 -0
- package/dist/index.js +136 -12
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/commands/clean.ts +29 -3
- package/src/commands/scan.ts +23 -0
- package/src/commands/watch.ts +7 -3
- package/src/core/cleaner.ts +5 -0
- package/src/core/service.ts +53 -10
- package/src/scanners/claude-code.ts +55 -0
- package/src/scanners/types.ts +1 -1
- package/src/utils/paths.ts +7 -0
- package/README.ko.md +0 -213
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
release:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- name: Checkout
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
with:
|
|
18
|
+
fetch-depth: 0
|
|
19
|
+
fetch-tags: true
|
|
20
|
+
|
|
21
|
+
- name: Get version from tag
|
|
22
|
+
id: version
|
|
23
|
+
run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
|
|
24
|
+
|
|
25
|
+
- name: Get previous tag
|
|
26
|
+
id: prev_tag
|
|
27
|
+
run: |
|
|
28
|
+
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
|
|
29
|
+
echo "tag=$PREV_TAG" >> $GITHUB_OUTPUT
|
|
30
|
+
|
|
31
|
+
- name: Read CHANGELOG
|
|
32
|
+
id: changelog
|
|
33
|
+
env:
|
|
34
|
+
VERSION: ${{ steps.version.outputs.version }}
|
|
35
|
+
PREV_TAG: ${{ steps.prev_tag.outputs.tag }}
|
|
36
|
+
CURRENT_TAG: ${{ github.ref_name }}
|
|
37
|
+
REPO: ${{ github.repository }}
|
|
38
|
+
run: |
|
|
39
|
+
# Extract section for this version only (between version header and next ## header)
|
|
40
|
+
CHANGES=$(sed -n "/^## ${VERSION}$/,/^## /p" CHANGELOG.md | sed '1d;$d')
|
|
41
|
+
|
|
42
|
+
# Add Full Changelog link if previous tag exists
|
|
43
|
+
if [ -n "$PREV_TAG" ]; then
|
|
44
|
+
LINK="**Full Changelog**: https://github.com/${REPO}/compare/${PREV_TAG}...${CURRENT_TAG}"
|
|
45
|
+
CHANGES=$(printf "%s\n\n---\n\n%s" "$CHANGES" "$LINK")
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
echo "changes<<EOF" >> $GITHUB_OUTPUT
|
|
49
|
+
echo "$CHANGES" >> $GITHUB_OUTPUT
|
|
50
|
+
echo "EOF" >> $GITHUB_OUTPUT
|
|
51
|
+
|
|
52
|
+
- name: Create Release
|
|
53
|
+
uses: softprops/action-gh-release@v2
|
|
54
|
+
with:
|
|
55
|
+
body: ${{ steps.changelog.outputs.changes }}
|
|
56
|
+
env:
|
|
57
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.4
|
|
4
|
+
|
|
5
|
+
- Added support for Claude Code tasks
|
|
6
|
+
- Improve macOS background activity attribution for LaunchAgent (displays as `ai-session-tidy`)
|
|
7
|
+
|
|
8
|
+
## 0.1.3
|
|
9
|
+
|
|
10
|
+
- Fixed ignored folder patterns to match folders themselves (e.g., `/Downloads`, `/Documents`)
|
|
11
|
+
- Fixed log files not being cleared on service start, causing stale error messages
|
|
12
|
+
- Added TTY checks for interactive mode (`-i`) and confirmation prompts
|
|
13
|
+
- Added helpful error messages in non-TTY environments (scripts/CI)
|
|
14
|
+
- Changed `clean -i` to show clean "Selected items" list after selection
|
|
15
|
+
|
|
16
|
+
## 0.1.2
|
|
17
|
+
|
|
18
|
+
- Added system folder ignore patterns for watch mode
|
|
19
|
+
- Added automatic exclusion of macOS system folders (Library, Applications, Downloads, etc.)
|
|
20
|
+
- Added exclusion of hidden folders and node_modules from watching
|
|
21
|
+
|
|
22
|
+
## 0.1.1
|
|
23
|
+
|
|
24
|
+
- Fixed npm publish configuration
|
|
25
|
+
|
|
26
|
+
## 0.1.0
|
|
27
|
+
|
|
28
|
+
- Added scan command to find orphaned sessions from Claude Code and Cursor
|
|
29
|
+
- Added clean command with interactive mode and trash support
|
|
30
|
+
- Added watch command for automatic cleanup on project deletion
|
|
31
|
+
- Added background daemon with auto-start at login (macOS)
|
|
32
|
+
- Added config command for managing watch paths, delay, and depth
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# AI Session Tidy
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**Automatically clean up orphaned sessions from AI coding tools**
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@sooink/ai-session-tidy)
|
|
8
8
|
[](https://nodejs.org/)
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
## The Problem
|
|
17
17
|
|
|
18
|
-
AI coding tools like **Claude Code** and **Cursor** store session data locally—conversation history, file snapshots, todos, and more.
|
|
18
|
+
AI coding tools like **Claude Code** and **Cursor** store session data locally—conversation history, file snapshots, todos, tasks, and more.
|
|
19
19
|
|
|
20
20
|
When you delete, move, or rename a project, the session data becomes orphaned:
|
|
21
21
|
|
|
@@ -26,7 +26,8 @@ When you delete, move, or rename a project, the session data becomes orphaned:
|
|
|
26
26
|
│ ├── -Users-you-temp-worktree/ # 👈 Worktree removed
|
|
27
27
|
│ └── -Users-you-renamed-project/ # 👈 Project renamed
|
|
28
28
|
├── todos/ # Orphaned todo files
|
|
29
|
-
|
|
29
|
+
├── file-history/ # Orphaned rewind snapshots
|
|
30
|
+
└── tasks/ # Orphaned task folders
|
|
30
31
|
```
|
|
31
32
|
|
|
32
33
|
This becomes especially problematic with **git worktree workflows**, where branches are frequently created and removed—each leaving behind session data that accumulates quickly.
|
|
@@ -104,11 +105,11 @@ ai-session-tidy --json # JSON output
|
|
|
104
105
|
```
|
|
105
106
|
⚠ Found 2 session folder(s) + 1 config entry(ies) + 3 session-env folder(s) (156.2 MB)
|
|
106
107
|
|
|
107
|
-
|
|
108
|
-
│ Tool │ Sessions │ Config │ Env │ Todos │ History │ Size │ Scan Time │
|
|
109
|
-
|
|
110
|
-
│ claude-code │ 2 │ 1 │ 3 │ - │ - │ 156.2 MB │ 45ms │
|
|
111
|
-
|
|
108
|
+
┌─────────────┬──────────┬────────┬─────┬───────┬─────────┬───────┬──────────┬───────────┐
|
|
109
|
+
│ Tool │ Sessions │ Config │ Env │ Todos │ History │ Tasks │ Size │ Scan Time │
|
|
110
|
+
├─────────────┼──────────┼────────┼─────┼───────┼─────────┼───────┼──────────┼───────────┤
|
|
111
|
+
│ claude-code │ 2 │ 1 │ 3 │ - │ - │ - │ 156.2 MB │ 45ms │
|
|
112
|
+
└─────────────┴──────────┴────────┴─────┴───────┴─────────┴───────┴──────────┴───────────┘
|
|
112
113
|
|
|
113
114
|
Session Folders:
|
|
114
115
|
|
|
@@ -183,6 +184,7 @@ ai-session-tidy config reset # Reset to defaults
|
|
|
183
184
|
| `~/.claude/session-env/{uuid}/` | Session environment | Empty folder |
|
|
184
185
|
| `~/.claude/todos/{uuid}-*.json` | Todo files | Session gone |
|
|
185
186
|
| `~/.claude/file-history/{uuid}/` | Rewind snapshots | Session gone |
|
|
187
|
+
| `~/.claude/tasks/{uuid}/` | Task folders | Session gone |
|
|
186
188
|
|
|
187
189
|
### Cursor
|
|
188
190
|
|
|
Binary file
|
package/assets/demo.gif
CHANGED
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -119,6 +119,9 @@ function getClaudeTodosDir() {
|
|
|
119
119
|
function getClaudeFileHistoryDir() {
|
|
120
120
|
return join2(homedir(), ".claude", "file-history");
|
|
121
121
|
}
|
|
122
|
+
function getClaudeTasksDir() {
|
|
123
|
+
return join2(homedir(), ".claude", "tasks");
|
|
124
|
+
}
|
|
122
125
|
function tildify(path) {
|
|
123
126
|
const home = homedir();
|
|
124
127
|
if (path.startsWith(home)) {
|
|
@@ -139,6 +142,7 @@ var ClaudeCodeScanner = class {
|
|
|
139
142
|
sessionEnvDir;
|
|
140
143
|
todosDir;
|
|
141
144
|
fileHistoryDir;
|
|
145
|
+
tasksDir;
|
|
142
146
|
constructor(projectsDirOrOptions) {
|
|
143
147
|
if (typeof projectsDirOrOptions === "string") {
|
|
144
148
|
this.projectsDir = projectsDirOrOptions;
|
|
@@ -146,18 +150,21 @@ var ClaudeCodeScanner = class {
|
|
|
146
150
|
this.sessionEnvDir = null;
|
|
147
151
|
this.todosDir = null;
|
|
148
152
|
this.fileHistoryDir = null;
|
|
153
|
+
this.tasksDir = null;
|
|
149
154
|
} else if (projectsDirOrOptions) {
|
|
150
155
|
this.projectsDir = projectsDirOrOptions.projectsDir ?? getClaudeProjectsDir();
|
|
151
156
|
this.configPath = projectsDirOrOptions.configPath === void 0 ? getClaudeConfigPath() : projectsDirOrOptions.configPath;
|
|
152
157
|
this.sessionEnvDir = projectsDirOrOptions.sessionEnvDir === void 0 ? getClaudeSessionEnvDir() : projectsDirOrOptions.sessionEnvDir;
|
|
153
158
|
this.todosDir = projectsDirOrOptions.todosDir === void 0 ? getClaudeTodosDir() : projectsDirOrOptions.todosDir;
|
|
154
159
|
this.fileHistoryDir = projectsDirOrOptions.fileHistoryDir === void 0 ? getClaudeFileHistoryDir() : projectsDirOrOptions.fileHistoryDir;
|
|
160
|
+
this.tasksDir = projectsDirOrOptions.tasksDir === void 0 ? getClaudeTasksDir() : projectsDirOrOptions.tasksDir;
|
|
155
161
|
} else {
|
|
156
162
|
this.projectsDir = getClaudeProjectsDir();
|
|
157
163
|
this.configPath = getClaudeConfigPath();
|
|
158
164
|
this.sessionEnvDir = getClaudeSessionEnvDir();
|
|
159
165
|
this.todosDir = getClaudeTodosDir();
|
|
160
166
|
this.fileHistoryDir = getClaudeFileHistoryDir();
|
|
167
|
+
this.tasksDir = getClaudeTasksDir();
|
|
161
168
|
}
|
|
162
169
|
}
|
|
163
170
|
async isAvailable() {
|
|
@@ -204,6 +211,8 @@ var ClaudeCodeScanner = class {
|
|
|
204
211
|
sessions.push(...todosSessions);
|
|
205
212
|
const fileHistorySessions = await this.scanFileHistoryDir(validSessionIds);
|
|
206
213
|
sessions.push(...fileHistorySessions);
|
|
214
|
+
const tasksSessions = await this.scanTasksDir(validSessionIds);
|
|
215
|
+
sessions.push(...tasksSessions);
|
|
207
216
|
const totalSize = sessions.reduce((sum, s) => sum + s.size, 0);
|
|
208
217
|
return {
|
|
209
218
|
toolName: this.name,
|
|
@@ -373,6 +382,40 @@ var ClaudeCodeScanner = class {
|
|
|
373
382
|
}
|
|
374
383
|
return orphanedHistories;
|
|
375
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* Detect orphaned folders from ~/.claude/tasks
|
|
387
|
+
* Folder name is the session UUID, contains .lock file
|
|
388
|
+
*/
|
|
389
|
+
async scanTasksDir(validSessionIds) {
|
|
390
|
+
if (!this.tasksDir) {
|
|
391
|
+
return [];
|
|
392
|
+
}
|
|
393
|
+
const orphanedTasks = [];
|
|
394
|
+
try {
|
|
395
|
+
await access(this.tasksDir);
|
|
396
|
+
const entries = await readdir2(this.tasksDir, { withFileTypes: true });
|
|
397
|
+
for (const entry of entries) {
|
|
398
|
+
if (!entry.isDirectory()) continue;
|
|
399
|
+
const sessionId = entry.name;
|
|
400
|
+
if (!validSessionIds.has(sessionId)) {
|
|
401
|
+
const taskPath = join3(this.tasksDir, entry.name);
|
|
402
|
+
const size = await getDirectorySize(taskPath);
|
|
403
|
+
const taskStat = await stat2(taskPath);
|
|
404
|
+
orphanedTasks.push({
|
|
405
|
+
toolName: this.name,
|
|
406
|
+
sessionPath: taskPath,
|
|
407
|
+
projectPath: sessionId,
|
|
408
|
+
// Session UUID
|
|
409
|
+
size,
|
|
410
|
+
lastModified: taskStat.mtime,
|
|
411
|
+
type: "tasks"
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
} catch {
|
|
416
|
+
}
|
|
417
|
+
return orphanedTasks;
|
|
418
|
+
}
|
|
376
419
|
/**
|
|
377
420
|
* Extract project path (cwd) from JSONL file
|
|
378
421
|
*/
|
|
@@ -616,6 +659,7 @@ function outputTable(results, verbose) {
|
|
|
616
659
|
const sessionEnvEntries = allSessions.filter((s) => s.type === "session-env");
|
|
617
660
|
const todosEntries = allSessions.filter((s) => s.type === "todos");
|
|
618
661
|
const fileHistoryEntries = allSessions.filter((s) => s.type === "file-history");
|
|
662
|
+
const tasksEntries = allSessions.filter((s) => s.type === "tasks");
|
|
619
663
|
const totalSize = results.reduce((sum, r) => sum + r.totalSize, 0);
|
|
620
664
|
if (allSessions.length === 0) {
|
|
621
665
|
logger.success("No orphaned sessions found.");
|
|
@@ -638,6 +682,9 @@ function outputTable(results, verbose) {
|
|
|
638
682
|
if (fileHistoryEntries.length > 0) {
|
|
639
683
|
parts.push(`${fileHistoryEntries.length} file-history folder(s)`);
|
|
640
684
|
}
|
|
685
|
+
if (tasksEntries.length > 0) {
|
|
686
|
+
parts.push(`${tasksEntries.length} tasks folder(s)`);
|
|
687
|
+
}
|
|
641
688
|
logger.warn(`Found ${parts.join(" + ")} (${formatSize(totalSize)})`);
|
|
642
689
|
console.log();
|
|
643
690
|
const summaryTable = new Table({
|
|
@@ -648,6 +695,7 @@ function outputTable(results, verbose) {
|
|
|
648
695
|
chalk2.cyan("Env"),
|
|
649
696
|
chalk2.cyan("Todos"),
|
|
650
697
|
chalk2.cyan("History"),
|
|
698
|
+
chalk2.cyan("Tasks"),
|
|
651
699
|
chalk2.cyan("Size"),
|
|
652
700
|
chalk2.cyan("Scan Time")
|
|
653
701
|
],
|
|
@@ -660,6 +708,7 @@ function outputTable(results, verbose) {
|
|
|
660
708
|
const envs = result.sessions.filter((s) => s.type === "session-env").length;
|
|
661
709
|
const todos = result.sessions.filter((s) => s.type === "todos").length;
|
|
662
710
|
const histories = result.sessions.filter((s) => s.type === "file-history").length;
|
|
711
|
+
const tasks = result.sessions.filter((s) => s.type === "tasks").length;
|
|
663
712
|
summaryTable.push([
|
|
664
713
|
result.toolName,
|
|
665
714
|
folders > 0 ? String(folders) : "-",
|
|
@@ -667,6 +716,7 @@ function outputTable(results, verbose) {
|
|
|
667
716
|
envs > 0 ? String(envs) : "-",
|
|
668
717
|
todos > 0 ? String(todos) : "-",
|
|
669
718
|
histories > 0 ? String(histories) : "-",
|
|
719
|
+
tasks > 0 ? String(tasks) : "-",
|
|
670
720
|
formatSize(result.totalSize),
|
|
671
721
|
`${result.scanDuration.toFixed(0)}ms`
|
|
672
722
|
]);
|
|
@@ -746,6 +796,19 @@ function outputTable(results, verbose) {
|
|
|
746
796
|
console.log();
|
|
747
797
|
}
|
|
748
798
|
}
|
|
799
|
+
if (tasksEntries.length > 0) {
|
|
800
|
+
console.log();
|
|
801
|
+
console.log(chalk2.bold("Orphaned Tasks:"));
|
|
802
|
+
console.log();
|
|
803
|
+
for (const entry of tasksEntries) {
|
|
804
|
+
const folderName = entry.sessionPath.split("/").pop() || entry.sessionPath;
|
|
805
|
+
console.log(
|
|
806
|
+
` ${chalk2.cyan("[tasks]")} ${chalk2.white(folderName)} ${chalk2.dim(`(${formatSize(entry.size)})`)}`
|
|
807
|
+
);
|
|
808
|
+
console.log(` ${chalk2.dim("\u2192")} ${tildify(entry.sessionPath)}`);
|
|
809
|
+
console.log();
|
|
810
|
+
}
|
|
811
|
+
}
|
|
749
812
|
}
|
|
750
813
|
console.log();
|
|
751
814
|
console.log(
|
|
@@ -804,7 +867,8 @@ var Cleaner = class {
|
|
|
804
867
|
session: 0,
|
|
805
868
|
sessionEnv: 0,
|
|
806
869
|
todos: 0,
|
|
807
|
-
fileHistory: 0
|
|
870
|
+
fileHistory: 0,
|
|
871
|
+
tasks: 0
|
|
808
872
|
},
|
|
809
873
|
skippedCount: 0,
|
|
810
874
|
alreadyGoneCount: 0,
|
|
@@ -835,6 +899,9 @@ var Cleaner = class {
|
|
|
835
899
|
case "file-history":
|
|
836
900
|
result.deletedByType.fileHistory++;
|
|
837
901
|
break;
|
|
902
|
+
case "tasks":
|
|
903
|
+
result.deletedByType.tasks++;
|
|
904
|
+
break;
|
|
838
905
|
default:
|
|
839
906
|
result.deletedByType.session++;
|
|
840
907
|
}
|
|
@@ -914,12 +981,14 @@ function formatGroupChoice(group) {
|
|
|
914
981
|
const labels = {
|
|
915
982
|
"session-env": "empty session-env folder",
|
|
916
983
|
"todos": "orphaned todos file",
|
|
917
|
-
"file-history": "orphaned file-history folder"
|
|
984
|
+
"file-history": "orphaned file-history folder",
|
|
985
|
+
"tasks": "orphaned tasks folder"
|
|
918
986
|
};
|
|
919
987
|
const colors = {
|
|
920
988
|
"session-env": chalk3.green,
|
|
921
989
|
"todos": chalk3.magenta,
|
|
922
|
-
"file-history": chalk3.blue
|
|
990
|
+
"file-history": chalk3.blue,
|
|
991
|
+
"tasks": chalk3.cyan
|
|
923
992
|
};
|
|
924
993
|
const label = labels[group.type] || group.type;
|
|
925
994
|
const count = group.sessions.length;
|
|
@@ -955,8 +1024,9 @@ var cleanCommand = new Command2("clean").description("Remove orphaned session da
|
|
|
955
1024
|
const fileHistoryEntries = allSessions.filter(
|
|
956
1025
|
(s) => s.type === "file-history"
|
|
957
1026
|
);
|
|
1027
|
+
const tasksEntries = allSessions.filter((s) => s.type === "tasks");
|
|
958
1028
|
const individualSessions = allSessions.filter(
|
|
959
|
-
(s) => s.type !== "session-env" && s.type !== "todos" && s.type !== "file-history"
|
|
1029
|
+
(s) => s.type !== "session-env" && s.type !== "todos" && s.type !== "file-history" && s.type !== "tasks"
|
|
960
1030
|
);
|
|
961
1031
|
const groupChoices = [];
|
|
962
1032
|
if (sessionEnvEntries.length > 0) {
|
|
@@ -980,6 +1050,13 @@ var cleanCommand = new Command2("clean").description("Remove orphaned session da
|
|
|
980
1050
|
totalSize: fileHistoryEntries.reduce((sum, s) => sum + s.size, 0)
|
|
981
1051
|
});
|
|
982
1052
|
}
|
|
1053
|
+
if (tasksEntries.length > 0) {
|
|
1054
|
+
groupChoices.push({
|
|
1055
|
+
type: "tasks",
|
|
1056
|
+
sessions: tasksEntries,
|
|
1057
|
+
totalSize: tasksEntries.reduce((sum, s) => sum + s.size, 0)
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
983
1060
|
console.log();
|
|
984
1061
|
const parts = [];
|
|
985
1062
|
if (folderSessions.length > 0) {
|
|
@@ -997,6 +1074,9 @@ var cleanCommand = new Command2("clean").description("Remove orphaned session da
|
|
|
997
1074
|
if (fileHistoryEntries.length > 0) {
|
|
998
1075
|
parts.push(`${fileHistoryEntries.length} file-history folder(s)`);
|
|
999
1076
|
}
|
|
1077
|
+
if (tasksEntries.length > 0) {
|
|
1078
|
+
parts.push(`${tasksEntries.length} tasks folder(s)`);
|
|
1079
|
+
}
|
|
1000
1080
|
logger.warn(`Found ${parts.join(" + ")} (${formatSize(totalSize)})`);
|
|
1001
1081
|
if (options.verbose && !options.interactive) {
|
|
1002
1082
|
console.log();
|
|
@@ -1096,6 +1176,9 @@ var cleanCommand = new Command2("clean").description("Remove orphaned session da
|
|
|
1096
1176
|
const dryRunHistories = sessionsToClean.filter(
|
|
1097
1177
|
(s) => s.type === "file-history"
|
|
1098
1178
|
);
|
|
1179
|
+
const dryRunTasks = sessionsToClean.filter(
|
|
1180
|
+
(s) => s.type === "tasks"
|
|
1181
|
+
);
|
|
1099
1182
|
for (const session of dryRunFolders) {
|
|
1100
1183
|
console.log(
|
|
1101
1184
|
` ${chalk3.red("Would delete:")} ${tildify(session.sessionPath)} (${formatSize(session.size)})`
|
|
@@ -1128,6 +1211,12 @@ var cleanCommand = new Command2("clean").description("Remove orphaned session da
|
|
|
1128
1211
|
` ${chalk3.blue("Would delete:")} ${dryRunHistories.length} file-history folder(s) (${formatSize(dryRunHistories.reduce((sum, s) => sum + s.size, 0))})`
|
|
1129
1212
|
);
|
|
1130
1213
|
}
|
|
1214
|
+
if (dryRunTasks.length > 0) {
|
|
1215
|
+
console.log();
|
|
1216
|
+
console.log(
|
|
1217
|
+
` ${chalk3.cyan("Would delete:")} ${dryRunTasks.length} tasks folder(s) (${formatSize(dryRunTasks.reduce((sum, s) => sum + s.size, 0))})`
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1131
1220
|
return;
|
|
1132
1221
|
}
|
|
1133
1222
|
if (!options.force) {
|
|
@@ -1174,6 +1263,9 @@ var cleanCommand = new Command2("clean").description("Remove orphaned session da
|
|
|
1174
1263
|
if (deletedByType.fileHistory > 0) {
|
|
1175
1264
|
parts2.push(`${deletedByType.fileHistory} file-history`);
|
|
1176
1265
|
}
|
|
1266
|
+
if (deletedByType.tasks > 0) {
|
|
1267
|
+
parts2.push(`${deletedByType.tasks} tasks`);
|
|
1268
|
+
}
|
|
1177
1269
|
const summary = parts2.length > 0 ? parts2.join(" + ") : `${cleanResult.deletedCount} item(s)`;
|
|
1178
1270
|
logger.success(
|
|
1179
1271
|
`${action}: ${summary} (${formatSize(cleanResult.totalSizeDeleted)})`
|
|
@@ -1504,9 +1596,24 @@ import { existsSync as existsSync4 } from "fs";
|
|
|
1504
1596
|
import { execSync } from "child_process";
|
|
1505
1597
|
var SERVICE_LABEL = "sooink.ai-session-tidy.watcher";
|
|
1506
1598
|
var PLIST_FILENAME = `${SERVICE_LABEL}.plist`;
|
|
1599
|
+
var BIN_NAME = "ai-session-tidy";
|
|
1600
|
+
var BUNDLE_IDENTIFIER = "io.github.sooink.ai-session-tidy";
|
|
1507
1601
|
function getPlistPath() {
|
|
1508
1602
|
return join7(homedir4(), "Library", "LaunchAgents", PLIST_FILENAME);
|
|
1509
1603
|
}
|
|
1604
|
+
function getBinPath() {
|
|
1605
|
+
try {
|
|
1606
|
+
const binPath = execSync(`which ${BIN_NAME}`, {
|
|
1607
|
+
encoding: "utf-8",
|
|
1608
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1609
|
+
}).trim();
|
|
1610
|
+
if (binPath && existsSync4(binPath)) {
|
|
1611
|
+
return binPath;
|
|
1612
|
+
}
|
|
1613
|
+
} catch {
|
|
1614
|
+
}
|
|
1615
|
+
return null;
|
|
1616
|
+
}
|
|
1510
1617
|
function getNodePath() {
|
|
1511
1618
|
return process.execPath;
|
|
1512
1619
|
}
|
|
@@ -1517,20 +1624,35 @@ function getScriptPath() {
|
|
|
1517
1624
|
}
|
|
1518
1625
|
throw new Error("Could not determine script path");
|
|
1519
1626
|
}
|
|
1627
|
+
function getProgramArgs(args) {
|
|
1628
|
+
const binPath = getBinPath();
|
|
1629
|
+
if (binPath) {
|
|
1630
|
+
return [binPath, ...args];
|
|
1631
|
+
}
|
|
1632
|
+
return [getNodePath(), getScriptPath(), ...args];
|
|
1633
|
+
}
|
|
1520
1634
|
function generatePlist(options) {
|
|
1521
|
-
const
|
|
1522
|
-
const argsXml = allArgs.map((arg) => ` <string>${arg}</string>`).join("\n");
|
|
1635
|
+
const argsXml = options.programArgs.map((arg) => ` <string>${arg}</string>`).join("\n");
|
|
1523
1636
|
const home = homedir4();
|
|
1637
|
+
const nodeBinDir = dirname3(process.execPath);
|
|
1638
|
+
const systemPath = process.env["PATH"] ?? "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
|
|
1639
|
+
const envPath = systemPath.includes(nodeBinDir) ? systemPath : `${nodeBinDir}:${systemPath}`;
|
|
1524
1640
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
1525
1641
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1526
1642
|
<plist version="1.0">
|
|
1527
1643
|
<dict>
|
|
1528
1644
|
<key>Label</key>
|
|
1529
1645
|
<string>${options.label}</string>
|
|
1646
|
+
<key>AssociatedBundleIdentifiers</key>
|
|
1647
|
+
<array>
|
|
1648
|
+
<string>${BUNDLE_IDENTIFIER}</string>
|
|
1649
|
+
</array>
|
|
1530
1650
|
<key>EnvironmentVariables</key>
|
|
1531
1651
|
<dict>
|
|
1532
1652
|
<key>HOME</key>
|
|
1533
1653
|
<string>${home}</string>
|
|
1654
|
+
<key>PATH</key>
|
|
1655
|
+
<string>${envPath}</string>
|
|
1534
1656
|
</dict>
|
|
1535
1657
|
<key>ProgramArguments</key>
|
|
1536
1658
|
<array>
|
|
@@ -1573,9 +1695,7 @@ var ServiceManager = class {
|
|
|
1573
1695
|
await writeFile2(stderrPath, "", "utf-8");
|
|
1574
1696
|
const plistContent = generatePlist({
|
|
1575
1697
|
label: SERVICE_LABEL,
|
|
1576
|
-
|
|
1577
|
-
scriptPath: getScriptPath(),
|
|
1578
|
-
args: ["watch", "run"]
|
|
1698
|
+
programArgs: getProgramArgs(["watch", "run"])
|
|
1579
1699
|
});
|
|
1580
1700
|
await writeFile2(this.plistPath, plistContent, "utf-8");
|
|
1581
1701
|
}
|
|
@@ -1857,10 +1977,11 @@ async function runWatcher(options) {
|
|
|
1857
1977
|
watchPaths: validPaths,
|
|
1858
1978
|
delayMs,
|
|
1859
1979
|
depth,
|
|
1860
|
-
ignorePaths: getIgnorePaths(),
|
|
1980
|
+
ignorePaths: getIgnorePaths() ?? [],
|
|
1861
1981
|
onDelete: async (events) => {
|
|
1862
|
-
|
|
1863
|
-
|
|
1982
|
+
const firstEvent = events[0];
|
|
1983
|
+
if (events.length === 1 && firstEvent) {
|
|
1984
|
+
logger.info(`Detected deletion: ${tildify(firstEvent.path)}`);
|
|
1864
1985
|
} else {
|
|
1865
1986
|
logger.info(`Detected ${events.length} deletions (debounced)`);
|
|
1866
1987
|
if (options.verbose) {
|
|
@@ -1897,6 +2018,9 @@ async function runWatcher(options) {
|
|
|
1897
2018
|
if (deletedByType.fileHistory > 0) {
|
|
1898
2019
|
parts.push(`${deletedByType.fileHistory} file-history`);
|
|
1899
2020
|
}
|
|
2021
|
+
if (deletedByType.tasks > 0) {
|
|
2022
|
+
parts.push(`${deletedByType.tasks} tasks`);
|
|
2023
|
+
}
|
|
1900
2024
|
const summary = parts.length > 0 ? parts.join(" + ") : `${cleanResult.deletedCount} item(s)`;
|
|
1901
2025
|
logger.success(
|
|
1902
2026
|
`${action}: ${summary} (${formatSize(cleanResult.totalSizeDeleted)})`
|