@gamaze/hicortex 0.3.7 → 0.3.9

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
@@ -228,6 +228,118 @@ Tell them: "Get a license key at https://hicortex.gamaze.com/ — after purchase
228
228
  (0, node_fs_1.writeFileSync)((0, node_path_1.join)(CC_COMMANDS_DIR, "hicortex-activate.md"), activateContent);
229
229
  console.log(` ✓ Installed /learn and /hicortex-activate commands in ${CC_COMMANDS_DIR}`);
230
230
  }
231
+ /**
232
+ * Read LLM config from OC's openclaw.json + auth-profiles.json.
233
+ * Used as fallback when no env vars are set (e.g. Claude Max subscription users).
234
+ */
235
+ function readOcLlmConfig() {
236
+ try {
237
+ // Read primary model from openclaw.json
238
+ const ocRaw = (0, node_fs_1.readFileSync)(OC_CONFIG, "utf-8");
239
+ const oc = JSON.parse(ocRaw);
240
+ const primary = oc?.agents?.defaults?.model?.primary;
241
+ if (!primary || typeof primary !== "string")
242
+ return null;
243
+ const [providerHint, ...rest] = primary.includes("/") ? primary.split("/") : primary.split(":");
244
+ const model = rest.join("/") || undefined;
245
+ // Read base URL from providers config
246
+ const baseUrl = oc?.models?.providers?.[providerHint]?.baseUrl;
247
+ // Read API key from auth-profiles
248
+ const { readdirSync } = require("node:fs");
249
+ const agentsDir = (0, node_path_1.join)((0, node_os_1.homedir)(), ".openclaw", "agents");
250
+ let apiKey;
251
+ try {
252
+ for (const agentId of readdirSync(agentsDir)) {
253
+ try {
254
+ const authPath = (0, node_path_1.join)(agentsDir, agentId, "agent", "auth-profiles.json");
255
+ const auth = JSON.parse((0, node_fs_1.readFileSync)(authPath, "utf-8"));
256
+ for (const [profileId, profile] of Object.entries(auth?.profiles ?? {})) {
257
+ const p = profile;
258
+ if (p?.provider === providerHint || profileId.startsWith(`${providerHint}:`)) {
259
+ if (p?.key) {
260
+ apiKey = p.key;
261
+ break;
262
+ }
263
+ }
264
+ }
265
+ if (apiKey)
266
+ break;
267
+ }
268
+ catch { /* skip */ }
269
+ }
270
+ }
271
+ catch { /* no agents dir */ }
272
+ if (!apiKey || !baseUrl)
273
+ return null;
274
+ return { apiKey, baseUrl, provider: providerHint, model };
275
+ }
276
+ catch {
277
+ return null;
278
+ }
279
+ }
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).
284
+ */
285
+ function persistLlmConfig() {
286
+ const configPath = (0, node_path_1.join)(HICORTEX_HOME, "config.json");
287
+ // Read existing config (may have licenseKey)
288
+ let config = {};
289
+ try {
290
+ config = JSON.parse((0, node_fs_1.readFileSync)(configPath, "utf-8"));
291
+ }
292
+ catch { /* new file */ }
293
+ // Don't overwrite if LLM config already persisted
294
+ if (config.llmApiKey && config.llmBaseUrl) {
295
+ console.log(` ✓ LLM config already in ${configPath}`);
296
+ return;
297
+ }
298
+ // Detect from environment (same priority as resolveLlmConfigForCC)
299
+ const hcKey = process.env.HICORTEX_LLM_API_KEY;
300
+ const hcUrl = process.env.HICORTEX_LLM_BASE_URL;
301
+ const hcModel = process.env.HICORTEX_LLM_MODEL;
302
+ if (hcKey && hcUrl) {
303
+ config.llmApiKey = hcKey;
304
+ config.llmBaseUrl = hcUrl;
305
+ if (hcModel)
306
+ config.llmModel = hcModel;
307
+ }
308
+ else if (process.env.ANTHROPIC_API_KEY) {
309
+ config.llmApiKey = process.env.ANTHROPIC_API_KEY;
310
+ config.llmBaseUrl = process.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com";
311
+ config.llmProvider = "anthropic";
312
+ }
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";
322
+ }
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");
336
+ return;
337
+ }
338
+ }
339
+ (0, node_fs_1.mkdirSync)(HICORTEX_HOME, { recursive: true });
340
+ (0, node_fs_1.writeFileSync)(configPath, JSON.stringify(config, null, 2));
341
+ console.log(` ✓ LLM config saved to ${configPath}`);
342
+ }
231
343
  /**
232
344
  * Determine the npm package specifier for the daemon.
233
345
  * Uses tag-based resolution so restarts pick up new versions automatically.
@@ -428,6 +540,8 @@ async function runInit() {
428
540
  }
429
541
  console.log();
430
542
  // Phase 3: Execute
543
+ // Persist LLM config from current environment for the daemon
544
+ persistLlmConfig();
431
545
  // Install daemon if needed
432
546
  if (!d.localServer && !d.remoteServer) {
433
547
  installDaemon();
@@ -179,13 +179,18 @@ 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
183
- const llmConfig = (0, llm_js_1.resolveLlmConfigForCC)();
182
+ // LLM config: read from config.json first (persisted by init), then env vars
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
189
  llm = new llm_js_1.LlmClient(llmConfig);
185
190
  console.log(`[hicortex] LLM: ${llmConfig.provider}/${llmConfig.model} (reflect: ${llmConfig.reflectModel})`);
186
191
  // License: read from options, config file, or env var
187
192
  const licenseKey = options.licenseKey
188
- ?? readConfigLicenseKey(stateDir)
193
+ ?? savedConfig?.licenseKey
189
194
  ?? process.env.HICORTEX_LICENSE_KEY;
190
195
  (0, license_js_1.validateLicense)(licenseKey, stateDir).catch((err) => console.log(`[hicortex] License validation failed: ${err}`));
191
196
  if (licenseKey) {
@@ -196,6 +201,8 @@ async function startServer(options = {}) {
196
201
  cancelConsolidation = (0, consolidate_js_1.scheduleConsolidation)(db, llm, embedder_js_1.embed, consolidateHour);
197
202
  // Seed lesson on first run
198
203
  await (0, seed_lesson_js_1.injectSeedLesson)(db);
204
+ // Self-heal: fix pinned version in daemon config
205
+ fixDaemonVersionPin();
199
206
  // Stats
200
207
  const stats = (0, db_js_1.getStats)(db, dbPath);
201
208
  console.log(`[hicortex] Ready: ${stats.memories} memories, ${stats.links} links, ` +
@@ -281,20 +288,55 @@ async function startServer(options = {}) {
281
288
  // Helpers
282
289
  // ---------------------------------------------------------------------------
283
290
  /**
284
- * Read license key from ~/.hicortex/config.json.
285
- * Written by /hicortex-activate CC command.
291
+ * Read ~/.hicortex/config.json (persisted by init with LLM and license config).
286
292
  */
287
- function readConfigLicenseKey(stateDir) {
293
+ function readConfigFile(stateDir) {
288
294
  try {
289
295
  const { readFileSync } = require("node:fs");
290
296
  const { join } = require("node:path");
291
297
  const configPath = join(stateDir, "config.json");
292
- const raw = readFileSync(configPath, "utf-8");
293
- const config = JSON.parse(raw);
294
- return config.licenseKey || undefined;
298
+ return JSON.parse(readFileSync(configPath, "utf-8"));
299
+ }
300
+ catch {
301
+ return null;
302
+ }
303
+ }
304
+ /**
305
+ * Self-heal: if the daemon plist/systemd unit has a pinned version
306
+ * (e.g. @gamaze/hicortex@0.3.4), rewrite it to use the bare package
307
+ * name so future restarts pick up the latest version automatically.
308
+ */
309
+ function fixDaemonVersionPin() {
310
+ try {
311
+ const os = require("node:os");
312
+ const fs = require("node:fs");
313
+ const path = require("node:path");
314
+ if (os.platform() === "darwin") {
315
+ const plistPath = path.join(os.homedir(), "Library", "LaunchAgents", "com.gamaze.hicortex.plist");
316
+ if (!fs.existsSync(plistPath))
317
+ return;
318
+ const content = fs.readFileSync(plistPath, "utf-8");
319
+ // Match @gamaze/hicortex@X.Y.Z (pinned to specific version)
320
+ if (/@gamaze\/hicortex@\d+\.\d+\.\d+/.test(content)) {
321
+ const fixed = content.replace(/@gamaze\/hicortex@\d+\.\d+\.\d+/, "@gamaze/hicortex");
322
+ fs.writeFileSync(plistPath, fixed);
323
+ console.log("[hicortex] Fixed daemon config: removed pinned version (will use latest on next restart)");
324
+ }
325
+ }
326
+ else if (os.platform() === "linux") {
327
+ const servicePath = path.join(os.homedir(), ".config", "systemd", "user", "hicortex.service");
328
+ if (!fs.existsSync(servicePath))
329
+ return;
330
+ const content = fs.readFileSync(servicePath, "utf-8");
331
+ if (/@gamaze\/hicortex@\d+\.\d+\.\d+/.test(content)) {
332
+ const fixed = content.replace(/@gamaze\/hicortex@\d+\.\d+\.\d+/, "@gamaze/hicortex");
333
+ fs.writeFileSync(servicePath, fixed);
334
+ console.log("[hicortex] Fixed daemon config: removed pinned version");
335
+ }
336
+ }
295
337
  }
296
338
  catch {
297
- return undefined;
339
+ // Non-fatal
298
340
  }
299
341
  }
300
342
  function formatResults(results) {
@@ -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.7",
5
+ "version": "0.3.9",
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.7",
3
+ "version": "0.3.9",
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": {