@gamaze/hicortex 0.3.9 → 0.3.11
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/dist/init.js +57 -36
- package/dist/llm.d.ts +14 -0
- package/dist/llm.js +73 -1
- package/dist/mcp-server.js +19 -6
- package/dist/nightly.js +29 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/init.js
CHANGED
|
@@ -278,11 +278,10 @@ function readOcLlmConfig() {
|
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
280
|
/**
|
|
281
|
-
* Detect
|
|
282
|
-
*
|
|
283
|
-
* don't inherit shell env vars).
|
|
281
|
+
* Detect or ask for LLM config and persist to ~/.hicortex/config.json.
|
|
282
|
+
* The daemon can't inherit shell env vars, so we persist here.
|
|
284
283
|
*/
|
|
285
|
-
function persistLlmConfig() {
|
|
284
|
+
async function persistLlmConfig() {
|
|
286
285
|
const configPath = (0, node_path_1.join)(HICORTEX_HOME, "config.json");
|
|
287
286
|
// Read existing config (may have licenseKey)
|
|
288
287
|
let config = {};
|
|
@@ -291,54 +290,76 @@ function persistLlmConfig() {
|
|
|
291
290
|
}
|
|
292
291
|
catch { /* new file */ }
|
|
293
292
|
// Don't overwrite if LLM config already persisted
|
|
294
|
-
if (config.llmApiKey && config.llmBaseUrl) {
|
|
295
|
-
console.log(` ✓ LLM config already
|
|
293
|
+
if (config.llmBackend || (config.llmApiKey && config.llmBaseUrl)) {
|
|
294
|
+
console.log(` ✓ LLM config already configured`);
|
|
296
295
|
return;
|
|
297
296
|
}
|
|
298
|
-
//
|
|
297
|
+
// Try auto-detect from environment
|
|
299
298
|
const hcKey = process.env.HICORTEX_LLM_API_KEY;
|
|
300
299
|
const hcUrl = process.env.HICORTEX_LLM_BASE_URL;
|
|
301
|
-
const hcModel = process.env.HICORTEX_LLM_MODEL;
|
|
302
300
|
if (hcKey && hcUrl) {
|
|
303
301
|
config.llmApiKey = hcKey;
|
|
304
302
|
config.llmBaseUrl = hcUrl;
|
|
305
|
-
if (
|
|
306
|
-
config.llmModel =
|
|
303
|
+
if (process.env.HICORTEX_LLM_MODEL)
|
|
304
|
+
config.llmModel = process.env.HICORTEX_LLM_MODEL;
|
|
305
|
+
saveConfig(configPath, config);
|
|
306
|
+
console.log(` ✓ LLM config auto-detected from HICORTEX_LLM_* env vars`);
|
|
307
|
+
return;
|
|
307
308
|
}
|
|
308
|
-
|
|
309
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
309
310
|
config.llmApiKey = process.env.ANTHROPIC_API_KEY;
|
|
310
311
|
config.llmBaseUrl = process.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com";
|
|
311
312
|
config.llmProvider = "anthropic";
|
|
313
|
+
saveConfig(configPath, config);
|
|
314
|
+
console.log(` ✓ LLM config auto-detected from ANTHROPIC_API_KEY`);
|
|
315
|
+
return;
|
|
312
316
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
config.
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
config
|
|
317
|
+
// Try OC auth-profiles
|
|
318
|
+
const ocLlm = readOcLlmConfig();
|
|
319
|
+
if (ocLlm) {
|
|
320
|
+
config.llmApiKey = ocLlm.apiKey;
|
|
321
|
+
config.llmBaseUrl = ocLlm.baseUrl;
|
|
322
|
+
config.llmProvider = ocLlm.provider;
|
|
323
|
+
if (ocLlm.model)
|
|
324
|
+
config.llmModel = ocLlm.model;
|
|
325
|
+
saveConfig(configPath, config);
|
|
326
|
+
console.log(` ✓ LLM config auto-detected from OpenClaw (${ocLlm.provider})`);
|
|
327
|
+
return;
|
|
322
328
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
329
|
+
// Not auto-detected — ask the user
|
|
330
|
+
console.log("\n LLM config not auto-detected. Hicortex needs an LLM for nightly learning.\n");
|
|
331
|
+
console.log(" How should Hicortex access an LLM?\n");
|
|
332
|
+
console.log(" 1. Use Claude subscription via CLI (Haiku model, no API key needed)");
|
|
333
|
+
console.log(" 2. Enter an API key manually (Anthropic, OpenAI, or other)");
|
|
334
|
+
console.log(" 3. Cancel installation\n");
|
|
335
|
+
const choice = await ask(" Choice [1]: ");
|
|
336
|
+
const selected = choice === "2" ? 2 : choice === "3" ? 3 : 1;
|
|
337
|
+
if (selected === 1) {
|
|
338
|
+
config.llmBackend = "claude-cli";
|
|
339
|
+
saveConfig(configPath, config);
|
|
340
|
+
console.log(` ✓ Configured to use Claude CLI (Haiku model via subscription)`);
|
|
341
|
+
}
|
|
342
|
+
else if (selected === 2) {
|
|
343
|
+
const apiKey = await ask(" API key: ");
|
|
344
|
+
if (!apiKey) {
|
|
345
|
+
console.log(" ⚠ No key entered. Skipping.");
|
|
336
346
|
return;
|
|
337
347
|
}
|
|
348
|
+
const baseUrl = await ask(" Base URL [https://api.anthropic.com]: ");
|
|
349
|
+
config.llmApiKey = apiKey;
|
|
350
|
+
config.llmBaseUrl = baseUrl || "https://api.anthropic.com";
|
|
351
|
+
config.llmProvider = "anthropic";
|
|
352
|
+
saveConfig(configPath, config);
|
|
353
|
+
console.log(` ✓ API key saved`);
|
|
338
354
|
}
|
|
355
|
+
else {
|
|
356
|
+
console.log("\n Hicortex requires an LLM to function. Installation cancelled.");
|
|
357
|
+
process.exit(0);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function saveConfig(configPath, config) {
|
|
339
361
|
(0, node_fs_1.mkdirSync)(HICORTEX_HOME, { recursive: true });
|
|
340
362
|
(0, node_fs_1.writeFileSync)(configPath, JSON.stringify(config, null, 2));
|
|
341
|
-
console.log(` ✓ LLM config saved to ${configPath}`);
|
|
342
363
|
}
|
|
343
364
|
/**
|
|
344
365
|
* Determine the npm package specifier for the daemon.
|
|
@@ -540,8 +561,8 @@ async function runInit() {
|
|
|
540
561
|
}
|
|
541
562
|
console.log();
|
|
542
563
|
// Phase 3: Execute
|
|
543
|
-
// Persist LLM config
|
|
544
|
-
persistLlmConfig();
|
|
564
|
+
// Persist LLM config for the daemon
|
|
565
|
+
await persistLlmConfig();
|
|
545
566
|
// Install daemon if needed
|
|
546
567
|
if (!d.localServer && !d.remoteServer) {
|
|
547
568
|
installDaemon();
|
package/dist/llm.d.ts
CHANGED
|
@@ -44,6 +44,15 @@ export declare function resolveLlmConfigForCC(overrides?: {
|
|
|
44
44
|
llmModel?: string;
|
|
45
45
|
reflectModel?: string;
|
|
46
46
|
}): LlmConfig;
|
|
47
|
+
/**
|
|
48
|
+
* Find the claude CLI binary. Returns the full path or null.
|
|
49
|
+
*/
|
|
50
|
+
export declare function findClaudeBinary(): string | null;
|
|
51
|
+
/**
|
|
52
|
+
* Create an LlmConfig that uses the claude CLI as backend.
|
|
53
|
+
* baseUrl field stores the path to the claude binary.
|
|
54
|
+
*/
|
|
55
|
+
export declare function claudeCliConfig(claudePath: string): LlmConfig;
|
|
47
56
|
export declare class RateLimitError extends Error {
|
|
48
57
|
retryAfterMs: number;
|
|
49
58
|
constructor(retryAfterMs: number);
|
|
@@ -68,6 +77,11 @@ export declare class LlmClient {
|
|
|
68
77
|
*/
|
|
69
78
|
completeDistill(prompt: string, maxTokens?: number): Promise<string>;
|
|
70
79
|
private complete;
|
|
80
|
+
/**
|
|
81
|
+
* Claude CLI: shell out to `claude -p` for subscription users.
|
|
82
|
+
* No API key needed — uses CC's authenticated session.
|
|
83
|
+
*/
|
|
84
|
+
private completeClaude;
|
|
71
85
|
/**
|
|
72
86
|
* Ollama: use /api/generate with think:false (important for qwen3.5 models).
|
|
73
87
|
*/
|
package/dist/llm.js
CHANGED
|
@@ -22,6 +22,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
22
22
|
exports.LlmClient = exports.RateLimitError = void 0;
|
|
23
23
|
exports.resolveLlmConfig = resolveLlmConfig;
|
|
24
24
|
exports.resolveLlmConfigForCC = resolveLlmConfigForCC;
|
|
25
|
+
exports.findClaudeBinary = findClaudeBinary;
|
|
26
|
+
exports.claudeCliConfig = claudeCliConfig;
|
|
25
27
|
const node_fs_1 = require("node:fs");
|
|
26
28
|
const node_path_1 = require("node:path");
|
|
27
29
|
const node_os_1 = require("node:os");
|
|
@@ -122,7 +124,12 @@ function resolveLlmConfigForCC(overrides) {
|
|
|
122
124
|
provider: "google",
|
|
123
125
|
};
|
|
124
126
|
}
|
|
125
|
-
// 4.
|
|
127
|
+
// 4. Claude CLI fallback (subscription users)
|
|
128
|
+
const claudePath = findClaudeBinary();
|
|
129
|
+
if (claudePath) {
|
|
130
|
+
return claudeCliConfig(claudePath);
|
|
131
|
+
}
|
|
132
|
+
// 5. Ollama fallback (truly last resort)
|
|
126
133
|
return {
|
|
127
134
|
baseUrl: "http://localhost:11434",
|
|
128
135
|
apiKey: "",
|
|
@@ -333,6 +340,45 @@ function getDefaultUrlForProvider(provider) {
|
|
|
333
340
|
function getEnvKeyForZai() {
|
|
334
341
|
return process.env.ZAI_API_KEY ?? process.env.LLM_API_KEY;
|
|
335
342
|
}
|
|
343
|
+
/**
|
|
344
|
+
* Find the claude CLI binary. Returns the full path or null.
|
|
345
|
+
*/
|
|
346
|
+
function findClaudeBinary() {
|
|
347
|
+
const { execSync } = require("node:child_process");
|
|
348
|
+
const { existsSync } = require("node:fs");
|
|
349
|
+
const { join } = require("node:path");
|
|
350
|
+
const { homedir } = require("node:os");
|
|
351
|
+
// Check common paths
|
|
352
|
+
const candidates = [
|
|
353
|
+
join(homedir(), ".local", "bin", "claude"),
|
|
354
|
+
"/usr/local/bin/claude",
|
|
355
|
+
"/opt/homebrew/bin/claude",
|
|
356
|
+
];
|
|
357
|
+
for (const p of candidates) {
|
|
358
|
+
if (existsSync(p))
|
|
359
|
+
return p;
|
|
360
|
+
}
|
|
361
|
+
// Fall back to which
|
|
362
|
+
try {
|
|
363
|
+
return execSync("which claude 2>/dev/null", { encoding: "utf-8" }).trim() || null;
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Create an LlmConfig that uses the claude CLI as backend.
|
|
371
|
+
* baseUrl field stores the path to the claude binary.
|
|
372
|
+
*/
|
|
373
|
+
function claudeCliConfig(claudePath) {
|
|
374
|
+
return {
|
|
375
|
+
baseUrl: claudePath,
|
|
376
|
+
apiKey: "",
|
|
377
|
+
model: "haiku",
|
|
378
|
+
reflectModel: "haiku",
|
|
379
|
+
provider: "claude-cli",
|
|
380
|
+
};
|
|
381
|
+
}
|
|
336
382
|
// ---------------------------------------------------------------------------
|
|
337
383
|
// LLM Client class
|
|
338
384
|
// ---------------------------------------------------------------------------
|
|
@@ -390,6 +436,9 @@ class LlmClient {
|
|
|
390
436
|
if (this.isRateLimited) {
|
|
391
437
|
throw new RateLimitError(this.rateLimitedUntil - Date.now());
|
|
392
438
|
}
|
|
439
|
+
if (this.config.provider === "claude-cli") {
|
|
440
|
+
return this.completeClaude(model, prompt, timeoutMs);
|
|
441
|
+
}
|
|
393
442
|
if (this.config.provider === "ollama") {
|
|
394
443
|
return this.completeOllama(model, prompt, maxTokens, timeoutMs);
|
|
395
444
|
}
|
|
@@ -398,6 +447,29 @@ class LlmClient {
|
|
|
398
447
|
}
|
|
399
448
|
return this.completeOpenAiCompat(model, prompt, maxTokens, timeoutMs);
|
|
400
449
|
}
|
|
450
|
+
/**
|
|
451
|
+
* Claude CLI: shell out to `claude -p` for subscription users.
|
|
452
|
+
* No API key needed — uses CC's authenticated session.
|
|
453
|
+
*/
|
|
454
|
+
async completeClaude(model, prompt, timeoutMs) {
|
|
455
|
+
const { execSync } = require("node:child_process");
|
|
456
|
+
const claudePath = this.config.baseUrl; // baseUrl stores the claude binary path
|
|
457
|
+
try {
|
|
458
|
+
const raw = execSync(`${claudePath} -p ${JSON.stringify(prompt)} --model ${model} --max-turns 1 --output-format json --no-session-persistence < /dev/null`, { encoding: "utf-8", timeout: timeoutMs, maxBuffer: 10 * 1024 * 1024 });
|
|
459
|
+
const data = JSON.parse(raw);
|
|
460
|
+
if (data.is_error) {
|
|
461
|
+
throw new Error(`Claude CLI error: ${data.result}`);
|
|
462
|
+
}
|
|
463
|
+
return (data.result ?? "").trim();
|
|
464
|
+
}
|
|
465
|
+
catch (err) {
|
|
466
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
467
|
+
if (msg.includes("rate") || msg.includes("429") || msg.includes("overloaded")) {
|
|
468
|
+
this.handleRateLimit({ headers: { get: () => null } });
|
|
469
|
+
}
|
|
470
|
+
throw new Error(`Claude CLI failed: ${msg}`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
401
473
|
/**
|
|
402
474
|
* Ollama: use /api/generate with think:false (important for qwen3.5 models).
|
|
403
475
|
*/
|
package/dist/mcp-server.js
CHANGED
|
@@ -179,13 +179,26 @@ async function startServer(options = {}) {
|
|
|
179
179
|
console.log(`[hicortex] Initializing database at ${dbPath}`);
|
|
180
180
|
db = (0, db_js_1.initDb)(dbPath);
|
|
181
181
|
stateDir = dbPath.replace(/\/hicortex\.db$/, "");
|
|
182
|
-
// LLM config:
|
|
182
|
+
// LLM config: check config.json first, then env vars, then claude CLI
|
|
183
183
|
const savedConfig = readConfigFile(stateDir);
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
184
|
+
let llmConfig;
|
|
185
|
+
if (savedConfig?.llmBackend === "claude-cli") {
|
|
186
|
+
const claudePath = (0, llm_js_1.findClaudeBinary)();
|
|
187
|
+
if (claudePath) {
|
|
188
|
+
llmConfig = (0, llm_js_1.claudeCliConfig)(claudePath);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
console.warn("[hicortex] claude-cli configured but claude binary not found, falling back");
|
|
192
|
+
llmConfig = (0, llm_js_1.resolveLlmConfigForCC)();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
llmConfig = (0, llm_js_1.resolveLlmConfigForCC)({
|
|
197
|
+
llmBaseUrl: savedConfig?.llmBaseUrl,
|
|
198
|
+
llmApiKey: savedConfig?.llmApiKey,
|
|
199
|
+
llmModel: savedConfig?.llmModel,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
189
202
|
llm = new llm_js_1.LlmClient(llmConfig);
|
|
190
203
|
console.log(`[hicortex] LLM: ${llmConfig.provider}/${llmConfig.model} (reflect: ${llmConfig.reflectModel})`);
|
|
191
204
|
// License: read from options, config file, or env var
|
package/dist/nightly.js
CHANGED
|
@@ -58,6 +58,15 @@ const claude_md_js_1 = require("./claude-md.js");
|
|
|
58
58
|
const license_js_1 = require("./license.js");
|
|
59
59
|
const HICORTEX_HOME = (0, node_path_1.join)((0, node_os_1.homedir)(), ".hicortex");
|
|
60
60
|
const LAST_RUN_PATH = (0, node_path_1.join)(HICORTEX_HOME, "nightly-last-run.txt");
|
|
61
|
+
function readNightlyConfig(stateDir) {
|
|
62
|
+
try {
|
|
63
|
+
const configPath = (0, node_path_1.join)(stateDir, "config.json");
|
|
64
|
+
return JSON.parse((0, node_fs_1.readFileSync)(configPath, "utf-8"));
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
61
70
|
function readConfigLicenseKey(stateDir) {
|
|
62
71
|
try {
|
|
63
72
|
const configPath = (0, node_path_1.join)(stateDir, "config.json");
|
|
@@ -97,8 +106,26 @@ async function runNightly(options = {}) {
|
|
|
97
106
|
// License: read from config file or env var
|
|
98
107
|
const licenseKey = readConfigLicenseKey(stateDir) ?? process.env.HICORTEX_LICENSE_KEY;
|
|
99
108
|
await (0, license_js_1.validateLicense)(licenseKey, stateDir);
|
|
100
|
-
// Init LLM
|
|
101
|
-
|
|
109
|
+
// Init LLM: check config.json first, then auto-detect
|
|
110
|
+
let llmConfig;
|
|
111
|
+
const savedConfig = readNightlyConfig(stateDir);
|
|
112
|
+
if (savedConfig?.llmBackend === "claude-cli") {
|
|
113
|
+
const claudePath = (0, llm_js_1.findClaudeBinary)();
|
|
114
|
+
if (claudePath) {
|
|
115
|
+
llmConfig = (0, llm_js_1.claudeCliConfig)(claudePath);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
console.warn("[hicortex] claude-cli configured but binary not found, falling back");
|
|
119
|
+
llmConfig = (0, llm_js_1.resolveLlmConfigForCC)();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
llmConfig = (0, llm_js_1.resolveLlmConfigForCC)({
|
|
124
|
+
llmBaseUrl: savedConfig?.llmBaseUrl,
|
|
125
|
+
llmApiKey: savedConfig?.llmApiKey,
|
|
126
|
+
llmModel: savedConfig?.llmModel,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
102
129
|
const llm = new llm_js_1.LlmClient(llmConfig);
|
|
103
130
|
console.log(`[hicortex] LLM: ${llmConfig.provider}/${llmConfig.model}`);
|
|
104
131
|
// Step 1: Read new CC transcripts
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "hicortex",
|
|
3
3
|
"name": "Hicortex — Long-term Memory That Learns",
|
|
4
4
|
"description": "Your agents remember past decisions, avoid repeated mistakes, and get smarter every day. Nightly reflection generates actionable lessons that automatically update agent behavior.",
|
|
5
|
-
"version": "0.3.
|
|
5
|
+
"version": "0.3.11",
|
|
6
6
|
"kind": "lifecycle",
|
|
7
7
|
"skills": ["./skills/hicortex-memory", "./skills/hicortex-learn", "./skills/hicortex-activate"],
|
|
8
8
|
"configSchema": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gamaze/hicortex",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.11",
|
|
4
4
|
"description": "Human-like memory for self-improving AI agents. Automatic capturing, nightly reflection, and cross-agent learning. Works with Claude Code and OpenClaw.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|