pi-session-cleanup 1.1.2 → 1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [Unreleased]
4
+
5
+ ## [1.1.4] - 2026-06-16
6
+
7
+ ### Fixed
8
+ - Added round-trip date validation for parsed calendar timestamps so invalid dates (e.g., month 13 or day 32) no longer produce false-positive sort keys.
9
+
10
+ ## 1.1.3 - 2026-06-01
11
+
12
+ - Deferred command module loading for `/session-cleanup` and `/nix` handlers while preserving inline completions.
13
+ - Widened Pi coding-agent and Pi TUI peer dependency ranges to include `^0.77.0 || ^0.78.0`.
14
+
3
15
  ## 1.1.2 - 2026-05-26
4
16
 
5
17
  - Widened peer dependency ranges to `^0.74.0 || ^0.75.0`.
package/package.json CHANGED
@@ -1,66 +1,66 @@
1
- {
2
- "name": "pi-session-cleanup",
3
- "version": "1.1.2",
4
- "description": "Pi extension for interactive batch session cleanup and safe deletion.",
5
- "type": "module",
6
- "main": "./index.ts",
7
- "exports": {
8
- ".": "./index.ts"
9
- },
10
- "files": [
11
- "index.ts",
12
- "src",
13
- "config/config.example.json",
14
- "README.md",
15
- "CHANGELOG.md",
16
- "LICENSE"
17
- ],
18
- "scripts": {
19
- "build": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.json --noCheck",
20
- "lint": "npm run build",
21
- "test:clean": "node -e \"require('node:fs').rmSync('.test-dist', { recursive: true, force: true })\"",
22
- "pretest": "npm run test:clean",
23
- "test": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.test.json && node --test .test-dist/test/*.test.js",
24
- "posttest": "npm run test:clean",
25
- "check": "npm run lint && npm run test",
26
- "package:dry-run": "npm pack --dry-run"
27
- },
28
- "keywords": [
29
- "pi-package",
30
- "pi",
31
- "pi-extension",
32
- "session",
33
- "cleanup",
34
- "delete",
35
- "pi-coding-agent",
36
- "pi-tui",
37
- "session-management",
38
- "tui",
39
- "safe-delete"
40
- ],
41
- "author": "MasuRii",
42
- "license": "MIT",
43
- "engines": {
44
- "node": ">=20"
45
- },
46
- "publishConfig": {
47
- "access": "public"
48
- },
49
- "pi": {
50
- "extensions": [
51
- "./index.ts"
52
- ]
53
- },
54
- "peerDependencies": {
55
- "@earendil-works/pi-coding-agent": "^0.74.0 || ^0.75.0",
56
- "@earendil-works/pi-tui": "^0.74.0 || ^0.75.0"
57
- },
58
- "repository": {
59
- "type": "git",
60
- "url": "git+https://github.com/MasuRii/pi-session-cleanup.git"
61
- },
62
- "bugs": {
63
- "url": "https://github.com/MasuRii/pi-session-cleanup/issues"
64
- },
65
- "homepage": "https://github.com/MasuRii/pi-session-cleanup#readme"
66
- }
1
+ {
2
+ "name": "pi-session-cleanup",
3
+ "version": "1.1.4",
4
+ "description": "Pi extension for interactive batch session cleanup and safe deletion.",
5
+ "type": "module",
6
+ "main": "./index.ts",
7
+ "exports": {
8
+ ".": "./index.ts"
9
+ },
10
+ "files": [
11
+ "index.ts",
12
+ "src",
13
+ "config/config.example.json",
14
+ "README.md",
15
+ "CHANGELOG.md",
16
+ "LICENSE"
17
+ ],
18
+ "scripts": {
19
+ "build": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.json --noCheck",
20
+ "lint": "npm run build",
21
+ "test:clean": "node -e \"require('node:fs').rmSync('.test-dist', { recursive: true, force: true })\"",
22
+ "pretest": "npm run test:clean",
23
+ "test": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.test.json && node --test .test-dist/test/*.test.js",
24
+ "posttest": "npm run test:clean",
25
+ "check": "npm run lint && npm run test",
26
+ "package:dry-run": "npm pack --dry-run"
27
+ },
28
+ "keywords": [
29
+ "pi-package",
30
+ "pi",
31
+ "pi-extension",
32
+ "session",
33
+ "cleanup",
34
+ "delete",
35
+ "pi-coding-agent",
36
+ "pi-tui",
37
+ "session-management",
38
+ "tui",
39
+ "safe-delete"
40
+ ],
41
+ "author": "MasuRii",
42
+ "license": "MIT",
43
+ "engines": {
44
+ "node": ">=20"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "pi": {
50
+ "extensions": [
51
+ "./index.ts"
52
+ ]
53
+ },
54
+ "peerDependencies": {
55
+ "@earendil-works/pi-coding-agent": "^0.74.0 || ^0.75.0 || ^0.78.0 || ^0.79.0",
56
+ "@earendil-works/pi-tui": "^0.74.0 || ^0.75.0 || ^0.78.0 || ^0.79.0"
57
+ },
58
+ "repository": {
59
+ "type": "git",
60
+ "url": "git+https://github.com/MasuRii/pi-session-cleanup.git"
61
+ },
62
+ "bugs": {
63
+ "url": "https://github.com/MasuRii/pi-session-cleanup/issues"
64
+ },
65
+ "homepage": "https://github.com/MasuRii/pi-session-cleanup#readme"
66
+ }
package/src/index.ts CHANGED
@@ -1,16 +1,104 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
 
3
3
  import { SESSION_CLEANUP_COMMAND, SESSION_NIX_COMMAND } from "./constants.js";
4
- import {
5
- getSessionCleanupArgumentCompletions,
6
- handleSessionCleanupCommand,
7
- } from "./session-cleanup-command.js";
8
- import {
9
- getSessionNixArgumentCompletions,
10
- handleSessionNixCommand,
11
- } from "./session-nix-command.js";
12
4
  import { flushScheduledSessionDeletionForQuit } from "./session-quit-shutdown.js";
13
5
 
6
+ type SessionCleanupCommandModule = typeof import("./session-cleanup-command.js");
7
+ type SessionNixCommandModule = typeof import("./session-nix-command.js");
8
+ type CommandCompletion = {
9
+ value: string;
10
+ label: string;
11
+ description?: string;
12
+ };
13
+
14
+ const SESSION_CLEANUP_ARGUMENT_COMPLETIONS = [
15
+ {
16
+ value: "current",
17
+ label: "current",
18
+ description: "List sessions from the current working directory only",
19
+ },
20
+ {
21
+ value: "all",
22
+ label: "all",
23
+ description: "List sessions across every working directory",
24
+ },
25
+ {
26
+ value: "help",
27
+ label: "help",
28
+ description: "Show usage",
29
+ },
30
+ ] as const satisfies readonly CommandCompletion[];
31
+
32
+ const SESSION_NIX_ARGUMENT_COMPLETIONS = [
33
+ {
34
+ value: "quit",
35
+ label: "quit",
36
+ description: "Delete the current session and quit Pi immediately",
37
+ },
38
+ {
39
+ value: "agent",
40
+ label: "agent",
41
+ description: "Start a fresh session with a selected target agent",
42
+ },
43
+ {
44
+ value: "help",
45
+ label: "help",
46
+ description: "Show usage",
47
+ },
48
+ ] as const satisfies readonly CommandCompletion[];
49
+
50
+ let sessionCleanupCommandModule: SessionCleanupCommandModule | undefined;
51
+ let sessionCleanupCommandModulePromise: Promise<SessionCleanupCommandModule> | undefined;
52
+ let sessionNixCommandModule: SessionNixCommandModule | undefined;
53
+ let sessionNixCommandModulePromise: Promise<SessionNixCommandModule> | undefined;
54
+
55
+ function loadSessionCleanupCommandModule(): Promise<SessionCleanupCommandModule> {
56
+ if (sessionCleanupCommandModule) {
57
+ return Promise.resolve(sessionCleanupCommandModule);
58
+ }
59
+
60
+ sessionCleanupCommandModulePromise ??= import("./session-cleanup-command.js").then(
61
+ (module) => {
62
+ sessionCleanupCommandModule = module;
63
+ return module;
64
+ },
65
+ );
66
+ return sessionCleanupCommandModulePromise;
67
+ }
68
+
69
+ function loadSessionNixCommandModule(): Promise<SessionNixCommandModule> {
70
+ if (sessionNixCommandModule) {
71
+ return Promise.resolve(sessionNixCommandModule);
72
+ }
73
+
74
+ sessionNixCommandModulePromise ??= import("./session-nix-command.js").then(
75
+ (module) => {
76
+ sessionNixCommandModule = module;
77
+ return module;
78
+ },
79
+ );
80
+ return sessionNixCommandModulePromise;
81
+ }
82
+
83
+ function getMatchedCompletions(
84
+ argumentPrefix: string,
85
+ completions: readonly CommandCompletion[],
86
+ ): CommandCompletion[] | null {
87
+ const normalizedPrefix = argumentPrefix.trim().toLowerCase();
88
+ if (!normalizedPrefix) {
89
+ return completions.map((completion) => ({ ...completion }));
90
+ }
91
+
92
+ const matched = completions.filter((completion) =>
93
+ completion.value.startsWith(normalizedPrefix),
94
+ );
95
+ if (matched.length === 0) {
96
+ return null;
97
+ }
98
+
99
+ return matched.map((completion) => ({ ...completion }));
100
+ }
101
+
14
102
  export default function sessionCleanupExtension(pi: ExtensionAPI): void {
15
103
  pi.on("session_shutdown", async (_event, ctx) => {
16
104
  await flushScheduledSessionDeletionForQuit(ctx);
@@ -19,8 +107,10 @@ export default function sessionCleanupExtension(pi: ExtensionAPI): void {
19
107
  pi.registerCommand(SESSION_CLEANUP_COMMAND, {
20
108
  description:
21
109
  "Batch-select previous sessions and delete them with confirmation.",
22
- getArgumentCompletions: getSessionCleanupArgumentCompletions,
110
+ getArgumentCompletions: (argumentPrefix) =>
111
+ getMatchedCompletions(argumentPrefix, SESSION_CLEANUP_ARGUMENT_COMPLETIONS),
23
112
  handler: async (args, ctx) => {
113
+ const { handleSessionCleanupCommand } = await loadSessionCleanupCommandModule();
24
114
  await handleSessionCleanupCommand(args, ctx);
25
115
  },
26
116
  });
@@ -28,8 +118,10 @@ export default function sessionCleanupExtension(pi: ExtensionAPI): void {
28
118
  pi.registerCommand(SESSION_NIX_COMMAND, {
29
119
  description:
30
120
  "Start a fresh session, switch to a target agent, or delete the current session and quit Pi.",
31
- getArgumentCompletions: getSessionNixArgumentCompletions,
121
+ getArgumentCompletions: (argumentPrefix) =>
122
+ getMatchedCompletions(argumentPrefix, SESSION_NIX_ARGUMENT_COMPLETIONS),
32
123
  handler: async (args, ctx) => {
124
+ const { handleSessionNixCommand } = await loadSessionNixCommandModule();
33
125
  await handleSessionNixCommand(args, ctx);
34
126
  },
35
127
  });
@@ -69,7 +69,23 @@ function parseCalendarCandidate(path: string): number | null {
69
69
  }
70
70
 
71
71
  const utcTimestamp = Date.UTC(year, month - 1, day, hour, minute, second);
72
- return Number.isFinite(utcTimestamp) ? utcTimestamp : null;
72
+ if (!Number.isFinite(utcTimestamp)) {
73
+ return null;
74
+ }
75
+
76
+ const parsed = new Date(utcTimestamp);
77
+ if (
78
+ parsed.getUTCFullYear() !== year ||
79
+ parsed.getUTCMonth() !== month - 1 ||
80
+ parsed.getUTCDate() !== day ||
81
+ parsed.getUTCHours() !== hour ||
82
+ parsed.getUTCMinutes() !== minute ||
83
+ parsed.getUTCSeconds() !== second
84
+ ) {
85
+ return null;
86
+ }
87
+
88
+ return utcTimestamp;
73
89
  }
74
90
 
75
91
  function timestampFromPath(sessionPath: string): number {