session-steward 0.8.0 → 0.10.0

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,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.10.0] - 2026-08-30
4
+
5
+ ### Added
6
+
7
+ - Added full MCP support for finding, inspecting, cleaning, restoring, and automatically cleaning Codex and Claude Code sessions.
8
+
9
+ ## [0.9.0] - 2026-08-27
10
+
11
+ ### Added
12
+
13
+ - Add MCP server for Codex, Claude Code, and other local clients. It can inspect sessions, storage, timelines, and token usage; cleanup remains in the browser and terminal with the existing review and backup safeguards.
14
+ - MCP session searches understand inactivity, workspace, and minimum transcript size filters.
15
+
3
16
  ## [0.8.0] - 2026-08-23
4
17
 
5
18
  ### Added
@@ -130,6 +143,8 @@
130
143
  - Support for custom Codex home folders and a saved folder preference.
131
144
  - Streaming and bounded-memory discovery for large session collections and transcripts.
132
145
 
146
+ [0.10.0]: https://github.com/mallikcheripally/session-steward/compare/v0.9.0...v0.10.0
147
+ [0.9.0]: https://github.com/mallikcheripally/session-steward/compare/v0.8.0...v0.9.0
133
148
  [0.8.0]: https://github.com/mallikcheripally/session-steward/compare/v0.7.0...v0.8.0
134
149
  [0.7.0]: https://github.com/mallikcheripally/session-steward/compare/v0.6.0...v0.7.0
135
150
  [0.6.0]: https://github.com/mallikcheripally/session-steward/compare/v0.5.2...v0.6.0
package/README.md CHANGED
@@ -4,17 +4,17 @@
4
4
  [![Build status](https://img.shields.io/github/actions/workflow/status/mallikcheripally/session-steward/validate.yml?branch=main&style=flat-square&label=build)](https://github.com/mallikcheripally/session-steward/actions/workflows/validate.yml)
5
5
  [![License: MIT](https://img.shields.io/npm/l/session-steward?style=flat-square)](https://github.com/mallikcheripally/session-steward/blob/main/LICENSE)
6
6
 
7
- A local Codex and Claude Code session manager for safely reviewing, backing up, and deleting old sessions from a browser UI or terminal CLI.
7
+ A local Codex and Claude Code session manager for safely reviewing, backing up, and deleting old sessions from a browser UI or terminal CLI, or through MCP with ChatGPT or Claude.
8
8
 
9
9
  AI coding tools can accumulate hundreds or thousands of local sessions. A session may leave behind transcripts, history, logs, checkpoints, and linked artifacts, so manual cleanup can easily miss related data.
10
10
 
11
- Session Steward makes session cleanup safer by finding those records, showing what cleanup will affect, creating a local backup, removing supported data, and verifying the result afterward. Everything runs locally, and your session data stays on your computer.
11
+ Session Steward makes session cleanup safer by finding those records, showing what cleanup will affect, creating a local backup, removing supported data, and verifying the result afterward. Session Steward runs locally.
12
12
 
13
13
  ![Session Steward cleanup demo](https://raw.githubusercontent.com/mallikcheripally/session-steward/main/docs/session-steward-demo.gif)
14
14
 
15
15
  ## Manage local Codex and Claude Code sessions
16
16
 
17
- - Review sessions in a browser UI or terminal CLI.
17
+ - Review and clean up sessions from the browser, terminal, ChatGPT, or Claude through MCP.
18
18
  - Switch between Codex and Claude Code without installing another package.
19
19
  - See session counts and the storage used by recognized session files.
20
20
  - Find sessions inactive for 30, 60, or 90 days.
@@ -33,7 +33,7 @@ Session Steward makes session cleanup safer by finding those records, showing wh
33
33
  - Cleanup is verified before the backup is removed.
34
34
  - Unrecognized storage is reported and left untouched.
35
35
  - Thorough cleanup is unavailable when the detected storage format is not supported.
36
- - Session contents are never sent over the network.
36
+ - Cleanup stays local and does not upload session contents.
37
37
 
38
38
  At startup, Session Steward may contact the public npm registry to check for a newer version.
39
39
 
@@ -206,6 +206,75 @@ Run `inactive`, `archive`, or `workspace` without a value to clear that filter.
206
206
 
207
207
  Use `session-steward-cli --help` to see all available options.
208
208
 
209
+ ## Clean up sessions with ChatGPT or Claude
210
+
211
+ Connect Session Steward once, then ask ChatGPT or Claude to find old or large
212
+ sessions, delete them safely, restore a backup, or clean sessions automatically
213
+ on a schedule.
214
+
215
+ Connect it to ChatGPT and Codex:
216
+
217
+ ```bash
218
+ codex mcp add session-steward -- session-steward-mcp
219
+ ```
220
+
221
+ Connect it to Claude Code:
222
+
223
+ ```bash
224
+ claude mcp add --scope user session-steward -- session-steward-mcp
225
+ ```
226
+
227
+ You can then ask things like:
228
+
229
+ - “Find sessions I have not used in 60 days.”
230
+ - “Show sessions from this workspace, largest first.”
231
+ - “Delete those sessions.”
232
+ - “Every 12 days, delete sessions I have not used in 45 days.”
233
+ - “Restore my latest backup.”
234
+
235
+ Cleanup uses the same local backup and verification checks as the browser and
236
+ terminal. Codex or Claude Code asks for approval before cleanup, restore, or
237
+ schedule changes.
238
+
239
+ Scheduled cleanup continues in the background after you close Codex or Claude
240
+ Code. You can ask to pause, resume, run, change, or remove a schedule. Before
241
+ uninstalling Session Steward, stop scheduled cleanup:
242
+
243
+ ```bash
244
+ session-steward-scheduler --stop
245
+ ```
246
+
247
+ Check or remove the MCP connection at any time:
248
+
249
+ ```bash
250
+ codex mcp list
251
+ codex mcp remove session-steward
252
+
253
+ claude mcp list
254
+ claude mcp remove --scope user session-steward
255
+ ```
256
+
257
+ For another MCP client, add a local server named `session-steward`:
258
+
259
+ ```json
260
+ {
261
+ "mcpServers": {
262
+ "session-steward": {
263
+ "command": "session-steward-mcp"
264
+ }
265
+ }
266
+ }
267
+ ```
268
+
269
+ The MCP server uses the same provider folders selected in the browser or
270
+ terminal.
271
+
272
+ ### Privacy
273
+
274
+ The MCP server runs locally. Session details can include messages, commands,
275
+ file names, and workspace paths, and your MCP client may send that information
276
+ to its AI provider.
277
+
209
278
  ## Use a custom provider folder
210
279
 
211
280
  The browser interface displays the active provider folder. Select **Change** to choose another existing folder and remember it for later browser and terminal sessions.
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { parseArgs } from "node:util";
4
+
5
+ import packageMetadata from "../package.json" with { type: "json" };
6
+ import { assertSupportedNode } from "../lib/runtime.mjs";
7
+
8
+ assertSupportedNode();
9
+
10
+ const { values } = parseArgs({
11
+ allowPositionals: false,
12
+ options: {
13
+ "claude-home": { type: "string" },
14
+ "codex-home": { type: "string" },
15
+ help: { short: "h", type: "boolean" },
16
+ version: { short: "v", type: "boolean" },
17
+ },
18
+ });
19
+
20
+ if (values.help) {
21
+ process.stdout.write(`Usage: session-steward-mcp [options]
22
+
23
+ Run Session Steward's MCP server over stdio. MCP clients start and stop this
24
+ process automatically. Destructive tools use the client's normal approval flow.
25
+
26
+ Options:
27
+ --codex-home <path> Use a custom Codex session folder
28
+ --claude-home <path> Use a custom Claude session folder
29
+ -h, --help Show this help
30
+ -v, --version Show the installed version
31
+ `);
32
+ process.exit(0);
33
+ }
34
+
35
+ if (values.version) {
36
+ process.stdout.write(`${packageMetadata.version}\n`);
37
+ process.exit(0);
38
+ }
39
+
40
+ const { createProviderSettings } = await import("../lib/settings.mjs");
41
+ const { serveMcp } = await import("../lib/mcp.mjs");
42
+ const providerHomeOverrides = {};
43
+
44
+ if (values["codex-home"] !== undefined) {
45
+ providerHomeOverrides.codex = values["codex-home"];
46
+ }
47
+ if (values["claude-home"] !== undefined) {
48
+ providerHomeOverrides["claude-code"] = values["claude-home"];
49
+ }
50
+
51
+ const settings = await createProviderSettings({ providerHomeOverrides });
52
+ const handle = serveMcp({
53
+ onerror: (error) => process.stderr.write(`Session Steward MCP error: ${error.message}\n`),
54
+ settings,
55
+ });
56
+ let closing = false;
57
+
58
+ async function close() {
59
+ if (closing) return;
60
+ closing = true;
61
+ await handle.close();
62
+ }
63
+
64
+ process.once("SIGINT", () => void close());
65
+ process.once("SIGTERM", () => void close());
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { parseArgs } from "node:util";
4
+
5
+ import { assertSupportedNode } from "../lib/runtime.mjs";
6
+
7
+ assertSupportedNode();
8
+
9
+ const { values } = parseArgs({
10
+ allowPositionals: false,
11
+ options: {
12
+ "config-directory": { type: "string" },
13
+ help: { short: "h", type: "boolean" },
14
+ "run-due": { type: "boolean" },
15
+ start: { type: "boolean" },
16
+ status: { type: "boolean" },
17
+ stop: { type: "boolean" },
18
+ },
19
+ });
20
+
21
+ if (values.help) {
22
+ process.stdout.write(`Usage: session-steward-scheduler <option>
23
+
24
+ Options:
25
+ --start Start automatic cleanup and enable it after restart
26
+ --stop Stop automatic cleanup and remove its startup task
27
+ --status Show whether automatic cleanup is running
28
+ --run-due Run due cleanup schedules once and exit
29
+ --config-directory <path> Use a specific Session Steward settings folder
30
+ -h, --help Show this help
31
+ `);
32
+ } else if ([values["run-due"], values.start, values.status, values.stop].filter(Boolean).length !== 1) {
33
+ process.stderr.write("Choose exactly one of --start, --stop, --status, or --run-due.\n");
34
+ process.exitCode = 1;
35
+ } else if (values["run-due"]) {
36
+ const { createCleanupScheduleStore, runDueCleanupSchedules } = await import(
37
+ "../lib/cleanup-schedules.mjs"
38
+ );
39
+ const { createProviderSettings } = await import("../lib/settings.mjs");
40
+ const settings = await createProviderSettings({
41
+ configDirectory: values["config-directory"],
42
+ });
43
+ const scheduleStore = createCleanupScheduleStore({
44
+ configDirectory: settings.getConfigDirectory(),
45
+ });
46
+ const results = await runDueCleanupSchedules({ scheduleStore, settings });
47
+ if (results.some((result) => ["failed", "recovery-failed"].includes(result.status))) {
48
+ process.exitCode = 1;
49
+ }
50
+ } else {
51
+ const { createCleanupSchedulerService } = await import(
52
+ "../lib/cleanup-scheduler-service.mjs"
53
+ );
54
+ const service = createCleanupSchedulerService({
55
+ configDirectory: values["config-directory"],
56
+ });
57
+ const status = values.start
58
+ ? await service.start()
59
+ : values.stop ? await service.stop() : await service.status();
60
+ process.stdout.write(`Automatic cleanup is ${status.running ? "running" : "stopped"}.\n`);
61
+ }
@@ -0,0 +1,205 @@
1
+ import { execFile as execFileCallback } from "node:child_process";
2
+ import { promises as fs } from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { promisify } from "node:util";
7
+
8
+ import { getDefaultConfigDirectory } from "./platform.mjs";
9
+
10
+ const execFile = promisify(execFileCallback);
11
+ const LABEL = "com.mallikcheripally.session-steward.cleanup";
12
+ const WINDOWS_TASK_NAME = "Session Steward Cleanup";
13
+ const RUNNER_PATH = fileURLToPath(new URL("../bin/session-steward-scheduler.mjs", import.meta.url));
14
+
15
+ function xml(value) {
16
+ return String(value)
17
+ .replaceAll("&", "&amp;")
18
+ .replaceAll("<", "&lt;")
19
+ .replaceAll(">", "&gt;")
20
+ .replaceAll('"', "&quot;")
21
+ .replaceAll("'", "&apos;");
22
+ }
23
+
24
+ function systemdArgument(value) {
25
+ return `"${String(value)
26
+ .replaceAll("%", "%%")
27
+ .replaceAll("\\", "\\\\")
28
+ .replaceAll('"', '\\"')
29
+ .replaceAll("$", "\\$")
30
+ .replaceAll("`", "\\`")}"`;
31
+ }
32
+
33
+ function windowsArgument(value) {
34
+ return `"${String(value).replaceAll(/(\\*)"/gu, "$1$1\\\"").replaceAll(/(\\+)$/gu, "$1$1")}"`;
35
+ }
36
+
37
+ async function writePrivateFile(filePath, contents) {
38
+ await fs.mkdir(path.dirname(filePath), { mode: 0o700, recursive: true });
39
+ await fs.writeFile(filePath, contents, { encoding: "utf8", mode: 0o600 });
40
+ }
41
+
42
+ async function exists(filePath) {
43
+ try {
44
+ await fs.access(filePath);
45
+ return true;
46
+ } catch {
47
+ return false;
48
+ }
49
+ }
50
+
51
+ export function createCleanupSchedulerService({
52
+ configDirectory = getDefaultConfigDirectory(),
53
+ environment = process.env,
54
+ execute = execFile,
55
+ home = os.homedir(),
56
+ nodePath = process.execPath,
57
+ platform = process.platform,
58
+ runnerPath = RUNNER_PATH,
59
+ userId = typeof process.getuid === "function" ? process.getuid() : null,
60
+ } = {}) {
61
+ const launchAgentPath = path.join(home, "Library", "LaunchAgents", `${LABEL}.plist`);
62
+ const xdgConfigHome = path.isAbsolute(environment.XDG_CONFIG_HOME || "")
63
+ ? environment.XDG_CONFIG_HOME
64
+ : path.join(home, ".config");
65
+ const systemdDirectory = path.join(xdgConfigHome, "systemd", "user");
66
+ const systemdServicePath = path.join(systemdDirectory, "session-steward-cleanup.service");
67
+ const systemdTimerPath = path.join(systemdDirectory, "session-steward-cleanup.timer");
68
+
69
+ async function command(commandName, args, { allowFailure = false } = {}) {
70
+ try {
71
+ await execute(commandName, args, { windowsHide: true });
72
+ return true;
73
+ } catch (error) {
74
+ if (allowFailure) return false;
75
+ throw new Error("Session Steward could not update the automatic cleanup scheduler.", {
76
+ cause: error,
77
+ });
78
+ }
79
+ }
80
+
81
+ async function start() {
82
+ if (platform === "darwin") {
83
+ if (!Number.isSafeInteger(userId)) throw new Error("Session Steward could not identify this user.");
84
+ const target = `gui/${userId}`;
85
+ await writePrivateFile(launchAgentPath, `<?xml version="1.0" encoding="UTF-8"?>
86
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
87
+ <plist version="1.0"><dict>
88
+ <key>Label</key><string>${LABEL}</string>
89
+ <key>ProgramArguments</key><array>
90
+ <string>${xml(nodePath)}</string>
91
+ <string>${xml(runnerPath)}</string>
92
+ <string>--run-due</string>
93
+ <string>--config-directory</string>
94
+ <string>${xml(configDirectory)}</string>
95
+ </array>
96
+ <key>RunAtLoad</key><true/>
97
+ <key>StartInterval</key><integer>900</integer>
98
+ </dict></plist>
99
+ `);
100
+ await command("launchctl", ["bootout", `${target}/${LABEL}`], { allowFailure: true });
101
+ await command("launchctl", ["bootstrap", target, launchAgentPath]);
102
+ await command("launchctl", ["enable", `${target}/${LABEL}`]);
103
+ return status();
104
+ }
105
+
106
+ if (platform === "linux") {
107
+ await writePrivateFile(systemdServicePath, `[Unit]
108
+ Description=Session Steward automatic cleanup
109
+
110
+ [Service]
111
+ Type=oneshot
112
+ ExecStart=${systemdArgument(nodePath)} ${systemdArgument(runnerPath)} --run-due --config-directory ${systemdArgument(configDirectory)}
113
+ `);
114
+ await writePrivateFile(systemdTimerPath, `[Unit]
115
+ Description=Run Session Steward automatic cleanup
116
+
117
+ [Timer]
118
+ OnBootSec=2min
119
+ OnUnitActiveSec=15min
120
+ Persistent=true
121
+
122
+ [Install]
123
+ WantedBy=timers.target
124
+ `);
125
+ await command("systemctl", ["--user", "daemon-reload"]);
126
+ await command("systemctl", ["--user", "enable", "--now", "session-steward-cleanup.timer"]);
127
+ return status();
128
+ }
129
+
130
+ if (platform === "win32") {
131
+ const taskCommand = [
132
+ nodePath,
133
+ runnerPath,
134
+ "--run-due",
135
+ "--config-directory",
136
+ configDirectory,
137
+ ].map(windowsArgument).join(" ");
138
+ await command("schtasks", [
139
+ "/Create", "/F", "/SC", "MINUTE", "/MO", "15",
140
+ "/TN", WINDOWS_TASK_NAME,
141
+ "/TR", taskCommand,
142
+ ]);
143
+ return status();
144
+ }
145
+
146
+ throw new Error(`Automatic cleanup is not supported on ${platform}.`);
147
+ }
148
+
149
+ async function stop() {
150
+ if (platform === "darwin") {
151
+ if (Number.isSafeInteger(userId)) {
152
+ await command("launchctl", ["bootout", `gui/${userId}/${LABEL}`], {
153
+ allowFailure: true,
154
+ });
155
+ }
156
+ await fs.rm(launchAgentPath, { force: true });
157
+ return status();
158
+ }
159
+ if (platform === "linux") {
160
+ await command("systemctl", ["--user", "disable", "--now", "session-steward-cleanup.timer"], {
161
+ allowFailure: true,
162
+ });
163
+ await Promise.all([
164
+ fs.rm(systemdServicePath, { force: true }),
165
+ fs.rm(systemdTimerPath, { force: true }),
166
+ ]);
167
+ await command("systemctl", ["--user", "daemon-reload"], { allowFailure: true });
168
+ return status();
169
+ }
170
+ if (platform === "win32") {
171
+ await command("schtasks", ["/Delete", "/F", "/TN", WINDOWS_TASK_NAME], {
172
+ allowFailure: true,
173
+ });
174
+ return status();
175
+ }
176
+ return { platform, running: false, supported: false };
177
+ }
178
+
179
+ async function status() {
180
+ if (platform === "darwin") {
181
+ const running = Number.isSafeInteger(userId)
182
+ ? await command("launchctl", ["print", `gui/${userId}/${LABEL}`], { allowFailure: true })
183
+ : false;
184
+ return { platform, running, supported: true };
185
+ }
186
+ if (platform === "linux") {
187
+ const configured = await exists(systemdTimerPath);
188
+ const running = configured
189
+ ? await command("systemctl", ["--user", "is-active", "--quiet", "session-steward-cleanup.timer"], {
190
+ allowFailure: true,
191
+ })
192
+ : false;
193
+ return { platform, running, supported: true };
194
+ }
195
+ if (platform === "win32") {
196
+ const running = await command("schtasks", ["/Query", "/TN", WINDOWS_TASK_NAME], {
197
+ allowFailure: true,
198
+ });
199
+ return { platform, running, supported: true };
200
+ }
201
+ return { platform, running: false, supported: false };
202
+ }
203
+
204
+ return { start, status, stop };
205
+ }