@gamaze/hicortex 0.11.0 → 0.12.0

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.
@@ -92,6 +92,12 @@ async function showNightlyStatus() {
92
92
  catch { /* not installed */ }
93
93
  }
94
94
  console.log(`Timer: ${timerInfo}${!timerActive ? " ⚠ Pipeline will NOT run automatically" : ""}`);
95
+ // Scheduled runs log here (launchd plist / systemd unit both append) —
96
+ // point operators at it, since the runs' output is not in journalctl.
97
+ const nightlyLogPath = (0, node_path_1.join)(HICORTEX_HOME, "nightly.log");
98
+ if ((0, node_fs_1.existsSync)(nightlyLogPath)) {
99
+ console.log(`Log: ${nightlyLogPath}`);
100
+ }
95
101
  // DB stats
96
102
  const dbPath = (0, db_js_1.resolveDbPath)();
97
103
  if ((0, node_fs_1.existsSync)(dbPath)) {
package/dist/nightly.js CHANGED
@@ -103,10 +103,29 @@ function writeLastRun(stateDir = HICORTEX_HOME) {
103
103
  return s;
104
104
  }, stateDir);
105
105
  }
106
+ const NIGHTLY_LOG_MAX_BYTES = 1024 * 1024; // 1 MB — years of normal runs
107
+ /**
108
+ * Keep ~/.hicortex/nightly.log bounded. The launchd plist and systemd unit
109
+ * both append to it forever with no rotation, and the typical volatile-journal
110
+ * target is a Raspberry Pi on a small SD card. Copy-then-truncate (not rename)
111
+ * because the process's own stdout may hold an O_APPEND fd on this very file —
112
+ * truncation keeps that fd valid and subsequent writes land at the new end.
113
+ */
114
+ function rotateNightlyLog(stateDir = HICORTEX_HOME) {
115
+ const logPath = (0, node_path_1.join)(stateDir, "nightly.log");
116
+ try {
117
+ if ((0, node_fs_1.statSync)(logPath).size <= NIGHTLY_LOG_MAX_BYTES)
118
+ return;
119
+ (0, node_fs_1.copyFileSync)(logPath, `${logPath}.old`);
120
+ (0, node_fs_1.truncateSync)(logPath);
121
+ }
122
+ catch { /* no log file, or unreadable — nothing to rotate */ }
123
+ }
106
124
  async function runNightly(options = {}) {
107
125
  const dryRun = options.dryRun ?? false;
108
126
  const captureOnly = options.captureOnly ?? false;
109
127
  const stateDir = options.stateDir ?? HICORTEX_HOME;
128
+ rotateNightlyLog(stateDir);
110
129
  // One-time migration of legacy state files (no-op if state.json exists)
111
130
  (0, state_js_1.migrateLegacyState)(stateDir);
112
131
  // Check mode: client or server
package/dist/uninstall.js CHANGED
@@ -32,29 +32,45 @@ async function runUninstall() {
32
32
  return;
33
33
  }
34
34
  console.log();
35
- // 1. Stop and remove daemon
35
+ // 1. Stop and remove daemon + nightly timer (both units, or the timer
36
+ // keeps firing against a half-removed install)
36
37
  const os = (0, node_os_1.platform)();
37
38
  if (os === "darwin") {
38
- const plistPath = (0, node_path_1.join)((0, node_os_1.homedir)(), "Library", "LaunchAgents", "com.gamaze.hicortex.plist");
39
- if ((0, node_fs_1.existsSync)(plistPath)) {
40
- try {
41
- (0, node_child_process_1.execSync)(`launchctl unload ${plistPath} 2>/dev/null`);
39
+ for (const name of ["com.gamaze.hicortex.plist", "com.gamaze.hicortex-nightly.plist"]) {
40
+ const plistPath = (0, node_path_1.join)((0, node_os_1.homedir)(), "Library", "LaunchAgents", name);
41
+ if ((0, node_fs_1.existsSync)(plistPath)) {
42
+ try {
43
+ (0, node_child_process_1.execSync)(`launchctl unload ${plistPath} 2>/dev/null`);
44
+ }
45
+ catch { /* not loaded */ }
46
+ (0, node_fs_1.unlinkSync)(plistPath);
47
+ console.log(` ✓ Removed ${name}`);
42
48
  }
43
- catch { /* not loaded */ }
44
- (0, node_fs_1.unlinkSync)(plistPath);
45
- console.log(" ✓ Removed launchd daemon");
46
49
  }
47
50
  }
48
51
  else if (os === "linux") {
49
52
  try {
50
53
  (0, node_child_process_1.execSync)("systemctl --user disable --now hicortex.service 2>/dev/null");
51
- const servicePath = (0, node_path_1.join)((0, node_os_1.homedir)(), ".config", "systemd", "user", "hicortex.service");
52
- if ((0, node_fs_1.existsSync)(servicePath))
53
- (0, node_fs_1.unlinkSync)(servicePath);
54
- (0, node_child_process_1.execSync)("systemctl --user daemon-reload 2>/dev/null");
55
- console.log(" ✓ Removed systemd service");
56
54
  }
57
55
  catch { /* not installed */ }
56
+ try {
57
+ (0, node_child_process_1.execSync)("systemctl --user disable --now hicortex-nightly.timer 2>/dev/null");
58
+ }
59
+ catch { /* not installed */ }
60
+ const unitDir = (0, node_path_1.join)((0, node_os_1.homedir)(), ".config", "systemd", "user");
61
+ for (const name of ["hicortex.service", "hicortex-nightly.timer", "hicortex-nightly.service"]) {
62
+ const unitPath = (0, node_path_1.join)(unitDir, name);
63
+ try {
64
+ if ((0, node_fs_1.existsSync)(unitPath))
65
+ (0, node_fs_1.unlinkSync)(unitPath);
66
+ }
67
+ catch { /* leave it */ }
68
+ }
69
+ try {
70
+ (0, node_child_process_1.execSync)("systemctl --user daemon-reload 2>/dev/null");
71
+ }
72
+ catch { /* fine */ }
73
+ console.log(" ✓ Removed systemd service + nightly timer");
58
74
  }
59
75
  // 2. Remove MCP from CC
60
76
  try {
package/dist/viz.d.ts CHANGED
@@ -51,6 +51,21 @@ export declare function readVizHtml(): string;
51
51
  * when the asset cannot be read.
52
52
  */
53
53
  export declare function vizHandler(): express.RequestHandler;
54
+ /**
55
+ * Resolve the on-disk path of the context-layer editor page. Throws (fail
56
+ * explicitly) when the asset is missing — same contract as resolveVizHtmlPath.
57
+ * assets/ sits next to both dist/ (dist/viz.js → ../assets/) and src/
58
+ * (src/viz.ts → ../assets/ under tsx), so one sibling candidate covers both.
59
+ */
60
+ export declare function resolveContextHtmlPath(): string;
61
+ /** Read the context editor page. Read at request time so a reinstall is live. */
62
+ export declare function readContextHtml(): string;
63
+ /**
64
+ * Express handler for GET /context/ui — the PRIMARY edit surface for the
65
+ * standing context layer. 503 with the usual {error} shape when the asset
66
+ * cannot be read, exactly like vizHandler.
67
+ */
68
+ export declare function contextUiHandler(): express.RequestHandler;
54
69
  /**
55
70
  * Resolve the on-disk path of an allowlisted vendor bundle, or null when the
56
71
  * requested name is not on the allowlist. The filesystem path is built ONLY
package/dist/viz.js CHANGED
@@ -31,6 +31,9 @@ exports.createAuthMiddleware = createAuthMiddleware;
31
31
  exports.resolveVizHtmlPath = resolveVizHtmlPath;
32
32
  exports.readVizHtml = readVizHtml;
33
33
  exports.vizHandler = vizHandler;
34
+ exports.resolveContextHtmlPath = resolveContextHtmlPath;
35
+ exports.readContextHtml = readContextHtml;
36
+ exports.contextUiHandler = contextUiHandler;
34
37
  exports.resolveVizVendorPath = resolveVizVendorPath;
35
38
  exports.vizVendorHandler = vizVendorHandler;
36
39
  const node_fs_1 = require("node:fs");
@@ -70,6 +73,14 @@ function createAuthMiddleware(authToken) {
70
73
  // normal Authorization header on its data fetches.
71
74
  if (req.method === "GET" && req.path === "/viz")
72
75
  return next();
76
+ // The /context/ui page SHELL is public for the same reason as /viz: a
77
+ // self-contained static editor page, no data and no secrets (it ships
78
+ // verbatim in the npm tarball). The standing-context DATA it edits comes
79
+ // from GET/PUT /context, which stay bearer-only (localhost bypass) like
80
+ // every other data route; the page collects the token client-side and
81
+ // sends it as a normal Authorization header on its /context fetches.
82
+ if (req.method === "GET" && req.path === "/context/ui")
83
+ return next();
73
84
  // The pinned renderer bundles the /viz page loads (#139) are public for
74
85
  // the same reason as the shell: static third-party code shipped verbatim
75
86
  // in the npm tarball, zero data. Kept tight: GET only, and ONLY names on
@@ -136,6 +147,43 @@ function vizHandler() {
136
147
  }
137
148
  };
138
149
  }
150
+ // ---------------------------------------------------------------------------
151
+ // Context layer editor page (/context/ui, 0.12 — spec 2026-07-12 §5)
152
+ // ---------------------------------------------------------------------------
153
+ /**
154
+ * Resolve the on-disk path of the context-layer editor page. Throws (fail
155
+ * explicitly) when the asset is missing — same contract as resolveVizHtmlPath.
156
+ * assets/ sits next to both dist/ (dist/viz.js → ../assets/) and src/
157
+ * (src/viz.ts → ../assets/ under tsx), so one sibling candidate covers both.
158
+ */
159
+ function resolveContextHtmlPath() {
160
+ const candidates = [(0, node_path_1.join)(__dirname, "..", "assets", "context.html")];
161
+ for (const candidate of candidates) {
162
+ if ((0, node_fs_1.existsSync)(candidate))
163
+ return candidate;
164
+ }
165
+ throw new Error(`context.html asset not found — looked in: ${candidates.join(", ")}. ` +
166
+ `The package install is incomplete (assets/ missing).`);
167
+ }
168
+ /** Read the context editor page. Read at request time so a reinstall is live. */
169
+ function readContextHtml() {
170
+ return (0, node_fs_1.readFileSync)(resolveContextHtmlPath(), "utf-8");
171
+ }
172
+ /**
173
+ * Express handler for GET /context/ui — the PRIMARY edit surface for the
174
+ * standing context layer. 503 with the usual {error} shape when the asset
175
+ * cannot be read, exactly like vizHandler.
176
+ */
177
+ function contextUiHandler() {
178
+ return (_req, res) => {
179
+ try {
180
+ res.type("html").send(readContextHtml());
181
+ }
182
+ catch (err) {
183
+ res.status(503).json({ error: err instanceof Error ? err.message : String(err) });
184
+ }
185
+ };
186
+ }
139
187
  /**
140
188
  * Resolve the on-disk path of an allowlisted vendor bundle, or null when the
141
189
  * requested name is not on the allowlist. The filesystem path is built ONLY
@@ -15,28 +15,27 @@ Gives [Hermes](https://github.com/nousresearch/hermes-agent) agents self-learnin
15
15
  | `prefetch(query)` | recall relevant memories before each turn | `GET /search` |
16
16
  | `queue_prefetch(query)` | background recall for the next turn | `GET /search` |
17
17
  | `system_prompt_block()` | inject distilled lessons + memory index | `GET /lessons` |
18
- | `get_tool_schemas()` | exposes the 8 unified tools + `hicortex_recall_recent` | see tool table below |
18
+ | `get_tool_schemas()` | exposes the 8 unified tools | see tool table below |
19
19
 
20
20
  That's the whole surface. No `sync_turn`, no compaction/session-end capture — those are intentionally absent.
21
21
 
22
- ### Tools (unified 8 + 1 Hermes-specific)
22
+ ### Tools (unified 8)
23
23
 
24
24
  | Tool | REST call | Description |
25
25
  |---|---|---|
26
26
  | `hicortex_search` | `GET /search` | Semantic search over long-term memory |
27
- | `hicortex_context` | `GET /context` | Recent context memories by project |
27
+ | `hicortex_recent` | `GET /recent` | Recent memories by project (queryless recall; was `hicortex_context`/`hicortex_recall_recent` before 0.12) |
28
28
  | `hicortex_ingest` | `POST /ingest` | Store a new memory |
29
29
  | `hicortex_lessons` | `GET /lessons` | Get distilled lessons |
30
30
  | `hicortex_index` | `GET /index` | Knowledge domain index |
31
31
  | `hicortex_graph` | `GET /graph` | Graph queries (neighbors/hubs/path) |
32
32
  | `hicortex_update` | `POST /update` | Update a memory (re-embeds on content change) |
33
33
  | `hicortex_delete` | `POST /delete` | Permanently delete a memory and its links |
34
- | `hicortex_recall_recent` | `GET /context` | Hermes-specific alias for context recall |
35
34
 
36
35
  ## Prerequisites
37
36
 
38
37
  - A reachable Hicortex server (default `http://localhost:8787`). Stand one up with `npx @gamaze/hicortex init`.
39
- - The server needs the REST `/search`, `/context`, `/lessons` endpoints (Hicortex ≥ 0.7).
38
+ - The server needs the REST `/search`, `/recent`, `/lessons` endpoints (Hicortex ≥ 0.12 — this plugin version does not talk to older servers; upgrade the server first).
40
39
 
41
40
  ## Install
42
41
 
@@ -1,7 +1,7 @@
1
1
  """Hicortex memory provider plugin for Hermes — recall-only.
2
2
 
3
3
  Recall: prefetch() -> GET /search (relevant memories before each turn)
4
- tools -> hicortex_search / hicortex_recall_recent
4
+ tools -> hicortex_search / hicortex_recent
5
5
  system_prompt_block -> lessons injected into the system prompt
6
6
 
7
7
  Capture is NOT the plugin's job. A nightly reader on the Hicortex server
@@ -83,14 +83,14 @@ class HicortexClient:
83
83
  {"query": query, "limit": limit, "project": project, "privacy": privacy},
84
84
  ).get("results", [])
85
85
 
86
- def context(
86
+ def recent(
87
87
  self,
88
88
  project: Optional[str] = None,
89
89
  limit: int = 10,
90
90
  privacy: Optional[str] = None,
91
91
  ) -> list[dict]:
92
92
  return self._get(
93
- "/context", {"project": project, "limit": limit, "privacy": privacy}
93
+ "/recent", {"project": project, "limit": limit, "privacy": privacy}
94
94
  ).get("results", [])
95
95
 
96
96
  def lessons(self) -> dict[str, Any]:
@@ -1,6 +1,6 @@
1
1
  name: hicortex
2
- version: 0.4.0
3
- description: "Self-learning memory for Hermes agents — every session is distilled into lessons overnight, and your agent wakes up wiser. Injects fresh lessons each turn and exposes the full 8-tool memory surface (search, context, ingest, lessons, index, graph, update, delete) via a shared Hicortex server. Stdlib-only."
2
+ version: 0.5.0
3
+ description: "Self-learning memory for Hermes agents — every session is distilled into lessons overnight, and your agent wakes up wiser. Injects fresh lessons each turn and exposes the full 8-tool memory surface (search, recent, ingest, lessons, index, graph, update, delete) via a shared Hicortex server. Stdlib-only."
4
4
  pip_dependencies: []
5
5
  hooks: []
6
6
  requires_env:
@@ -2,7 +2,7 @@
2
2
 
3
3
  Recall: prefetch() -> GET /search (relevant memories before each turn)
4
4
  queue_prefetch() -> GET /search (background recall for the next turn)
5
- tools -> hicortex_search / hicortex_recall_recent
5
+ tools -> hicortex_search / hicortex_recent
6
6
  system_prompt_block -> lessons + memory index injected into the prompt
7
7
 
8
8
  Capture is NOT the plugin's job. A nightly reader on the Hicortex server
@@ -152,7 +152,7 @@ class HicortexProvider(MemoryProvider):
152
152
  lines = [
153
153
  "## Hicortex long-term memory",
154
154
  "You have shared long-term memory across sessions. Use `hicortex_search` "
155
- "for specific recall and `hicortex_recall_recent` for recent context.",
155
+ "for specific recall and `hicortex_recent` for recent memories by project.",
156
156
  ]
157
157
  if lessons:
158
158
  lines.append("Lessons:")
@@ -188,24 +188,11 @@ class HicortexProvider(MemoryProvider):
188
188
  },
189
189
  },
190
190
  {
191
- "name": "hicortex_recall_recent",
192
- "description": "Recall recent context memories, optionally filtered by project.",
193
- "parameters": {
194
- "type": "object",
195
- "properties": {
196
- "project": {"type": "string"},
197
- "limit": {
198
- "type": "number",
199
- "description": "Max results (default 10)",
200
- },
201
- },
202
- },
203
- },
204
- {
205
- "name": "hicortex_context",
191
+ "name": "hicortex_recent",
206
192
  "description": (
207
- "Get recent context memories, optionally filtered by project. "
208
- "Useful to recall what happened recently."
193
+ "Get recent memories, optionally filtered by project. Queryless recall "
194
+ "of the latest memories by project, ranked by importance. Useful to "
195
+ "catch up on what happened recently."
209
196
  ),
210
197
  "parameters": {
211
198
  "type": "object",
@@ -335,8 +322,8 @@ class HicortexProvider(MemoryProvider):
335
322
  )
336
323
  return json.dumps(hits)
337
324
 
338
- elif tool_name in ("hicortex_recall_recent", "hicortex_context"):
339
- hits = client.context(
325
+ elif tool_name == "hicortex_recent":
326
+ hits = client.recent(
340
327
  project=args.get("project") or self._project,
341
328
  limit=int(args.get("limit", 10)),
342
329
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gamaze/hicortex",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Self-learning memory for AI agents — experience captured automatically, distilled into lessons overnight, shared across your whole fleet. Works with Hermes, OpenClaw, Claude Code, and Pi.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -16,7 +16,7 @@ You have access to a long-term memory system that stores knowledge from past ses
16
16
  - You're about to make a decision that might contradict past work
17
17
  - Starting work on a project you've worked on before
18
18
 
19
- ## When to use hicortex_context
19
+ ## When to use hicortex_recent
20
20
 
21
21
  - At the start of a session to recall recent project state
22
22
  - When switching between projects to load relevant context