@gamaze/hicortex 0.3.9 → 0.3.10

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 CHANGED
@@ -278,11 +278,10 @@ function readOcLlmConfig() {
278
278
  }
279
279
  }
280
280
  /**
281
- * Detect LLM API key from current shell environment and persist to
282
- * ~/.hicortex/config.json so the daemon can use it (launchd/systemd
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 in ${configPath}`);
293
+ if (config.llmBackend || (config.llmApiKey && config.llmBaseUrl)) {
294
+ console.log(` ✓ LLM config already configured`);
296
295
  return;
297
296
  }
298
- // Detect from environment (same priority as resolveLlmConfigForCC)
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 (hcModel)
306
- config.llmModel = hcModel;
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
- else if (process.env.ANTHROPIC_API_KEY) {
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
- else if (process.env.OPENAI_API_KEY) {
314
- config.llmApiKey = process.env.OPENAI_API_KEY;
315
- config.llmBaseUrl = process.env.OPENAI_BASE_URL ?? "https://api.openai.com";
316
- config.llmProvider = "openai";
317
- }
318
- else if (process.env.GOOGLE_API_KEY) {
319
- config.llmApiKey = process.env.GOOGLE_API_KEY;
320
- config.llmBaseUrl = "https://generativelanguage.googleapis.com/v1beta";
321
- config.llmProvider = "google";
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
- else {
324
- // Last resort: try OC auth-profiles if OC is installed
325
- const ocLlm = readOcLlmConfig();
326
- if (ocLlm) {
327
- config.llmApiKey = ocLlm.apiKey;
328
- config.llmBaseUrl = ocLlm.baseUrl;
329
- config.llmProvider = ocLlm.provider;
330
- if (ocLlm.model)
331
- config.llmModel = ocLlm.model;
332
- }
333
- else {
334
- console.log(" No LLM API key found in environment or OC config. Server will use Ollama fallback.");
335
- console.log(" Set ANTHROPIC_API_KEY and re-run init, or edit ~/.hicortex/config.json");
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. Skip for now (can configure later in ~/.hicortex/config.json)\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`);
354
+ }
355
+ else {
356
+ console.log(" ⚠ Skipped. Nightly learning won't work until LLM is configured.");
357
+ return;
338
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 from current environment for the daemon
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. Ollama fallback
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
  */
@@ -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: read from config.json first (persisted by init), then env vars
182
+ // LLM config: check config.json first, then env vars, then claude CLI
183
183
  const savedConfig = readConfigFile(stateDir);
184
- const llmConfig = (0, llm_js_1.resolveLlmConfigForCC)({
185
- llmBaseUrl: savedConfig?.llmBaseUrl,
186
- llmApiKey: savedConfig?.llmApiKey,
187
- llmModel: savedConfig?.llmModel,
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
- const llmConfig = (0, llm_js_1.resolveLlmConfigForCC)();
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
@@ -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.9",
5
+ "version": "0.3.10",
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.9",
3
+ "version": "0.3.10",
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": {