@alaarab/cortex 1.15.2 → 1.15.4

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.
@@ -607,7 +607,7 @@ export function getListItems(cortexPath, profile, state, healthLineCount) {
607
607
  return getProjectSkills(cortexPath, state.project).map((s) => ({ name: s.name, text: s.path }));
608
608
  }
609
609
  case "Hooks": {
610
- return getHookEntries(cortexPath).map((e) => ({ name: e.tool, text: e.enabled ? "enabled" : "disabled" }));
610
+ return getHookEntries(cortexPath).map((e) => ({ name: e.event, text: e.enabled ? "active" : "inactive" }));
611
611
  }
612
612
  case "Health":
613
613
  return Array.from({ length: Math.max(1, healthLineCount) }, (_, i) => ({ id: String(i) }));
@@ -663,7 +663,7 @@ export async function activateSelected(host) {
663
663
  break;
664
664
  case "Hooks":
665
665
  if (item.name) {
666
- host.setMessage(` ${item.text === "enabled" ? style.boldGreen("enabled") : style.dim("disabled")} ${style.bold(item.name)}`);
666
+ host.setMessage(` ${item.text === "active" ? style.boldGreen("active") : style.dim("inactive")} ${style.bold(item.name)}`);
667
667
  }
668
668
  break;
669
669
  }
@@ -758,15 +758,18 @@ export async function doViewAction(host, key) {
758
758
  });
759
759
  }
760
760
  else if (key === "a") {
761
- host.setMessage(` Use "cortex skills add ${project ?? "<project>"} <path>" to add a skill`);
761
+ if (!project) {
762
+ host.setMessage("Select a project first.");
763
+ return;
764
+ }
765
+ host.startInput("skill-add", "");
762
766
  }
763
767
  break;
764
768
  case "Hooks":
765
- if ((key === "a" || key === "d") && item?.name) {
769
+ if (key === "a" || key === "d") {
766
770
  const enable = key === "a";
767
- const prefs = { hookTools: { ...((getHookEntries(host.cortexPath).reduce((acc, e) => ({ ...acc, [e.tool]: e.enabled }), {}))), [item.name]: enable } };
768
- writeInstallPreferences(host.cortexPath, prefs);
769
- host.setMessage(` ${enable ? style.boldGreen("Enabled") : style.dim("Disabled")} hooks for ${item.name}`);
771
+ writeInstallPreferences(host.cortexPath, { hooksEnabled: enable });
772
+ host.setMessage(` Hooks ${enable ? style.boldGreen("enabled") : style.dim("disabled")} — takes effect next session`);
770
773
  }
771
774
  break;
772
775
  }
@@ -48,6 +48,7 @@ export function renderBottomBar(state, navMode, inputCtx, inputBuf) {
48
48
  command: "cmd",
49
49
  add: "add task",
50
50
  "learn-add": "add finding",
51
+ "skill-add": "new skill name",
51
52
  "mq-edit": "edit Memory Queue item",
52
53
  };
53
54
  const label = labels[inputCtx] || inputCtx;
@@ -433,20 +434,20 @@ export function renderSkillsView(ctx, cursor, height) {
433
434
  }
434
435
  return vp.lines;
435
436
  }
436
- // ── Hooks view ─────────────────────────────────────────────────────────────
437
- const HOOK_TOOLS = ["claude", "copilot", "cursor", "codex"];
437
+ const LIFECYCLE_HOOKS = [
438
+ { event: "UserPromptSubmit", description: "inject context before each prompt" },
439
+ { event: "Stop", description: "auto-save findings after each response" },
440
+ { event: "SessionStart", description: "git pull at session start" },
441
+ ];
438
442
  export function getHookEntries(cortexPath) {
439
443
  const prefs = readInstallPreferences(cortexPath);
440
444
  const hooksEnabled = prefs.hooksEnabled !== false;
441
- const toolPrefs = (prefs.hookTools && typeof prefs.hookTools === "object") ? prefs.hookTools : {};
442
- return HOOK_TOOLS.map((tool) => ({
443
- tool,
444
- enabled: hooksEnabled && toolPrefs[tool] !== false,
445
- }));
445
+ return LIFECYCLE_HOOKS.map((h) => ({ ...h, enabled: hooksEnabled }));
446
446
  }
447
447
  export function renderHooksView(ctx, cursor, height) {
448
448
  const cols = process.stdout.columns || 80;
449
449
  const entries = getHookEntries(ctx.cortexPath);
450
+ const allEnabled = entries.every((e) => e.enabled);
450
451
  const allLines = [];
451
452
  let cursorFirstLine = 0;
452
453
  let cursorLastLine = 0;
@@ -455,16 +456,24 @@ export function renderHooksView(ctx, cursor, height) {
455
456
  const isSelected = i === cursor;
456
457
  if (isSelected)
457
458
  cursorFirstLine = allLines.length;
458
- const statusBadge = e.enabled ? style.boldGreen("enabled ") : style.dim("disabled");
459
- let row = ` ${style.dim((i + 1).toString().padEnd(3))} ${statusBadge} ${style.bold(e.tool)}`;
460
- if (isSelected)
461
- row = `\x1b[7m${padToWidth(row, cols)}${RESET}`;
462
- else
463
- row = truncateLine(row, cols);
464
- allLines.push(row);
459
+ const statusBadge = e.enabled ? style.boldGreen("active ") : style.dim("inactive");
460
+ let nameRow = ` ${style.dim((i + 1).toString().padEnd(3))} ${statusBadge} ${style.bold(e.event)}`;
461
+ let descRow = ` ${style.dim(e.description)}`;
462
+ if (isSelected) {
463
+ nameRow = `\x1b[7m${padToWidth(nameRow, cols)}${RESET}`;
464
+ descRow = `\x1b[7m${padToWidth(descRow, cols)}${RESET}`;
465
+ }
466
+ else {
467
+ nameRow = truncateLine(nameRow, cols);
468
+ descRow = truncateLine(descRow, cols);
469
+ }
470
+ allLines.push(nameRow);
471
+ allLines.push(descRow);
465
472
  if (isSelected)
466
473
  cursorLastLine = allLines.length - 1;
467
474
  }
475
+ allLines.push("");
476
+ allLines.push(style.dim(` hooks: ${allEnabled ? style.boldGreen("ON") : style.boldRed("OFF")} · ${style.dim("a = enable all · d = disable all")}`));
468
477
  const usableHeight = Math.max(1, height - (allLines.length > height ? 1 : 0));
469
478
  const vp = lineViewport(allLines, cursorFirstLine, cursorLastLine, usableHeight, ctx.currentScroll());
470
479
  ctx.setScroll(vp.scrollStart);
package/mcp/dist/shell.js CHANGED
@@ -140,6 +140,27 @@ export class CortexShell {
140
140
  this.setMessage(` ${resultMsg(addFinding(this.cortexPath, p, buf))}`);
141
141
  break;
142
142
  }
143
+ case "skill-add": {
144
+ const p = this.ensureProjectSelected();
145
+ if (!p)
146
+ return;
147
+ const name = buf.trim().replace(/\.md$/i, "").replace(/[^a-zA-Z0-9_-]/g, "-");
148
+ if (!name) {
149
+ this.setMessage(" No name entered.");
150
+ return;
151
+ }
152
+ const destDir = path.join(this.cortexPath, p, "skills");
153
+ fs.mkdirSync(destDir, { recursive: true });
154
+ const dest = path.join(destDir, `${name}.md`);
155
+ if (fs.existsSync(dest)) {
156
+ this.setMessage(` Skill "${name}" already exists.`);
157
+ return;
158
+ }
159
+ const template = `# ${name}\n\nDescribe what this skill does.\n\n## Usage\n\n\`\`\`\nExample usage here\n\`\`\`\n`;
160
+ fs.writeFileSync(dest, template, "utf8");
161
+ this.setMessage(` Created skill "${name}" — edit ${dest}`);
162
+ break;
163
+ }
143
164
  case "mq-edit": {
144
165
  const p = this.ensureProjectSelected();
145
166
  if (!p)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/cortex",
3
- "version": "1.15.2",
3
+ "version": "1.15.4",
4
4
  "description": "Long-term memory for AI agents. Stored as markdown in a git repo you own.",
5
5
  "type": "module",
6
6
  "bin": {