@wipcomputer/memory-crystal 0.7.29 → 0.7.32
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 +75 -0
- package/SKILL.md +1 -1
- package/package.json +1 -1
- package/scripts/migrate-lance-to-sqlite.mjs +2 -1
- package/_trash/RELEASE-NOTES-v0-7-23.md +0 -48
- package/_trash/RELEASE-NOTES-v0-7-25.md +0 -24
- package/_trash/RELEASE-NOTES-v0-7-26.md +0 -7
- package/_trash/RELEASE-NOTES-v0-7-28.md +0 -31
- package/_trash/RELEASE-NOTES-v0-7-29.md +0 -28
- package/_trash/RELEASE-NOTES-v0-7-4.md +0 -64
- package/_trash/RELEASE-NOTES-v0-7-5.md +0 -19
- package/dist/bridge.d.ts +0 -7
- package/dist/bridge.js +0 -14
- package/dist/bulk-copy.d.ts +0 -17
- package/dist/bulk-copy.js +0 -90
- package/dist/cc-hook.d.ts +0 -8
- package/dist/cc-hook.js +0 -368
- package/dist/cc-poller.d.ts +0 -1
- package/dist/cc-poller.js +0 -550
- package/dist/chunk-25LXQJ4Z.js +0 -110
- package/dist/chunk-2DRXIRQW.js +0 -97
- package/dist/chunk-2ZNH5F6E.js +0 -1281
- package/dist/chunk-3G3SFYYI.js +0 -288
- package/dist/chunk-3RG5ZIWI.js +0 -10
- package/dist/chunk-3S6TI23B.js +0 -97
- package/dist/chunk-3VFIJYS4.js +0 -818
- package/dist/chunk-52QE3YI3.js +0 -1169
- package/dist/chunk-57RP3DIN.js +0 -1205
- package/dist/chunk-5HSZ4W2P.js +0 -62
- package/dist/chunk-645IPXW3.js +0 -290
- package/dist/chunk-7A7ELD4C.js +0 -1205
- package/dist/chunk-7FYY4GZM.js +0 -1205
- package/dist/chunk-7IUE7ODU.js +0 -254
- package/dist/chunk-7RMLKZIS.js +0 -108
- package/dist/chunk-AA3OPP4Z.js +0 -432
- package/dist/chunk-AEWLSYPH.js +0 -72
- package/dist/chunk-ASSZDR6I.js +0 -108
- package/dist/chunk-AYRJVWUC.js +0 -1205
- package/dist/chunk-CCYI5O3D.js +0 -148
- package/dist/chunk-D3I3ZSE2.js +0 -411
- package/dist/chunk-DACSKLY6.js +0 -219
- package/dist/chunk-DW5B4BL7.js +0 -108
- package/dist/chunk-EKSACBTJ.js +0 -1070
- package/dist/chunk-EXEZZADG.js +0 -248
- package/dist/chunk-F3Y7EL7K.js +0 -83
- package/dist/chunk-FBQWSDPC.js +0 -1328
- package/dist/chunk-FHRZNOMW.js +0 -1205
- package/dist/chunk-IM7N24MT.js +0 -129
- package/dist/chunk-IPNYIXFK.js +0 -1178
- package/dist/chunk-J7MRSZIO.js +0 -167
- package/dist/chunk-JITKI2OI.js +0 -106
- package/dist/chunk-JWZXYVET.js +0 -1068
- package/dist/chunk-KCQUXVYT.js +0 -108
- package/dist/chunk-KOQ43OX6.js +0 -1281
- package/dist/chunk-KYVWO6ZM.js +0 -1069
- package/dist/chunk-L3VHARQH.js +0 -413
- package/dist/chunk-LBWDS6BE.js +0 -288
- package/dist/chunk-LOVAHSQV.js +0 -411
- package/dist/chunk-LQOYCAGG.js +0 -446
- package/dist/chunk-LWAIPJ2W.js +0 -146
- package/dist/chunk-M5DHKW7M.js +0 -127
- package/dist/chunk-MBKCIJHM.js +0 -1328
- package/dist/chunk-MK42FMEG.js +0 -147
- package/dist/chunk-MOBMYHKL.js +0 -1205
- package/dist/chunk-MPLTNMRG.js +0 -67
- package/dist/chunk-NIJCVN3O.js +0 -147
- package/dist/chunk-NZCFSZQ7.js +0 -1205
- package/dist/chunk-O2UITJGH.js +0 -465
- package/dist/chunk-OCRA44AZ.js +0 -108
- package/dist/chunk-P3KJR66H.js +0 -117
- package/dist/chunk-PEK6JH65.js +0 -432
- package/dist/chunk-PJ6FFKEX.js +0 -77
- package/dist/chunk-PLUBBZYR.js +0 -800
- package/dist/chunk-PNKVD2UK.js +0 -26
- package/dist/chunk-PSQZURHO.js +0 -229
- package/dist/chunk-SGL6ISBJ.js +0 -1061
- package/dist/chunk-SJABZZT5.js +0 -97
- package/dist/chunk-TD3P3K32.js +0 -1199
- package/dist/chunk-TMDZJJKV.js +0 -288
- package/dist/chunk-UNHVZB5G.js +0 -411
- package/dist/chunk-VAFTWSTE.js +0 -1061
- package/dist/chunk-VNFXFQBB.js +0 -217
- package/dist/chunk-X3GVFKSJ.js +0 -1205
- package/dist/chunk-XZ3S56RQ.js +0 -1061
- package/dist/chunk-Y72C7F6O.js +0 -148
- package/dist/chunk-YLICP577.js +0 -1205
- package/dist/chunk-YX6AXLVK.js +0 -159
- package/dist/chunk-ZCQYHTNU.js +0 -146
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +0 -1105
- package/dist/cloud-crystal.js +0 -6
- package/dist/core.d.ts +0 -232
- package/dist/core.js +0 -12
- package/dist/crypto.d.ts +0 -20
- package/dist/crypto.js +0 -27
- package/dist/crystal-capture.sh +0 -29
- package/dist/crystal-serve.d.ts +0 -4
- package/dist/crystal-serve.js +0 -252
- package/dist/dev-update-SZ2Z4WCQ.js +0 -6
- package/dist/discover.d.ts +0 -30
- package/dist/discover.js +0 -177
- package/dist/doctor.d.ts +0 -9
- package/dist/doctor.js +0 -334
- package/dist/dream-weaver.d.ts +0 -8
- package/dist/dream-weaver.js +0 -56
- package/dist/file-sync.d.ts +0 -48
- package/dist/file-sync.js +0 -18
- package/dist/installer.d.ts +0 -61
- package/dist/installer.js +0 -676
- package/dist/ldm-backup.sh +0 -116
- package/dist/ldm.d.ts +0 -50
- package/dist/ldm.js +0 -32
- package/dist/mcp-server.d.ts +0 -1
- package/dist/mcp-server.js +0 -265
- package/dist/migrate.d.ts +0 -1
- package/dist/migrate.js +0 -89
- package/dist/mirror-sync.d.ts +0 -1
- package/dist/mirror-sync.js +0 -159
- package/dist/oc-backfill.d.ts +0 -19
- package/dist/oc-backfill.js +0 -74
- package/dist/openclaw.d.ts +0 -5
- package/dist/openclaw.js +0 -423
- package/dist/pair.d.ts +0 -4
- package/dist/pair.js +0 -75
- package/dist/poller.d.ts +0 -1
- package/dist/poller.js +0 -634
- package/dist/role.d.ts +0 -24
- package/dist/role.js +0 -13
- package/dist/search-pipeline-4K4OJSSS.js +0 -255
- package/dist/search-pipeline-4PRS6LI7.js +0 -280
- package/dist/search-pipeline-7UJMXPLO.js +0 -280
- package/dist/search-pipeline-DQTRLGBH.js +0 -74
- package/dist/search-pipeline-HNG37REH.js +0 -282
- package/dist/search-pipeline-IZFPLBUB.js +0 -280
- package/dist/search-pipeline-MID6F26Q.js +0 -73
- package/dist/search-pipeline-N52JZFNN.js +0 -282
- package/dist/search-pipeline-OPB2PRQQ.js +0 -280
- package/dist/search-pipeline-VXTE5HAD.js +0 -262
- package/dist/search-pipeline-XHFKADRG.js +0 -73
- package/dist/staging.d.ts +0 -29
- package/dist/staging.js +0 -21
- package/dist/summarize.d.ts +0 -19
- package/dist/summarize.js +0 -10
- package/dist/worker-demo.js +0 -186
- package/dist/worker-mcp.js +0 -404
- package/dist/worker.js +0 -137
package/dist/cc-poller.js
DELETED
|
@@ -1,550 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
createCrystal,
|
|
4
|
-
resolveConfig
|
|
5
|
-
} from "./chunk-FBQWSDPC.js";
|
|
6
|
-
import {
|
|
7
|
-
ensureLdm,
|
|
8
|
-
ldmPaths,
|
|
9
|
-
resolveStatePath,
|
|
10
|
-
stateWritePath
|
|
11
|
-
} from "./chunk-EXEZZADG.js";
|
|
12
|
-
|
|
13
|
-
// src/cc-poller.ts
|
|
14
|
-
import {
|
|
15
|
-
readFileSync,
|
|
16
|
-
writeFileSync,
|
|
17
|
-
appendFileSync,
|
|
18
|
-
existsSync,
|
|
19
|
-
mkdirSync,
|
|
20
|
-
statSync,
|
|
21
|
-
openSync,
|
|
22
|
-
readSync,
|
|
23
|
-
closeSync,
|
|
24
|
-
copyFileSync,
|
|
25
|
-
readdirSync
|
|
26
|
-
} from "fs";
|
|
27
|
-
import { join, basename } from "path";
|
|
28
|
-
var HOME = process.env.HOME || "";
|
|
29
|
-
var CC_AGENT_ID = process.env.CRYSTAL_AGENT_ID || "cc-mini";
|
|
30
|
-
var PRIVATE_MODE_PATH = resolveStatePath("memory-capture-state.json");
|
|
31
|
-
var WATERMARK_PATH = resolveStatePath("cc-capture-watermark.json");
|
|
32
|
-
var CC_ENABLED_PATH = resolveStatePath("cc-capture-enabled.json");
|
|
33
|
-
var CC_PROJECTS_DIR = join(HOME, ".claude", "projects");
|
|
34
|
-
var SESSION_EXPORT_DIR = process.env.CRYSTAL_SESSION_EXPORT_DIR || join(ldmPaths().agentRoot, "memory", "sessions");
|
|
35
|
-
var EXPORT_WATERMARK_PATH = resolveStatePath("cc-export-watermark.json");
|
|
36
|
-
function isPrivateMode() {
|
|
37
|
-
try {
|
|
38
|
-
if (existsSync(PRIVATE_MODE_PATH)) {
|
|
39
|
-
const state = JSON.parse(readFileSync(PRIVATE_MODE_PATH, "utf-8"));
|
|
40
|
-
return state.enabled === false;
|
|
41
|
-
}
|
|
42
|
-
} catch {
|
|
43
|
-
}
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
function isCaptureEnabled() {
|
|
47
|
-
try {
|
|
48
|
-
if (existsSync(CC_ENABLED_PATH)) {
|
|
49
|
-
const state = JSON.parse(readFileSync(CC_ENABLED_PATH, "utf-8"));
|
|
50
|
-
return state.enabled !== false;
|
|
51
|
-
}
|
|
52
|
-
} catch {
|
|
53
|
-
}
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
function loadWatermark() {
|
|
57
|
-
try {
|
|
58
|
-
if (existsSync(WATERMARK_PATH)) {
|
|
59
|
-
return JSON.parse(readFileSync(WATERMARK_PATH, "utf-8"));
|
|
60
|
-
}
|
|
61
|
-
} catch {
|
|
62
|
-
}
|
|
63
|
-
return { files: {}, lastRun: null };
|
|
64
|
-
}
|
|
65
|
-
function saveWatermark(wm) {
|
|
66
|
-
const writePath = stateWritePath("cc-capture-watermark.json");
|
|
67
|
-
wm.lastRun = (/* @__PURE__ */ new Date()).toISOString();
|
|
68
|
-
writeFileSync(writePath, JSON.stringify(wm, null, 2));
|
|
69
|
-
}
|
|
70
|
-
function extractMessages(filePath, lastByteOffset) {
|
|
71
|
-
const fileSize = statSync(filePath).size;
|
|
72
|
-
if (lastByteOffset >= fileSize) {
|
|
73
|
-
return { messages: [], newByteOffset: fileSize };
|
|
74
|
-
}
|
|
75
|
-
const fd = openSync(filePath, "r");
|
|
76
|
-
const bufSize = fileSize - lastByteOffset;
|
|
77
|
-
const buf = Buffer.alloc(bufSize);
|
|
78
|
-
readSync(fd, buf, 0, bufSize, lastByteOffset);
|
|
79
|
-
closeSync(fd);
|
|
80
|
-
const lines = buf.toString("utf-8").split("\n").filter(Boolean);
|
|
81
|
-
const messages = [];
|
|
82
|
-
for (const line of lines) {
|
|
83
|
-
try {
|
|
84
|
-
const obj = JSON.parse(line);
|
|
85
|
-
if (obj.type !== "user" && obj.type !== "assistant") continue;
|
|
86
|
-
const msg = obj.message;
|
|
87
|
-
if (!msg) continue;
|
|
88
|
-
let text = "";
|
|
89
|
-
if (typeof msg.content === "string") {
|
|
90
|
-
text = msg.content;
|
|
91
|
-
} else if (Array.isArray(msg.content)) {
|
|
92
|
-
const parts = [];
|
|
93
|
-
for (const block of msg.content) {
|
|
94
|
-
if (block.type === "text" && block.text) parts.push(block.text);
|
|
95
|
-
if (block.type === "thinking" && block.thinking) parts.push(`[thinking] ${block.thinking}`);
|
|
96
|
-
}
|
|
97
|
-
text = parts.join("\n\n");
|
|
98
|
-
}
|
|
99
|
-
if (text.length < 20) continue;
|
|
100
|
-
messages.push({
|
|
101
|
-
role: msg.role || obj.type,
|
|
102
|
-
text,
|
|
103
|
-
timestamp: obj.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
|
|
104
|
-
sessionId: obj.sessionId || "unknown"
|
|
105
|
-
});
|
|
106
|
-
} catch {
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return { messages, newByteOffset: fileSize };
|
|
110
|
-
}
|
|
111
|
-
function discoverSessionFiles() {
|
|
112
|
-
const files = [];
|
|
113
|
-
if (!existsSync(CC_PROJECTS_DIR)) return files;
|
|
114
|
-
try {
|
|
115
|
-
for (const projectDir of readdirSync(CC_PROJECTS_DIR)) {
|
|
116
|
-
const projectPath = join(CC_PROJECTS_DIR, projectDir);
|
|
117
|
-
try {
|
|
118
|
-
if (!statSync(projectPath).isDirectory()) continue;
|
|
119
|
-
} catch {
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
try {
|
|
123
|
-
for (const file of readdirSync(projectPath)) {
|
|
124
|
-
if (file.endsWith(".jsonl") && !file.startsWith(".")) {
|
|
125
|
-
files.push(join(projectPath, file));
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
} catch {
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
} catch {
|
|
133
|
-
}
|
|
134
|
-
return files;
|
|
135
|
-
}
|
|
136
|
-
function appendDailyLog(messages, sessionFile) {
|
|
137
|
-
try {
|
|
138
|
-
const paths = ldmPaths();
|
|
139
|
-
if (!existsSync(paths.root)) return;
|
|
140
|
-
if (!existsSync(paths.daily)) mkdirSync(paths.daily, { recursive: true });
|
|
141
|
-
const now = /* @__PURE__ */ new Date();
|
|
142
|
-
const dateStr = now.toISOString().slice(0, 10);
|
|
143
|
-
const timeStr = now.toLocaleTimeString("en-US", {
|
|
144
|
-
hour: "2-digit",
|
|
145
|
-
minute: "2-digit",
|
|
146
|
-
hour12: false,
|
|
147
|
-
timeZone: "America/Los_Angeles"
|
|
148
|
-
});
|
|
149
|
-
const logPath = join(paths.daily, `${dateStr}.md`);
|
|
150
|
-
const userMsg = messages.find((m) => m.role === "user");
|
|
151
|
-
if (!userMsg) return;
|
|
152
|
-
const snippet = userMsg.text.slice(0, 120).replace(/\n/g, " ").trim();
|
|
153
|
-
const sessionId = basename(sessionFile, ".jsonl").slice(0, 8);
|
|
154
|
-
const line = `- **${timeStr}** [${sessionId}] ${snippet}${userMsg.text.length > 120 ? "..." : ""}
|
|
155
|
-
`;
|
|
156
|
-
if (!existsSync(logPath)) {
|
|
157
|
-
writeFileSync(logPath, `# ${dateStr} - CC Daily Log
|
|
158
|
-
|
|
159
|
-
`);
|
|
160
|
-
}
|
|
161
|
-
appendFileSync(logPath, line);
|
|
162
|
-
} catch {
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
function archiveTranscript(transcriptPath) {
|
|
166
|
-
try {
|
|
167
|
-
if (isPrivateMode()) return;
|
|
168
|
-
const paths = ensureLdm();
|
|
169
|
-
const dest = join(paths.transcripts, basename(transcriptPath));
|
|
170
|
-
if (existsSync(dest)) {
|
|
171
|
-
const srcMtime = statSync(transcriptPath).mtimeMs;
|
|
172
|
-
const dstMtime = statSync(dest).mtimeMs;
|
|
173
|
-
if (srcMtime <= dstMtime) return;
|
|
174
|
-
}
|
|
175
|
-
copyFileSync(transcriptPath, dest);
|
|
176
|
-
} catch {
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
function loadExportWatermark() {
|
|
180
|
-
try {
|
|
181
|
-
if (existsSync(EXPORT_WATERMARK_PATH)) {
|
|
182
|
-
return JSON.parse(readFileSync(EXPORT_WATERMARK_PATH, "utf-8"));
|
|
183
|
-
}
|
|
184
|
-
} catch {
|
|
185
|
-
}
|
|
186
|
-
return {};
|
|
187
|
-
}
|
|
188
|
-
function saveExportWatermark(data) {
|
|
189
|
-
const writePath = stateWritePath("cc-export-watermark.json");
|
|
190
|
-
writeFileSync(writePath, JSON.stringify(data, null, 2));
|
|
191
|
-
}
|
|
192
|
-
function formatTimestamp(ts) {
|
|
193
|
-
if (!ts) return "";
|
|
194
|
-
return new Date(ts).toLocaleString("en-US", {
|
|
195
|
-
weekday: "short",
|
|
196
|
-
year: "numeric",
|
|
197
|
-
month: "short",
|
|
198
|
-
day: "numeric",
|
|
199
|
-
hour: "2-digit",
|
|
200
|
-
minute: "2-digit",
|
|
201
|
-
timeZoneName: "short"
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
function exportSessionToMarkdown(filePath) {
|
|
205
|
-
try {
|
|
206
|
-
const exportWm = loadExportWatermark();
|
|
207
|
-
const fileName = basename(filePath);
|
|
208
|
-
const currentSize = statSync(filePath).size;
|
|
209
|
-
const lastSize = exportWm[fileName] || 0;
|
|
210
|
-
if (currentSize === lastSize) return;
|
|
211
|
-
if (!existsSync(SESSION_EXPORT_DIR)) mkdirSync(SESSION_EXPORT_DIR, { recursive: true });
|
|
212
|
-
const content = readFileSync(filePath, "utf-8");
|
|
213
|
-
const lines = content.split("\n").filter((l) => l.trim());
|
|
214
|
-
const sessionId = basename(filePath, ".jsonl");
|
|
215
|
-
let firstTs = null;
|
|
216
|
-
let lastTs = null;
|
|
217
|
-
let model = "unknown";
|
|
218
|
-
const turns = [];
|
|
219
|
-
for (const line of lines) {
|
|
220
|
-
let entry;
|
|
221
|
-
try {
|
|
222
|
-
entry = JSON.parse(line);
|
|
223
|
-
} catch {
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
if (entry.type === "file-history-snapshot") continue;
|
|
227
|
-
if (entry.type === "summary") {
|
|
228
|
-
turns.push({ role: "system", content: `*[Session continued. Summary provided.]*
|
|
229
|
-
|
|
230
|
-
${entry.summary || ""}`, timestamp: entry.timestamp });
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
const ts = entry.timestamp;
|
|
234
|
-
if (ts && !firstTs) firstTs = ts;
|
|
235
|
-
if (ts) lastTs = ts;
|
|
236
|
-
if (entry.message?.model) model = entry.message.model;
|
|
237
|
-
if (entry.type === "user") {
|
|
238
|
-
const text = extractContentText(entry.message?.content);
|
|
239
|
-
if (text && !text.startsWith("<system-reminder>")) {
|
|
240
|
-
turns.push({ role: "human", content: text, timestamp: ts });
|
|
241
|
-
}
|
|
242
|
-
} else if (entry.type === "assistant") {
|
|
243
|
-
const text = extractContentText(entry.message?.content);
|
|
244
|
-
if (text) turns.push({ role: "assistant", content: text, timestamp: ts });
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
let md = `# Claude Code Session
|
|
248
|
-
|
|
249
|
-
`;
|
|
250
|
-
md += `**Session ID:** \`${sessionId}\`
|
|
251
|
-
`;
|
|
252
|
-
md += `**Model:** ${model}
|
|
253
|
-
`;
|
|
254
|
-
md += `**Started:** ${formatTimestamp(firstTs)}
|
|
255
|
-
`;
|
|
256
|
-
md += `**Ended:** ${formatTimestamp(lastTs)}
|
|
257
|
-
`;
|
|
258
|
-
md += `**Turns:** ${turns.length}
|
|
259
|
-
|
|
260
|
-
---
|
|
261
|
-
|
|
262
|
-
`;
|
|
263
|
-
for (const t of turns) {
|
|
264
|
-
const time = t.timestamp ? `*${formatTimestamp(t.timestamp)}*` : "";
|
|
265
|
-
if (t.role === "human") {
|
|
266
|
-
md += `## Parker
|
|
267
|
-
${time ? time + "\n\n" : ""}${t.content}
|
|
268
|
-
|
|
269
|
-
`;
|
|
270
|
-
} else if (t.role === "assistant") {
|
|
271
|
-
md += `## Claude Code
|
|
272
|
-
${time ? time + "\n\n" : ""}${t.content}
|
|
273
|
-
|
|
274
|
-
`;
|
|
275
|
-
} else {
|
|
276
|
-
md += `---
|
|
277
|
-
|
|
278
|
-
${t.content}
|
|
279
|
-
|
|
280
|
-
---
|
|
281
|
-
|
|
282
|
-
`;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
const mtime = statSync(filePath).mtime;
|
|
286
|
-
const date = mtime.toISOString().split("T")[0];
|
|
287
|
-
const shortId = sessionId.slice(0, 8);
|
|
288
|
-
const outPath = join(SESSION_EXPORT_DIR, `${date}-session-${shortId}.md`);
|
|
289
|
-
writeFileSync(outPath, md);
|
|
290
|
-
exportWm[fileName] = currentSize;
|
|
291
|
-
saveExportWatermark(exportWm);
|
|
292
|
-
} catch {
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
function extractContentText(content) {
|
|
296
|
-
if (typeof content === "string") return content;
|
|
297
|
-
if (Array.isArray(content)) {
|
|
298
|
-
return content.map((b) => {
|
|
299
|
-
if (typeof b === "string") return b;
|
|
300
|
-
if (b.type === "text") return b.text;
|
|
301
|
-
if (b.type === "tool_use") return `\`[Tool: ${b.name}]\``;
|
|
302
|
-
return "";
|
|
303
|
-
}).filter(Boolean).join("\n\n");
|
|
304
|
-
}
|
|
305
|
-
return "";
|
|
306
|
-
}
|
|
307
|
-
var BATCH_SIZE = 200;
|
|
308
|
-
async function ingestLocal(messages, crystal) {
|
|
309
|
-
const maxSingleChunkChars = 2e3 * 4;
|
|
310
|
-
const chunks = [];
|
|
311
|
-
for (const msg of messages) {
|
|
312
|
-
if (msg.text.length <= maxSingleChunkChars) {
|
|
313
|
-
chunks.push({
|
|
314
|
-
text: msg.text,
|
|
315
|
-
role: msg.role,
|
|
316
|
-
source_type: "conversation",
|
|
317
|
-
source_id: `cc:${msg.sessionId}`,
|
|
318
|
-
agent_id: CC_AGENT_ID,
|
|
319
|
-
token_count: Math.ceil(msg.text.length / 4),
|
|
320
|
-
created_at: msg.timestamp
|
|
321
|
-
});
|
|
322
|
-
} else {
|
|
323
|
-
for (const ct of crystal.chunkText(msg.text)) {
|
|
324
|
-
chunks.push({
|
|
325
|
-
text: ct,
|
|
326
|
-
role: msg.role,
|
|
327
|
-
source_type: "conversation",
|
|
328
|
-
source_id: `cc:${msg.sessionId}`,
|
|
329
|
-
agent_id: CC_AGENT_ID,
|
|
330
|
-
token_count: Math.ceil(ct.length / 4),
|
|
331
|
-
created_at: msg.timestamp
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
let total = 0;
|
|
337
|
-
for (let i = 0; i < chunks.length; i += BATCH_SIZE) {
|
|
338
|
-
const batch = chunks.slice(i, i + BATCH_SIZE);
|
|
339
|
-
let retries = 0;
|
|
340
|
-
while (retries < 4) {
|
|
341
|
-
try {
|
|
342
|
-
total += await crystal.ingest(batch);
|
|
343
|
-
break;
|
|
344
|
-
} catch (err) {
|
|
345
|
-
retries++;
|
|
346
|
-
if (retries >= 4) throw err;
|
|
347
|
-
const delay = Math.min(1e3 * 2 ** retries, 3e4);
|
|
348
|
-
process.stderr.write(` [retry ${retries}] ${err.message}, waiting ${delay}ms
|
|
349
|
-
`);
|
|
350
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
return total;
|
|
355
|
-
}
|
|
356
|
-
async function pollOnce() {
|
|
357
|
-
if (isPrivateMode() || !isCaptureEnabled()) {
|
|
358
|
-
return { filesScanned: 0, chunksIngested: 0, errors: [] };
|
|
359
|
-
}
|
|
360
|
-
const wm = loadWatermark();
|
|
361
|
-
const sessionFiles = discoverSessionFiles();
|
|
362
|
-
let totalChunks = 0;
|
|
363
|
-
let filesScanned = 0;
|
|
364
|
-
const errors = [];
|
|
365
|
-
let crystal = null;
|
|
366
|
-
for (const filePath of sessionFiles) {
|
|
367
|
-
filesScanned++;
|
|
368
|
-
if (!wm.files[filePath]) {
|
|
369
|
-
wm.files[filePath] = { lastByteOffset: 0, lastTimestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
370
|
-
}
|
|
371
|
-
const lastOffset = wm.files[filePath].lastByteOffset;
|
|
372
|
-
const fileSize = statSync(filePath).size;
|
|
373
|
-
if (lastOffset >= fileSize) continue;
|
|
374
|
-
const { messages, newByteOffset } = extractMessages(filePath, lastOffset);
|
|
375
|
-
if (messages.length === 0) {
|
|
376
|
-
wm.files[filePath] = { lastByteOffset: newByteOffset, lastTimestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
377
|
-
continue;
|
|
378
|
-
}
|
|
379
|
-
const totalTokens = messages.reduce((sum, m) => sum + Math.ceil(m.text.length / 4), 0);
|
|
380
|
-
if (totalTokens < 100) {
|
|
381
|
-
wm.files[filePath] = { lastByteOffset: newByteOffset, lastTimestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
382
|
-
continue;
|
|
383
|
-
}
|
|
384
|
-
try {
|
|
385
|
-
if (!crystal) {
|
|
386
|
-
const config = resolveConfig();
|
|
387
|
-
crystal = createCrystal(config);
|
|
388
|
-
await crystal.init();
|
|
389
|
-
}
|
|
390
|
-
const count = await ingestLocal(messages, crystal);
|
|
391
|
-
totalChunks += count;
|
|
392
|
-
wm.files[filePath] = { lastByteOffset: newByteOffset, lastTimestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
393
|
-
archiveTranscript(filePath);
|
|
394
|
-
appendDailyLog(messages, filePath);
|
|
395
|
-
exportSessionToMarkdown(filePath);
|
|
396
|
-
process.stderr.write(`[cc-poller] ${count} chunks (${totalTokens} tokens) from ${basename(filePath)}
|
|
397
|
-
`);
|
|
398
|
-
} catch (err) {
|
|
399
|
-
errors.push(`${basename(filePath)}: ${err.message}`);
|
|
400
|
-
process.stderr.write(`[cc-poller] error on ${basename(filePath)}: ${err.message}
|
|
401
|
-
`);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
saveWatermark(wm);
|
|
405
|
-
return { filesScanned, chunksIngested: totalChunks, errors };
|
|
406
|
-
}
|
|
407
|
-
function showStatus() {
|
|
408
|
-
const wm = loadWatermark();
|
|
409
|
-
const sessionFiles = discoverSessionFiles();
|
|
410
|
-
console.log("CC Poller Status");
|
|
411
|
-
console.log("================\n");
|
|
412
|
-
console.log(`Capture: ${isCaptureEnabled() ? "ON" : "OFF"}`);
|
|
413
|
-
console.log(`Private mode: ${isPrivateMode() ? "ON (blocks capture)" : "OFF"}`);
|
|
414
|
-
console.log(`Agent ID: ${CC_AGENT_ID}`);
|
|
415
|
-
console.log(`Last run: ${wm.lastRun || "never"}`);
|
|
416
|
-
console.log(`
|
|
417
|
-
Session files: ${sessionFiles.length}
|
|
418
|
-
`);
|
|
419
|
-
for (const filePath of sessionFiles) {
|
|
420
|
-
const fileSize = statSync(filePath).size;
|
|
421
|
-
const wmEntry = wm.files[filePath];
|
|
422
|
-
const lastOffset = wmEntry?.lastByteOffset || 0;
|
|
423
|
-
const behind = fileSize - lastOffset;
|
|
424
|
-
const mtime = statSync(filePath).mtime;
|
|
425
|
-
const age = Date.now() - mtime.getTime();
|
|
426
|
-
const ageStr = age < 6e4 ? `${Math.round(age / 1e3)}s ago` : age < 36e5 ? `${Math.round(age / 6e4)}m ago` : age < 864e5 ? `${Math.round(age / 36e5)}h ago` : `${Math.round(age / 864e5)}d ago`;
|
|
427
|
-
const status = behind === 0 ? "IN SYNC" : behind < 1024 ? `${behind}B behind` : behind < 1048576 ? `${(behind / 1024).toFixed(1)}KB behind` : `${(behind / 1048576).toFixed(1)}MB behind`;
|
|
428
|
-
const statusIcon = behind === 0 ? "OK" : behind > 1048576 ? "CRITICAL" : "BEHIND";
|
|
429
|
-
console.log(` ${basename(filePath, ".jsonl").slice(0, 8)} ${(fileSize / 1048576).toFixed(1)}MB modified ${ageStr} ${status} ${statusIcon}`);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
async function healthCheck() {
|
|
433
|
-
const wm = loadWatermark();
|
|
434
|
-
const sessionFiles = discoverSessionFiles();
|
|
435
|
-
console.log("Memory Crystal Health Check");
|
|
436
|
-
console.log("===========================\n");
|
|
437
|
-
console.log("JSONL Sessions:");
|
|
438
|
-
let activeFiles = 0;
|
|
439
|
-
let totalBehind = 0;
|
|
440
|
-
for (const filePath of sessionFiles) {
|
|
441
|
-
const fileSize = statSync(filePath).size;
|
|
442
|
-
const mtime = statSync(filePath).mtime;
|
|
443
|
-
const age = Date.now() - mtime.getTime();
|
|
444
|
-
const isActive = age < 72e5;
|
|
445
|
-
if (isActive) activeFiles++;
|
|
446
|
-
const wmEntry = wm.files[filePath];
|
|
447
|
-
const lastOffset = wmEntry?.lastByteOffset || 0;
|
|
448
|
-
const behind = fileSize - lastOffset;
|
|
449
|
-
totalBehind += behind;
|
|
450
|
-
if (isActive || behind > 0) {
|
|
451
|
-
const ageStr = age < 36e5 ? `${Math.round(age / 6e4)}m ago` : `${Math.round(age / 36e5)}h ago`;
|
|
452
|
-
const statusIcon = behind === 0 ? "OK" : behind > 1048576 ? "CRITICAL" : "WARNING";
|
|
453
|
-
console.log(` ${basename(filePath, ".jsonl").slice(0, 8)} ${(fileSize / 1048576).toFixed(1)}MB last write: ${ageStr} ${statusIcon}`);
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
console.log("\nDaily MD Logs:");
|
|
457
|
-
const paths = ldmPaths();
|
|
458
|
-
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
459
|
-
const todayLog = join(paths.daily, `${today}.md`);
|
|
460
|
-
if (existsSync(todayLog)) {
|
|
461
|
-
const content = readFileSync(todayLog, "utf-8");
|
|
462
|
-
const lineCount = content.split("\n").filter((l) => l.startsWith("- ")).length;
|
|
463
|
-
console.log(` ${today}.md exists, ${lineCount} entries OK`);
|
|
464
|
-
} else if (activeFiles > 0) {
|
|
465
|
-
console.log(` ${today}.md MISSING WARNING: active sessions but no daily log`);
|
|
466
|
-
} else {
|
|
467
|
-
console.log(` ${today}.md not created (no active sessions) OK`);
|
|
468
|
-
}
|
|
469
|
-
console.log("\nCrystal Chunks:");
|
|
470
|
-
try {
|
|
471
|
-
const config = resolveConfig();
|
|
472
|
-
const crystal = createCrystal(config);
|
|
473
|
-
await crystal.init();
|
|
474
|
-
const status = await crystal.status();
|
|
475
|
-
console.log(` Total chunks: ${status.chunks}`);
|
|
476
|
-
console.log(` Total memories: ${status.memories}`);
|
|
477
|
-
if (crystal.db) {
|
|
478
|
-
const recentRow = crystal.db.prepare(
|
|
479
|
-
"SELECT COUNT(*) as cnt FROM chunks WHERE created_at > datetime('now', '-2 hours')"
|
|
480
|
-
).get();
|
|
481
|
-
const latestRow = crystal.db.prepare(
|
|
482
|
-
"SELECT MAX(created_at) as latest FROM chunks"
|
|
483
|
-
).get();
|
|
484
|
-
if (recentRow.cnt > 0) {
|
|
485
|
-
console.log(` Chunks in last 2h: ${recentRow.cnt} OK`);
|
|
486
|
-
} else if (activeFiles > 0) {
|
|
487
|
-
console.log(` Chunks in last 2h: 0 CRITICAL: active sessions but no recent chunks`);
|
|
488
|
-
}
|
|
489
|
-
console.log(` Latest chunk: ${latestRow.latest || "none"}`);
|
|
490
|
-
}
|
|
491
|
-
} catch (err) {
|
|
492
|
-
console.log(` Error checking Crystal: ${err.message} CRITICAL`);
|
|
493
|
-
}
|
|
494
|
-
console.log("\nSync Status:");
|
|
495
|
-
if (totalBehind === 0) {
|
|
496
|
-
console.log(" All sessions in sync OK");
|
|
497
|
-
} else {
|
|
498
|
-
const behindStr = totalBehind > 1048576 ? `${(totalBehind / 1048576).toFixed(1)}MB` : `${(totalBehind / 1024).toFixed(1)}KB`;
|
|
499
|
-
console.log(` Total data behind: ${behindStr} ${totalBehind > 1048576 ? "CRITICAL" : "WARNING"}`);
|
|
500
|
-
}
|
|
501
|
-
console.log(`
|
|
502
|
-
Last poller run: ${wm.lastRun || "never"}`);
|
|
503
|
-
}
|
|
504
|
-
async function watchMode() {
|
|
505
|
-
const intervalMs = parseInt(process.env.CRYSTAL_POLL_INTERVAL || "30000", 10);
|
|
506
|
-
process.stderr.write(`[cc-poller] watching every ${intervalMs / 1e3}s. Ctrl+C to stop.
|
|
507
|
-
`);
|
|
508
|
-
while (true) {
|
|
509
|
-
try {
|
|
510
|
-
const result = await pollOnce();
|
|
511
|
-
if (result.chunksIngested > 0) {
|
|
512
|
-
process.stderr.write(`[cc-poller] ingested ${result.chunksIngested} chunks from ${result.filesScanned} files
|
|
513
|
-
`);
|
|
514
|
-
}
|
|
515
|
-
} catch (err) {
|
|
516
|
-
process.stderr.write(`[cc-poller] poll error: ${err.message}
|
|
517
|
-
`);
|
|
518
|
-
}
|
|
519
|
-
await new Promise((r) => setTimeout(r, intervalMs));
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
var args = process.argv.slice(2);
|
|
523
|
-
if (args.includes("--status")) {
|
|
524
|
-
showStatus();
|
|
525
|
-
process.exit(0);
|
|
526
|
-
}
|
|
527
|
-
if (args.includes("--health")) {
|
|
528
|
-
healthCheck().then(() => process.exit(0)).catch((err) => {
|
|
529
|
-
console.error(err.message);
|
|
530
|
-
process.exit(1);
|
|
531
|
-
});
|
|
532
|
-
} else if (args.includes("--watch")) {
|
|
533
|
-
watchMode().catch((err) => {
|
|
534
|
-
process.stderr.write(`[cc-poller] fatal: ${err.message}
|
|
535
|
-
`);
|
|
536
|
-
process.exit(1);
|
|
537
|
-
});
|
|
538
|
-
} else {
|
|
539
|
-
pollOnce().then((result) => {
|
|
540
|
-
if (result.chunksIngested > 0 || result.errors.length > 0) {
|
|
541
|
-
process.stderr.write(`[cc-poller] done: ${result.chunksIngested} chunks, ${result.filesScanned} files, ${result.errors.length} errors
|
|
542
|
-
`);
|
|
543
|
-
}
|
|
544
|
-
process.exit(result.errors.length > 0 ? 1 : 0);
|
|
545
|
-
}).catch((err) => {
|
|
546
|
-
process.stderr.write(`[cc-poller] fatal: ${err.message}
|
|
547
|
-
`);
|
|
548
|
-
process.exit(1);
|
|
549
|
-
});
|
|
550
|
-
}
|
package/dist/chunk-25LXQJ4Z.js
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
init_ldm,
|
|
3
|
-
resolveSecretPath
|
|
4
|
-
} from "./chunk-PSQZURHO.js";
|
|
5
|
-
|
|
6
|
-
// src/crypto.ts
|
|
7
|
-
init_ldm();
|
|
8
|
-
import { readFileSync, existsSync } from "fs";
|
|
9
|
-
import { createCipheriv, createDecipheriv, createHmac, randomBytes, hkdfSync } from "crypto";
|
|
10
|
-
import { createHash } from "crypto";
|
|
11
|
-
var KEY_PATH = process.env.CRYSTAL_RELAY_KEY_PATH || resolveSecretPath("crystal-relay-key");
|
|
12
|
-
function loadRelayKey() {
|
|
13
|
-
if (!existsSync(KEY_PATH)) {
|
|
14
|
-
throw new Error(
|
|
15
|
-
`Relay key not found at ${KEY_PATH}
|
|
16
|
-
Generate one: openssl rand -base64 32 > ${KEY_PATH} && chmod 600 ${KEY_PATH}
|
|
17
|
-
Copy the same key to all trusted machines.`
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
const raw = readFileSync(KEY_PATH, "utf-8").trim();
|
|
21
|
-
const key = Buffer.from(raw, "base64");
|
|
22
|
-
if (key.length !== 32) {
|
|
23
|
-
throw new Error(`Relay key must be 32 bytes (256 bits). Got ${key.length} bytes. Regenerate with: openssl rand -base64 32`);
|
|
24
|
-
}
|
|
25
|
-
return key;
|
|
26
|
-
}
|
|
27
|
-
function deriveSigningKey(masterKey) {
|
|
28
|
-
return Buffer.from(hkdfSync("sha256", masterKey, "", "crystal-relay-sign", 32));
|
|
29
|
-
}
|
|
30
|
-
function encrypt(plaintext, masterKey) {
|
|
31
|
-
const nonce = randomBytes(12);
|
|
32
|
-
const cipher = createCipheriv("aes-256-gcm", masterKey, nonce);
|
|
33
|
-
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
34
|
-
const tag = cipher.getAuthTag();
|
|
35
|
-
const signingKey = deriveSigningKey(masterKey);
|
|
36
|
-
const hmacData = Buffer.concat([nonce, ciphertext, tag]);
|
|
37
|
-
const hmac = createHmac("sha256", signingKey).update(hmacData).digest("hex");
|
|
38
|
-
return {
|
|
39
|
-
v: 1,
|
|
40
|
-
nonce: nonce.toString("base64"),
|
|
41
|
-
ciphertext: ciphertext.toString("base64"),
|
|
42
|
-
tag: tag.toString("base64"),
|
|
43
|
-
hmac
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
function decrypt(payload, masterKey) {
|
|
47
|
-
if (payload.v !== 1) {
|
|
48
|
-
throw new Error(`Unknown payload version: ${payload.v}`);
|
|
49
|
-
}
|
|
50
|
-
const nonce = Buffer.from(payload.nonce, "base64");
|
|
51
|
-
const ciphertext = Buffer.from(payload.ciphertext, "base64");
|
|
52
|
-
const tag = Buffer.from(payload.tag, "base64");
|
|
53
|
-
const signingKey = deriveSigningKey(masterKey);
|
|
54
|
-
const hmacData = Buffer.concat([nonce, ciphertext, tag]);
|
|
55
|
-
const expectedHmac = createHmac("sha256", signingKey).update(hmacData).digest("hex");
|
|
56
|
-
if (payload.hmac !== expectedHmac) {
|
|
57
|
-
throw new Error("HMAC verification failed \u2014 blob rejected (tampered or wrong key)");
|
|
58
|
-
}
|
|
59
|
-
const decipher = createDecipheriv("aes-256-gcm", masterKey, nonce);
|
|
60
|
-
decipher.setAuthTag(tag);
|
|
61
|
-
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
62
|
-
}
|
|
63
|
-
function encryptJSON(data, masterKey) {
|
|
64
|
-
const plaintext = Buffer.from(JSON.stringify(data), "utf-8");
|
|
65
|
-
return encrypt(plaintext, masterKey);
|
|
66
|
-
}
|
|
67
|
-
function decryptJSON(payload, masterKey) {
|
|
68
|
-
const plaintext = decrypt(payload, masterKey);
|
|
69
|
-
return JSON.parse(plaintext.toString("utf-8"));
|
|
70
|
-
}
|
|
71
|
-
function encryptFile(filePath, masterKey) {
|
|
72
|
-
const plaintext = readFileSync(filePath);
|
|
73
|
-
return encrypt(plaintext, masterKey);
|
|
74
|
-
}
|
|
75
|
-
var RELAY_KEY_PATH = KEY_PATH;
|
|
76
|
-
function generateRelayKey() {
|
|
77
|
-
return randomBytes(32);
|
|
78
|
-
}
|
|
79
|
-
function encodePairingString(key) {
|
|
80
|
-
if (key.length !== 32) throw new Error("Key must be 32 bytes");
|
|
81
|
-
return `mc1:${key.toString("base64")}`;
|
|
82
|
-
}
|
|
83
|
-
function decodePairingString(str) {
|
|
84
|
-
const trimmed = str.trim();
|
|
85
|
-
if (!trimmed.startsWith("mc1:")) {
|
|
86
|
-
throw new Error("Invalid pairing string (expected mc1: prefix)");
|
|
87
|
-
}
|
|
88
|
-
const key = Buffer.from(trimmed.slice(4), "base64");
|
|
89
|
-
if (key.length !== 32) {
|
|
90
|
-
throw new Error(`Invalid key length: expected 32 bytes, got ${key.length}`);
|
|
91
|
-
}
|
|
92
|
-
return key;
|
|
93
|
-
}
|
|
94
|
-
function hashBuffer(data) {
|
|
95
|
-
return createHash("sha256").update(data).digest("hex");
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export {
|
|
99
|
-
loadRelayKey,
|
|
100
|
-
encrypt,
|
|
101
|
-
decrypt,
|
|
102
|
-
encryptJSON,
|
|
103
|
-
decryptJSON,
|
|
104
|
-
encryptFile,
|
|
105
|
-
RELAY_KEY_PATH,
|
|
106
|
-
generateRelayKey,
|
|
107
|
-
encodePairingString,
|
|
108
|
-
decodePairingString,
|
|
109
|
-
hashBuffer
|
|
110
|
-
};
|