code-session-memory 0.4.4 → 0.7.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/README.md +78 -58
- package/dist/mcp/index.js +23 -2
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/server.d.ts +4 -2
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +11 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/src/cli-sessions.d.ts +6 -7
- package/dist/src/cli-sessions.d.ts.map +1 -1
- package/dist/src/cli-sessions.js +238 -178
- package/dist/src/cli-sessions.js.map +1 -1
- package/dist/src/cli.js +272 -12
- package/dist/src/cli.js.map +1 -1
- package/dist/src/cursor-to-messages.d.ts +64 -0
- package/dist/src/cursor-to-messages.d.ts.map +1 -0
- package/dist/src/cursor-to-messages.js +243 -0
- package/dist/src/cursor-to-messages.js.map +1 -0
- package/dist/src/cursor-transcript-to-messages.d.ts +22 -0
- package/dist/src/cursor-transcript-to-messages.d.ts.map +1 -0
- package/dist/src/cursor-transcript-to-messages.js +79 -0
- package/dist/src/cursor-transcript-to-messages.js.map +1 -0
- package/dist/src/database.d.ts +13 -2
- package/dist/src/database.d.ts.map +1 -1
- package/dist/src/database.js +42 -8
- package/dist/src/database.js.map +1 -1
- package/dist/src/indexer-cli-cursor.d.ts +25 -0
- package/dist/src/indexer-cli-cursor.d.ts.map +1 -0
- package/dist/src/indexer-cli-cursor.js +118 -0
- package/dist/src/indexer-cli-cursor.js.map +1 -0
- package/dist/src/indexer-cli.js +76 -6
- package/dist/src/indexer-cli.js.map +1 -1
- package/dist/src/indexer.d.ts.map +1 -1
- package/dist/src/indexer.js +46 -9
- package/dist/src/indexer.js.map +1 -1
- package/dist/src/opencode-db-to-messages.d.ts +30 -0
- package/dist/src/opencode-db-to-messages.d.ts.map +1 -0
- package/dist/src/opencode-db-to-messages.js +88 -0
- package/dist/src/opencode-db-to-messages.js.map +1 -0
- package/dist/src/types.d.ts +6 -1
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +3 -2
- package/plugin/memory.ts +9 -1
- package/skill/memory.md +7 -2
package/dist/src/cli-sessions.js
CHANGED
|
@@ -3,12 +3,10 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* sessions sub-commands for code-session-memory CLI
|
|
5
5
|
*
|
|
6
|
-
* sessions [list] Browse sessions (
|
|
7
|
-
* sessions list --filter Browse with filter step first
|
|
6
|
+
* sessions [list] Browse sessions (3-level tree: source → date → session)
|
|
8
7
|
* sessions print [id] Print all chunks of a session to stdout
|
|
9
|
-
* sessions print --filter Pick session interactively with filter, then print
|
|
10
8
|
* sessions delete [id] Delete a session from the DB
|
|
11
|
-
* sessions
|
|
9
|
+
* sessions purge [--days <n>] [--yes] Delete all sessions older than N days
|
|
12
10
|
*/
|
|
13
11
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
12
|
if (k2 === undefined) k2 = k;
|
|
@@ -47,6 +45,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
47
45
|
exports.cmdSessionsList = cmdSessionsList;
|
|
48
46
|
exports.cmdSessionsPrint = cmdSessionsPrint;
|
|
49
47
|
exports.cmdSessionsDelete = cmdSessionsDelete;
|
|
48
|
+
exports.cmdSessionsPurge = cmdSessionsPurge;
|
|
50
49
|
exports.cmdSessions = cmdSessions;
|
|
51
50
|
const clack = __importStar(require("@clack/prompts"));
|
|
52
51
|
const database_1 = require("./database");
|
|
@@ -66,7 +65,11 @@ function fmtDate(unixMs) {
|
|
|
66
65
|
return new Date(unixMs).toISOString().slice(0, 10);
|
|
67
66
|
}
|
|
68
67
|
function fmtSource(source) {
|
|
69
|
-
|
|
68
|
+
if (source === "opencode")
|
|
69
|
+
return cyan("opencode");
|
|
70
|
+
if (source === "cursor")
|
|
71
|
+
return green("cursor");
|
|
72
|
+
return yellow("claude-code");
|
|
70
73
|
}
|
|
71
74
|
function fmtTitle(title) {
|
|
72
75
|
return title || dim("(untitled)");
|
|
@@ -77,11 +80,14 @@ function fmtProject(project) {
|
|
|
77
80
|
const parts = project.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
78
81
|
return parts.length > 2 ? dim("…/" + parts.slice(-2).join("/")) : dim(project);
|
|
79
82
|
}
|
|
83
|
+
function fmtChunks(n) {
|
|
84
|
+
return `${n} chunk${n !== 1 ? "s" : ""}`;
|
|
85
|
+
}
|
|
80
86
|
function hr(char = "─", width = 72) {
|
|
81
87
|
return char.repeat(width);
|
|
82
88
|
}
|
|
83
89
|
// ---------------------------------------------------------------------------
|
|
84
|
-
// DB helpers
|
|
90
|
+
// DB helpers
|
|
85
91
|
// ---------------------------------------------------------------------------
|
|
86
92
|
function withDb(fn) {
|
|
87
93
|
const dbPath = (0, database_1.resolveDbPath)();
|
|
@@ -94,129 +100,131 @@ function withDb(fn) {
|
|
|
94
100
|
}
|
|
95
101
|
}
|
|
96
102
|
// ---------------------------------------------------------------------------
|
|
97
|
-
// Label
|
|
103
|
+
// Label builders
|
|
98
104
|
// ---------------------------------------------------------------------------
|
|
99
105
|
function sessionLabel(s) {
|
|
100
|
-
const
|
|
101
|
-
const chunks = `${s.chunk_count} chunk${s.chunk_count !== 1 ? "s" : ""}`;
|
|
106
|
+
const chunks = fmtChunks(s.chunk_count);
|
|
102
107
|
const title = fmtTitle(s.session_title).padEnd(40);
|
|
103
|
-
return `${title} ${
|
|
108
|
+
return `${title} ${chunks}`;
|
|
104
109
|
}
|
|
105
110
|
function sessionHint(s) {
|
|
106
111
|
return `${fmtSource(s.source)} ${fmtProject(s.project)}`;
|
|
107
112
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
{ value: "claude-code", label: "Claude Code" },
|
|
119
|
-
],
|
|
120
|
-
initialValue: "all",
|
|
121
|
-
});
|
|
122
|
-
if (clack.isCancel(sourceAnswer)) {
|
|
123
|
-
clack.cancel("Cancelled.");
|
|
124
|
-
process.exit(0);
|
|
113
|
+
async function pickSessionTree(allSessions) {
|
|
114
|
+
if (allSessions.length === 0)
|
|
115
|
+
return null;
|
|
116
|
+
// ── Level 1: choose source ──────────────────────────────────────────────
|
|
117
|
+
// Group by source, count sessions + chunks
|
|
118
|
+
const sourceMap = new Map();
|
|
119
|
+
for (const s of allSessions) {
|
|
120
|
+
if (!sourceMap.has(s.source))
|
|
121
|
+
sourceMap.set(s.source, []);
|
|
122
|
+
sourceMap.get(s.source).push(s);
|
|
125
123
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
{
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
124
|
+
// Sort sources by session count descending
|
|
125
|
+
const sources = [...sourceMap.entries()].sort((a, b) => b[1].length - a[1].length);
|
|
126
|
+
const sourceOptions = [
|
|
127
|
+
...sources.map(([src, rows]) => ({
|
|
128
|
+
value: src,
|
|
129
|
+
label: sourceLabelText(src),
|
|
130
|
+
hint: `${rows.length} session${rows.length !== 1 ? "s" : ""} ${fmtChunks(rows.reduce((n, r) => n + r.chunk_count, 0))}`,
|
|
131
|
+
})),
|
|
132
|
+
{ value: "__all__", label: dim("All sessions"), hint: `${allSessions.length} total` },
|
|
133
|
+
{ value: "__exit__", label: dim("Exit") },
|
|
134
|
+
];
|
|
135
|
+
const srcChoice = await clack.select({
|
|
136
|
+
message: "Source",
|
|
137
|
+
options: sourceOptions,
|
|
138
|
+
maxItems: 8,
|
|
137
139
|
});
|
|
138
|
-
if (clack.isCancel(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const nowMs = Date.now();
|
|
147
|
-
const DAY_MS = 86400 * 1000;
|
|
148
|
-
if (dateAnswer === "7d") {
|
|
149
|
-
filter.fromDate = nowMs - 7 * DAY_MS;
|
|
150
|
-
}
|
|
151
|
-
else if (dateAnswer === "30d") {
|
|
152
|
-
filter.fromDate = nowMs - 30 * DAY_MS;
|
|
153
|
-
}
|
|
154
|
-
else if (dateAnswer === "90d") {
|
|
155
|
-
filter.fromDate = nowMs - 90 * DAY_MS;
|
|
140
|
+
if (clack.isCancel(srcChoice) || srcChoice === "__exit__")
|
|
141
|
+
return null;
|
|
142
|
+
const filteredBySrc = srcChoice === "__all__" ? allSessions : sourceMap.get(srcChoice);
|
|
143
|
+
// ── Level 2: choose date (skip if only one date or "All sessions") ──────
|
|
144
|
+
let filteredByDate;
|
|
145
|
+
if (srcChoice === "__all__") {
|
|
146
|
+
// Skip date level — go straight to all sessions
|
|
147
|
+
filteredByDate = filteredBySrc;
|
|
156
148
|
}
|
|
157
|
-
else
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
});
|
|
166
|
-
if (clack.isCancel(nAnswer)) {
|
|
167
|
-
clack.cancel("Cancelled.");
|
|
168
|
-
process.exit(0);
|
|
149
|
+
else {
|
|
150
|
+
// Group by YYYY-MM-DD
|
|
151
|
+
const dateMap = new Map();
|
|
152
|
+
for (const s of filteredBySrc) {
|
|
153
|
+
const d = fmtDate(s.updated_at);
|
|
154
|
+
if (!dateMap.has(d))
|
|
155
|
+
dateMap.set(d, []);
|
|
156
|
+
dateMap.get(d).push(s);
|
|
169
157
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
158
|
+
if (dateMap.size === 1) {
|
|
159
|
+
// Only one date — skip level 2
|
|
160
|
+
filteredByDate = filteredBySrc;
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
const dates = [...dateMap.entries()].sort((a, b) => b[0].localeCompare(a[0]));
|
|
164
|
+
const dateOptions = [
|
|
165
|
+
...dates.map(([date, rows]) => ({
|
|
166
|
+
value: date,
|
|
167
|
+
label: date,
|
|
168
|
+
hint: `${rows.length} session${rows.length !== 1 ? "s" : ""}`,
|
|
169
|
+
})),
|
|
170
|
+
{ value: "__back__", label: dim("Back") },
|
|
171
|
+
];
|
|
172
|
+
const dateChoice = await clack.select({
|
|
173
|
+
message: `Date ${dim("(" + sourceLabelText(srcChoice) + ")")}`,
|
|
174
|
+
options: dateOptions,
|
|
175
|
+
maxItems: 10,
|
|
176
|
+
});
|
|
177
|
+
if (clack.isCancel(dateChoice) || dateChoice === "__back__") {
|
|
178
|
+
// Go back to level 1
|
|
179
|
+
return pickSessionTree(allSessions);
|
|
180
|
+
}
|
|
181
|
+
filteredByDate = dateMap.get(dateChoice);
|
|
184
182
|
}
|
|
185
|
-
filter.toDate = nowMs - Number(nAnswer) * DAY_MS;
|
|
186
|
-
}
|
|
187
|
-
return filter;
|
|
188
|
-
}
|
|
189
|
-
// ---------------------------------------------------------------------------
|
|
190
|
-
// Shared session picker
|
|
191
|
-
// Returns the chosen SessionRow, or null if the user cancelled / exited.
|
|
192
|
-
// When withFilter is true, runs the filter step first.
|
|
193
|
-
// ---------------------------------------------------------------------------
|
|
194
|
-
async function pickSession(withFilter) {
|
|
195
|
-
let filter = {};
|
|
196
|
-
if (withFilter) {
|
|
197
|
-
filter = await runFilterStep();
|
|
198
|
-
console.log();
|
|
199
|
-
}
|
|
200
|
-
const sessions = withDb((db) => (0, database_2.listSessions)(db, filter));
|
|
201
|
-
if (sessions.length === 0) {
|
|
202
|
-
clack.log.warn("No sessions match the current filter.");
|
|
203
|
-
return null;
|
|
204
183
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
184
|
+
// ── Level 3: choose session ──────────────────────────────────────────────
|
|
185
|
+
const sessionOptions = [
|
|
186
|
+
...filteredByDate.map((s) => ({
|
|
187
|
+
value: s.session_id,
|
|
188
|
+
label: sessionLabel(s),
|
|
189
|
+
hint: sessionHint(s),
|
|
190
|
+
})),
|
|
191
|
+
{ value: "__back__", label: dim("Back") },
|
|
192
|
+
];
|
|
193
|
+
const backLabel = srcChoice === "__all__"
|
|
194
|
+
? "Source"
|
|
195
|
+
: dateMap_size(filteredBySrc) === 1 ? "Source" : "Date";
|
|
196
|
+
const sessionOptions2 = sessionOptions.map((o) => o.value === "__back__" ? { ...o, hint: `back to ${backLabel}` } : o);
|
|
197
|
+
const sessionChoice = await clack.select({
|
|
198
|
+
message: srcChoice === "__all__"
|
|
199
|
+
? `Session ${dim("(all sources)")}`
|
|
200
|
+
: `Session ${dim("(" + sourceLabelText(srcChoice) + ")")}`,
|
|
201
|
+
options: sessionOptions2,
|
|
215
202
|
maxItems: 12,
|
|
216
203
|
});
|
|
217
|
-
if (clack.isCancel(
|
|
218
|
-
|
|
219
|
-
|
|
204
|
+
if (clack.isCancel(sessionChoice) || sessionChoice === "__back__") {
|
|
205
|
+
if (srcChoice === "__all__" || dateMap_size(filteredBySrc) === 1) {
|
|
206
|
+
// Back to level 1
|
|
207
|
+
return pickSessionTree(allSessions);
|
|
208
|
+
}
|
|
209
|
+
// Back to level 2 — re-run from source choice but pre-select the date level
|
|
210
|
+
// Simplest: restart from level 1 (preserves the loop invariant)
|
|
211
|
+
return pickSessionTree(allSessions);
|
|
212
|
+
}
|
|
213
|
+
return filteredByDate.find((s) => s.session_id === sessionChoice) ?? null;
|
|
214
|
+
}
|
|
215
|
+
// Helper: plain text source label (no ANSI) for use in hints/messages
|
|
216
|
+
function sourceLabelText(source) {
|
|
217
|
+
if (source === "opencode")
|
|
218
|
+
return "OpenCode";
|
|
219
|
+
if (source === "cursor")
|
|
220
|
+
return "Cursor";
|
|
221
|
+
if (source === "claude-code")
|
|
222
|
+
return "Claude Code";
|
|
223
|
+
return source;
|
|
224
|
+
}
|
|
225
|
+
// Helper to count unique dates in a session list without capturing dateMap
|
|
226
|
+
function dateMap_size(sessions) {
|
|
227
|
+
return new Set(sessions.map((s) => fmtDate(s.updated_at))).size;
|
|
220
228
|
}
|
|
221
229
|
// ---------------------------------------------------------------------------
|
|
222
230
|
// Action loop for a selected session (used by sessions list)
|
|
@@ -225,7 +233,7 @@ async function sessionActionLoop(session) {
|
|
|
225
233
|
while (true) {
|
|
226
234
|
clack.log.message([
|
|
227
235
|
`${bold(fmtTitle(session.session_title))}`,
|
|
228
|
-
`${fmtSource(session.source)} ${fmtDate(session.updated_at)} ${session.chunk_count}
|
|
236
|
+
`${fmtSource(session.source)} ${fmtDate(session.updated_at)} ${fmtChunks(session.chunk_count)}`,
|
|
229
237
|
`Project: ${session.project || dim("—")}`,
|
|
230
238
|
`ID: ${dim(session.session_id)}`,
|
|
231
239
|
].join("\n"), { symbol: "○" });
|
|
@@ -246,7 +254,7 @@ async function sessionActionLoop(session) {
|
|
|
246
254
|
}
|
|
247
255
|
if (action === "delete") {
|
|
248
256
|
const confirmed = await clack.confirm({
|
|
249
|
-
message: `Delete "${fmtTitle(session.session_title)}" (${session.chunk_count}
|
|
257
|
+
message: `Delete "${fmtTitle(session.session_title)}" (${fmtChunks(session.chunk_count)})?`,
|
|
250
258
|
initialValue: false,
|
|
251
259
|
});
|
|
252
260
|
if (clack.isCancel(confirmed) || !confirmed) {
|
|
@@ -263,59 +271,44 @@ async function sessionActionLoop(session) {
|
|
|
263
271
|
// ---------------------------------------------------------------------------
|
|
264
272
|
// sessions list
|
|
265
273
|
// ---------------------------------------------------------------------------
|
|
266
|
-
async function cmdSessionsList(
|
|
267
|
-
|
|
268
|
-
if (!withFilter) {
|
|
269
|
-
clack.intro(bold("Sessions"));
|
|
270
|
-
}
|
|
271
|
-
// Main browse loop — re-shown after back/delete so the user can keep browsing
|
|
272
|
-
let filter = {};
|
|
273
|
-
let filterResolved = false;
|
|
274
|
+
async function cmdSessionsList() {
|
|
275
|
+
clack.intro(bold("Sessions"));
|
|
274
276
|
while (true) {
|
|
275
|
-
|
|
276
|
-
if (withFilter && !filterResolved) {
|
|
277
|
-
filter = await runFilterStep();
|
|
278
|
-
filterResolved = true;
|
|
279
|
-
console.log();
|
|
280
|
-
}
|
|
281
|
-
const sessions = withDb((db) => (0, database_2.listSessions)(db, filter));
|
|
277
|
+
const sessions = withDb((db) => (0, database_2.listSessions)(db));
|
|
282
278
|
if (sessions.length === 0) {
|
|
283
|
-
clack.log.warn("No sessions
|
|
279
|
+
clack.log.warn("No sessions indexed yet.");
|
|
284
280
|
clack.outro("Done.");
|
|
285
281
|
return;
|
|
286
282
|
}
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
options: [
|
|
290
|
-
...sessions.map((s) => ({
|
|
291
|
-
value: s.session_id,
|
|
292
|
-
label: sessionLabel(s),
|
|
293
|
-
hint: sessionHint(s),
|
|
294
|
-
})),
|
|
295
|
-
{ value: "__exit__", label: dim("Exit") },
|
|
296
|
-
],
|
|
297
|
-
maxItems: 12,
|
|
298
|
-
});
|
|
299
|
-
if (clack.isCancel(chosen) || chosen === "__exit__") {
|
|
283
|
+
const session = await pickSessionTree(sessions);
|
|
284
|
+
if (!session) {
|
|
300
285
|
clack.outro("Done.");
|
|
301
286
|
return;
|
|
302
287
|
}
|
|
303
|
-
const session = sessions.find((s) => s.session_id === chosen);
|
|
304
288
|
const result = await sessionActionLoop(session);
|
|
305
289
|
if (result === "exit")
|
|
306
290
|
return;
|
|
307
|
-
// result === "back" → loop
|
|
291
|
+
// result === "back" → loop, refresh session list and restart tree
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// ---------------------------------------------------------------------------
|
|
295
|
+
// Shared session picker (for print / delete sub-commands)
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
async function pickSession() {
|
|
298
|
+
const sessions = withDb((db) => (0, database_2.listSessions)(db));
|
|
299
|
+
if (sessions.length === 0) {
|
|
300
|
+
clack.log.warn("No sessions indexed yet.");
|
|
301
|
+
return null;
|
|
308
302
|
}
|
|
303
|
+
return pickSessionTree(sessions);
|
|
309
304
|
}
|
|
310
305
|
// ---------------------------------------------------------------------------
|
|
311
306
|
// sessions print [id]
|
|
312
307
|
// ---------------------------------------------------------------------------
|
|
313
|
-
async function cmdSessionsPrint(sessionId
|
|
314
|
-
const withFilter = args.includes("--filter");
|
|
308
|
+
async function cmdSessionsPrint(sessionId) {
|
|
315
309
|
if (!sessionId) {
|
|
316
|
-
// No ID given — launch interactive picker
|
|
317
310
|
clack.intro(bold("Print session"));
|
|
318
|
-
const session = await pickSession(
|
|
311
|
+
const session = await pickSession();
|
|
319
312
|
if (!session) {
|
|
320
313
|
clack.outro("Cancelled.");
|
|
321
314
|
return;
|
|
@@ -340,15 +333,18 @@ function printSession(sessionId) {
|
|
|
340
333
|
const useTty = process.stdout.isTTY;
|
|
341
334
|
const b = (s) => useTty ? bold(s) : s;
|
|
342
335
|
const d = (s) => useTty ? dim(s) : s;
|
|
343
|
-
const
|
|
344
|
-
|
|
336
|
+
const fmtSrc = (src) => {
|
|
337
|
+
if (!useTty)
|
|
338
|
+
return src;
|
|
339
|
+
return fmtSource(src);
|
|
340
|
+
};
|
|
345
341
|
const title = session?.session_title || "(untitled)";
|
|
346
342
|
const source = session?.source || "unknown";
|
|
347
343
|
const date = session ? fmtDate(session.updated_at) : "unknown";
|
|
348
344
|
const project = session?.project || "—";
|
|
349
345
|
console.log(hr());
|
|
350
346
|
console.log(`${b("Session:")} ${title}`);
|
|
351
|
-
console.log(`${b("Source:")} ${
|
|
347
|
+
console.log(`${b("Source:")} ${fmtSrc(source)} ${d(date)}`);
|
|
352
348
|
console.log(`${b("Project:")} ${project}`);
|
|
353
349
|
console.log(`${b("ID:")} ${d(sessionId)}`);
|
|
354
350
|
console.log(`${b("Chunks:")} ${chunks.length}`);
|
|
@@ -367,13 +363,11 @@ function printSession(sessionId) {
|
|
|
367
363
|
// ---------------------------------------------------------------------------
|
|
368
364
|
// sessions delete [id]
|
|
369
365
|
// ---------------------------------------------------------------------------
|
|
370
|
-
async function cmdSessionsDelete(sessionId
|
|
371
|
-
const withFilter = args.includes("--filter");
|
|
366
|
+
async function cmdSessionsDelete(sessionId) {
|
|
372
367
|
clack.intro(bold("Delete session"));
|
|
373
368
|
let session = null;
|
|
374
369
|
if (!sessionId) {
|
|
375
|
-
|
|
376
|
-
session = await pickSession(withFilter);
|
|
370
|
+
session = await pickSession();
|
|
377
371
|
if (!session) {
|
|
378
372
|
clack.outro("Cancelled.");
|
|
379
373
|
return;
|
|
@@ -391,12 +385,12 @@ async function cmdSessionsDelete(sessionId, args = []) {
|
|
|
391
385
|
}
|
|
392
386
|
clack.log.message([
|
|
393
387
|
`${bold(fmtTitle(session.session_title))}`,
|
|
394
|
-
`${fmtSource(session.source)} ${fmtDate(session.updated_at)} ${session.chunk_count}
|
|
388
|
+
`${fmtSource(session.source)} ${fmtDate(session.updated_at)} ${fmtChunks(session.chunk_count)}`,
|
|
395
389
|
`Project: ${session.project || dim("—")}`,
|
|
396
390
|
`ID: ${dim(session.session_id)}`,
|
|
397
391
|
].join("\n"), { symbol: "○" });
|
|
398
392
|
const confirmed = await clack.confirm({
|
|
399
|
-
message: `Delete this session (${session.chunk_count}
|
|
393
|
+
message: `Delete this session (${fmtChunks(session.chunk_count)})?`,
|
|
400
394
|
initialValue: false,
|
|
401
395
|
});
|
|
402
396
|
if (clack.isCancel(confirmed) || !confirmed) {
|
|
@@ -409,6 +403,78 @@ async function cmdSessionsDelete(sessionId, args = []) {
|
|
|
409
403
|
clack.outro("Done.");
|
|
410
404
|
}
|
|
411
405
|
// ---------------------------------------------------------------------------
|
|
406
|
+
// sessions purge [--days <n>] [--yes]
|
|
407
|
+
// ---------------------------------------------------------------------------
|
|
408
|
+
async function cmdSessionsPurge(args) {
|
|
409
|
+
const DAY_MS = 86400 * 1000;
|
|
410
|
+
// Parse --days <n>
|
|
411
|
+
let days;
|
|
412
|
+
const daysIdx = args.indexOf("--days");
|
|
413
|
+
if (daysIdx !== -1 && args[daysIdx + 1]) {
|
|
414
|
+
const parsed = Number(args[daysIdx + 1]);
|
|
415
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
416
|
+
console.error("--days must be a positive integer");
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
419
|
+
days = parsed;
|
|
420
|
+
}
|
|
421
|
+
const skipConfirm = args.includes("--yes");
|
|
422
|
+
if (!skipConfirm) {
|
|
423
|
+
clack.intro(bold("Purge old sessions"));
|
|
424
|
+
}
|
|
425
|
+
// Prompt for days if not provided
|
|
426
|
+
if (days === undefined) {
|
|
427
|
+
const answer = await clack.text({
|
|
428
|
+
message: "Delete sessions older than how many days?",
|
|
429
|
+
placeholder: "30",
|
|
430
|
+
validate(v) {
|
|
431
|
+
const n = Number(v);
|
|
432
|
+
if (!v || !Number.isInteger(n) || n <= 0)
|
|
433
|
+
return "Please enter a positive integer.";
|
|
434
|
+
},
|
|
435
|
+
});
|
|
436
|
+
if (clack.isCancel(answer)) {
|
|
437
|
+
clack.cancel("Cancelled.");
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
days = Number(answer);
|
|
441
|
+
}
|
|
442
|
+
const cutoff = Date.now() - days * DAY_MS;
|
|
443
|
+
const candidates = withDb((db) => (0, database_2.listSessions)(db, { toDate: cutoff }));
|
|
444
|
+
if (candidates.length === 0) {
|
|
445
|
+
const msg = `No sessions older than ${days} day${days !== 1 ? "s" : ""} found.`;
|
|
446
|
+
if (skipConfirm) {
|
|
447
|
+
console.log(msg);
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
clack.log.info(msg);
|
|
451
|
+
clack.outro("Nothing to purge.");
|
|
452
|
+
}
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
const totalChunks = candidates.reduce((n, s) => n + s.chunk_count, 0);
|
|
456
|
+
const summary = `${candidates.length} session${candidates.length !== 1 ? "s" : ""} (${totalChunks} chunks) older than ${days} day${days !== 1 ? "s" : ""}`;
|
|
457
|
+
if (skipConfirm) {
|
|
458
|
+
// Non-interactive: just do it
|
|
459
|
+
const result = withDb((db) => (0, database_2.deleteSessionsOlderThan)(db, cutoff));
|
|
460
|
+
console.log(`Deleted ${result.sessions} sessions (${result.chunks} chunks).`);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
clack.log.warn(`Found ${summary}.`);
|
|
464
|
+
const confirmed = await clack.confirm({
|
|
465
|
+
message: `Permanently delete ${summary}?`,
|
|
466
|
+
initialValue: false,
|
|
467
|
+
});
|
|
468
|
+
if (clack.isCancel(confirmed) || !confirmed) {
|
|
469
|
+
clack.cancel("Purge cancelled — database was not modified.");
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const result = withDb((db) => (0, database_2.deleteSessionsOlderThan)(db, cutoff));
|
|
473
|
+
clack.log.success(`Deleted ${result.sessions} sessions (${result.chunks} chunks).`);
|
|
474
|
+
clack.log.warn("Note: sessions will be re-indexed on the next agent turn if source files still exist.");
|
|
475
|
+
clack.outro("Done.");
|
|
476
|
+
}
|
|
477
|
+
// ---------------------------------------------------------------------------
|
|
412
478
|
// Help
|
|
413
479
|
// ---------------------------------------------------------------------------
|
|
414
480
|
function sessionsHelp() {
|
|
@@ -417,22 +483,18 @@ function sessionsHelp() {
|
|
|
417
483
|
${b("sessions")} — Browse, inspect, and delete indexed sessions
|
|
418
484
|
|
|
419
485
|
${b("Usage:")}
|
|
420
|
-
npx code-session-memory sessions Browse sessions
|
|
421
|
-
npx code-session-memory sessions --filter Apply source/date filters before browsing
|
|
486
|
+
npx code-session-memory sessions Browse sessions (tree: source → date → session)
|
|
422
487
|
npx code-session-memory sessions list Same as above (explicit sub-command)
|
|
423
|
-
npx code-session-memory sessions list --filter Same with filter step
|
|
424
488
|
|
|
425
489
|
npx code-session-memory sessions print Pick a session interactively, then print
|
|
426
|
-
npx code-session-memory sessions print --filter Pick with filter, then print
|
|
427
490
|
npx code-session-memory sessions print <id> Print all chunks of a session directly
|
|
428
491
|
|
|
429
492
|
npx code-session-memory sessions delete Pick a session interactively, then delete
|
|
430
|
-
npx code-session-memory sessions delete --filter Pick with filter, then delete
|
|
431
493
|
npx code-session-memory sessions delete <id> Delete a session directly
|
|
432
494
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
495
|
+
npx code-session-memory sessions purge Delete sessions older than N days (interactive)
|
|
496
|
+
npx code-session-memory sessions purge --days <n> Non-interactive, prompts for confirmation
|
|
497
|
+
npx code-session-memory sessions purge --days <n> --yes Fully non-interactive (no confirmation)
|
|
436
498
|
|
|
437
499
|
${b("Notes:")}
|
|
438
500
|
- Deleting a session only removes it from the DB. If the source files still
|
|
@@ -441,36 +503,34 @@ ${b("Notes:")}
|
|
|
441
503
|
`);
|
|
442
504
|
}
|
|
443
505
|
// ---------------------------------------------------------------------------
|
|
444
|
-
// Entry point
|
|
506
|
+
// Entry point
|
|
445
507
|
// ---------------------------------------------------------------------------
|
|
446
508
|
async function cmdSessions(argv) {
|
|
447
509
|
const sub = argv[0] ?? "list";
|
|
448
|
-
// Help flag anywhere in argv
|
|
449
510
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
450
511
|
sessionsHelp();
|
|
451
512
|
return;
|
|
452
513
|
}
|
|
453
514
|
switch (sub) {
|
|
454
515
|
case "list":
|
|
455
|
-
await cmdSessionsList(
|
|
516
|
+
await cmdSessionsList();
|
|
456
517
|
break;
|
|
457
518
|
case "print": {
|
|
458
|
-
// argv[1] is either an ID or a flag (--filter) or absent
|
|
459
519
|
const maybeId = argv[1] && !argv[1].startsWith("-") ? argv[1] : undefined;
|
|
460
|
-
|
|
461
|
-
await cmdSessionsPrint(maybeId, restArgs);
|
|
520
|
+
await cmdSessionsPrint(maybeId);
|
|
462
521
|
break;
|
|
463
522
|
}
|
|
464
523
|
case "delete": {
|
|
465
524
|
const maybeId = argv[1] && !argv[1].startsWith("-") ? argv[1] : undefined;
|
|
466
|
-
|
|
467
|
-
await cmdSessionsDelete(maybeId, restArgs);
|
|
525
|
+
await cmdSessionsDelete(maybeId);
|
|
468
526
|
break;
|
|
469
527
|
}
|
|
528
|
+
case "purge":
|
|
529
|
+
await cmdSessionsPurge(argv.slice(1));
|
|
530
|
+
break;
|
|
470
531
|
default:
|
|
471
|
-
// Treat unknown first arg as implicit "list" (e.g. `sessions --filter`)
|
|
472
532
|
if (sub.startsWith("-")) {
|
|
473
|
-
await cmdSessionsList(
|
|
533
|
+
await cmdSessionsList();
|
|
474
534
|
}
|
|
475
535
|
else {
|
|
476
536
|
console.error(`Unknown sessions sub-command: ${sub}`);
|