@gamaze/hicortex 0.2.1 → 0.3.1
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 +77 -37
- package/dist/claude-md.d.ts +28 -0
- package/dist/claude-md.js +142 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +84 -0
- package/dist/consolidate.js +1 -1
- package/dist/db.d.ts +10 -0
- package/dist/db.js +74 -0
- package/dist/index.js +10 -14
- package/dist/init.d.ts +17 -0
- package/dist/init.js +397 -0
- package/dist/llm.d.ts +20 -2
- package/dist/llm.js +82 -2
- package/dist/mcp-server.d.ts +19 -0
- package/dist/mcp-server.js +299 -0
- package/dist/nightly.d.ts +15 -0
- package/dist/nightly.js +181 -0
- package/dist/status.d.ts +4 -0
- package/dist/status.js +120 -0
- package/dist/transcript-reader.d.ts +20 -0
- package/dist/transcript-reader.js +126 -0
- package/dist/uninstall.d.ts +5 -0
- package/dist/uninstall.js +84 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +17 -9
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Hicortex MCP HTTP/SSE Server.
|
|
4
|
+
*
|
|
5
|
+
* Persistent HTTP server that exposes Hicortex tools via MCP protocol.
|
|
6
|
+
* Shared across all CC sessions (and future Codex/Gemini adapters).
|
|
7
|
+
* One process, one DB connection, one embedder — no per-session overhead.
|
|
8
|
+
*
|
|
9
|
+
* Endpoints:
|
|
10
|
+
* GET /health — health check
|
|
11
|
+
* GET /sse — SSE stream for MCP clients
|
|
12
|
+
* POST /messages — message endpoint for MCP clients
|
|
13
|
+
*/
|
|
14
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
17
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
19
|
+
}
|
|
20
|
+
Object.defineProperty(o, k2, desc);
|
|
21
|
+
}) : (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
o[k2] = m[k];
|
|
24
|
+
}));
|
|
25
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
26
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
27
|
+
}) : function(o, v) {
|
|
28
|
+
o["default"] = v;
|
|
29
|
+
});
|
|
30
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
31
|
+
var ownKeys = function(o) {
|
|
32
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
33
|
+
var ar = [];
|
|
34
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
35
|
+
return ar;
|
|
36
|
+
};
|
|
37
|
+
return ownKeys(o);
|
|
38
|
+
};
|
|
39
|
+
return function (mod) {
|
|
40
|
+
if (mod && mod.__esModule) return mod;
|
|
41
|
+
var result = {};
|
|
42
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
43
|
+
__setModuleDefault(result, mod);
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
})();
|
|
47
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
48
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
49
|
+
};
|
|
50
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
|
+
exports.startServer = startServer;
|
|
52
|
+
const express_1 = __importDefault(require("express"));
|
|
53
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
54
|
+
const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
|
|
55
|
+
const zod_1 = require("zod");
|
|
56
|
+
const db_js_1 = require("./db.js");
|
|
57
|
+
const llm_js_1 = require("./llm.js");
|
|
58
|
+
const license_js_1 = require("./license.js");
|
|
59
|
+
const embedder_js_1 = require("./embedder.js");
|
|
60
|
+
const storage = __importStar(require("./storage.js"));
|
|
61
|
+
const retrieval = __importStar(require("./retrieval.js"));
|
|
62
|
+
const consolidate_js_1 = require("./consolidate.js");
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Server state
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
let db = null;
|
|
67
|
+
let llm = null;
|
|
68
|
+
let cancelConsolidation = null;
|
|
69
|
+
let stateDir = "";
|
|
70
|
+
const VERSION = "0.3.0";
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// MCP Server setup
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
function createMcpServer() {
|
|
75
|
+
const server = new mcp_js_1.McpServer({
|
|
76
|
+
name: "hicortex",
|
|
77
|
+
version: VERSION,
|
|
78
|
+
});
|
|
79
|
+
// -- hicortex_search --
|
|
80
|
+
server.tool("hicortex_search", "Search long-term memory using semantic similarity. Returns the most relevant memories from past sessions.", {
|
|
81
|
+
query: zod_1.z.string().describe("Search query text"),
|
|
82
|
+
limit: zod_1.z.number().optional().describe("Max results (default 5)"),
|
|
83
|
+
project: zod_1.z.string().optional().describe("Filter by project name"),
|
|
84
|
+
}, async ({ query, limit, project }) => {
|
|
85
|
+
if (!db)
|
|
86
|
+
return { content: [{ type: "text", text: "Hicortex not initialized" }], isError: true };
|
|
87
|
+
try {
|
|
88
|
+
const results = await retrieval.retrieve(db, embedder_js_1.embed, query, { limit, project });
|
|
89
|
+
return { content: [{ type: "text", text: formatResults(results) }] };
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
return { content: [{ type: "text", text: `Search failed: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
// -- hicortex_context --
|
|
96
|
+
server.tool("hicortex_context", "Get recent context memories, optionally filtered by project. Useful to recall what happened recently.", {
|
|
97
|
+
project: zod_1.z.string().optional().describe("Filter by project name"),
|
|
98
|
+
limit: zod_1.z.number().optional().describe("Max results (default 10)"),
|
|
99
|
+
}, async ({ project, limit }) => {
|
|
100
|
+
if (!db)
|
|
101
|
+
return { content: [{ type: "text", text: "Hicortex not initialized" }], isError: true };
|
|
102
|
+
try {
|
|
103
|
+
const results = retrieval.searchContext(db, { project, limit });
|
|
104
|
+
return { content: [{ type: "text", text: formatResults(results) }] };
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
return { content: [{ type: "text", text: `Context search failed: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
// -- hicortex_ingest --
|
|
111
|
+
server.tool("hicortex_ingest", "Store a new memory in long-term storage. Use for important facts, decisions, or lessons.", {
|
|
112
|
+
content: zod_1.z.string().describe("Memory content to store"),
|
|
113
|
+
project: zod_1.z.string().optional().describe("Project this memory belongs to"),
|
|
114
|
+
memory_type: zod_1.z.enum(["episode", "lesson", "fact", "decision"]).optional().describe("Type of memory (default: episode)"),
|
|
115
|
+
}, async ({ content, project, memory_type }) => {
|
|
116
|
+
if (!db)
|
|
117
|
+
return { content: [{ type: "text", text: "Hicortex not initialized" }], isError: true };
|
|
118
|
+
const features = (0, license_js_1.getFeatures)(stateDir);
|
|
119
|
+
if (features.maxMemories > 0 && storage.countMemories(db) >= features.maxMemories) {
|
|
120
|
+
return {
|
|
121
|
+
content: [{
|
|
122
|
+
type: "text",
|
|
123
|
+
text: `Free tier limit reached (${features.maxMemories} memories). ` +
|
|
124
|
+
`Your existing memories and lessons still work — search and recall are unaffected. ` +
|
|
125
|
+
`New memories won't be saved until you upgrade.\n\n` +
|
|
126
|
+
`Upgrade for unlimited usage: https://hicortex.gamaze.com/`
|
|
127
|
+
}],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
const embedding = await (0, embedder_js_1.embed)(content);
|
|
132
|
+
const id = storage.insertMemory(db, content, embedding, {
|
|
133
|
+
sourceAgent: "claude-code/manual",
|
|
134
|
+
project,
|
|
135
|
+
memoryType: memory_type ?? "episode",
|
|
136
|
+
privacy: "WORK",
|
|
137
|
+
});
|
|
138
|
+
return { content: [{ type: "text", text: `Memory stored (id: ${id.slice(0, 8)})` }] };
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
return { content: [{ type: "text", text: `Ingest failed: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
// -- hicortex_lessons --
|
|
145
|
+
server.tool("hicortex_lessons", "Get actionable lessons learned from past sessions. Auto-generated insights about mistakes to avoid.", {
|
|
146
|
+
days: zod_1.z.number().optional().describe("Look back N days (default 7)"),
|
|
147
|
+
project: zod_1.z.string().optional().describe("Filter by project name"),
|
|
148
|
+
}, async ({ days, project }) => {
|
|
149
|
+
if (!db)
|
|
150
|
+
return { content: [{ type: "text", text: "Hicortex not initialized" }], isError: true };
|
|
151
|
+
try {
|
|
152
|
+
const lessons = storage.getLessons(db, days ?? 7, project);
|
|
153
|
+
if (lessons.length === 0) {
|
|
154
|
+
return { content: [{ type: "text", text: "No lessons found for the specified period." }] };
|
|
155
|
+
}
|
|
156
|
+
const text = lessons.map((l) => `- ${l.content.slice(0, 500)}`).join("\n");
|
|
157
|
+
return { content: [{ type: "text", text }] };
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
return { content: [{ type: "text", text: `Lessons fetch failed: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
return server;
|
|
164
|
+
}
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// HTTP server with SSE transport
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
async function startServer(options = {}) {
|
|
169
|
+
const port = options.port ?? 8787;
|
|
170
|
+
const host = options.host ?? "127.0.0.1";
|
|
171
|
+
// Initialize core
|
|
172
|
+
const dbPath = (0, db_js_1.resolveDbPath)(options.dbPath);
|
|
173
|
+
console.log(`[hicortex] Initializing database at ${dbPath}`);
|
|
174
|
+
db = (0, db_js_1.initDb)(dbPath);
|
|
175
|
+
stateDir = dbPath.replace(/\/hicortex\.db$/, "");
|
|
176
|
+
// LLM config
|
|
177
|
+
const llmConfig = (0, llm_js_1.resolveLlmConfigForCC)();
|
|
178
|
+
llm = new llm_js_1.LlmClient(llmConfig);
|
|
179
|
+
console.log(`[hicortex] LLM: ${llmConfig.provider}/${llmConfig.model} (reflect: ${llmConfig.reflectModel})`);
|
|
180
|
+
// License: read from options, config file, or env var
|
|
181
|
+
const licenseKey = options.licenseKey
|
|
182
|
+
?? readConfigLicenseKey(stateDir)
|
|
183
|
+
?? process.env.HICORTEX_LICENSE_KEY;
|
|
184
|
+
(0, license_js_1.validateLicense)(licenseKey, stateDir).catch((err) => console.log(`[hicortex] License validation failed: ${err}`));
|
|
185
|
+
if (licenseKey) {
|
|
186
|
+
console.log(`[hicortex] License key configured`);
|
|
187
|
+
}
|
|
188
|
+
// Schedule nightly consolidation
|
|
189
|
+
const consolidateHour = options.consolidateHour ?? 2;
|
|
190
|
+
cancelConsolidation = (0, consolidate_js_1.scheduleConsolidation)(db, llm, embedder_js_1.embed, consolidateHour);
|
|
191
|
+
// Stats
|
|
192
|
+
const stats = (0, db_js_1.getStats)(db, dbPath);
|
|
193
|
+
console.log(`[hicortex] Ready: ${stats.memories} memories, ${stats.links} links, ` +
|
|
194
|
+
`${Math.round(stats.db_size_bytes / 1024)} KB`);
|
|
195
|
+
// Create MCP server
|
|
196
|
+
const mcpServer = createMcpServer();
|
|
197
|
+
// Express app
|
|
198
|
+
const app = (0, express_1.default)();
|
|
199
|
+
app.use(express_1.default.json());
|
|
200
|
+
// SSE transport management
|
|
201
|
+
const transports = new Map();
|
|
202
|
+
// Health endpoint
|
|
203
|
+
app.get("/health", (_req, res) => {
|
|
204
|
+
const s = db ? (0, db_js_1.getStats)(db, dbPath) : { memories: 0, links: 0, db_size_bytes: 0, by_type: {} };
|
|
205
|
+
res.json({
|
|
206
|
+
status: "ok",
|
|
207
|
+
version: VERSION,
|
|
208
|
+
memories: s.memories,
|
|
209
|
+
links: s.links,
|
|
210
|
+
db_size_kb: Math.round(s.db_size_bytes / 1024),
|
|
211
|
+
llm: `${llmConfig.provider}/${llmConfig.model}`,
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
// SSE endpoint — client connects here to establish MCP session
|
|
215
|
+
app.get("/sse", async (req, res) => {
|
|
216
|
+
const transport = new sse_js_1.SSEServerTransport("/messages", res);
|
|
217
|
+
transports.set(transport.sessionId, transport);
|
|
218
|
+
transport.onclose = () => {
|
|
219
|
+
transports.delete(transport.sessionId);
|
|
220
|
+
};
|
|
221
|
+
await mcpServer.connect(transport);
|
|
222
|
+
});
|
|
223
|
+
// Message endpoint — client POSTs MCP messages here
|
|
224
|
+
app.post("/messages", async (req, res) => {
|
|
225
|
+
const sessionId = req.query.sessionId;
|
|
226
|
+
if (!sessionId || !transports.has(sessionId)) {
|
|
227
|
+
res.status(400).json({ error: "Invalid or missing sessionId" });
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const transport = transports.get(sessionId);
|
|
231
|
+
// Pass parsed body since express.json() already consumed the stream
|
|
232
|
+
await transport.handlePostMessage(req, res, req.body);
|
|
233
|
+
});
|
|
234
|
+
// Start listening
|
|
235
|
+
const server = app.listen(port, host, () => {
|
|
236
|
+
console.log(`[hicortex] MCP server listening on http://${host}:${port}`);
|
|
237
|
+
console.log(`[hicortex] SSE endpoint: http://${host}:${port}/sse`);
|
|
238
|
+
console.log(`[hicortex] Health: http://${host}:${port}/health`);
|
|
239
|
+
});
|
|
240
|
+
server.on("error", (err) => {
|
|
241
|
+
if (err.code === "EADDRINUSE") {
|
|
242
|
+
console.error(`[hicortex] Port ${port} is already in use. ` +
|
|
243
|
+
`Another Hicortex server or service may be running.\n` +
|
|
244
|
+
` Check: lsof -i :${port}\n` +
|
|
245
|
+
` Use a different port: npx @gamaze/hicortex server --port ${port + 1}`);
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
throw err;
|
|
249
|
+
});
|
|
250
|
+
// Graceful shutdown
|
|
251
|
+
const shutdown = () => {
|
|
252
|
+
console.log("[hicortex] Shutting down...");
|
|
253
|
+
if (cancelConsolidation) {
|
|
254
|
+
cancelConsolidation();
|
|
255
|
+
cancelConsolidation = null;
|
|
256
|
+
}
|
|
257
|
+
for (const transport of transports.values()) {
|
|
258
|
+
transport.close().catch(() => { });
|
|
259
|
+
}
|
|
260
|
+
transports.clear();
|
|
261
|
+
server.close(() => {
|
|
262
|
+
if (db) {
|
|
263
|
+
db.close();
|
|
264
|
+
db = null;
|
|
265
|
+
}
|
|
266
|
+
console.log("[hicortex] Server stopped.");
|
|
267
|
+
process.exit(0);
|
|
268
|
+
});
|
|
269
|
+
};
|
|
270
|
+
process.on("SIGINT", shutdown);
|
|
271
|
+
process.on("SIGTERM", shutdown);
|
|
272
|
+
}
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
// Helpers
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
/**
|
|
277
|
+
* Read license key from ~/.hicortex/config.json.
|
|
278
|
+
* Written by /hicortex-activate CC command.
|
|
279
|
+
*/
|
|
280
|
+
function readConfigLicenseKey(stateDir) {
|
|
281
|
+
try {
|
|
282
|
+
const { readFileSync } = require("node:fs");
|
|
283
|
+
const { join } = require("node:path");
|
|
284
|
+
const configPath = join(stateDir, "config.json");
|
|
285
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
286
|
+
const config = JSON.parse(raw);
|
|
287
|
+
return config.licenseKey || undefined;
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
return undefined;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function formatResults(results) {
|
|
294
|
+
if (results.length === 0)
|
|
295
|
+
return "No memories found.";
|
|
296
|
+
return results
|
|
297
|
+
.map((r) => `[${r.memory_type}] (score: ${r.score.toFixed(3)}, strength: ${r.effective_strength.toFixed(3)}) ${r.content.slice(0, 500)}`)
|
|
298
|
+
.join("\n\n");
|
|
299
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nightly pipeline — manual trigger or called by the persistent server.
|
|
3
|
+
*
|
|
4
|
+
* Steps:
|
|
5
|
+
* 1. Read new CC transcripts since last run
|
|
6
|
+
* 2. Distill each session into memories via LLM
|
|
7
|
+
* 3. Run consolidation (scoring, reflection, linking, decay)
|
|
8
|
+
* 4. Inject lessons into CLAUDE.md
|
|
9
|
+
* 5. Update last-run timestamp
|
|
10
|
+
*/
|
|
11
|
+
export declare function runNightly(options?: {
|
|
12
|
+
dryRun?: boolean;
|
|
13
|
+
dbPath?: string;
|
|
14
|
+
stateDir?: string;
|
|
15
|
+
}): Promise<void>;
|
package/dist/nightly.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Nightly pipeline — manual trigger or called by the persistent server.
|
|
4
|
+
*
|
|
5
|
+
* Steps:
|
|
6
|
+
* 1. Read new CC transcripts since last run
|
|
7
|
+
* 2. Distill each session into memories via LLM
|
|
8
|
+
* 3. Run consolidation (scoring, reflection, linking, decay)
|
|
9
|
+
* 4. Inject lessons into CLAUDE.md
|
|
10
|
+
* 5. Update last-run timestamp
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.runNightly = runNightly;
|
|
47
|
+
const node_fs_1 = require("node:fs");
|
|
48
|
+
const node_path_1 = require("node:path");
|
|
49
|
+
const node_os_1 = require("node:os");
|
|
50
|
+
const db_js_1 = require("./db.js");
|
|
51
|
+
const llm_js_1 = require("./llm.js");
|
|
52
|
+
const embedder_js_1 = require("./embedder.js");
|
|
53
|
+
const storage = __importStar(require("./storage.js"));
|
|
54
|
+
const distiller_js_1 = require("./distiller.js");
|
|
55
|
+
const consolidate_js_1 = require("./consolidate.js");
|
|
56
|
+
const transcript_reader_js_1 = require("./transcript-reader.js");
|
|
57
|
+
const claude_md_js_1 = require("./claude-md.js");
|
|
58
|
+
const license_js_1 = require("./license.js");
|
|
59
|
+
const HICORTEX_HOME = (0, node_path_1.join)((0, node_os_1.homedir)(), ".hicortex");
|
|
60
|
+
const LAST_RUN_PATH = (0, node_path_1.join)(HICORTEX_HOME, "nightly-last-run.txt");
|
|
61
|
+
function readConfigLicenseKey(stateDir) {
|
|
62
|
+
try {
|
|
63
|
+
const configPath = (0, node_path_1.join)(stateDir, "config.json");
|
|
64
|
+
const raw = (0, node_fs_1.readFileSync)(configPath, "utf-8");
|
|
65
|
+
const config = JSON.parse(raw);
|
|
66
|
+
return config.licenseKey || undefined;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function readLastRun() {
|
|
73
|
+
try {
|
|
74
|
+
const ts = (0, node_fs_1.readFileSync)(LAST_RUN_PATH, "utf-8").trim();
|
|
75
|
+
const d = new Date(ts);
|
|
76
|
+
if (!isNaN(d.getTime()))
|
|
77
|
+
return d;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// No file — first run
|
|
81
|
+
}
|
|
82
|
+
return new Date(0); // Process everything
|
|
83
|
+
}
|
|
84
|
+
function writeLastRun() {
|
|
85
|
+
(0, node_fs_1.mkdirSync)(HICORTEX_HOME, { recursive: true });
|
|
86
|
+
(0, node_fs_1.writeFileSync)(LAST_RUN_PATH, new Date().toISOString());
|
|
87
|
+
}
|
|
88
|
+
async function runNightly(options = {}) {
|
|
89
|
+
const dryRun = options.dryRun ?? false;
|
|
90
|
+
const dbPath = (0, db_js_1.resolveDbPath)(options.dbPath);
|
|
91
|
+
const stateDir = options.stateDir ?? HICORTEX_HOME;
|
|
92
|
+
console.log(`[hicortex] Nightly pipeline starting${dryRun ? " (dry run)" : ""}`);
|
|
93
|
+
console.log(`[hicortex] DB: ${dbPath}`);
|
|
94
|
+
// Init DB
|
|
95
|
+
const db = (0, db_js_1.initDb)(dbPath);
|
|
96
|
+
try {
|
|
97
|
+
// License: read from config file or env var
|
|
98
|
+
const licenseKey = readConfigLicenseKey(stateDir) ?? process.env.HICORTEX_LICENSE_KEY;
|
|
99
|
+
await (0, license_js_1.validateLicense)(licenseKey, stateDir);
|
|
100
|
+
// Init LLM
|
|
101
|
+
const llmConfig = (0, llm_js_1.resolveLlmConfigForCC)();
|
|
102
|
+
const llm = new llm_js_1.LlmClient(llmConfig);
|
|
103
|
+
console.log(`[hicortex] LLM: ${llmConfig.provider}/${llmConfig.model}`);
|
|
104
|
+
// Step 1: Read new CC transcripts
|
|
105
|
+
const since = readLastRun();
|
|
106
|
+
console.log(`[hicortex] Reading CC transcripts since ${since.toISOString()}`);
|
|
107
|
+
const batches = (0, transcript_reader_js_1.readCcTranscripts)(since);
|
|
108
|
+
console.log(`[hicortex] Found ${batches.length} new session(s)`);
|
|
109
|
+
if (batches.length === 0 && !dryRun) {
|
|
110
|
+
// Still run consolidation — there may be unscored memories from OC
|
|
111
|
+
console.log(`[hicortex] No new CC transcripts. Running consolidation only.`);
|
|
112
|
+
}
|
|
113
|
+
// Step 2: Distill each session
|
|
114
|
+
let memoriesIngested = 0;
|
|
115
|
+
const features = (0, license_js_1.getFeatures)(stateDir);
|
|
116
|
+
for (const batch of batches) {
|
|
117
|
+
const transcript = (0, distiller_js_1.extractConversationText)(batch.entries);
|
|
118
|
+
if (transcript.length < 200) {
|
|
119
|
+
console.log(`[hicortex] Skip ${batch.sessionId.slice(0, 8)} (${batch.projectName}): too short`);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
console.log(`[hicortex] Distilling ${batch.sessionId.slice(0, 8)} (${batch.projectName}, ${batch.date})`);
|
|
123
|
+
if (dryRun) {
|
|
124
|
+
console.log(`[hicortex] [dry-run] Would distill ${transcript.length} chars`);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
// Check cap before distilling
|
|
128
|
+
if (features.maxMemories > 0 && storage.countMemories(db) >= features.maxMemories) {
|
|
129
|
+
console.log(`[hicortex] Free tier limit (${features.maxMemories} memories). Skipping new ingestion. ` +
|
|
130
|
+
`Upgrade: https://hicortex.gamaze.com/`);
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const entries = await (0, distiller_js_1.distillSession)(llm, transcript, batch.projectName, batch.date);
|
|
135
|
+
for (const entry of entries) {
|
|
136
|
+
try {
|
|
137
|
+
const embedding = await (0, embedder_js_1.embed)(entry);
|
|
138
|
+
storage.insertMemory(db, entry, embedding, {
|
|
139
|
+
sourceAgent: `claude-code/${batch.projectName}`,
|
|
140
|
+
sourceSession: batch.sessionId,
|
|
141
|
+
project: batch.projectName,
|
|
142
|
+
privacy: "WORK",
|
|
143
|
+
memoryType: "episode",
|
|
144
|
+
});
|
|
145
|
+
memoriesIngested++;
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
149
|
+
console.error(`[hicortex] Failed to ingest entry: ${msg}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
console.log(`[hicortex] → ${entries.length} memories extracted`);
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
156
|
+
console.error(`[hicortex] Distillation failed: ${msg}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
console.log(`[hicortex] Distillation complete: ${memoriesIngested} new memories`);
|
|
160
|
+
// Step 3: Consolidation
|
|
161
|
+
if (!dryRun) {
|
|
162
|
+
console.log(`[hicortex] Running consolidation...`);
|
|
163
|
+
const report = await (0, consolidate_js_1.runConsolidation)(db, llm, embedder_js_1.embed, dryRun);
|
|
164
|
+
console.log(`[hicortex] Consolidation ${report.status} in ${report.elapsed_seconds}s` +
|
|
165
|
+
(report.stages.reflection ? ` (${report.stages.reflection.lessons_generated} lessons)` : ""));
|
|
166
|
+
}
|
|
167
|
+
// Step 4: Inject lessons into CLAUDE.md
|
|
168
|
+
if (!dryRun) {
|
|
169
|
+
const injection = (0, claude_md_js_1.injectLessons)(db, { stateDir });
|
|
170
|
+
console.log(`[hicortex] CLAUDE.md updated: ${injection.lessonsCount} lessons at ${injection.path}`);
|
|
171
|
+
}
|
|
172
|
+
// Step 5: Update last-run timestamp
|
|
173
|
+
if (!dryRun) {
|
|
174
|
+
writeLastRun();
|
|
175
|
+
}
|
|
176
|
+
console.log(`[hicortex] Nightly pipeline complete.`);
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
db.close();
|
|
180
|
+
}
|
|
181
|
+
}
|
package/dist/status.d.ts
ADDED
package/dist/status.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Hicortex status — show current configuration and stats.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runStatus = runStatus;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = require("node:path");
|
|
9
|
+
const node_os_1 = require("node:os");
|
|
10
|
+
const node_child_process_1 = require("node:child_process");
|
|
11
|
+
const db_js_1 = require("./db.js");
|
|
12
|
+
const HICORTEX_HOME = (0, node_path_1.join)((0, node_os_1.homedir)(), ".hicortex");
|
|
13
|
+
const CC_SETTINGS = (0, node_path_1.join)((0, node_os_1.homedir)(), ".claude", "settings.json");
|
|
14
|
+
const OC_CONFIG = (0, node_path_1.join)((0, node_os_1.homedir)(), ".openclaw", "openclaw.json");
|
|
15
|
+
async function runStatus() {
|
|
16
|
+
console.log("Hicortex Status");
|
|
17
|
+
console.log("─".repeat(40));
|
|
18
|
+
// DB
|
|
19
|
+
const dbPath = (0, db_js_1.resolveDbPath)();
|
|
20
|
+
const dbExists = (0, node_fs_1.existsSync)(dbPath);
|
|
21
|
+
console.log(`DB: ${dbPath} ${dbExists ? "" : "(not found)"}`);
|
|
22
|
+
if (dbExists) {
|
|
23
|
+
try {
|
|
24
|
+
const { initDb, getStats } = await import("./db.js");
|
|
25
|
+
const db = initDb(dbPath);
|
|
26
|
+
const stats = getStats(db, dbPath);
|
|
27
|
+
const typeStr = Object.entries(stats.by_type).map(([k, v]) => `${k}=${v}`).join(", ");
|
|
28
|
+
console.log(`Memories: ${stats.memories} (${typeStr || "none"})`);
|
|
29
|
+
console.log(`Links: ${stats.links}`);
|
|
30
|
+
console.log(`DB size: ${(stats.db_size_bytes / 1024).toFixed(1)} KB`);
|
|
31
|
+
db.close();
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
console.log(`DB error: ${err instanceof Error ? err.message : String(err)}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// License
|
|
38
|
+
const configPath = (0, node_path_1.join)(HICORTEX_HOME, "config.json");
|
|
39
|
+
let licenseKey = "";
|
|
40
|
+
try {
|
|
41
|
+
const config = JSON.parse((0, node_fs_1.readFileSync)(configPath, "utf-8"));
|
|
42
|
+
licenseKey = config.licenseKey ?? "";
|
|
43
|
+
}
|
|
44
|
+
catch { /* no config */ }
|
|
45
|
+
console.log(`License: ${licenseKey ? "configured" : "free tier (250 memories)"}`);
|
|
46
|
+
console.log();
|
|
47
|
+
// Adapters
|
|
48
|
+
console.log("Adapters:");
|
|
49
|
+
// OC
|
|
50
|
+
let ocInstalled = false;
|
|
51
|
+
try {
|
|
52
|
+
const raw = (0, node_fs_1.readFileSync)(OC_CONFIG, "utf-8");
|
|
53
|
+
const config = JSON.parse(raw);
|
|
54
|
+
const entries = config?.plugins?.entries ?? {};
|
|
55
|
+
const installs = config?.plugins?.installs ?? {};
|
|
56
|
+
ocInstalled = "hicortex" in entries || "hicortex" in installs;
|
|
57
|
+
}
|
|
58
|
+
catch { /* no OC */ }
|
|
59
|
+
console.log(` OC plugin: ${ocInstalled ? "installed" : "not found"}`);
|
|
60
|
+
// CC
|
|
61
|
+
let ccRegistered = false;
|
|
62
|
+
let ccUrl = "";
|
|
63
|
+
try {
|
|
64
|
+
const raw = (0, node_fs_1.readFileSync)(CC_SETTINGS, "utf-8");
|
|
65
|
+
const settings = JSON.parse(raw);
|
|
66
|
+
const hc = settings?.mcpServers?.hicortex;
|
|
67
|
+
if (hc) {
|
|
68
|
+
ccRegistered = true;
|
|
69
|
+
ccUrl = hc.url ?? "";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch { /* no CC settings */ }
|
|
73
|
+
console.log(` CC MCP: ${ccRegistered ? `registered → ${ccUrl}` : "not registered"}`);
|
|
74
|
+
console.log();
|
|
75
|
+
// Server status
|
|
76
|
+
console.log("Server:");
|
|
77
|
+
let serverRunning = false;
|
|
78
|
+
try {
|
|
79
|
+
const resp = await fetch("http://127.0.0.1:8787/health", {
|
|
80
|
+
signal: AbortSignal.timeout(2000),
|
|
81
|
+
});
|
|
82
|
+
if (resp.ok) {
|
|
83
|
+
const data = await resp.json();
|
|
84
|
+
serverRunning = true;
|
|
85
|
+
console.log(` Status: running (${data.llm})`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch { /* not running */ }
|
|
89
|
+
if (!serverRunning)
|
|
90
|
+
console.log(" Status: not running");
|
|
91
|
+
// Daemon
|
|
92
|
+
const os = (0, node_os_1.platform)();
|
|
93
|
+
if (os === "darwin") {
|
|
94
|
+
try {
|
|
95
|
+
const out = (0, node_child_process_1.execSync)("launchctl list 2>/dev/null | grep hicortex", { encoding: "utf-8" });
|
|
96
|
+
console.log(` Daemon: launchd (${out.trim() ? "loaded" : "not loaded"})`);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
console.log(" Daemon: launchd (not installed)");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else if (os === "linux") {
|
|
103
|
+
try {
|
|
104
|
+
const out = (0, node_child_process_1.execSync)("systemctl --user is-active hicortex.service 2>/dev/null", { encoding: "utf-8" }).trim();
|
|
105
|
+
console.log(` Daemon: systemd (${out})`);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
console.log(" Daemon: systemd (not installed)");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Last nightly run
|
|
112
|
+
const lastRunPath = (0, node_path_1.join)(HICORTEX_HOME, "nightly-last-run.txt");
|
|
113
|
+
try {
|
|
114
|
+
const ts = (0, node_fs_1.readFileSync)(lastRunPath, "utf-8").trim();
|
|
115
|
+
console.log(` Last run: ${ts}`);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
console.log(" Last run: never");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CC transcript reader — reads Claude Code .jsonl session files.
|
|
3
|
+
*
|
|
4
|
+
* CC stores transcripts at ~/.claude/projects/<project-hash>/<session-uuid>.jsonl.
|
|
5
|
+
* Each line is a JSON object with type, message, timestamp, etc.
|
|
6
|
+
*
|
|
7
|
+
* The reader scans for new sessions since the last nightly run
|
|
8
|
+
* and feeds them to the existing distiller pipeline.
|
|
9
|
+
*/
|
|
10
|
+
export interface TranscriptBatch {
|
|
11
|
+
sessionId: string;
|
|
12
|
+
projectName: string;
|
|
13
|
+
date: string;
|
|
14
|
+
entries: unknown[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Read all CC transcripts modified since `since`.
|
|
18
|
+
* Returns one batch per session file.
|
|
19
|
+
*/
|
|
20
|
+
export declare function readCcTranscripts(since: Date, projectsDir?: string): TranscriptBatch[];
|