session-steward 0.9.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,11 @@
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
+
3
9
  ## [0.9.0] - 2026-08-27
4
10
 
5
11
  ### Added
@@ -137,6 +143,7 @@
137
143
  - Support for custom Codex home folders and a saved folder preference.
138
144
  - Streaming and bounded-memory discovery for large session collections and transcripts.
139
145
 
146
+ [0.10.0]: https://github.com/mallikcheripally/session-steward/compare/v0.9.0...v0.10.0
140
147
  [0.9.0]: https://github.com/mallikcheripally/session-steward/compare/v0.8.0...v0.9.0
141
148
  [0.8.0]: https://github.com/mallikcheripally/session-steward/compare/v0.7.0...v0.8.0
142
149
  [0.7.0]: https://github.com/mallikcheripally/session-steward/compare/v0.6.0...v0.7.0
package/README.md CHANGED
@@ -4,7 +4,7 @@
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. It also includes an MCP server for inspecting sessions with AI.
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
 
@@ -14,7 +14,7 @@ Session Steward makes session cleanup safer by finding those records, showing wh
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.
@@ -22,7 +22,6 @@ Session Steward makes session cleanup safer by finding those records, showing wh
22
22
  - Inspect session details and affected records before deletion.
23
23
  - Read a session timeline of what you asked, what changed, and which commands ran.
24
24
  - See how many tokens a session used, split into fresh input, cached input, cache writes, and output.
25
- - Let compatible local MCP clients inspect sessions and help identify what is worth reviewing.
26
25
  - Choose standard or thorough cleanup.
27
26
  - Use custom Codex or Claude home folders across browser and terminal sessions.
28
27
 
@@ -34,7 +33,7 @@ Session Steward makes session cleanup safer by finding those records, showing wh
34
33
  - Cleanup is verified before the backup is removed.
35
34
  - Unrecognized storage is reported and left untouched.
36
35
  - Thorough cleanup is unavailable when the detected storage format is not supported.
37
- - Browser and terminal cleanup stay local and do not upload session contents.
36
+ - Cleanup stays local and does not upload session contents.
38
37
 
39
38
  At startup, Session Steward may contact the public npm registry to check for a newer version.
40
39
 
@@ -207,34 +206,45 @@ Run `inactive`, `archive`, or `workspace` without a value to clear that filter.
207
206
 
208
207
  Use `session-steward-cli --help` to see all available options.
209
208
 
210
- ## Read-only MCP server
209
+ ## Clean up sessions with ChatGPT or Claude
211
210
 
212
- Session Steward includes `session-steward-mcp`, a local MCP server for Codex,
213
- and Claude Code. It can list and inspect sessions, timelines, storage, and
214
- token usage. It cannot delete, back up, restore, or change anything.
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.
215
214
 
216
- Once configured, you can ask naturally:
217
-
218
- - “Find old chats I have not used in 2 months that are over 500 MB.”
219
- - “Show sessions from project x workspace, sorted by size, inactive for a month.”
220
- - “Show the timeline and token usage for this session.”
221
-
222
- Install Session Steward globally first, then register it with the clients you
223
- use.
224
-
225
- For Codex:
215
+ Connect it to ChatGPT and Codex:
226
216
 
227
217
  ```bash
228
218
  codex mcp add session-steward -- session-steward-mcp
229
219
  ```
230
220
 
231
- For Claude Code:
221
+ Connect it to Claude Code:
232
222
 
233
223
  ```bash
234
224
  claude mcp add --scope user session-steward -- session-steward-mcp
235
225
  ```
236
226
 
237
- Check or remove the configuration at any time:
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:
238
248
 
239
249
  ```bash
240
250
  codex mcp list
@@ -244,12 +254,7 @@ claude mcp list
244
254
  claude mcp remove --scope user session-steward
245
255
  ```
246
256
 
247
- Removing the MCP configuration does not uninstall Session Steward or change any
248
- sessions. Use the normal npm uninstall command only if you also want to remove
249
- the package.
250
-
251
- For another compatible client, configure a local `stdio` server named
252
- `session-steward` with `session-steward-mcp` as its command:
257
+ For another MCP client, add a local server named `session-steward`:
253
258
 
254
259
  ```json
255
260
  {
@@ -261,31 +266,14 @@ For another compatible client, configure a local `stdio` server named
261
266
  }
262
267
  ```
263
268
 
264
- ### Custom provider folders
269
+ The MCP server uses the same provider folders selected in the browser or
270
+ terminal.
265
271
 
266
- The MCP server uses the same saved Codex and Claude home folders as the browser
267
- and terminal interfaces. You can override either folder in the MCP command:
268
-
269
- ```bash
270
- codex mcp add session-steward -- session-steward-mcp \
271
- --codex-home /path/to/.codex \
272
- --claude-home /path/to/.claude
273
- ```
272
+ ### Privacy
274
273
 
275
- ### Privacy and access
276
-
277
- - The MCP server runs locally with your user account's file permissions.
278
- - Every tool call must choose `codex` or `claude-code`; it does not combine the
279
- providers silently.
280
- - Lists and timelines are bounded, and provider database and transcript paths
281
- are omitted from results.
282
- - Timeline results can contain session messages, commands, file names, and
283
- workspace paths. Only request a timeline when that content is appropriate to
284
- share with the configured client.
285
- - Session Steward does not upload MCP results itself. Your MCP client may send
286
- tool results to its AI provider under that product's privacy terms.
287
- - MCP tools are read-only. Cleanup remains available only through the browser UI
288
- and terminal CLI, with the existing review, backup, and verification flow.
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.
289
277
 
290
278
  ## Use a custom provider folder
291
279
 
@@ -20,8 +20,8 @@ const { values } = parseArgs({
20
20
  if (values.help) {
21
21
  process.stdout.write(`Usage: session-steward-mcp [options]
22
22
 
23
- Run Session Steward's read-only MCP server over stdio. MCP clients start and
24
- stop this process automatically.
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
25
 
26
26
  Options:
27
27
  --codex-home <path> Use a custom Codex session folder
@@ -38,7 +38,7 @@ if (values.version) {
38
38
  }
39
39
 
40
40
  const { createProviderSettings } = await import("../lib/settings.mjs");
41
- const { serveReadOnlyMcp } = await import("../lib/mcp.mjs");
41
+ const { serveMcp } = await import("../lib/mcp.mjs");
42
42
  const providerHomeOverrides = {};
43
43
 
44
44
  if (values["codex-home"] !== undefined) {
@@ -49,7 +49,7 @@ if (values["claude-home"] !== undefined) {
49
49
  }
50
50
 
51
51
  const settings = await createProviderSettings({ providerHomeOverrides });
52
- const handle = serveReadOnlyMcp({
52
+ const handle = serveMcp({
53
53
  onerror: (error) => process.stderr.write(`Session Steward MCP error: ${error.message}\n`),
54
54
  settings,
55
55
  });
@@ -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
+ }