@gamaze/hicortex 0.3.6 → 0.3.8

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
@@ -106,6 +106,11 @@ async function detect() {
106
106
  // ---------------------------------------------------------------------------
107
107
  function registerCcMcp(serverUrl) {
108
108
  try {
109
+ // Remove existing entry first (idempotent — ignore if not found)
110
+ try {
111
+ (0, node_child_process_1.execSync)("claude mcp remove hicortex 2>/dev/null", { encoding: "utf-8", stdio: "pipe" });
112
+ }
113
+ catch { /* not found */ }
109
114
  // Use claude CLI to register — it knows the correct config format and location
110
115
  (0, node_child_process_1.execSync)(`claude mcp add hicortex --transport sse ${serverUrl}/sse`, { encoding: "utf-8", stdio: "pipe" });
111
116
  console.log(` ✓ Registered MCP server via claude CLI`);
@@ -223,21 +228,84 @@ Tell them: "Get a license key at https://hicortex.gamaze.com/ — after purchase
223
228
  (0, node_fs_1.writeFileSync)((0, node_path_1.join)(CC_COMMANDS_DIR, "hicortex-activate.md"), activateContent);
224
229
  console.log(` ✓ Installed /learn and /hicortex-activate commands in ${CC_COMMANDS_DIR}`);
225
230
  }
226
- function getPackageVersion() {
231
+ /**
232
+ * Detect LLM API key from current shell environment and persist to
233
+ * ~/.hicortex/config.json so the daemon can use it (launchd/systemd
234
+ * don't inherit shell env vars).
235
+ */
236
+ function persistLlmConfig() {
237
+ const configPath = (0, node_path_1.join)(HICORTEX_HOME, "config.json");
238
+ // Read existing config (may have licenseKey)
239
+ let config = {};
227
240
  try {
228
- const pkgPath = (0, node_path_1.join)(__dirname, "..", "package.json");
229
- const pkg = JSON.parse((0, node_fs_1.readFileSync)(pkgPath, "utf-8"));
230
- return pkg.version ?? "latest";
241
+ config = JSON.parse((0, node_fs_1.readFileSync)(configPath, "utf-8"));
231
242
  }
232
- catch {
233
- return "latest";
243
+ catch { /* new file */ }
244
+ // Don't overwrite if LLM config already persisted
245
+ if (config.llmApiKey && config.llmBaseUrl) {
246
+ console.log(` ✓ LLM config already in ${configPath}`);
247
+ return;
248
+ }
249
+ // Detect from environment (same priority as resolveLlmConfigForCC)
250
+ const hcKey = process.env.HICORTEX_LLM_API_KEY;
251
+ const hcUrl = process.env.HICORTEX_LLM_BASE_URL;
252
+ const hcModel = process.env.HICORTEX_LLM_MODEL;
253
+ if (hcKey && hcUrl) {
254
+ config.llmApiKey = hcKey;
255
+ config.llmBaseUrl = hcUrl;
256
+ if (hcModel)
257
+ config.llmModel = hcModel;
258
+ }
259
+ else if (process.env.ANTHROPIC_API_KEY) {
260
+ config.llmApiKey = process.env.ANTHROPIC_API_KEY;
261
+ config.llmBaseUrl = process.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com";
262
+ config.llmProvider = "anthropic";
263
+ }
264
+ else if (process.env.OPENAI_API_KEY) {
265
+ config.llmApiKey = process.env.OPENAI_API_KEY;
266
+ config.llmBaseUrl = process.env.OPENAI_BASE_URL ?? "https://api.openai.com";
267
+ config.llmProvider = "openai";
268
+ }
269
+ else if (process.env.GOOGLE_API_KEY) {
270
+ config.llmApiKey = process.env.GOOGLE_API_KEY;
271
+ config.llmBaseUrl = "https://generativelanguage.googleapis.com/v1beta";
272
+ config.llmProvider = "google";
273
+ }
274
+ else {
275
+ console.log(" ⚠ No LLM API key found in environment. Server will use Ollama fallback.");
276
+ console.log(" Set ANTHROPIC_API_KEY and re-run init, or edit ~/.hicortex/config.json");
277
+ return;
278
+ }
279
+ (0, node_fs_1.mkdirSync)(HICORTEX_HOME, { recursive: true });
280
+ (0, node_fs_1.writeFileSync)(configPath, JSON.stringify(config, null, 2));
281
+ console.log(` ✓ LLM config saved to ${configPath}`);
282
+ }
283
+ /**
284
+ * Determine the npm package specifier for the daemon.
285
+ * Uses tag-based resolution so restarts pick up new versions automatically.
286
+ *
287
+ * Checks if the current version matches the npm `latest` tag.
288
+ * If not (e.g. running from @next), uses @gamaze/hicortex@next.
289
+ * If it does match latest, uses bare @gamaze/hicortex.
290
+ */
291
+ function getPackageSpec() {
292
+ try {
293
+ const currentVersion = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, "..", "package.json"), "utf-8")).version;
294
+ const latestVersion = (0, node_child_process_1.execSync)("npm view @gamaze/hicortex version 2>/dev/null", {
295
+ encoding: "utf-8",
296
+ timeout: 5000,
297
+ }).trim();
298
+ if (currentVersion !== latestVersion) {
299
+ return "@gamaze/hicortex@next";
300
+ }
234
301
  }
302
+ catch { /* can't check — default to bare */ }
303
+ return "@gamaze/hicortex";
235
304
  }
236
305
  function installDaemon() {
237
306
  const os = (0, node_os_1.platform)();
238
307
  const npxPath = findNpxPath();
239
- const version = getPackageVersion();
240
- const packageSpec = `@gamaze/hicortex@${version}`;
308
+ const packageSpec = getPackageSpec();
241
309
  if (os === "darwin") {
242
310
  return installLaunchd(npxPath, packageSpec);
243
311
  }
@@ -412,6 +480,8 @@ async function runInit() {
412
480
  }
413
481
  console.log();
414
482
  // Phase 3: Execute
483
+ // Persist LLM config from current environment for the daemon
484
+ persistLlmConfig();
415
485
  // Install daemon if needed
416
486
  if (!d.localServer && !d.remoteServer) {
417
487
  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,16 +201,16 @@ 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, ` +
202
209
  `${Math.round(stats.db_size_bytes / 1024)} KB`);
203
- // Create MCP server
204
- const mcpServer = createMcpServer();
205
210
  // Express app
206
211
  const app = (0, express_1.default)();
207
212
  app.use(express_1.default.json());
208
- // SSE transport management
213
+ // SSE transport management — each connection gets its own McpServer instance
209
214
  const transports = new Map();
210
215
  // Health endpoint
211
216
  app.get("/health", (_req, res) => {
@@ -219,9 +224,10 @@ async function startServer(options = {}) {
219
224
  llm: `${llmConfig.provider}/${llmConfig.model}`,
220
225
  });
221
226
  });
222
- // SSE endpoint — client connects here to establish MCP session
227
+ // SSE endpoint — each connection gets its own McpServer + transport
223
228
  app.get("/sse", async (req, res) => {
224
229
  const transport = new sse_js_1.SSEServerTransport("/messages", res);
230
+ const mcpServer = createMcpServer();
225
231
  transports.set(transport.sessionId, transport);
226
232
  transport.onclose = () => {
227
233
  transports.delete(transport.sessionId);
@@ -282,20 +288,55 @@ async function startServer(options = {}) {
282
288
  // Helpers
283
289
  // ---------------------------------------------------------------------------
284
290
  /**
285
- * Read license key from ~/.hicortex/config.json.
286
- * Written by /hicortex-activate CC command.
291
+ * Read ~/.hicortex/config.json (persisted by init with LLM and license config).
287
292
  */
288
- function readConfigLicenseKey(stateDir) {
293
+ function readConfigFile(stateDir) {
289
294
  try {
290
295
  const { readFileSync } = require("node:fs");
291
296
  const { join } = require("node:path");
292
297
  const configPath = join(stateDir, "config.json");
293
- const raw = readFileSync(configPath, "utf-8");
294
- const config = JSON.parse(raw);
295
- 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
+ }
296
337
  }
297
338
  catch {
298
- return undefined;
339
+ // Non-fatal
299
340
  }
300
341
  }
301
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.6",
5
+ "version": "0.3.8",
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.6",
3
+ "version": "0.3.8",
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": {