session-steward 0.11.1 → 0.11.2

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,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.11.2] - 2026-09-23
4
+
5
+ ### Added
6
+
7
+ - Added `session-steward update` with version output and a sudo prompt when needed.
8
+
9
+ ### Changed
10
+
11
+ - Updated compatibility coverage for current Codex and Claude releases, including Codex memory v2 storage.
12
+
3
13
  ## [0.11.1] - 2026-09-14
4
14
 
5
15
  ### Added
@@ -174,6 +184,7 @@
174
184
  - Support for custom Codex home folders and a saved folder preference.
175
185
  - Streaming and bounded-memory discovery for large session collections and transcripts.
176
186
 
187
+ [0.11.2]: https://github.com/mallikcheripally/session-steward/compare/v0.11.1...v0.11.2
177
188
  [0.11.1]: https://github.com/mallikcheripally/session-steward/compare/v0.11.0...v0.11.1
178
189
  [0.11.0]: https://github.com/mallikcheripally/session-steward/compare/v0.10.3...v0.11.0
179
190
  [0.10.3]: https://github.com/mallikcheripally/session-steward/compare/v0.10.2...v0.10.3
package/README.md CHANGED
@@ -192,10 +192,12 @@ Archiving a Claude Desktop session does not delete it. It remains available unti
192
192
  ## Update or uninstall
193
193
 
194
194
  ```bash
195
- npm install --global session-steward@latest
195
+ session-steward update
196
196
  npm uninstall --global session-steward
197
197
  ```
198
198
 
199
+ If npm needs root access, the update command asks before retrying with `sudo`.
200
+
199
201
  Uninstalling does not remove provider sessions, recovery backups, or saved folder preferences.
200
202
 
201
203
  ## Troubleshooting
@@ -13,6 +13,20 @@ assertSupportedNode();
13
13
  if (process.argv[2] === "mcp") {
14
14
  process.argv.splice(2, 1);
15
15
  await import("./session-steward-mcp.mjs");
16
+ } else if (process.argv[2] === "update") {
17
+ process.argv.splice(2, 1);
18
+ if (process.argv.length !== 2) {
19
+ process.stderr.write("Usage: session-steward update\n");
20
+ process.exitCode = 1;
21
+ } else {
22
+ const { updateSessionSteward } = await import("../lib/self-update.mjs");
23
+ try {
24
+ process.exitCode = await updateSessionSteward();
25
+ } catch (error) {
26
+ process.stderr.write(`Could not run the update: ${error.message}\n`);
27
+ process.exitCode = 1;
28
+ }
29
+ }
16
30
  } else {
17
31
  await startBrowser();
18
32
  }
@@ -33,9 +47,11 @@ async function startBrowser() {
33
47
  if (values.help) {
34
48
  process.stdout.write(`Usage: session-steward [options]
35
49
  session-steward mcp [options]
50
+ session-steward update
36
51
 
37
52
  Commands:
38
53
  mcp Run the MCP server over stdio
54
+ update Install the latest version with npm
39
55
 
40
56
  Options:
41
57
  --codex-home <path> Use a custom Codex session folder
@@ -19,15 +19,15 @@ const PROVIDER_ID = "claude-code";
19
19
  const COMPATIBILITY_PROFILE = Object.freeze({
20
20
  id: "claude-local-store-2026-08",
21
21
  builtFor: {
22
- claudeCli: ["2.1.199", "2.1.220", "2.1.228", "2.1.237", "2.1.267"],
23
- claudeDesktop: ["1.24012.9", "1.28929.0", "1.32885.1", "1.40609.0", "1.49585.0"],
22
+ claudeCli: ["2.1.199", "2.1.220", "2.1.228", "2.1.237", "2.1.267", "2.1.280"],
23
+ claudeDesktop: ["1.24012.9", "1.28929.0", "1.32885.1", "1.40609.0", "1.49585.0", "2.2553.1"],
24
24
  },
25
25
  });
26
26
  const SUPPORTED_ENTRYPOINTS = new Set(["cli", "claude-desktop"]);
27
27
  const SHARED_DESKTOP_STATE_FILES = new Set(["scheduled-tasks.json"]);
28
28
  const KNOWN_TOP_LEVEL = new Set([
29
29
  ".DS_Store", ".last-cleanup", ".last-update-result.json", "agents", "backups", "cache", "commands", "debug", "downloads", "file-history", "history.jsonl", "history.jsonl.lock",
30
- "ide", "paste-cache", "plans", "plugins", "projects", "session-env", "sessions", "settings.json",
30
+ "ide", "image-cache", "paste-cache", "plans", "plugins", "projects", "session-env", "sessions", "settings.json",
31
31
  "settings.local.json", "shell-snapshots", "skills", "stats-cache.json", "tasks", "telemetry",
32
32
  "todos", "uploads", "usage-data", "mcp-needs-auth-cache.json", "session-steward-backups",
33
33
  ]);
@@ -4,12 +4,24 @@ import path from "node:path";
4
4
  import { queryRows } from "../../storage/sqlite.mjs";
5
5
 
6
6
  const CACHE_TTL_MS = 2_000;
7
+ const MEMORY_TABLES = [
8
+ { name: "stage1_outputs", requiredColumns: ["thread_id"] },
9
+ {
10
+ name: "jobs",
11
+ optional: true,
12
+ requiredColumns: [
13
+ "kind", "job_key", "status", "worker_id", "ownership_token", "started_at",
14
+ "finished_at", "lease_until", "retry_at", "retry_remaining", "last_error",
15
+ "input_watermark", "last_success_watermark",
16
+ ],
17
+ },
18
+ ];
7
19
 
8
20
  export const CODEX_DATABASE_PROFILE = Object.freeze({
9
- id: "codex-local-store-2026-08",
21
+ id: "codex-local-store-2026-09",
10
22
  builtFor: {
11
- chatgptDesktop: ["26.727.40816", "26.803.61601", "26.818.21641", "26.818.22352", "26.825.41651", "26.903.61454", "26.903.71938"],
12
- codexCli: ["0.144.1", "0.146.0", "0.147.0", "0.148.0", "0.153.4", "0.154.0"],
23
+ chatgptDesktop: ["26.727.40816", "26.803.61601", "26.818.21641", "26.818.22352", "26.825.41651", "26.903.61454", "26.903.71938", "26.917.51856"],
24
+ codexCli: ["0.144.1", "0.146.0", "0.147.0", "0.148.0", "0.153.4", "0.154.0", "0.156.1"],
13
25
  },
14
26
  });
15
27
 
@@ -30,28 +42,13 @@ const SCHEMA_REQUIREMENTS = Object.freeze({
30
42
  fallback: "memories_1.sqlite",
31
43
  pattern: /^memories_(\d+)\.sqlite$/u,
32
44
  required: false,
33
- tables: [
34
- { name: "stage1_outputs", requiredColumns: ["thread_id"] },
35
- {
36
- name: "jobs",
37
- optional: true,
38
- requiredColumns: [
39
- "kind",
40
- "job_key",
41
- "status",
42
- "worker_id",
43
- "ownership_token",
44
- "started_at",
45
- "finished_at",
46
- "lease_until",
47
- "retry_at",
48
- "retry_remaining",
49
- "last_error",
50
- "input_watermark",
51
- "last_success_watermark",
52
- ],
53
- },
54
- ],
45
+ tables: MEMORY_TABLES,
46
+ },
47
+ memoriesV2: {
48
+ fallback: "memories_v2_1.sqlite",
49
+ pattern: /^memories_v2_(\d+)\.sqlite$/u,
50
+ required: false,
51
+ tables: MEMORY_TABLES,
55
52
  },
56
53
  goals: {
57
54
  fallback: "goals_1.sqlite",
@@ -479,7 +479,7 @@ export function getCodexPaths(codexHomeInput, { refresh = false } = {}) {
479
479
  const archivedSessionsDirectory = path.join(codexHome, "archived_sessions");
480
480
  const sessionsDirectory = path.join(codexHome, "sessions");
481
481
  const resolution = resolveCodexDatabases(codexHome, { refresh });
482
- const { goals, logs, memories, queue, state, threadHistory } = resolution.families;
482
+ const { goals, logs, memories, memoriesV2, queue, state, threadHistory } = resolution.families;
483
483
  const stateDatabasePath = state.primary?.path ?? path.join(codexHome, "state_5.sqlite");
484
484
  const logsDatabasePath = logs.primary?.path ?? path.join(codexHome, "logs_2.sqlite");
485
485
  const memoryDatabasePath = memories.primary?.path ?? path.join(codexHome, "memories_1.sqlite");
@@ -500,7 +500,8 @@ export function getCodexPaths(codexHomeInput, { refresh = false } = {}) {
500
500
  logsDatabasePath,
501
501
  logsDatabasePaths: allValidDatabases(logs).map((database) => database.path),
502
502
  memoryDatabasePath,
503
- memoryDatabasePaths: allValidDatabases(memories).map((database) => database.path),
503
+ memoryDatabasePaths: [memories, memoriesV2].flatMap((family) =>
504
+ allValidDatabases(family).map((database) => database.path)),
504
505
  queueDatabasePath,
505
506
  queueDatabasePaths: allValidDatabases(queue).map((database) => database.path),
506
507
  resolvedDatabases: databaseFamilySummary(resolution),
@@ -971,6 +972,8 @@ function stateSchema(database) {
971
972
  ? database.tables.thread_dynamic_tools
972
973
  : inspectSqliteTable(database.path, "thread_dynamic_tools");
973
974
  const schema = {
975
+ attachmentTables: ["thread_artifacts", "thread_attachments"]
976
+ .filter((tableName) => inspectSqliteTable(database.path, tableName).exists),
974
977
  columns,
975
978
  hasDynamicTools: Boolean(dynamicTools?.exists),
976
979
  hasSpawnEdges: Boolean(spawn?.exists),
@@ -2408,6 +2411,9 @@ export async function fingerprintSessionDeletion({ plan, scope, store }) {
2408
2411
 
2409
2412
  for (const database of store.stateDatabases ?? [{ path: store.stateDatabasePath }]) {
2410
2413
  fingerprintRowsForIds(hash, database.path, "threads", "id", plan.ids);
2414
+ for (const tableName of stateSchema(database).attachmentTables) {
2415
+ fingerprintRowsForIds(hash, database.path, tableName, "thread_id", plan.ids);
2416
+ }
2411
2417
  if (stateSchema(database).hasDynamicTools) {
2412
2418
  fingerprintRowsForIds(hash, database.path, "thread_dynamic_tools", "thread_id", plan.ids);
2413
2419
  }
@@ -0,0 +1,95 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createInterface } from "node:readline/promises";
3
+
4
+ import packageMetadata from "../package.json" with { type: "json" };
5
+ import { getCommandInvocation } from "./platform.mjs";
6
+
7
+ const PACKAGE_SPEC = `${packageMetadata.name}@latest`;
8
+ const INSTALL_ARGS = ["install", "--global", PACKAGE_SPEC];
9
+ const VERSION_ARGS = ["list", "--global", "--depth=0", "--json", packageMetadata.name];
10
+
11
+ async function runCommand(command, args, { captureStdout = false, platform, stderr, stdout }) {
12
+ const invocation = getCommandInvocation(command, args, { platform });
13
+
14
+ return new Promise((resolve, reject) => {
15
+ const child = spawn(invocation.command, invocation.args, {
16
+ stdio: ["inherit", "pipe", "pipe"],
17
+ windowsHide: invocation.windowsHide,
18
+ });
19
+ let errorOutput = "";
20
+ let output = "";
21
+
22
+ child.stdout.on("data", (chunk) => {
23
+ if (captureStdout) output = (output + chunk.toString()).slice(-16_384);
24
+ else stdout.write(chunk);
25
+ });
26
+ child.stderr.on("data", (chunk) => {
27
+ stderr.write(chunk);
28
+ errorOutput = (errorOutput + chunk.toString()).slice(-8_192);
29
+ });
30
+ child.on("error", reject);
31
+ child.on("close", (exitCode) => resolve({ exitCode: exitCode ?? 1, errorOutput, output }));
32
+ });
33
+ }
34
+
35
+ async function confirmSudo({ stdin, stdout }) {
36
+ const prompt = createInterface({ input: stdin, output: stdout });
37
+ try {
38
+ const answer = await prompt.question("Retry the update with sudo? [y/N] ");
39
+ return /^(?:y|yes)$/iu.test(answer.trim());
40
+ } finally {
41
+ prompt.close();
42
+ }
43
+ }
44
+
45
+ export async function updateSessionSteward({
46
+ confirm = confirmSudo,
47
+ currentVersion = packageMetadata.version,
48
+ platform = process.platform,
49
+ run = runCommand,
50
+ stdin = process.stdin,
51
+ stdout = process.stdout,
52
+ stderr = process.stderr,
53
+ } = {}) {
54
+ stdout.write(`Current version: ${currentVersion}\nUpdating Session Steward with npm...\n`);
55
+ let result = await run("npm", INSTALL_ARGS, { platform, stderr, stdout });
56
+ let usedSudo = false;
57
+
58
+ if (result.exitCode !== 0 && /\b(?:EACCES|EPERM)\b/u.test(result.errorOutput)) {
59
+ if (platform === "win32") {
60
+ stderr.write("Global npm install needs permission. Open an elevated terminal and run session-steward update.\n");
61
+ } else if (stdin.isTTY && stdout.isTTY) {
62
+ stdout.write("The global npm install needs elevated permission.\n");
63
+ if (await confirm({ stdin, stdout })) {
64
+ usedSudo = true;
65
+ result = await run("sudo", ["npm", ...INSTALL_ARGS], { platform, stderr, stdout });
66
+ }
67
+ } else {
68
+ stderr.write("Global npm install needs elevated permission. Run sudo session-steward update in a terminal.\n");
69
+ }
70
+ }
71
+
72
+ if (result.exitCode === 0) {
73
+ let installedVersion;
74
+ try {
75
+ const versionResult = await run(
76
+ usedSudo ? "sudo" : "npm",
77
+ usedSudo ? ["npm", ...VERSION_ARGS] : VERSION_ARGS,
78
+ { captureStdout: true, platform, stderr, stdout },
79
+ );
80
+ if (versionResult.exitCode === 0) {
81
+ installedVersion = JSON.parse(versionResult.output)?.dependencies?.[packageMetadata.name]?.version;
82
+ }
83
+ } catch {
84
+ // Installation succeeded, but npm could not confirm the installed version.
85
+ }
86
+
87
+ if (typeof installedVersion === "string" && /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.+-]+)?$/u.test(installedVersion)) {
88
+ stdout.write(`Updated version: ${installedVersion}\n`);
89
+ } else {
90
+ stderr.write("Update completed, but the installed version could not be confirmed. Run session-steward --version.\n");
91
+ }
92
+ }
93
+
94
+ return result.exitCode;
95
+ }
@@ -80,6 +80,6 @@ export async function findAvailableUpdate({
80
80
  }
81
81
  }
82
82
 
83
- export function formatUpdateNotice({ latestVersion, packageName }) {
84
- return `Session Steward ${latestVersion} is available. Update with: npm install -g ${packageName}@latest`;
83
+ export function formatUpdateNotice({ latestVersion }) {
84
+ return `Session Steward ${latestVersion} is available. Update with: session-steward update`;
85
85
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "session-steward",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
4
4
  "mcpName": "io.github.mallikcheripally/session-steward",
5
5
  "description": "Codex and Claude Code session manager - browse and delete old sessions. Browser UI + CLI + MCP",
6
6
  "license": "MIT",