clawvault 2.4.4 → 2.4.5
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/bin/clawvault.js +4 -0
- package/bin/command-registration.test.js +13 -1
- package/bin/help-contract.test.js +2 -0
- package/bin/register-config-commands.js +153 -0
- package/bin/register-config-route-commands.test.js +114 -0
- package/bin/register-resilience-commands.js +37 -2
- package/bin/register-resilience-commands.test.js +81 -0
- package/bin/register-route-commands.js +114 -0
- package/bin/test-helpers/cli-command-fixtures.js +10 -0
- package/dist/{chunk-XDCFXFGH.js → chunk-22WE3J4F.js} +1 -1
- package/dist/chunk-3PJIGGWV.js +49 -0
- package/dist/{chunk-GBIDDDSL.js → chunk-4JJL47IJ.js} +1 -1
- package/dist/{chunk-FDJIZKCW.js → chunk-6B3JWM7J.js} +12 -48
- package/dist/{chunk-SNEMCQP7.js → chunk-B3SMJZIZ.js} +1 -1
- package/dist/{chunk-MZZJLQNQ.js → chunk-F55HGNU4.js} +6 -0
- package/dist/{chunk-BMOQI62Q.js → chunk-HNMFXFYP.js} +5 -3
- package/dist/chunk-OIWVQYQF.js +284 -0
- package/dist/{chunk-FEFPBHH4.js → chunk-RXEIQ3KQ.js} +452 -22
- package/dist/{chunk-DHJPXGC7.js → chunk-U2ONVV7N.js} +1 -1
- package/dist/{chunk-IFTEGE4D.js → chunk-YIRWDQKA.js} +4 -2
- package/dist/commands/checkpoint.js +1 -1
- package/dist/commands/context.js +4 -3
- package/dist/commands/doctor.js +3 -2
- package/dist/commands/observe.js +3 -2
- package/dist/commands/rebuild.js +3 -2
- package/dist/commands/recover.d.ts +13 -1
- package/dist/commands/recover.js +10 -2
- package/dist/commands/replay.js +3 -2
- package/dist/commands/setup.js +3 -2
- package/dist/commands/sleep.js +5 -4
- package/dist/commands/status.js +3 -2
- package/dist/commands/wake.js +5 -4
- package/dist/index.d.ts +27 -1
- package/dist/index.js +36 -14
- package/package.json +1 -1
- package/dist/chunk-IWYZAXKJ.js +0 -146
|
@@ -1,46 +1,3 @@
|
|
|
1
|
-
// src/types.ts
|
|
2
|
-
var DEFAULT_CATEGORIES = [
|
|
3
|
-
"preferences",
|
|
4
|
-
"decisions",
|
|
5
|
-
"patterns",
|
|
6
|
-
"people",
|
|
7
|
-
"projects",
|
|
8
|
-
"goals",
|
|
9
|
-
"transcripts",
|
|
10
|
-
"inbox",
|
|
11
|
-
"templates",
|
|
12
|
-
"lessons",
|
|
13
|
-
"agents",
|
|
14
|
-
"commitments",
|
|
15
|
-
"handoffs",
|
|
16
|
-
"research",
|
|
17
|
-
"tasks",
|
|
18
|
-
"backlog"
|
|
19
|
-
];
|
|
20
|
-
var MEMORY_TYPES = [
|
|
21
|
-
"fact",
|
|
22
|
-
"feeling",
|
|
23
|
-
"decision",
|
|
24
|
-
"lesson",
|
|
25
|
-
"commitment",
|
|
26
|
-
"preference",
|
|
27
|
-
"relationship",
|
|
28
|
-
"project"
|
|
29
|
-
];
|
|
30
|
-
var TYPE_TO_CATEGORY = {
|
|
31
|
-
fact: "facts",
|
|
32
|
-
feeling: "feelings",
|
|
33
|
-
decision: "decisions",
|
|
34
|
-
lesson: "lessons",
|
|
35
|
-
commitment: "commitments",
|
|
36
|
-
preference: "preferences",
|
|
37
|
-
relationship: "people",
|
|
38
|
-
project: "projects"
|
|
39
|
-
};
|
|
40
|
-
var DEFAULT_CONFIG = {
|
|
41
|
-
categories: DEFAULT_CATEGORIES
|
|
42
|
-
};
|
|
43
|
-
|
|
44
1
|
// src/lib/search.ts
|
|
45
2
|
import { execFileSync, spawnSync } from "child_process";
|
|
46
3
|
import * as fs from "fs";
|
|
@@ -71,8 +28,19 @@ function extractJsonPayload(raw) {
|
|
|
71
28
|
if (end <= start) return null;
|
|
72
29
|
return raw.slice(start, end + 1);
|
|
73
30
|
}
|
|
31
|
+
function stripQmdNoise(raw) {
|
|
32
|
+
return raw.split("\n").filter((line) => {
|
|
33
|
+
const t = line.trim();
|
|
34
|
+
if (!t) return true;
|
|
35
|
+
if (t.startsWith("[node-llama-cpp]")) return false;
|
|
36
|
+
if (t.startsWith("Expanding query")) return false;
|
|
37
|
+
if (t.startsWith("Searching ") && t.endsWith("queries...")) return false;
|
|
38
|
+
if (/^[├└─│]/.test(t)) return false;
|
|
39
|
+
return true;
|
|
40
|
+
}).join("\n");
|
|
41
|
+
}
|
|
74
42
|
function parseQmdOutput(raw) {
|
|
75
|
-
const trimmed = raw.trim();
|
|
43
|
+
const trimmed = stripQmdNoise(raw).trim();
|
|
76
44
|
if (!trimmed) return [];
|
|
77
45
|
const direct = tryParseJson(trimmed);
|
|
78
46
|
const extracted = direct ? null : extractJsonPayload(trimmed);
|
|
@@ -368,10 +336,6 @@ function extractTags(content) {
|
|
|
368
336
|
}
|
|
369
337
|
|
|
370
338
|
export {
|
|
371
|
-
DEFAULT_CATEGORIES,
|
|
372
|
-
MEMORY_TYPES,
|
|
373
|
-
TYPE_TO_CATEGORY,
|
|
374
|
-
DEFAULT_CONFIG,
|
|
375
339
|
QMD_INSTALL_URL,
|
|
376
340
|
QMD_INSTALL_COMMAND,
|
|
377
341
|
QmdUnavailableError,
|
|
@@ -6,6 +6,7 @@ var CLAWVAULT_DIR = ".clawvault";
|
|
|
6
6
|
var CHECKPOINT_FILE = "last-checkpoint.json";
|
|
7
7
|
var SESSION_STATE_FILE = "session-state.json";
|
|
8
8
|
var DIRTY_DEATH_FLAG = "dirty-death.flag";
|
|
9
|
+
var CHECKPOINT_HISTORY_DIR = "checkpoints";
|
|
9
10
|
var pendingCheckpoint = null;
|
|
10
11
|
var pendingData = null;
|
|
11
12
|
function ensureClawvaultDir(vaultPath) {
|
|
@@ -18,6 +19,11 @@ function ensureClawvaultDir(vaultPath) {
|
|
|
18
19
|
function writeCheckpointToDisk(dir, data) {
|
|
19
20
|
const checkpointPath = path.join(dir, CHECKPOINT_FILE);
|
|
20
21
|
fs.writeFileSync(checkpointPath, JSON.stringify(data, null, 2));
|
|
22
|
+
const historyDir = path.join(dir, CHECKPOINT_HISTORY_DIR);
|
|
23
|
+
fs.mkdirSync(historyDir, { recursive: true });
|
|
24
|
+
const historyFileName = `${data.timestamp.replace(/[:.]/g, "-")}.json`;
|
|
25
|
+
const historyPath = path.join(historyDir, historyFileName);
|
|
26
|
+
fs.writeFileSync(historyPath, JSON.stringify(data, null, 2));
|
|
21
27
|
const flagPath = path.join(dir, DIRTY_DEATH_FLAG);
|
|
22
28
|
fs.writeFileSync(flagPath, data.timestamp);
|
|
23
29
|
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
|
-
DEFAULT_CATEGORIES,
|
|
3
2
|
QmdUnavailableError,
|
|
4
3
|
SearchEngine,
|
|
5
|
-
TYPE_TO_CATEGORY,
|
|
6
4
|
extractTags,
|
|
7
5
|
extractWikiLinks,
|
|
8
6
|
hasQmd,
|
|
9
7
|
qmdEmbed,
|
|
10
8
|
qmdUpdate
|
|
11
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-6B3JWM7J.js";
|
|
10
|
+
import {
|
|
11
|
+
DEFAULT_CATEGORIES,
|
|
12
|
+
TYPE_TO_CATEGORY
|
|
13
|
+
} from "./chunk-3PJIGGWV.js";
|
|
12
14
|
import {
|
|
13
15
|
buildOrUpdateMemoryGraphIndex
|
|
14
16
|
} from "./chunk-ZZA73MFY.js";
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatAge
|
|
3
|
+
} from "./chunk-7ZRP733D.js";
|
|
4
|
+
import {
|
|
5
|
+
checkDirtyDeath,
|
|
6
|
+
clearDirtyFlag
|
|
7
|
+
} from "./chunk-F55HGNU4.js";
|
|
8
|
+
|
|
9
|
+
// src/commands/recover.ts
|
|
10
|
+
import * as fs from "fs";
|
|
11
|
+
import * as path from "path";
|
|
12
|
+
var CLAWVAULT_DIR = ".clawvault";
|
|
13
|
+
var CHECKPOINT_FILE = "last-checkpoint.json";
|
|
14
|
+
var CHECKPOINT_HISTORY_DIR = "checkpoints";
|
|
15
|
+
function parseCheckpointFile(filePath) {
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
18
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const record = parsed;
|
|
22
|
+
const timestamp = typeof record.timestamp === "string" ? record.timestamp.trim() : "";
|
|
23
|
+
if (!timestamp) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const checkpoint = {
|
|
27
|
+
timestamp,
|
|
28
|
+
workingOn: typeof record.workingOn === "string" ? record.workingOn : null,
|
|
29
|
+
focus: typeof record.focus === "string" ? record.focus : null,
|
|
30
|
+
blocked: typeof record.blocked === "string" ? record.blocked : null
|
|
31
|
+
};
|
|
32
|
+
if (typeof record.sessionId === "string") {
|
|
33
|
+
checkpoint.sessionId = record.sessionId;
|
|
34
|
+
}
|
|
35
|
+
if (typeof record.sessionKey === "string") {
|
|
36
|
+
checkpoint.sessionKey = record.sessionKey;
|
|
37
|
+
}
|
|
38
|
+
if (typeof record.model === "string") {
|
|
39
|
+
checkpoint.model = record.model;
|
|
40
|
+
}
|
|
41
|
+
if (typeof record.tokenEstimate === "number" && Number.isFinite(record.tokenEstimate)) {
|
|
42
|
+
checkpoint.tokenEstimate = record.tokenEstimate;
|
|
43
|
+
}
|
|
44
|
+
if (typeof record.sessionStartedAt === "string") {
|
|
45
|
+
checkpoint.sessionStartedAt = record.sessionStartedAt;
|
|
46
|
+
}
|
|
47
|
+
if (typeof record.urgent === "boolean") {
|
|
48
|
+
checkpoint.urgent = record.urgent;
|
|
49
|
+
}
|
|
50
|
+
return checkpoint;
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function compareByTimestampDesc(left, right) {
|
|
56
|
+
const leftTime = Date.parse(left.timestamp);
|
|
57
|
+
const rightTime = Date.parse(right.timestamp);
|
|
58
|
+
if (!Number.isNaN(leftTime) && !Number.isNaN(rightTime)) {
|
|
59
|
+
return rightTime - leftTime;
|
|
60
|
+
}
|
|
61
|
+
return right.timestamp.localeCompare(left.timestamp);
|
|
62
|
+
}
|
|
63
|
+
async function checkRecoveryStatus(vaultPath) {
|
|
64
|
+
const { died, checkpoint, deathTime } = await checkDirtyDeath(vaultPath);
|
|
65
|
+
return { died, checkpoint, deathTime };
|
|
66
|
+
}
|
|
67
|
+
function listCheckpoints(vaultPath) {
|
|
68
|
+
const resolvedVaultPath = path.resolve(vaultPath);
|
|
69
|
+
const clawvaultDir = path.join(resolvedVaultPath, CLAWVAULT_DIR);
|
|
70
|
+
const historyDir = path.join(clawvaultDir, CHECKPOINT_HISTORY_DIR);
|
|
71
|
+
const checkpoints = [];
|
|
72
|
+
if (fs.existsSync(historyDir)) {
|
|
73
|
+
const files = fs.readdirSync(historyDir).filter((entry) => entry.endsWith(".json")).sort().reverse();
|
|
74
|
+
for (const fileName of files) {
|
|
75
|
+
const absolutePath = path.join(historyDir, fileName);
|
|
76
|
+
const parsed = parseCheckpointFile(absolutePath);
|
|
77
|
+
if (!parsed) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
checkpoints.push({
|
|
81
|
+
...parsed,
|
|
82
|
+
filePath: absolutePath
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (checkpoints.length === 0) {
|
|
87
|
+
const latestCheckpointPath = path.join(clawvaultDir, CHECKPOINT_FILE);
|
|
88
|
+
if (fs.existsSync(latestCheckpointPath)) {
|
|
89
|
+
const fallback = parseCheckpointFile(latestCheckpointPath);
|
|
90
|
+
if (fallback) {
|
|
91
|
+
checkpoints.push({
|
|
92
|
+
...fallback,
|
|
93
|
+
filePath: latestCheckpointPath
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return checkpoints.sort(compareByTimestampDesc);
|
|
99
|
+
}
|
|
100
|
+
async function recover(vaultPath, options = {}) {
|
|
101
|
+
const { clearFlag = false } = options;
|
|
102
|
+
const { died, checkpoint, deathTime } = await checkRecoveryStatus(vaultPath);
|
|
103
|
+
if (!died) {
|
|
104
|
+
return {
|
|
105
|
+
died: false,
|
|
106
|
+
deathTime: null,
|
|
107
|
+
checkpoint: null,
|
|
108
|
+
handoffPath: null,
|
|
109
|
+
handoffContent: null,
|
|
110
|
+
recoveryMessage: "No context death detected. Clean startup."
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const handoffsDir = path.join(vaultPath, "handoffs");
|
|
114
|
+
let handoffPath = null;
|
|
115
|
+
let handoffContent = null;
|
|
116
|
+
if (fs.existsSync(handoffsDir)) {
|
|
117
|
+
const files = fs.readdirSync(handoffsDir).filter((f) => f.startsWith("handoff-") && f.endsWith(".md")).sort().reverse();
|
|
118
|
+
if (files.length > 0) {
|
|
119
|
+
handoffPath = path.join(handoffsDir, files[0]);
|
|
120
|
+
handoffContent = fs.readFileSync(handoffPath, "utf-8");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
let message = "\u26A0\uFE0F **CONTEXT DEATH DETECTED**\n\n";
|
|
124
|
+
message += `Your previous session died at ${deathTime}.
|
|
125
|
+
|
|
126
|
+
`;
|
|
127
|
+
if (checkpoint) {
|
|
128
|
+
message += "**Last known state:**\n";
|
|
129
|
+
if (checkpoint.workingOn) {
|
|
130
|
+
message += `- Working on: ${checkpoint.workingOn}
|
|
131
|
+
`;
|
|
132
|
+
}
|
|
133
|
+
if (checkpoint.focus) {
|
|
134
|
+
message += `- Focus: ${checkpoint.focus}
|
|
135
|
+
`;
|
|
136
|
+
}
|
|
137
|
+
if (checkpoint.blocked) {
|
|
138
|
+
message += `- Blocked: ${checkpoint.blocked}
|
|
139
|
+
`;
|
|
140
|
+
}
|
|
141
|
+
message += "\n";
|
|
142
|
+
}
|
|
143
|
+
if (handoffPath) {
|
|
144
|
+
message += `**Last handoff:** ${path.basename(handoffPath)}
|
|
145
|
+
`;
|
|
146
|
+
message += "Review and resume from where you left off.\n";
|
|
147
|
+
} else {
|
|
148
|
+
message += "**No handoff found.** You may have lost context.\n";
|
|
149
|
+
}
|
|
150
|
+
if (clearFlag) {
|
|
151
|
+
await clearDirtyFlag(vaultPath);
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
died: true,
|
|
155
|
+
deathTime,
|
|
156
|
+
checkpoint,
|
|
157
|
+
handoffPath,
|
|
158
|
+
handoffContent,
|
|
159
|
+
recoveryMessage: message
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function formatRecoveryCheckStatus(info) {
|
|
163
|
+
if (!info.died) {
|
|
164
|
+
return "\u2713 Dirty death flag is clear.";
|
|
165
|
+
}
|
|
166
|
+
let output = "\u26A0\uFE0F Dirty death flag is set.\n";
|
|
167
|
+
output += `Death time: ${info.deathTime}
|
|
168
|
+
`;
|
|
169
|
+
if (info.checkpoint?.timestamp) {
|
|
170
|
+
const age = formatAge(Date.now() - new Date(info.checkpoint.timestamp).getTime());
|
|
171
|
+
output += `Last checkpoint: ${info.checkpoint.timestamp} (${age} ago)
|
|
172
|
+
`;
|
|
173
|
+
} else {
|
|
174
|
+
output += "Last checkpoint: unavailable\n";
|
|
175
|
+
}
|
|
176
|
+
output += "Use `clawvault recover --clear` after reviewing recovery details.";
|
|
177
|
+
return output;
|
|
178
|
+
}
|
|
179
|
+
function formatCheckpointList(checkpoints) {
|
|
180
|
+
if (checkpoints.length === 0) {
|
|
181
|
+
return "No checkpoints found.";
|
|
182
|
+
}
|
|
183
|
+
const headers = ["TIMESTAMP", "WORKING_ON", "FOCUS", "FILE"];
|
|
184
|
+
const rows = checkpoints.map((checkpoint) => ({
|
|
185
|
+
timestamp: checkpoint.timestamp,
|
|
186
|
+
workingOn: checkpoint.workingOn ?? "-",
|
|
187
|
+
focus: checkpoint.focus ?? "-",
|
|
188
|
+
file: path.basename(checkpoint.filePath)
|
|
189
|
+
}));
|
|
190
|
+
const timestampWidth = Math.max(headers[0].length, ...rows.map((row) => row.timestamp.length));
|
|
191
|
+
const workingOnWidth = Math.max(headers[1].length, ...rows.map((row) => row.workingOn.length));
|
|
192
|
+
const focusWidth = Math.max(headers[2].length, ...rows.map((row) => row.focus.length));
|
|
193
|
+
const fileWidth = Math.max(headers[3].length, ...rows.map((row) => row.file.length));
|
|
194
|
+
const lines = [];
|
|
195
|
+
lines.push(
|
|
196
|
+
`${headers[0].padEnd(timestampWidth)} ${headers[1].padEnd(workingOnWidth)} ${headers[2].padEnd(focusWidth)} ${headers[3].padEnd(fileWidth)}`
|
|
197
|
+
);
|
|
198
|
+
lines.push(
|
|
199
|
+
`${"-".repeat(timestampWidth)} ${"-".repeat(workingOnWidth)} ${"-".repeat(focusWidth)} ${"-".repeat(fileWidth)}`
|
|
200
|
+
);
|
|
201
|
+
for (const row of rows) {
|
|
202
|
+
lines.push(
|
|
203
|
+
`${row.timestamp.padEnd(timestampWidth)} ${row.workingOn.padEnd(workingOnWidth)} ${row.focus.padEnd(focusWidth)} ${row.file}`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
return lines.join("\n");
|
|
207
|
+
}
|
|
208
|
+
function formatRecoveryInfo(info, options = {}) {
|
|
209
|
+
const { verbose = false } = options;
|
|
210
|
+
if (!info.died) {
|
|
211
|
+
return "\u2713 Clean startup - no context death detected.";
|
|
212
|
+
}
|
|
213
|
+
let output = "\n\u26A0\uFE0F CONTEXT DEATH DETECTED\n";
|
|
214
|
+
output += "\u2550".repeat(40) + "\n\n";
|
|
215
|
+
output += `Death time: ${info.deathTime}
|
|
216
|
+
`;
|
|
217
|
+
if (info.checkpoint?.timestamp) {
|
|
218
|
+
const age = formatAge(Date.now() - new Date(info.checkpoint.timestamp).getTime());
|
|
219
|
+
output += `Checkpoint: ${info.checkpoint.timestamp} (${age} ago)
|
|
220
|
+
`;
|
|
221
|
+
}
|
|
222
|
+
output += "\n";
|
|
223
|
+
if (info.checkpoint) {
|
|
224
|
+
output += "Last checkpoint:\n";
|
|
225
|
+
if (info.checkpoint.workingOn) {
|
|
226
|
+
output += ` \u2022 Working on: ${info.checkpoint.workingOn}
|
|
227
|
+
`;
|
|
228
|
+
}
|
|
229
|
+
if (info.checkpoint.focus) {
|
|
230
|
+
output += ` \u2022 Focus: ${info.checkpoint.focus}
|
|
231
|
+
`;
|
|
232
|
+
}
|
|
233
|
+
if (info.checkpoint.blocked) {
|
|
234
|
+
output += ` \u2022 Blocked: ${info.checkpoint.blocked}
|
|
235
|
+
`;
|
|
236
|
+
}
|
|
237
|
+
if (info.checkpoint.sessionKey || info.checkpoint.model || info.checkpoint.tokenEstimate !== void 0) {
|
|
238
|
+
output += " \u2022 Session:\n";
|
|
239
|
+
if (info.checkpoint.sessionKey) {
|
|
240
|
+
output += ` - Key: ${info.checkpoint.sessionKey}
|
|
241
|
+
`;
|
|
242
|
+
}
|
|
243
|
+
if (info.checkpoint.model) {
|
|
244
|
+
output += ` - Model: ${info.checkpoint.model}
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
if (info.checkpoint.tokenEstimate !== void 0) {
|
|
248
|
+
output += ` - Token estimate: ${info.checkpoint.tokenEstimate}
|
|
249
|
+
`;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
output += "\n";
|
|
253
|
+
} else {
|
|
254
|
+
output += "No checkpoint data found.\n\n";
|
|
255
|
+
}
|
|
256
|
+
if (info.handoffPath) {
|
|
257
|
+
output += `Last handoff: ${path.basename(info.handoffPath)}
|
|
258
|
+
`;
|
|
259
|
+
} else {
|
|
260
|
+
output += "No handoff found - context may be lost.\n";
|
|
261
|
+
}
|
|
262
|
+
if (verbose) {
|
|
263
|
+
if (info.checkpoint) {
|
|
264
|
+
output += "\nCheckpoint JSON:\n";
|
|
265
|
+
output += JSON.stringify(info.checkpoint, null, 2) + "\n";
|
|
266
|
+
}
|
|
267
|
+
if (info.handoffContent) {
|
|
268
|
+
output += "\nHandoff content:\n";
|
|
269
|
+
output += info.handoffContent.trim() + "\n";
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
output += "\n" + "\u2550".repeat(40) + "\n";
|
|
273
|
+
output += "Run `clawvault recap` to see full context.\n";
|
|
274
|
+
return output;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export {
|
|
278
|
+
checkRecoveryStatus,
|
|
279
|
+
listCheckpoints,
|
|
280
|
+
recover,
|
|
281
|
+
formatRecoveryCheckStatus,
|
|
282
|
+
formatCheckpointList,
|
|
283
|
+
formatRecoveryInfo
|
|
284
|
+
};
|