@gamaze/hicortex 0.4.2 → 0.4.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.
- package/dist/claude-md.d.ts +2 -2
- package/dist/claude-md.js +8 -6
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +23 -9
- package/dist/consolidate.js +9 -22
- package/dist/db.d.ts +2 -0
- package/dist/db.js +88 -9
- package/dist/distiller.d.ts +4 -2
- package/dist/distiller.js +31 -10
- package/dist/extensions.d.ts +126 -0
- package/dist/extensions.js +154 -0
- package/dist/features.d.ts +37 -0
- package/dist/features.js +127 -0
- package/dist/index.js +20 -35
- package/dist/license.d.ts +13 -3
- package/dist/license.js +30 -41
- package/dist/mcp-server.js +50 -18
- package/dist/nightly-status.d.ts +11 -0
- package/dist/nightly-status.js +167 -0
- package/dist/nightly.js +102 -22
- package/dist/state.d.ts +64 -0
- package/dist/state.js +162 -0
- package/package.json +7 -3
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized feature gating — single source of truth for tier-dependent values.
|
|
3
|
+
*
|
|
4
|
+
* Why this exists:
|
|
5
|
+
* - getFeatures() in license.ts was sync but validateLicense was async, creating
|
|
6
|
+
* a race where Pro users got free-tier features during the validation window.
|
|
7
|
+
* - License checks were scattered across 8+ call sites with subtly different
|
|
8
|
+
* handling (e.g., consolidate.ts:308 had a dynamic import in a hot loop to
|
|
9
|
+
* dodge a circular import).
|
|
10
|
+
*
|
|
11
|
+
* All feature decisions now flow through this module. Call initFeatures() once at
|
|
12
|
+
* process boot before serving any requests; sync getters become deterministic.
|
|
13
|
+
*/
|
|
14
|
+
import type { LicenseInfo } from "./types.js";
|
|
15
|
+
/**
|
|
16
|
+
* Initialize the feature cache. Call ONCE at process boot before any feature
|
|
17
|
+
* gating queries. Race fix:
|
|
18
|
+
* 1. Synchronously load persisted tier from disk (instant, deterministic)
|
|
19
|
+
* 2. If no persisted tier and we have a key, AWAIT first validation
|
|
20
|
+
* 3. If persisted tier exists, kick off background re-validation
|
|
21
|
+
*
|
|
22
|
+
* After this returns, sync getters (isPro, lessonsLimit, etc.) are deterministic
|
|
23
|
+
* and reflect the user's actual tier — no more "free during validation window".
|
|
24
|
+
*/
|
|
25
|
+
export declare function initFeatures(licenseKey: string | undefined, stateDir?: string): Promise<void>;
|
|
26
|
+
/** Are we on a paid tier (Pro, Team, Lifetime)? */
|
|
27
|
+
export declare function isPro(): boolean;
|
|
28
|
+
/** Memory count cap. -1 = unlimited (paid). */
|
|
29
|
+
export declare function maxMemoriesAllowed(): number;
|
|
30
|
+
/** Has the memory cap been hit? Pass current count from caller. */
|
|
31
|
+
export declare function memoryCapReached(currentCount: number): boolean;
|
|
32
|
+
/** Number of lessons to inject into CLAUDE.md / before_agent_start. */
|
|
33
|
+
export declare function lessonsLimit(): number;
|
|
34
|
+
/** Is remote /ingest allowed? Free + Team yes, Pro (single-machine) no. */
|
|
35
|
+
export declare function remoteIngestAllowed(): boolean;
|
|
36
|
+
/** Direct read of the underlying features (for callers that need the full record). */
|
|
37
|
+
export declare function getCurrentFeatures(): LicenseInfo["features"];
|
package/dist/features.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Centralized feature gating — single source of truth for tier-dependent values.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists:
|
|
6
|
+
* - getFeatures() in license.ts was sync but validateLicense was async, creating
|
|
7
|
+
* a race where Pro users got free-tier features during the validation window.
|
|
8
|
+
* - License checks were scattered across 8+ call sites with subtly different
|
|
9
|
+
* handling (e.g., consolidate.ts:308 had a dynamic import in a hot loop to
|
|
10
|
+
* dodge a circular import).
|
|
11
|
+
*
|
|
12
|
+
* All feature decisions now flow through this module. Call initFeatures() once at
|
|
13
|
+
* process boot before serving any requests; sync getters become deterministic.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.initFeatures = initFeatures;
|
|
17
|
+
exports.isPro = isPro;
|
|
18
|
+
exports.maxMemoriesAllowed = maxMemoriesAllowed;
|
|
19
|
+
exports.memoryCapReached = memoryCapReached;
|
|
20
|
+
exports.lessonsLimit = lessonsLimit;
|
|
21
|
+
exports.remoteIngestAllowed = remoteIngestAllowed;
|
|
22
|
+
exports.getCurrentFeatures = getCurrentFeatures;
|
|
23
|
+
const node_path_1 = require("node:path");
|
|
24
|
+
const node_os_1 = require("node:os");
|
|
25
|
+
const license_js_1 = require("./license.js");
|
|
26
|
+
const state_js_1 = require("./state.js");
|
|
27
|
+
const DEFAULT_STATE_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), ".hicortex");
|
|
28
|
+
const FREE_FEATURES = {
|
|
29
|
+
reflection: true,
|
|
30
|
+
vectorSearch: true,
|
|
31
|
+
maxMemories: 250,
|
|
32
|
+
crossAgent: true,
|
|
33
|
+
remoteIngest: true,
|
|
34
|
+
};
|
|
35
|
+
let currentFeatures = FREE_FEATURES;
|
|
36
|
+
let initialized = false;
|
|
37
|
+
function persistTier(stateDir, info) {
|
|
38
|
+
(0, state_js_1.updateState)((s) => {
|
|
39
|
+
s.tier = {
|
|
40
|
+
tier: info.tier,
|
|
41
|
+
validatedAt: new Date().toISOString(),
|
|
42
|
+
features: info.features,
|
|
43
|
+
};
|
|
44
|
+
return s;
|
|
45
|
+
}, stateDir);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Initialize the feature cache. Call ONCE at process boot before any feature
|
|
49
|
+
* gating queries. Race fix:
|
|
50
|
+
* 1. Synchronously load persisted tier from disk (instant, deterministic)
|
|
51
|
+
* 2. If no persisted tier and we have a key, AWAIT first validation
|
|
52
|
+
* 3. If persisted tier exists, kick off background re-validation
|
|
53
|
+
*
|
|
54
|
+
* After this returns, sync getters (isPro, lessonsLimit, etc.) are deterministic
|
|
55
|
+
* and reflect the user's actual tier — no more "free during validation window".
|
|
56
|
+
*/
|
|
57
|
+
async function initFeatures(licenseKey, stateDir = DEFAULT_STATE_DIR) {
|
|
58
|
+
if (initialized)
|
|
59
|
+
return;
|
|
60
|
+
initialized = true;
|
|
61
|
+
// Step 1: Load persisted tier from state.json (instant)
|
|
62
|
+
const persisted = (0, state_js_1.loadState)(stateDir).tier;
|
|
63
|
+
if (persisted) {
|
|
64
|
+
currentFeatures = persisted.features;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
currentFeatures = FREE_FEATURES;
|
|
68
|
+
}
|
|
69
|
+
// Step 2: No key → free tier, done
|
|
70
|
+
if (!licenseKey)
|
|
71
|
+
return;
|
|
72
|
+
// Step 3: Validate
|
|
73
|
+
if (!persisted) {
|
|
74
|
+
// First-time: AWAIT validation so the very first request sees the right tier
|
|
75
|
+
try {
|
|
76
|
+
const info = await (0, license_js_1.validateLicense)(licenseKey, stateDir);
|
|
77
|
+
currentFeatures = info.features;
|
|
78
|
+
if (info.valid) {
|
|
79
|
+
persistTier(stateDir, info);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Validation failed (network, etc.) — stay on free
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
// Already have a persisted tier; re-validate in background
|
|
88
|
+
(0, license_js_1.validateLicense)(licenseKey, stateDir)
|
|
89
|
+
.then((info) => {
|
|
90
|
+
currentFeatures = info.features;
|
|
91
|
+
if (info.valid) {
|
|
92
|
+
persistTier(stateDir, info);
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
.catch(() => {
|
|
96
|
+
// Keep persisted features
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Public API — sync getters used everywhere in the codebase
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
/** Are we on a paid tier (Pro, Team, Lifetime)? */
|
|
104
|
+
function isPro() {
|
|
105
|
+
return currentFeatures.maxMemories === -1;
|
|
106
|
+
}
|
|
107
|
+
/** Memory count cap. -1 = unlimited (paid). */
|
|
108
|
+
function maxMemoriesAllowed() {
|
|
109
|
+
return currentFeatures.maxMemories;
|
|
110
|
+
}
|
|
111
|
+
/** Has the memory cap been hit? Pass current count from caller. */
|
|
112
|
+
function memoryCapReached(currentCount) {
|
|
113
|
+
const max = maxMemoriesAllowed();
|
|
114
|
+
return max > 0 && currentCount >= max;
|
|
115
|
+
}
|
|
116
|
+
/** Number of lessons to inject into CLAUDE.md / before_agent_start. */
|
|
117
|
+
function lessonsLimit() {
|
|
118
|
+
return isPro() ? 20 : 10;
|
|
119
|
+
}
|
|
120
|
+
/** Is remote /ingest allowed? Free + Team yes, Pro (single-machine) no. */
|
|
121
|
+
function remoteIngestAllowed() {
|
|
122
|
+
return currentFeatures.remoteIngest !== false;
|
|
123
|
+
}
|
|
124
|
+
/** Direct read of the underlying features (for callers that need the full record). */
|
|
125
|
+
function getCurrentFeatures() {
|
|
126
|
+
return currentFeatures;
|
|
127
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -42,7 +42,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
42
42
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
43
|
const node_path_1 = require("node:path");
|
|
44
44
|
const db_js_1 = require("./db.js");
|
|
45
|
-
const
|
|
45
|
+
const features_js_1 = require("./features.js");
|
|
46
|
+
const extensions_js_1 = require("./extensions.js");
|
|
47
|
+
const state_js_1 = require("./state.js");
|
|
46
48
|
const llm_js_1 = require("./llm.js");
|
|
47
49
|
const node_fs_1 = require("node:fs");
|
|
48
50
|
const node_os_1 = require("node:os");
|
|
@@ -87,8 +89,10 @@ exports.default = {
|
|
|
87
89
|
llm = new llm_js_1.LlmClient(llmConfig);
|
|
88
90
|
log(`[hicortex] LLM: ${llmConfig.provider}/${llmConfig.model} ` +
|
|
89
91
|
`(reflect: ${llmConfig.reflectModel})`);
|
|
90
|
-
//
|
|
91
|
-
(0,
|
|
92
|
+
// One-time migration of legacy state files (no-op if state.json exists)
|
|
93
|
+
(0, state_js_1.migrateLegacyState)(stateDir);
|
|
94
|
+
// License: init feature cache (sync after this returns)
|
|
95
|
+
await (0, features_js_1.initFeatures)(config.licenseKey, stateDir);
|
|
92
96
|
// Schedule nightly consolidation
|
|
93
97
|
const consolidateHour = config.consolidateHour ?? 2;
|
|
94
98
|
cancelConsolidation = (0, consolidate_js_1.scheduleConsolidation)(db, llm, embedder_js_1.embed, consolidateHour);
|
|
@@ -123,9 +127,13 @@ exports.default = {
|
|
|
123
127
|
const lessons = storage.getLessons(db, 7, ctx.project);
|
|
124
128
|
if (lessons.length === 0)
|
|
125
129
|
return {};
|
|
126
|
-
const
|
|
127
|
-
const
|
|
128
|
-
|
|
130
|
+
const maxLessons = (0, features_js_1.lessonsLimit)();
|
|
131
|
+
const selected = await (0, extensions_js_1.getLessonSelector)().select(lessons, {
|
|
132
|
+
maxLessons,
|
|
133
|
+
project: ctx.project,
|
|
134
|
+
agentId: ctx.agentId,
|
|
135
|
+
});
|
|
136
|
+
const formatted = selected.map((l) => {
|
|
129
137
|
// Extract just the lesson text from the structured content
|
|
130
138
|
const match = l.content.match(/## Lesson: (.+)/);
|
|
131
139
|
return match ? `- ${match[1]}` : `- ${l.content.slice(0, 200)}`;
|
|
@@ -136,9 +144,9 @@ exports.default = {
|
|
|
136
144
|
"\n";
|
|
137
145
|
// Daily friendly reminder when at memory cap
|
|
138
146
|
const memCount = storage.countMemories(db);
|
|
139
|
-
if (
|
|
147
|
+
if ((0, features_js_1.memoryCapReached)(memCount)) {
|
|
140
148
|
context +=
|
|
141
|
-
`\n---\nHicortex free tier: ${
|
|
149
|
+
`\n---\nHicortex free tier: ${(0, features_js_1.maxMemoriesAllowed)()} memories stored. ` +
|
|
142
150
|
`New memories can no longer be saved, and your agent has stopped learning and self-improving from new sessions. ` +
|
|
143
151
|
`Existing memories, lessons, and search still work. ` +
|
|
144
152
|
`Upgrade for unlimited usage: https://hicortex.gamaze.com/ ` +
|
|
@@ -169,10 +177,8 @@ exports.default = {
|
|
|
169
177
|
if (entries.length === 0)
|
|
170
178
|
return;
|
|
171
179
|
// Check license cap
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
storage.countMemories(db) >= features.maxMemories) {
|
|
175
|
-
console.warn(`[hicortex] Free tier limit reached (${features.maxMemories} memories). ` +
|
|
180
|
+
if ((0, features_js_1.memoryCapReached)(storage.countMemories(db))) {
|
|
181
|
+
console.warn(`[hicortex] Free tier limit reached (${(0, features_js_1.maxMemoriesAllowed)()} memories). ` +
|
|
176
182
|
`Search and lessons still work, but new memories won't be saved. ` +
|
|
177
183
|
`Upgrade for unlimited usage: https://hicortex.gamaze.com/`);
|
|
178
184
|
return;
|
|
@@ -299,12 +305,11 @@ exports.default = {
|
|
|
299
305
|
async execute(_callId, args, context) {
|
|
300
306
|
if (!db)
|
|
301
307
|
return { error: "Hicortex not initialized" };
|
|
302
|
-
|
|
303
|
-
if (features.maxMemories > 0 && storage.countMemories(db) >= features.maxMemories) {
|
|
308
|
+
if ((0, features_js_1.memoryCapReached)(storage.countMemories(db))) {
|
|
304
309
|
return {
|
|
305
310
|
content: [{
|
|
306
311
|
type: "text",
|
|
307
|
-
text: `Free tier limit reached (${
|
|
312
|
+
text: `Free tier limit reached (${(0, features_js_1.maxMemoriesAllowed)()} memories). ` +
|
|
308
313
|
`Your existing memories and lessons still work — search and recall are unaffected. ` +
|
|
309
314
|
`New memories won't be saved until you upgrade.\n\n` +
|
|
310
315
|
`Upgrade for unlimited usage: https://hicortex.gamaze.com/`
|
|
@@ -529,26 +534,6 @@ function persistProviderConfig(llmConfig, log) {
|
|
|
529
534
|
// Non-fatal — config works in memory even if we can't persist
|
|
530
535
|
}
|
|
531
536
|
}
|
|
532
|
-
/**
|
|
533
|
-
* Track when the memory cap was first hit. Returns days since cap was reached.
|
|
534
|
-
* Stores timestamp in stateDir/cap-hit.txt on first detection.
|
|
535
|
-
*/
|
|
536
|
-
function getDaysSinceCapHit(dir) {
|
|
537
|
-
const capFile = (0, node_path_1.join)(dir, "cap-hit.txt");
|
|
538
|
-
try {
|
|
539
|
-
const ts = (0, node_fs_1.readFileSync)(capFile, "utf-8").trim();
|
|
540
|
-
const hitDate = new Date(ts);
|
|
541
|
-
return Math.floor((Date.now() - hitDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
542
|
-
}
|
|
543
|
-
catch {
|
|
544
|
-
// First time hitting cap — record it
|
|
545
|
-
try {
|
|
546
|
-
(0, node_fs_1.writeFileSync)(capFile, new Date().toISOString());
|
|
547
|
-
}
|
|
548
|
-
catch { /* non-fatal */ }
|
|
549
|
-
return 0;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
537
|
const HICORTEX_TOOLS = [
|
|
553
538
|
"hicortex_search",
|
|
554
539
|
"hicortex_context",
|
package/dist/license.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* License API client.
|
|
3
|
+
*
|
|
4
|
+
* This module is a thin wrapper over the validation HTTP endpoint at
|
|
5
|
+
* https://hicortex.gamaze.com/api/validate. Persistence and feature gating
|
|
6
|
+
* live in features.ts + state.ts; this file does NOT touch disk.
|
|
7
|
+
*
|
|
8
|
+
* Offline grace: when the API is unreachable, we fall back to the cached
|
|
9
|
+
* tier in state.json (written by features.ts on the last successful
|
|
10
|
+
* validation). If the cached tier was validated within OFFLINE_GRACE_DAYS,
|
|
11
|
+
* we treat it as still valid.
|
|
12
|
+
*/
|
|
1
13
|
import type { LicenseInfo } from "./types.js";
|
|
2
|
-
/** Validate a license key against the Hicortex API */
|
|
14
|
+
/** Validate a license key against the Hicortex API. */
|
|
3
15
|
export declare function validateLicense(key: string | undefined, stateDir: string): Promise<LicenseInfo>;
|
|
4
|
-
/** Get current features, using cache or free tier defaults */
|
|
5
|
-
export declare function getFeatures(stateDir: string): LicenseInfo["features"];
|
package/dist/license.js
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* License API client.
|
|
4
|
+
*
|
|
5
|
+
* This module is a thin wrapper over the validation HTTP endpoint at
|
|
6
|
+
* https://hicortex.gamaze.com/api/validate. Persistence and feature gating
|
|
7
|
+
* live in features.ts + state.ts; this file does NOT touch disk.
|
|
8
|
+
*
|
|
9
|
+
* Offline grace: when the API is unreachable, we fall back to the cached
|
|
10
|
+
* tier in state.json (written by features.ts on the last successful
|
|
11
|
+
* validation). If the cached tier was validated within OFFLINE_GRACE_DAYS,
|
|
12
|
+
* we treat it as still valid.
|
|
13
|
+
*/
|
|
2
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
15
|
exports.validateLicense = validateLicense;
|
|
4
|
-
|
|
5
|
-
const node_fs_1 = require("node:fs");
|
|
6
|
-
const node_path_1 = require("node:path");
|
|
16
|
+
const state_js_1 = require("./state.js");
|
|
7
17
|
const VALIDATE_URL = "https://hicortex.gamaze.com/api/validate";
|
|
8
18
|
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
9
19
|
const OFFLINE_GRACE_DAYS = 7;
|
|
10
|
-
// In-memory cache
|
|
20
|
+
// In-memory cache for the current process
|
|
11
21
|
let cachedLicense = null;
|
|
12
22
|
let cacheTimestamp = 0;
|
|
13
23
|
const FREE_LICENSE = {
|
|
@@ -21,12 +31,12 @@ const FREE_LICENSE = {
|
|
|
21
31
|
remoteIngest: true,
|
|
22
32
|
},
|
|
23
33
|
};
|
|
24
|
-
/** Validate a license key against the Hicortex API */
|
|
34
|
+
/** Validate a license key against the Hicortex API. */
|
|
25
35
|
async function validateLicense(key, stateDir) {
|
|
26
36
|
// No key = free tier
|
|
27
37
|
if (!key)
|
|
28
38
|
return FREE_LICENSE;
|
|
29
|
-
// Check in-memory cache
|
|
39
|
+
// Check in-memory cache (24h TTL)
|
|
30
40
|
if (cachedLicense && Date.now() - cacheTimestamp < CACHE_TTL_MS) {
|
|
31
41
|
return cachedLicense;
|
|
32
42
|
}
|
|
@@ -41,57 +51,36 @@ async function validateLicense(key, stateDir) {
|
|
|
41
51
|
throw new Error(`HTTP ${resp.status}`);
|
|
42
52
|
}
|
|
43
53
|
const data = (await resp.json());
|
|
44
|
-
// Cache result
|
|
45
54
|
cachedLicense = data;
|
|
46
55
|
cacheTimestamp = Date.now();
|
|
47
|
-
// Persist last successful validation timestamp for offline grace
|
|
48
|
-
if (data.valid) {
|
|
49
|
-
persistValidationTimestamp(stateDir);
|
|
50
|
-
}
|
|
51
56
|
return data;
|
|
52
57
|
}
|
|
53
58
|
catch {
|
|
54
|
-
// Network failure — check offline grace period
|
|
55
|
-
return offlineFallback(
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
/** Get current features, using cache or free tier defaults */
|
|
59
|
-
function getFeatures(stateDir) {
|
|
60
|
-
if (cachedLicense)
|
|
61
|
-
return cachedLicense.features;
|
|
62
|
-
return FREE_LICENSE.features;
|
|
63
|
-
}
|
|
64
|
-
function persistValidationTimestamp(stateDir) {
|
|
65
|
-
try {
|
|
66
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(stateDir, "license-validated.txt"), new Date().toISOString());
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
// Non-critical
|
|
59
|
+
// Network failure — check offline grace period via state.tier
|
|
60
|
+
return offlineFallback(stateDir);
|
|
70
61
|
}
|
|
71
62
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Offline fallback: if state.tier was validated within the grace period,
|
|
65
|
+
* return its cached features as if validation succeeded. Otherwise, free tier.
|
|
66
|
+
*/
|
|
67
|
+
function offlineFallback(stateDir) {
|
|
68
|
+
const persisted = (0, state_js_1.loadState)(stateDir).tier;
|
|
69
|
+
if (!persisted)
|
|
75
70
|
return FREE_LICENSE;
|
|
76
71
|
try {
|
|
77
|
-
const lastValidated = new Date(
|
|
72
|
+
const lastValidated = new Date(persisted.validatedAt);
|
|
78
73
|
const daysSince = (Date.now() - lastValidated.getTime()) / (1000 * 60 * 60 * 24);
|
|
79
74
|
if (daysSince <= OFFLINE_GRACE_DAYS) {
|
|
80
|
-
|
|
81
|
-
return cachedLicense ?? {
|
|
75
|
+
return {
|
|
82
76
|
valid: true,
|
|
83
|
-
tier:
|
|
84
|
-
features:
|
|
85
|
-
reflection: true,
|
|
86
|
-
vectorSearch: true,
|
|
87
|
-
maxMemories: -1,
|
|
88
|
-
crossAgent: true,
|
|
89
|
-
},
|
|
77
|
+
tier: persisted.tier,
|
|
78
|
+
features: persisted.features,
|
|
90
79
|
};
|
|
91
80
|
}
|
|
92
81
|
}
|
|
93
82
|
catch {
|
|
94
|
-
// Corrupted
|
|
83
|
+
// Corrupted timestamp — fall through
|
|
95
84
|
}
|
|
96
85
|
return FREE_LICENSE;
|
|
97
86
|
}
|
package/dist/mcp-server.js
CHANGED
|
@@ -55,7 +55,8 @@ const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
|
|
|
55
55
|
const zod_1 = require("zod");
|
|
56
56
|
const db_js_1 = require("./db.js");
|
|
57
57
|
const llm_js_1 = require("./llm.js");
|
|
58
|
-
const
|
|
58
|
+
const features_js_1 = require("./features.js");
|
|
59
|
+
const state_js_1 = require("./state.js");
|
|
59
60
|
const embedder_js_1 = require("./embedder.js");
|
|
60
61
|
const storage = __importStar(require("./storage.js"));
|
|
61
62
|
const retrieval = __importStar(require("./retrieval.js"));
|
|
@@ -121,12 +122,11 @@ function createMcpServer() {
|
|
|
121
122
|
}, async ({ content, project, memory_type }) => {
|
|
122
123
|
if (!db)
|
|
123
124
|
return { content: [{ type: "text", text: "Hicortex not initialized" }], isError: true };
|
|
124
|
-
|
|
125
|
-
if (features.maxMemories > 0 && storage.countMemories(db) >= features.maxMemories) {
|
|
125
|
+
if ((0, features_js_1.memoryCapReached)(storage.countMemories(db))) {
|
|
126
126
|
return {
|
|
127
127
|
content: [{
|
|
128
128
|
type: "text",
|
|
129
|
-
text: `Free tier limit reached (${
|
|
129
|
+
text: `Free tier limit reached (${(0, features_js_1.maxMemoriesAllowed)()} memories). ` +
|
|
130
130
|
`Your existing memories and lessons still work — search and recall are unaffected. ` +
|
|
131
131
|
`New memories won't be saved until you upgrade.\n\n` +
|
|
132
132
|
`Upgrade for unlimited usage: https://hicortex.gamaze.com/`
|
|
@@ -296,11 +296,13 @@ async function startServer(options = {}) {
|
|
|
296
296
|
? `${llmConfig.reflectProvider}/${llmConfig.reflectModel}@${llmConfig.reflectBaseUrl}`
|
|
297
297
|
: llmConfig.reflectModel;
|
|
298
298
|
console.log(`[hicortex] LLM fast: ${llmConfig.provider}/${llmConfig.model}${distillInfo ? `, distill: ${distillInfo}` : ""}, reflect: ${reflectInfo}`);
|
|
299
|
-
//
|
|
299
|
+
// One-time migration of legacy state files (no-op if state.json exists)
|
|
300
|
+
(0, state_js_1.migrateLegacyState)(stateDir);
|
|
301
|
+
// License: read from options, config file, or env var, init feature cache
|
|
300
302
|
const licenseKey = options.licenseKey
|
|
301
303
|
?? savedConfig?.licenseKey
|
|
302
304
|
?? process.env.HICORTEX_LICENSE_KEY;
|
|
303
|
-
(0,
|
|
305
|
+
await (0, features_js_1.initFeatures)(licenseKey, stateDir);
|
|
304
306
|
if (licenseKey) {
|
|
305
307
|
console.log(`[hicortex] License key configured`);
|
|
306
308
|
}
|
|
@@ -368,6 +370,40 @@ async function startServer(options = {}) {
|
|
|
368
370
|
llm: `${llmConfig.provider}/${llmConfig.model}`,
|
|
369
371
|
});
|
|
370
372
|
});
|
|
373
|
+
// REST /lessons — return lessons + memory index for client CLAUDE.md injection
|
|
374
|
+
app.get("/lessons", (_req, res) => {
|
|
375
|
+
if (!db) {
|
|
376
|
+
res.status(503).json({ error: "Server not initialized" });
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
try {
|
|
380
|
+
const lessons = storage.getLessons(db, 30);
|
|
381
|
+
const totalCount = storage.countMemories(db);
|
|
382
|
+
// Project index
|
|
383
|
+
const projects = db
|
|
384
|
+
.prepare("SELECT project, COUNT(*) as cnt FROM memories WHERE project IS NOT NULL GROUP BY project ORDER BY cnt DESC LIMIT 10")
|
|
385
|
+
.all();
|
|
386
|
+
const sourceCount = db.prepare("SELECT COUNT(DISTINCT source_agent) as cnt FROM memories").get().cnt;
|
|
387
|
+
const lessonCount = lessons.length;
|
|
388
|
+
res.json({
|
|
389
|
+
lessons: lessons.map(l => ({
|
|
390
|
+
content: l.content,
|
|
391
|
+
created_at: l.created_at,
|
|
392
|
+
base_strength: l.base_strength,
|
|
393
|
+
access_count: l.access_count,
|
|
394
|
+
})),
|
|
395
|
+
index: {
|
|
396
|
+
total: totalCount,
|
|
397
|
+
lessonCount,
|
|
398
|
+
sourceCount,
|
|
399
|
+
projects: projects.map(p => ({ name: p.project, count: p.cnt })),
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
catch (err) {
|
|
404
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
405
|
+
}
|
|
406
|
+
});
|
|
371
407
|
// REST /ingest — accept pre-distilled memories from remote clients
|
|
372
408
|
app.post("/ingest", async (req, res) => {
|
|
373
409
|
if (!db) {
|
|
@@ -377,15 +413,12 @@ async function startServer(options = {}) {
|
|
|
377
413
|
// Pro license blocks remote ingest (upgrade to Team for multi-client)
|
|
378
414
|
const ip = req.ip ?? req.socket.remoteAddress ?? "";
|
|
379
415
|
const isLocal = ip === "127.0.0.1" || ip === "::1" || ip === "::ffff:127.0.0.1";
|
|
380
|
-
if (!isLocal) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
});
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
416
|
+
if (!isLocal && !(0, features_js_1.remoteIngestAllowed)()) {
|
|
417
|
+
res.status(403).json({
|
|
418
|
+
error: "Pro license is single-machine. Upgrade to Team for multi-client remote ingestion.",
|
|
419
|
+
upgrade: "https://hicortex.gamaze.com/",
|
|
420
|
+
});
|
|
421
|
+
return;
|
|
389
422
|
}
|
|
390
423
|
const { content, source_agent, project, memory_type, privacy, source_session, session_date } = req.body ?? {};
|
|
391
424
|
if (!content || typeof content !== "string") {
|
|
@@ -406,9 +439,8 @@ async function startServer(options = {}) {
|
|
|
406
439
|
}
|
|
407
440
|
}
|
|
408
441
|
// License check
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
res.status(429).json({ error: "Memory limit reached", limit: features.maxMemories });
|
|
442
|
+
if ((0, features_js_1.memoryCapReached)(storage.countMemories(db))) {
|
|
443
|
+
res.status(429).json({ error: "Memory limit reached", limit: (0, features_js_1.maxMemoriesAllowed)() });
|
|
412
444
|
return;
|
|
413
445
|
}
|
|
414
446
|
try {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nightly pipeline status — lightweight check without running the pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Shows:
|
|
5
|
+
* - Last run timestamp + age
|
|
6
|
+
* - Timer/schedule status (systemd/launchd)
|
|
7
|
+
* - DB memory count
|
|
8
|
+
* - Distillation source breakdown
|
|
9
|
+
* - Staleness warnings
|
|
10
|
+
*/
|
|
11
|
+
export declare function showNightlyStatus(): Promise<void>;
|