@gzoo/cortex 0.5.11 → 0.5.13
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/cortex.mjs +358 -159
- package/package.json +1 -1
- package/packages/cli/dist/commands/config.d.ts.map +1 -1
- package/packages/cli/dist/commands/config.js +9 -1
- package/packages/cli/dist/commands/config.js.map +1 -1
- package/packages/cli/dist/commands/ingest.js +3 -1
- package/packages/cli/dist/commands/ingest.js.map +1 -1
- package/packages/cli/dist/commands/mcp.js +1 -1
- package/packages/cli/dist/commands/mcp.js.map +1 -1
- package/packages/cli/dist/commands/models.js +2 -1
- package/packages/cli/dist/commands/models.js.map +1 -1
- package/packages/cli/dist/commands/serve.js +1 -1
- package/packages/cli/dist/commands/serve.js.map +1 -1
- package/packages/cli/dist/commands/status.d.ts.map +1 -1
- package/packages/cli/dist/commands/status.js +15 -4
- package/packages/cli/dist/commands/status.js.map +1 -1
- package/packages/cli/dist/commands/watch.js +1 -0
- package/packages/cli/dist/commands/watch.js.map +1 -1
- package/packages/cli/dist/index.js +1 -1
- package/packages/cli/dist/index.js.map +1 -1
- package/packages/graph/dist/migrations/002-add-indexes.d.ts +4 -0
- package/packages/graph/dist/migrations/002-add-indexes.d.ts.map +1 -0
- package/packages/graph/dist/migrations/002-add-indexes.js +30 -0
- package/packages/graph/dist/migrations/002-add-indexes.js.map +1 -0
- package/packages/graph/dist/query-engine.d.ts.map +1 -1
- package/packages/graph/dist/query-engine.js +16 -25
- package/packages/graph/dist/query-engine.js.map +1 -1
- package/packages/graph/dist/sqlite-store.d.ts +5 -1
- package/packages/graph/dist/sqlite-store.d.ts.map +1 -1
- package/packages/graph/dist/sqlite-store.js +152 -118
- package/packages/graph/dist/sqlite-store.js.map +1 -1
- package/packages/ingest/dist/parsers/conversation.js +2 -2
- package/packages/ingest/dist/parsers/conversation.js.map +1 -1
- package/packages/ingest/dist/pipeline.d.ts +7 -0
- package/packages/ingest/dist/pipeline.d.ts.map +1 -1
- package/packages/ingest/dist/pipeline.js +43 -5
- package/packages/ingest/dist/pipeline.js.map +1 -1
- package/packages/ingest/dist/watcher.d.ts +1 -0
- package/packages/ingest/dist/watcher.d.ts.map +1 -1
- package/packages/ingest/dist/watcher.js +22 -11
- package/packages/ingest/dist/watcher.js.map +1 -1
- package/packages/llm/dist/cache.js +5 -5
- package/packages/llm/dist/cache.js.map +1 -1
- package/packages/llm/dist/providers/ollama.d.ts +1 -0
- package/packages/llm/dist/providers/ollama.d.ts.map +1 -1
- package/packages/llm/dist/providers/ollama.js +68 -17
- package/packages/llm/dist/providers/ollama.js.map +1 -1
- package/packages/llm/dist/providers/openai-compatible.d.ts.map +1 -1
- package/packages/llm/dist/providers/openai-compatible.js +7 -1
- package/packages/llm/dist/providers/openai-compatible.js.map +1 -1
- package/packages/llm/dist/token-tracker.d.ts +1 -0
- package/packages/llm/dist/token-tracker.d.ts.map +1 -1
- package/packages/llm/dist/token-tracker.js +19 -0
- package/packages/llm/dist/token-tracker.js.map +1 -1
- package/packages/server/dist/index.d.ts.map +1 -1
- package/packages/server/dist/index.js +15 -2
- package/packages/server/dist/index.js.map +1 -1
- package/packages/server/dist/middleware/auth.d.ts.map +1 -1
- package/packages/server/dist/middleware/auth.js +4 -0
- package/packages/server/dist/middleware/auth.js.map +1 -1
- package/packages/server/dist/routes/status.js +1 -1
- package/packages/server/dist/routes/status.js.map +1 -1
package/dist/cortex.mjs
CHANGED
|
@@ -688,6 +688,43 @@ var init_initial = __esm({
|
|
|
688
688
|
}
|
|
689
689
|
});
|
|
690
690
|
|
|
691
|
+
// packages/graph/dist/migrations/002-add-indexes.js
|
|
692
|
+
function up2(db) {
|
|
693
|
+
const currentVersion = db.prepare("SELECT MAX(version) as v FROM schema_version").get()?.v ?? 0;
|
|
694
|
+
if (currentVersion >= MIGRATION_VERSION2)
|
|
695
|
+
return;
|
|
696
|
+
db.exec(`
|
|
697
|
+
-- Composite index for common entity queries (project + status + soft-delete filter)
|
|
698
|
+
CREATE INDEX IF NOT EXISTS idx_entities_project_status_deleted
|
|
699
|
+
ON entities(project_id, status, deleted_at);
|
|
700
|
+
|
|
701
|
+
-- Contradiction lookups by status and severity
|
|
702
|
+
CREATE INDEX IF NOT EXISTS idx_contradictions_status_severity
|
|
703
|
+
ON contradictions(status, severity);
|
|
704
|
+
|
|
705
|
+
-- Contradiction lookups by entity
|
|
706
|
+
CREATE INDEX IF NOT EXISTS idx_contradictions_entity_a
|
|
707
|
+
ON contradictions(entity_id_a);
|
|
708
|
+
|
|
709
|
+
CREATE INDEX IF NOT EXISTS idx_contradictions_entity_b
|
|
710
|
+
ON contradictions(entity_id_b);
|
|
711
|
+
|
|
712
|
+
-- Files by project + status (used during watch/ingest)
|
|
713
|
+
CREATE INDEX IF NOT EXISTS idx_files_project_status
|
|
714
|
+
ON files(project_id, status);
|
|
715
|
+
|
|
716
|
+
INSERT OR IGNORE INTO schema_version (version, applied_at)
|
|
717
|
+
VALUES (${MIGRATION_VERSION2}, datetime('now'));
|
|
718
|
+
`);
|
|
719
|
+
}
|
|
720
|
+
var MIGRATION_VERSION2;
|
|
721
|
+
var init_add_indexes = __esm({
|
|
722
|
+
"packages/graph/dist/migrations/002-add-indexes.js"() {
|
|
723
|
+
"use strict";
|
|
724
|
+
MIGRATION_VERSION2 = 2;
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
|
|
691
728
|
// packages/graph/dist/sqlite-store.js
|
|
692
729
|
import Database from "better-sqlite3";
|
|
693
730
|
import { randomUUID } from "node:crypto";
|
|
@@ -780,17 +817,22 @@ var init_sqlite_store = __esm({
|
|
|
780
817
|
"use strict";
|
|
781
818
|
init_dist();
|
|
782
819
|
init_initial();
|
|
820
|
+
init_add_indexes();
|
|
783
821
|
SQLiteStore = class {
|
|
784
822
|
db;
|
|
785
823
|
dbPath;
|
|
786
824
|
constructor(options = {}) {
|
|
787
825
|
const { dbPath = "~/.cortex/cortex.db", walMode = true, backupOnStartup = true } = options;
|
|
788
826
|
this.dbPath = resolveHomePath(dbPath);
|
|
789
|
-
mkdirSync3(dirname(this.dbPath), { recursive: true });
|
|
827
|
+
mkdirSync3(dirname(this.dbPath), { recursive: true, mode: 448 });
|
|
790
828
|
if (backupOnStartup) {
|
|
791
829
|
this.backupSync();
|
|
792
830
|
}
|
|
793
831
|
this.db = new Database(this.dbPath);
|
|
832
|
+
try {
|
|
833
|
+
chmodSync(this.dbPath, 384);
|
|
834
|
+
} catch {
|
|
835
|
+
}
|
|
794
836
|
if (walMode) {
|
|
795
837
|
this.db.pragma("journal_mode = WAL");
|
|
796
838
|
}
|
|
@@ -801,6 +843,7 @@ var init_sqlite_store = __esm({
|
|
|
801
843
|
migrate() {
|
|
802
844
|
try {
|
|
803
845
|
up(this.db);
|
|
846
|
+
up2(this.db);
|
|
804
847
|
} catch (err) {
|
|
805
848
|
throw new CortexError(GRAPH_DB_ERROR, "critical", "graph", `Migration failed: ${err instanceof Error ? err.message : String(err)}`, void 0, "Delete the database and restart.");
|
|
806
849
|
}
|
|
@@ -822,8 +865,17 @@ var init_sqlite_store = __esm({
|
|
|
822
865
|
close() {
|
|
823
866
|
this.db.close();
|
|
824
867
|
}
|
|
868
|
+
transaction(fn) {
|
|
869
|
+
return this.db.transaction(fn)();
|
|
870
|
+
}
|
|
825
871
|
// --- Entities ---
|
|
872
|
+
createEntitySync(entity) {
|
|
873
|
+
return this._createEntity(entity);
|
|
874
|
+
}
|
|
826
875
|
async createEntity(entity) {
|
|
876
|
+
return this._createEntity(entity);
|
|
877
|
+
}
|
|
878
|
+
_createEntity(entity) {
|
|
827
879
|
const id = randomUUID();
|
|
828
880
|
const ts = now();
|
|
829
881
|
this.db.prepare(`
|
|
@@ -903,13 +955,17 @@ var init_sqlite_store = __esm({
|
|
|
903
955
|
}
|
|
904
956
|
let sql;
|
|
905
957
|
if (query.search) {
|
|
958
|
+
const sanitizedSearch = query.search.replace(/[^a-zA-Z0-9\s]/g, " ").trim();
|
|
959
|
+
if (!sanitizedSearch) {
|
|
960
|
+
return [];
|
|
961
|
+
}
|
|
906
962
|
sql = `
|
|
907
963
|
SELECT e.* FROM entities e
|
|
908
964
|
JOIN entities_fts fts ON fts.rowid = e.rowid
|
|
909
965
|
WHERE fts.entities_fts MATCH ? AND ${conditions.join(" AND ")}
|
|
910
966
|
ORDER BY rank
|
|
911
967
|
`;
|
|
912
|
-
params.unshift(
|
|
968
|
+
params.unshift(sanitizedSearch);
|
|
913
969
|
} else {
|
|
914
970
|
sql = `
|
|
915
971
|
SELECT * FROM entities
|
|
@@ -945,22 +1001,35 @@ var init_sqlite_store = __esm({
|
|
|
945
1001
|
const row = this.db.prepare("SELECT * FROM relationships WHERE id = ?").get(id);
|
|
946
1002
|
return row ? rowToRelationship(row) : null;
|
|
947
1003
|
}
|
|
948
|
-
async getRelationshipsForEntity(entityId, direction = "both") {
|
|
1004
|
+
async getRelationshipsForEntity(entityId, direction = "both", limit = 200) {
|
|
949
1005
|
let sql;
|
|
950
1006
|
let params;
|
|
951
1007
|
if (direction === "out") {
|
|
952
|
-
sql = "SELECT * FROM relationships WHERE source_entity_id = ?";
|
|
953
|
-
params = [entityId];
|
|
1008
|
+
sql = "SELECT * FROM relationships WHERE source_entity_id = ? LIMIT ?";
|
|
1009
|
+
params = [entityId, limit];
|
|
954
1010
|
} else if (direction === "in") {
|
|
955
|
-
sql = "SELECT * FROM relationships WHERE target_entity_id = ?";
|
|
956
|
-
params = [entityId];
|
|
1011
|
+
sql = "SELECT * FROM relationships WHERE target_entity_id = ? LIMIT ?";
|
|
1012
|
+
params = [entityId, limit];
|
|
957
1013
|
} else {
|
|
958
|
-
sql = "SELECT * FROM relationships WHERE source_entity_id = ? OR target_entity_id = ?";
|
|
959
|
-
params = [entityId, entityId];
|
|
1014
|
+
sql = "SELECT * FROM relationships WHERE source_entity_id = ? OR target_entity_id = ? LIMIT ?";
|
|
1015
|
+
params = [entityId, entityId, limit];
|
|
960
1016
|
}
|
|
961
1017
|
const rows = this.db.prepare(sql).all(...params);
|
|
962
1018
|
return rows.map(rowToRelationship);
|
|
963
1019
|
}
|
|
1020
|
+
async getRelationshipsForEntities(entityIds) {
|
|
1021
|
+
if (entityIds.length === 0)
|
|
1022
|
+
return [];
|
|
1023
|
+
const placeholders = entityIds.map(() => "?").join(",");
|
|
1024
|
+
const sql = `
|
|
1025
|
+
SELECT * FROM relationships
|
|
1026
|
+
WHERE source_entity_id IN (${placeholders})
|
|
1027
|
+
OR target_entity_id IN (${placeholders})
|
|
1028
|
+
LIMIT 2000
|
|
1029
|
+
`;
|
|
1030
|
+
const rows = this.db.prepare(sql).all(...entityIds, ...entityIds);
|
|
1031
|
+
return rows.map(rowToRelationship);
|
|
1032
|
+
}
|
|
964
1033
|
async deleteRelationship(id) {
|
|
965
1034
|
this.db.prepare("DELETE FROM relationships WHERE id = ?").run(id);
|
|
966
1035
|
}
|
|
@@ -1388,13 +1457,100 @@ var init_vector_store = __esm({
|
|
|
1388
1457
|
function estimateTokens(text) {
|
|
1389
1458
|
return Math.ceil(text.length / AVG_CHARS_PER_TOKEN);
|
|
1390
1459
|
}
|
|
1391
|
-
var logger2, AVG_CHARS_PER_TOKEN, QueryEngine;
|
|
1460
|
+
var logger2, AVG_CHARS_PER_TOKEN, FTS_STOP_WORDS, QueryEngine;
|
|
1392
1461
|
var init_query_engine = __esm({
|
|
1393
1462
|
"packages/graph/dist/query-engine.js"() {
|
|
1394
1463
|
"use strict";
|
|
1395
1464
|
init_dist();
|
|
1396
1465
|
logger2 = createLogger("graph:query-engine");
|
|
1397
1466
|
AVG_CHARS_PER_TOKEN = 4;
|
|
1467
|
+
FTS_STOP_WORDS = /* @__PURE__ */ new Set([
|
|
1468
|
+
"a",
|
|
1469
|
+
"an",
|
|
1470
|
+
"the",
|
|
1471
|
+
"and",
|
|
1472
|
+
"or",
|
|
1473
|
+
"but",
|
|
1474
|
+
"in",
|
|
1475
|
+
"on",
|
|
1476
|
+
"at",
|
|
1477
|
+
"to",
|
|
1478
|
+
"for",
|
|
1479
|
+
"of",
|
|
1480
|
+
"with",
|
|
1481
|
+
"by",
|
|
1482
|
+
"from",
|
|
1483
|
+
"is",
|
|
1484
|
+
"are",
|
|
1485
|
+
"was",
|
|
1486
|
+
"were",
|
|
1487
|
+
"be",
|
|
1488
|
+
"been",
|
|
1489
|
+
"being",
|
|
1490
|
+
"have",
|
|
1491
|
+
"has",
|
|
1492
|
+
"had",
|
|
1493
|
+
"do",
|
|
1494
|
+
"does",
|
|
1495
|
+
"did",
|
|
1496
|
+
"will",
|
|
1497
|
+
"would",
|
|
1498
|
+
"could",
|
|
1499
|
+
"should",
|
|
1500
|
+
"may",
|
|
1501
|
+
"might",
|
|
1502
|
+
"shall",
|
|
1503
|
+
"can",
|
|
1504
|
+
"need",
|
|
1505
|
+
"must",
|
|
1506
|
+
"what",
|
|
1507
|
+
"which",
|
|
1508
|
+
"who",
|
|
1509
|
+
"how",
|
|
1510
|
+
"why",
|
|
1511
|
+
"when",
|
|
1512
|
+
"where",
|
|
1513
|
+
"that",
|
|
1514
|
+
"this",
|
|
1515
|
+
"these",
|
|
1516
|
+
"those",
|
|
1517
|
+
"it",
|
|
1518
|
+
"its",
|
|
1519
|
+
"me",
|
|
1520
|
+
"my",
|
|
1521
|
+
"you",
|
|
1522
|
+
"your",
|
|
1523
|
+
"we",
|
|
1524
|
+
"our",
|
|
1525
|
+
"they",
|
|
1526
|
+
"their",
|
|
1527
|
+
"he",
|
|
1528
|
+
"she",
|
|
1529
|
+
"i",
|
|
1530
|
+
"all",
|
|
1531
|
+
"any",
|
|
1532
|
+
"each",
|
|
1533
|
+
"some",
|
|
1534
|
+
"no",
|
|
1535
|
+
"not",
|
|
1536
|
+
"so",
|
|
1537
|
+
"yet",
|
|
1538
|
+
"use",
|
|
1539
|
+
"used",
|
|
1540
|
+
"using",
|
|
1541
|
+
"about",
|
|
1542
|
+
"tell",
|
|
1543
|
+
"know",
|
|
1544
|
+
"get",
|
|
1545
|
+
"got",
|
|
1546
|
+
"make",
|
|
1547
|
+
"made",
|
|
1548
|
+
"see",
|
|
1549
|
+
"give",
|
|
1550
|
+
"go",
|
|
1551
|
+
"come",
|
|
1552
|
+
"take"
|
|
1553
|
+
]);
|
|
1398
1554
|
QueryEngine = class {
|
|
1399
1555
|
sqliteStore;
|
|
1400
1556
|
vectorStore;
|
|
@@ -1430,16 +1586,8 @@ var init_query_engine = __esm({
|
|
|
1430
1586
|
}
|
|
1431
1587
|
const privacyFiltered = await this.filterByPrivacy(contextEntities);
|
|
1432
1588
|
const entityIds = new Set(privacyFiltered.map((e) => e.id));
|
|
1433
|
-
const
|
|
1434
|
-
|
|
1435
|
-
const rels = await this.sqliteStore.getRelationshipsForEntity(entity.id);
|
|
1436
|
-
for (const rel of rels) {
|
|
1437
|
-
if (entityIds.has(rel.sourceEntityId) && entityIds.has(rel.targetEntityId)) {
|
|
1438
|
-
relationships.push(rel);
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1442
|
-
const uniqueRels = [...new Map(relationships.map((r) => [r.id, r])).values()];
|
|
1589
|
+
const allRels = await this.sqliteStore.getRelationshipsForEntities([...entityIds]);
|
|
1590
|
+
const uniqueRels = allRels.filter((r) => entityIds.has(r.sourceEntityId) && entityIds.has(r.targetEntityId));
|
|
1443
1591
|
const relTokens = uniqueRels.reduce((sum, r) => sum + estimateTokens(r.description ?? "") + 20, 0);
|
|
1444
1592
|
const filteredTokens = privacyFiltered.reduce((sum, e) => sum + estimateTokens(e.content) + estimateTokens(e.name), 0);
|
|
1445
1593
|
logger2.debug("Context assembled", {
|
|
@@ -1493,94 +1641,7 @@ var init_query_engine = __esm({
|
|
|
1493
1641
|
* entities matching ANY meaningful keyword are returned.
|
|
1494
1642
|
*/
|
|
1495
1643
|
buildFtsQuery(query) {
|
|
1496
|
-
const
|
|
1497
|
-
"a",
|
|
1498
|
-
"an",
|
|
1499
|
-
"the",
|
|
1500
|
-
"and",
|
|
1501
|
-
"or",
|
|
1502
|
-
"but",
|
|
1503
|
-
"in",
|
|
1504
|
-
"on",
|
|
1505
|
-
"at",
|
|
1506
|
-
"to",
|
|
1507
|
-
"for",
|
|
1508
|
-
"of",
|
|
1509
|
-
"with",
|
|
1510
|
-
"by",
|
|
1511
|
-
"from",
|
|
1512
|
-
"is",
|
|
1513
|
-
"are",
|
|
1514
|
-
"was",
|
|
1515
|
-
"were",
|
|
1516
|
-
"be",
|
|
1517
|
-
"been",
|
|
1518
|
-
"being",
|
|
1519
|
-
"have",
|
|
1520
|
-
"has",
|
|
1521
|
-
"had",
|
|
1522
|
-
"do",
|
|
1523
|
-
"does",
|
|
1524
|
-
"did",
|
|
1525
|
-
"will",
|
|
1526
|
-
"would",
|
|
1527
|
-
"could",
|
|
1528
|
-
"should",
|
|
1529
|
-
"may",
|
|
1530
|
-
"might",
|
|
1531
|
-
"shall",
|
|
1532
|
-
"can",
|
|
1533
|
-
"need",
|
|
1534
|
-
"must",
|
|
1535
|
-
"what",
|
|
1536
|
-
"which",
|
|
1537
|
-
"who",
|
|
1538
|
-
"how",
|
|
1539
|
-
"why",
|
|
1540
|
-
"when",
|
|
1541
|
-
"where",
|
|
1542
|
-
"that",
|
|
1543
|
-
"this",
|
|
1544
|
-
"these",
|
|
1545
|
-
"those",
|
|
1546
|
-
"it",
|
|
1547
|
-
"its",
|
|
1548
|
-
"me",
|
|
1549
|
-
"my",
|
|
1550
|
-
"you",
|
|
1551
|
-
"your",
|
|
1552
|
-
"we",
|
|
1553
|
-
"our",
|
|
1554
|
-
"they",
|
|
1555
|
-
"their",
|
|
1556
|
-
"he",
|
|
1557
|
-
"she",
|
|
1558
|
-
"i",
|
|
1559
|
-
"all",
|
|
1560
|
-
"any",
|
|
1561
|
-
"each",
|
|
1562
|
-
"some",
|
|
1563
|
-
"no",
|
|
1564
|
-
"not",
|
|
1565
|
-
"so",
|
|
1566
|
-
"yet",
|
|
1567
|
-
"use",
|
|
1568
|
-
"used",
|
|
1569
|
-
"using",
|
|
1570
|
-
"about",
|
|
1571
|
-
"tell",
|
|
1572
|
-
"know",
|
|
1573
|
-
"get",
|
|
1574
|
-
"got",
|
|
1575
|
-
"make",
|
|
1576
|
-
"made",
|
|
1577
|
-
"see",
|
|
1578
|
-
"give",
|
|
1579
|
-
"go",
|
|
1580
|
-
"come",
|
|
1581
|
-
"take"
|
|
1582
|
-
]);
|
|
1583
|
-
const keywords = query.replace(/[^a-zA-Z0-9\s]/g, " ").toLowerCase().split(/\s+/).filter((w) => w.length >= 3 && !stopWords.has(w));
|
|
1644
|
+
const keywords = query.replace(/[^a-zA-Z0-9\s]/g, " ").toLowerCase().split(/\s+/).filter((w) => w.length >= 3 && !FTS_STOP_WORDS.has(w));
|
|
1584
1645
|
if (keywords.length === 0) {
|
|
1585
1646
|
return query.replace(/[^a-zA-Z0-9\s]/g, " ").trim();
|
|
1586
1647
|
}
|
|
@@ -1796,12 +1857,38 @@ var init_anthropic = __esm({
|
|
|
1796
1857
|
});
|
|
1797
1858
|
|
|
1798
1859
|
// packages/llm/dist/providers/ollama.js
|
|
1799
|
-
|
|
1860
|
+
function validateOllamaHost(host) {
|
|
1861
|
+
let parsed;
|
|
1862
|
+
try {
|
|
1863
|
+
parsed = new URL(host);
|
|
1864
|
+
} catch {
|
|
1865
|
+
throw new CortexError(LLM_PROVIDER_UNAVAILABLE, "high", "llm", `Invalid Ollama host URL: ${host}`, { host }, "Set a valid URL like http://localhost:11434", false);
|
|
1866
|
+
}
|
|
1867
|
+
const hostname = parsed.hostname;
|
|
1868
|
+
for (const pattern of BLOCKED_HOST_PATTERNS) {
|
|
1869
|
+
if (pattern.test(hostname)) {
|
|
1870
|
+
throw new CortexError(LLM_PROVIDER_UNAVAILABLE, "high", "llm", `Ollama host "${hostname}" is blocked \u2014 it matches a link-local or cloud metadata IP range.`, { host }, "Use a non-link-local address for Ollama.", false);
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
const localhostNames = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "0.0.0.0"]);
|
|
1874
|
+
if (!localhostNames.has(hostname)) {
|
|
1875
|
+
logger4.warn(`Ollama host is not localhost (${hostname}). Ensure the remote Ollama instance is trusted and network-secured.`);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
var logger4, BLOCKED_HOST_PATTERNS, OllamaProvider;
|
|
1800
1879
|
var init_ollama = __esm({
|
|
1801
1880
|
"packages/llm/dist/providers/ollama.js"() {
|
|
1802
1881
|
"use strict";
|
|
1803
1882
|
init_dist();
|
|
1804
1883
|
logger4 = createLogger("llm:ollama");
|
|
1884
|
+
BLOCKED_HOST_PATTERNS = [
|
|
1885
|
+
/^169\.254\./,
|
|
1886
|
+
// AWS/Azure metadata link-local
|
|
1887
|
+
/^fd[0-9a-f]{2}:/i,
|
|
1888
|
+
// IPv6 unique local (fd00::/8)
|
|
1889
|
+
/^fe80:/i
|
|
1890
|
+
// IPv6 link-local
|
|
1891
|
+
];
|
|
1805
1892
|
OllamaProvider = class {
|
|
1806
1893
|
name = "ollama";
|
|
1807
1894
|
type = "local";
|
|
@@ -1812,6 +1899,7 @@ var init_ollama = __esm({
|
|
|
1812
1899
|
numGpu;
|
|
1813
1900
|
timeoutMs;
|
|
1814
1901
|
keepAlive;
|
|
1902
|
+
streamInactivityTimeoutMs;
|
|
1815
1903
|
capabilities = {
|
|
1816
1904
|
supportedTasks: [
|
|
1817
1905
|
LLMTask.ENTITY_EXTRACTION,
|
|
@@ -1830,12 +1918,14 @@ var init_ollama = __esm({
|
|
|
1830
1918
|
};
|
|
1831
1919
|
constructor(options = {}) {
|
|
1832
1920
|
this.host = options.host ?? process.env["CORTEX_OLLAMA_HOST"] ?? "http://localhost:11434";
|
|
1921
|
+
validateOllamaHost(this.host);
|
|
1833
1922
|
this.model = options.model ?? "mistral:7b-instruct-q5_K_M";
|
|
1834
1923
|
this.embeddingModel = options.embeddingModel ?? "nomic-embed-text";
|
|
1835
1924
|
this.numCtx = options.numCtx ?? 8192;
|
|
1836
1925
|
this.numGpu = options.numGpu ?? -1;
|
|
1837
1926
|
this.timeoutMs = options.timeoutMs ?? 3e5;
|
|
1838
1927
|
this.keepAlive = options.keepAlive ?? "5m";
|
|
1928
|
+
this.streamInactivityTimeoutMs = 6e4;
|
|
1839
1929
|
this.capabilities.maxContextTokens = this.numCtx;
|
|
1840
1930
|
}
|
|
1841
1931
|
getModel() {
|
|
@@ -1942,25 +2032,43 @@ var init_ollama = __esm({
|
|
|
1942
2032
|
const decoder = new TextDecoder();
|
|
1943
2033
|
let inputTokens = 0;
|
|
1944
2034
|
let outputTokens = 0;
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
2035
|
+
const streamController = new AbortController();
|
|
2036
|
+
let inactivityTimer = setTimeout(() => streamController.abort(), this.streamInactivityTimeoutMs);
|
|
2037
|
+
try {
|
|
2038
|
+
while (true) {
|
|
2039
|
+
const readPromise = reader.read();
|
|
2040
|
+
const raceResult = await Promise.race([
|
|
2041
|
+
readPromise,
|
|
2042
|
+
new Promise((_, reject) => {
|
|
2043
|
+
streamController.signal.addEventListener("abort", () => reject(new Error("Stream inactivity timeout")), { once: true });
|
|
2044
|
+
if (streamController.signal.aborted) {
|
|
2045
|
+
reject(new Error("Stream inactivity timeout"));
|
|
2046
|
+
}
|
|
2047
|
+
})
|
|
2048
|
+
]);
|
|
2049
|
+
const { done, value } = raceResult;
|
|
2050
|
+
if (done)
|
|
2051
|
+
break;
|
|
2052
|
+
clearTimeout(inactivityTimer);
|
|
2053
|
+
inactivityTimer = setTimeout(() => streamController.abort(), this.streamInactivityTimeoutMs);
|
|
2054
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
2055
|
+
const lines = chunk.split("\n").filter((line) => line.trim());
|
|
2056
|
+
for (const line of lines) {
|
|
2057
|
+
try {
|
|
2058
|
+
const data = JSON.parse(line);
|
|
2059
|
+
if (data.response) {
|
|
2060
|
+
yield data.response;
|
|
2061
|
+
}
|
|
2062
|
+
if (data.done) {
|
|
2063
|
+
inputTokens = data.prompt_eval_count ?? 0;
|
|
2064
|
+
outputTokens = data.eval_count ?? 0;
|
|
2065
|
+
}
|
|
2066
|
+
} catch {
|
|
1960
2067
|
}
|
|
1961
|
-
} catch {
|
|
1962
2068
|
}
|
|
1963
2069
|
}
|
|
2070
|
+
} finally {
|
|
2071
|
+
clearTimeout(inactivityTimer);
|
|
1964
2072
|
}
|
|
1965
2073
|
return {
|
|
1966
2074
|
inputTokens,
|
|
@@ -2125,7 +2233,12 @@ var init_openai_compatible = __esm({
|
|
|
2125
2233
|
});
|
|
2126
2234
|
this.primaryModel = options.primaryModel ?? "gpt-4o";
|
|
2127
2235
|
this.fastModel = options.fastModel ?? "gpt-4o-mini";
|
|
2128
|
-
|
|
2236
|
+
try {
|
|
2237
|
+
const parsedUrl = new URL(options.baseUrl);
|
|
2238
|
+
this.isGemini = parsedUrl.hostname === "generativelanguage.googleapis.com";
|
|
2239
|
+
} catch {
|
|
2240
|
+
this.isGemini = false;
|
|
2241
|
+
}
|
|
2129
2242
|
logger5.info("OpenAI-compatible provider initialized", {
|
|
2130
2243
|
baseUrl: options.baseUrl,
|
|
2131
2244
|
primaryModel: this.primaryModel,
|
|
@@ -2252,7 +2365,7 @@ function estimateCost(model, inputTokens, outputTokens) {
|
|
|
2252
2365
|
const costs = MODEL_COSTS[model] ?? DEFAULT_COST;
|
|
2253
2366
|
return inputTokens / 1e6 * costs.input + outputTokens / 1e6 * costs.output;
|
|
2254
2367
|
}
|
|
2255
|
-
var logger6, MODEL_COSTS, DEFAULT_COST, TokenTracker;
|
|
2368
|
+
var logger6, MODEL_COSTS, DEFAULT_COST, MAX_IN_MEMORY_RECORDS, TokenTracker;
|
|
2256
2369
|
var init_token_tracker = __esm({
|
|
2257
2370
|
"packages/llm/dist/token-tracker.js"() {
|
|
2258
2371
|
"use strict";
|
|
@@ -2263,6 +2376,7 @@ var init_token_tracker = __esm({
|
|
|
2263
2376
|
"claude-haiku-4-5-20251001": { input: 0.8, output: 4 }
|
|
2264
2377
|
};
|
|
2265
2378
|
DEFAULT_COST = { input: 3, output: 15 };
|
|
2379
|
+
MAX_IN_MEMORY_RECORDS = 1e4;
|
|
2266
2380
|
TokenTracker = class {
|
|
2267
2381
|
records = [];
|
|
2268
2382
|
monthlyBudgetUsd;
|
|
@@ -2287,9 +2401,23 @@ var init_token_tracker = __esm({
|
|
|
2287
2401
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2288
2402
|
};
|
|
2289
2403
|
this.records.push(record);
|
|
2404
|
+
this.trimOldRecords();
|
|
2290
2405
|
this.checkBudget();
|
|
2291
2406
|
return record;
|
|
2292
2407
|
}
|
|
2408
|
+
trimOldRecords() {
|
|
2409
|
+
if (this.records.length <= MAX_IN_MEMORY_RECORDS)
|
|
2410
|
+
return;
|
|
2411
|
+
const currentMonth = (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
|
|
2412
|
+
const currentMonthRecords = this.records.filter((r) => r.timestamp.startsWith(currentMonth));
|
|
2413
|
+
const priorRecords = this.records.filter((r) => !r.timestamp.startsWith(currentMonth));
|
|
2414
|
+
if (currentMonthRecords.length >= MAX_IN_MEMORY_RECORDS) {
|
|
2415
|
+
this.records = currentMonthRecords.slice(-MAX_IN_MEMORY_RECORDS);
|
|
2416
|
+
} else {
|
|
2417
|
+
const keepFromPrior = MAX_IN_MEMORY_RECORDS - currentMonthRecords.length;
|
|
2418
|
+
this.records = [...priorRecords.slice(-keepFromPrior), ...currentMonthRecords];
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2293
2421
|
checkBudget() {
|
|
2294
2422
|
const spent = this.getCurrentMonthSpend();
|
|
2295
2423
|
const usedPercent = this.monthlyBudgetUsd > 0 ? spent / this.monthlyBudgetUsd : 0;
|
|
@@ -2402,9 +2530,9 @@ var init_cache = __esm({
|
|
|
2402
2530
|
if (!this.enabled)
|
|
2403
2531
|
return;
|
|
2404
2532
|
if (this.cache.size >= this.maxEntries) {
|
|
2405
|
-
const
|
|
2406
|
-
if (
|
|
2407
|
-
this.cache.delete(
|
|
2533
|
+
const firstKey = this.cache.keys().next().value;
|
|
2534
|
+
if (firstKey !== void 0) {
|
|
2535
|
+
this.cache.delete(firstKey);
|
|
2408
2536
|
}
|
|
2409
2537
|
}
|
|
2410
2538
|
const key = this.buildKey(contentHash, promptId, promptVersion);
|
|
@@ -3906,7 +4034,7 @@ function isConversationMarkdown(content) {
|
|
|
3906
4034
|
const lines = content.split("\n");
|
|
3907
4035
|
const headings = [];
|
|
3908
4036
|
for (const line of lines) {
|
|
3909
|
-
const m = line.match(/^#{1,3}\s+(
|
|
4037
|
+
const m = line.match(/^#{1,3}\s+(\S.*)$/);
|
|
3910
4038
|
if (m) {
|
|
3911
4039
|
headings.push(m[1].trim());
|
|
3912
4040
|
if (headings.length >= 2)
|
|
@@ -3988,7 +4116,7 @@ function parseConversationMarkdown(content) {
|
|
|
3988
4116
|
};
|
|
3989
4117
|
for (let i = 0; i < lines.length; i++) {
|
|
3990
4118
|
const line = lines[i];
|
|
3991
|
-
const headingMatch = line.match(/^#{1,3}\s+(
|
|
4119
|
+
const headingMatch = line.match(/^#{1,3}\s+(\S.*)$/);
|
|
3992
4120
|
if (headingMatch) {
|
|
3993
4121
|
flush(i);
|
|
3994
4122
|
currentRole = headingMatch[1].trim();
|
|
@@ -4176,6 +4304,11 @@ import { extname } from "node:path";
|
|
|
4176
4304
|
function escapeRegex(s) {
|
|
4177
4305
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4178
4306
|
}
|
|
4307
|
+
function globToRegex(pattern) {
|
|
4308
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
4309
|
+
const regexStr = escaped.replace(/\*\*/g, "___GLOBSTAR___").replace(/\*/g, "[^/\\\\]*").replace(/___GLOBSTAR___/g, ".*");
|
|
4310
|
+
return new RegExp("^" + regexStr + "$");
|
|
4311
|
+
}
|
|
4179
4312
|
var logger9, FileWatcher;
|
|
4180
4313
|
var init_watcher = __esm({
|
|
4181
4314
|
"packages/ingest/dist/watcher.js"() {
|
|
@@ -4187,8 +4320,15 @@ var init_watcher = __esm({
|
|
|
4187
4320
|
options;
|
|
4188
4321
|
handler = null;
|
|
4189
4322
|
debounceTimers = /* @__PURE__ */ new Map();
|
|
4323
|
+
compiledExcludePatterns;
|
|
4190
4324
|
constructor(options) {
|
|
4191
4325
|
this.options = options;
|
|
4326
|
+
this.compiledExcludePatterns = options.exclude.map((pattern) => {
|
|
4327
|
+
if (pattern.includes("*")) {
|
|
4328
|
+
return { pattern: globToRegex(pattern), isGlob: true };
|
|
4329
|
+
}
|
|
4330
|
+
return pattern;
|
|
4331
|
+
});
|
|
4192
4332
|
}
|
|
4193
4333
|
static fromConfig(config8) {
|
|
4194
4334
|
return new _FileWatcher({
|
|
@@ -4272,13 +4412,12 @@ var init_watcher = __esm({
|
|
|
4272
4412
|
}
|
|
4273
4413
|
isExcluded(filePath) {
|
|
4274
4414
|
const parts = filePath.split(/[\\/]/);
|
|
4275
|
-
for (const
|
|
4276
|
-
if (
|
|
4277
|
-
|
|
4278
|
-
if (parts.some((p) => re.test(p)))
|
|
4415
|
+
for (const compiled of this.compiledExcludePatterns) {
|
|
4416
|
+
if (typeof compiled === "string") {
|
|
4417
|
+
if (parts.some((p) => p === compiled))
|
|
4279
4418
|
return true;
|
|
4280
4419
|
} else {
|
|
4281
|
-
if (parts.some((p) => p
|
|
4420
|
+
if (parts.some((p) => compiled.pattern.test(p)))
|
|
4282
4421
|
return true;
|
|
4283
4422
|
}
|
|
4284
4423
|
}
|
|
@@ -4288,8 +4427,7 @@ var init_watcher = __esm({
|
|
|
4288
4427
|
const patterns = [];
|
|
4289
4428
|
for (const pattern of this.options.exclude) {
|
|
4290
4429
|
if (pattern.includes("*")) {
|
|
4291
|
-
|
|
4292
|
-
patterns.push(new RegExp(regexStr));
|
|
4430
|
+
patterns.push(globToRegex(pattern));
|
|
4293
4431
|
} else if (pattern.includes(".")) {
|
|
4294
4432
|
patterns.push(new RegExp(`(^|[\\\\/])${escapeRegex(pattern)}$`));
|
|
4295
4433
|
} else {
|
|
@@ -4466,10 +4604,34 @@ var init_pipeline = __esm({
|
|
|
4466
4604
|
// Shared across all ingestFile calls — prevents the same entity pair from being
|
|
4467
4605
|
// evaluated twice when multiple files ingest in the same batch.
|
|
4468
4606
|
checkedContradictionPairs = /* @__PURE__ */ new Set();
|
|
4607
|
+
// Pre-compiled secret patterns for scrubbing before cloud LLM calls
|
|
4608
|
+
compiledSecretPatterns;
|
|
4469
4609
|
constructor(router, store, options) {
|
|
4470
4610
|
this.router = router;
|
|
4471
4611
|
this.store = store;
|
|
4472
4612
|
this.options = options;
|
|
4613
|
+
this.compiledSecretPatterns = (options.secretPatterns ?? []).map((pattern) => {
|
|
4614
|
+
try {
|
|
4615
|
+
return new RegExp(pattern, "g");
|
|
4616
|
+
} catch {
|
|
4617
|
+
logger11.warn("Invalid secret pattern, skipping", { pattern });
|
|
4618
|
+
return null;
|
|
4619
|
+
}
|
|
4620
|
+
}).filter((r) => r !== null);
|
|
4621
|
+
}
|
|
4622
|
+
/**
|
|
4623
|
+
* Scrub secrets from content before sending to cloud LLMs.
|
|
4624
|
+
* Only applied for standard privacy (sensitive/restricted use local provider).
|
|
4625
|
+
*/
|
|
4626
|
+
scrubSecrets(content) {
|
|
4627
|
+
if (this.compiledSecretPatterns.length === 0)
|
|
4628
|
+
return content;
|
|
4629
|
+
let scrubbed = content;
|
|
4630
|
+
for (const re of this.compiledSecretPatterns) {
|
|
4631
|
+
re.lastIndex = 0;
|
|
4632
|
+
scrubbed = scrubbed.replace(re, "[SECRET_REDACTED]");
|
|
4633
|
+
}
|
|
4634
|
+
return scrubbed;
|
|
4473
4635
|
}
|
|
4474
4636
|
async ingestFile(filePath) {
|
|
4475
4637
|
try {
|
|
@@ -4545,9 +4707,12 @@ var init_pipeline = __esm({
|
|
|
4545
4707
|
const deduped = this.deduplicateEntities(allEntities);
|
|
4546
4708
|
logger11.debug("Extracted entities", { filePath, raw: allEntities.length, deduped: deduped.length });
|
|
4547
4709
|
const storedEntities = [];
|
|
4548
|
-
|
|
4549
|
-
const
|
|
4550
|
-
|
|
4710
|
+
this.store.transaction(() => {
|
|
4711
|
+
for (const entity of deduped) {
|
|
4712
|
+
storedEntities.push(this.store.createEntitySync(entity));
|
|
4713
|
+
}
|
|
4714
|
+
});
|
|
4715
|
+
for (const stored of storedEntities) {
|
|
4551
4716
|
eventBus.emit({
|
|
4552
4717
|
type: "entity.created",
|
|
4553
4718
|
payload: { entity: stored },
|
|
@@ -4608,6 +4773,7 @@ var init_pipeline = __esm({
|
|
|
4608
4773
|
async extractEntities(chunk, filePath, fileType) {
|
|
4609
4774
|
const contentHash = createHash2("sha256").update(chunk.content).digest("hex");
|
|
4610
4775
|
const privacyOverride = this.options.projectPrivacyLevel !== "standard" ? { forceProvider: "local" } : {};
|
|
4776
|
+
const safeContent = this.options.projectPrivacyLevel === "standard" ? this.scrubSecrets(chunk.content) : chunk.content;
|
|
4611
4777
|
try {
|
|
4612
4778
|
const result = await this.router.completeStructured({
|
|
4613
4779
|
systemPrompt: entity_extraction_exports.systemPrompt,
|
|
@@ -4615,7 +4781,7 @@ var init_pipeline = __esm({
|
|
|
4615
4781
|
filePath,
|
|
4616
4782
|
projectName: this.options.projectName,
|
|
4617
4783
|
fileType,
|
|
4618
|
-
content:
|
|
4784
|
+
content: safeContent
|
|
4619
4785
|
}),
|
|
4620
4786
|
promptId: entity_extraction_exports.PROMPT_ID,
|
|
4621
4787
|
promptVersion: entity_extraction_exports.PROMPT_VERSION,
|
|
@@ -5153,7 +5319,7 @@ var init_status = __esm({
|
|
|
5153
5319
|
for (let i = 0; i < 6; i++) {
|
|
5154
5320
|
try {
|
|
5155
5321
|
const pkg = JSON.parse(readFileSync9(resolve19(dir, "package.json"), "utf-8"));
|
|
5156
|
-
if (pkg.name === "gzoo-cortex" && pkg.version) {
|
|
5322
|
+
if ((pkg.name === "@gzoo/cortex" || pkg.name === "gzoo-cortex") && pkg.version) {
|
|
5157
5323
|
_version = pkg.version;
|
|
5158
5324
|
break;
|
|
5159
5325
|
}
|
|
@@ -5385,7 +5551,7 @@ async function startServer(options) {
|
|
|
5385
5551
|
callback(new Error("CORS not allowed"));
|
|
5386
5552
|
}
|
|
5387
5553
|
}));
|
|
5388
|
-
app.use(express.json());
|
|
5554
|
+
app.use(express.json({ limit: "1mb" }));
|
|
5389
5555
|
const isLocal = host === "127.0.0.1" || host === "localhost" || host === "::1";
|
|
5390
5556
|
if (!isLocal && !config8.server.auth.enabled) {
|
|
5391
5557
|
logger34.warn("Server bound to non-localhost without auth enabled. Set server.auth.enabled=true and server.auth.token in config, or set CORTEX_SERVER_AUTH_TOKEN env var.");
|
|
@@ -5393,6 +5559,15 @@ async function startServer(options) {
|
|
|
5393
5559
|
const rateLimitWindow = 6e4;
|
|
5394
5560
|
const rateLimitMax = 30;
|
|
5395
5561
|
const rateLimitMap = /* @__PURE__ */ new Map();
|
|
5562
|
+
const rateLimitCleanupInterval = setInterval(() => {
|
|
5563
|
+
const now2 = Date.now();
|
|
5564
|
+
for (const [key, entry] of rateLimitMap) {
|
|
5565
|
+
if (now2 >= entry.resetAt) {
|
|
5566
|
+
rateLimitMap.delete(key);
|
|
5567
|
+
}
|
|
5568
|
+
}
|
|
5569
|
+
}, 6e4);
|
|
5570
|
+
rateLimitCleanupInterval.unref();
|
|
5396
5571
|
const rateLimiter = (req, res, next) => {
|
|
5397
5572
|
const key = req.ip ?? "unknown";
|
|
5398
5573
|
const now2 = Date.now();
|
|
@@ -5412,11 +5587,12 @@ async function startServer(options) {
|
|
|
5412
5587
|
next();
|
|
5413
5588
|
};
|
|
5414
5589
|
const api = express.Router();
|
|
5590
|
+
api.use(rateLimiter);
|
|
5415
5591
|
api.use(createAuthMiddleware({ config: config8, host }));
|
|
5416
5592
|
api.use("/entities", createEntityRoutes(bundle));
|
|
5417
5593
|
api.use("/relationships", createRelationshipRoutes(bundle));
|
|
5418
5594
|
api.use("/projects", createProjectRoutes(bundle));
|
|
5419
|
-
api.use("/query",
|
|
5595
|
+
api.use("/query", createQueryRoutes(bundle));
|
|
5420
5596
|
api.use("/contradictions", createContradictionRoutes(bundle));
|
|
5421
5597
|
api.use("/", createStatusRoutes(bundle));
|
|
5422
5598
|
app.use("/api/v1", api);
|
|
@@ -5443,7 +5619,8 @@ async function startServer(options) {
|
|
|
5443
5619
|
maxFileSize: config8.ingest.maxFileSize,
|
|
5444
5620
|
batchSize: config8.ingest.batchSize,
|
|
5445
5621
|
projectPrivacyLevel: project.privacyLevel,
|
|
5446
|
-
mergeConfidenceThreshold: 0.85
|
|
5622
|
+
mergeConfidenceThreshold: 0.85,
|
|
5623
|
+
secretPatterns: config8.privacy.secretPatterns
|
|
5447
5624
|
});
|
|
5448
5625
|
const watcher = new FileWatcher2({
|
|
5449
5626
|
dirs: [project.rootPath],
|
|
@@ -5488,6 +5665,7 @@ async function startServer(options) {
|
|
|
5488
5665
|
});
|
|
5489
5666
|
const shutdown = () => {
|
|
5490
5667
|
logger34.info("Shutting down...");
|
|
5668
|
+
clearInterval(rateLimitCleanupInterval);
|
|
5491
5669
|
relay.close();
|
|
5492
5670
|
server.close();
|
|
5493
5671
|
bundle.store.close();
|
|
@@ -5852,7 +6030,8 @@ async function runWatch(projectName, opts, globals) {
|
|
|
5852
6030
|
maxFileSize: config8.ingest.maxFileSize,
|
|
5853
6031
|
batchSize: config8.ingest.batchSize,
|
|
5854
6032
|
projectPrivacyLevel: project.privacyLevel,
|
|
5855
|
-
mergeConfidenceThreshold: config8.graph.mergeConfidenceThreshold
|
|
6033
|
+
mergeConfidenceThreshold: config8.graph.mergeConfidenceThreshold,
|
|
6034
|
+
secretPatterns: config8.privacy.secretPatterns
|
|
5856
6035
|
});
|
|
5857
6036
|
const watcher = FileWatcher.fromConfig(config8.ingest);
|
|
5858
6037
|
let ingestedCount = 0;
|
|
@@ -6247,6 +6426,16 @@ import { resolve as resolve7 } from "node:path";
|
|
|
6247
6426
|
import { statSync as statSync3 } from "node:fs";
|
|
6248
6427
|
import chalk5 from "chalk";
|
|
6249
6428
|
var logger15 = createLogger("cli:status");
|
|
6429
|
+
function sanitizeUrl(url) {
|
|
6430
|
+
try {
|
|
6431
|
+
const parsed = new URL(url);
|
|
6432
|
+
parsed.username = "";
|
|
6433
|
+
parsed.password = "";
|
|
6434
|
+
return parsed.toString().replace(/\/$/, "");
|
|
6435
|
+
} catch {
|
|
6436
|
+
return "[invalid-url]";
|
|
6437
|
+
}
|
|
6438
|
+
}
|
|
6250
6439
|
async function checkOllamaAvailable(host) {
|
|
6251
6440
|
try {
|
|
6252
6441
|
const controller = new AbortController();
|
|
@@ -6322,7 +6511,7 @@ async function runStatus(globals) {
|
|
|
6322
6511
|
local: {
|
|
6323
6512
|
provider: "ollama",
|
|
6324
6513
|
available: ollamaAvailable,
|
|
6325
|
-
host: config8.llm.local.host,
|
|
6514
|
+
host: sanitizeUrl(config8.llm.local.host),
|
|
6326
6515
|
model: config8.llm.local.model,
|
|
6327
6516
|
numCtx: localProvider?.getNumCtx() ?? config8.llm.local.numCtx
|
|
6328
6517
|
}
|
|
@@ -6351,7 +6540,7 @@ async function runStatus(globals) {
|
|
|
6351
6540
|
const numGpu = localProvider?.getNumGpu() ?? config8.llm.local.numGpu;
|
|
6352
6541
|
console.log(chalk5.white("LLM Mode: ") + mode);
|
|
6353
6542
|
const cloudLabel = `${config8.llm.cloud.models.primary} / ${config8.llm.cloud.models.fast} (${config8.llm.cloud.provider})`;
|
|
6354
|
-
const localLabel = `${config8.llm.local.model} @ ${config8.llm.local.host}`;
|
|
6543
|
+
const localLabel = `${config8.llm.local.model} @ ${sanitizeUrl(config8.llm.local.host)}`;
|
|
6355
6544
|
const localDetail = `${numCtx.toLocaleString()} ctx | GPU: ${numGpu === -1 ? "auto" : numGpu} layers | ~30 tok/s est.`;
|
|
6356
6545
|
if (mode === "cloud-first") {
|
|
6357
6546
|
const llmStatus = hasApiKey ? chalk5.green("\u2713") : chalk5.red("\u2717");
|
|
@@ -6390,7 +6579,7 @@ async function runStatus(globals) {
|
|
|
6390
6579
|
let statusMsg = "";
|
|
6391
6580
|
if (mode === "local-only") {
|
|
6392
6581
|
statusOk = ollamaAvailable;
|
|
6393
|
-
statusMsg = ollamaAvailable ? "\u2713 Fully operational" : `\u26A0 Ollama not available at ${config8.llm.local.host}. Run \`ollama serve\`.`;
|
|
6582
|
+
statusMsg = ollamaAvailable ? "\u2713 Fully operational" : `\u26A0 Ollama not available at ${sanitizeUrl(config8.llm.local.host)}. Run \`ollama serve\`.`;
|
|
6394
6583
|
} else if (mode === "local-first") {
|
|
6395
6584
|
statusOk = ollamaAvailable || hasApiKey;
|
|
6396
6585
|
if (ollamaAvailable) {
|
|
@@ -6413,7 +6602,7 @@ async function runStatus(globals) {
|
|
|
6413
6602
|
}
|
|
6414
6603
|
} else {
|
|
6415
6604
|
statusOk = hasApiKey;
|
|
6416
|
-
statusMsg = hasApiKey ? "\u2713 Fully operational" : "\u26A0 API key not set. Run `cortex init`
|
|
6605
|
+
statusMsg = hasApiKey ? "\u2713 Fully operational" : "\u26A0 API key not set. Run `cortex init` to configure.";
|
|
6417
6606
|
}
|
|
6418
6607
|
console.log(chalk5.white("Status: ") + (statusOk ? chalk5.green(statusMsg) : chalk5.yellow(statusMsg)));
|
|
6419
6608
|
console.log("");
|
|
@@ -6613,10 +6802,13 @@ function registerConfigCommand(program2) {
|
|
|
6613
6802
|
await runExcludeRemove(pattern, globals);
|
|
6614
6803
|
});
|
|
6615
6804
|
}
|
|
6805
|
+
var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
6616
6806
|
function getNestedValue(obj, path) {
|
|
6617
6807
|
const parts = path.split(".");
|
|
6618
6808
|
let current = obj;
|
|
6619
6809
|
for (const part of parts) {
|
|
6810
|
+
if (DANGEROUS_KEYS.has(part))
|
|
6811
|
+
return void 0;
|
|
6620
6812
|
if (current === null || current === void 0 || typeof current !== "object") {
|
|
6621
6813
|
return void 0;
|
|
6622
6814
|
}
|
|
@@ -6629,12 +6821,17 @@ function setNestedValue(obj, path, value) {
|
|
|
6629
6821
|
let current = obj;
|
|
6630
6822
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
6631
6823
|
const part = parts[i];
|
|
6824
|
+
if (DANGEROUS_KEYS.has(part))
|
|
6825
|
+
throw new Error(`Invalid config key: ${part}`);
|
|
6632
6826
|
if (current[part] === void 0 || typeof current[part] !== "object") {
|
|
6633
6827
|
current[part] = {};
|
|
6634
6828
|
}
|
|
6635
6829
|
current = current[part];
|
|
6636
6830
|
}
|
|
6637
|
-
|
|
6831
|
+
const lastKey = parts[parts.length - 1];
|
|
6832
|
+
if (DANGEROUS_KEYS.has(lastKey))
|
|
6833
|
+
throw new Error(`Invalid config key: ${lastKey}`);
|
|
6834
|
+
current[lastKey] = value;
|
|
6638
6835
|
}
|
|
6639
6836
|
function parseValue(value) {
|
|
6640
6837
|
try {
|
|
@@ -7355,7 +7552,8 @@ async function runIngest(pattern, opts, globals) {
|
|
|
7355
7552
|
const lastSep = Math.max(resolvedPattern.lastIndexOf("/"), resolvedPattern.lastIndexOf("\\"));
|
|
7356
7553
|
const dir = lastSep >= 0 ? resolvedPattern.slice(0, lastSep) : process.cwd();
|
|
7357
7554
|
const filePattern = lastSep >= 0 ? resolvedPattern.slice(lastSep + 1) : resolvedPattern;
|
|
7358
|
-
const
|
|
7555
|
+
const escaped = filePattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
7556
|
+
const regex = new RegExp("^" + escaped.replace(/\*/g, ".*") + "$");
|
|
7359
7557
|
if (existsSync6(dir)) {
|
|
7360
7558
|
for (const entry of readdirSync(dir)) {
|
|
7361
7559
|
if (regex.test(entry)) {
|
|
@@ -7436,7 +7634,8 @@ Dry run complete: ~${totalSections * 3} entities estimated across ${filePaths.le
|
|
|
7436
7634
|
maxFileSize: config8.ingest.maxFileSize,
|
|
7437
7635
|
batchSize: config8.ingest.batchSize,
|
|
7438
7636
|
projectPrivacyLevel: project.privacyLevel,
|
|
7439
|
-
mergeConfidenceThreshold: config8.graph.mergeConfidenceThreshold
|
|
7637
|
+
mergeConfidenceThreshold: config8.graph.mergeConfidenceThreshold,
|
|
7638
|
+
secretPatterns: config8.privacy.secretPatterns
|
|
7440
7639
|
});
|
|
7441
7640
|
let totalEntities = 0;
|
|
7442
7641
|
let totalRelationships = 0;
|
|
@@ -7576,7 +7775,7 @@ async function runModelsPull(model, globals) {
|
|
|
7576
7775
|
Pulling model: ${chalk13.cyan(model)}`));
|
|
7577
7776
|
console.log(chalk13.dim("This may take several minutes for large models...\n"));
|
|
7578
7777
|
}
|
|
7579
|
-
const result = spawnSync("ollama", ["pull", model], { stdio: "inherit"
|
|
7778
|
+
const result = spawnSync("ollama", ["pull", model], { stdio: "inherit" });
|
|
7580
7779
|
if (result.status !== 0) {
|
|
7581
7780
|
console.error(chalk13.red(`
|
|
7582
7781
|
\u2717 Failed to pull model "${model}"`));
|
|
@@ -7722,7 +7921,7 @@ function findPackageRoot(startDir) {
|
|
|
7722
7921
|
try {
|
|
7723
7922
|
const pkgPath = resolve16(dir, "package.json");
|
|
7724
7923
|
const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
7725
|
-
if (pkg.name === "gzoo-cortex")
|
|
7924
|
+
if (pkg.name === "@gzoo/cortex" || pkg.name === "gzoo-cortex")
|
|
7726
7925
|
return dir;
|
|
7727
7926
|
} catch {
|
|
7728
7927
|
}
|
|
@@ -8018,7 +8217,7 @@ function findPkgRoot(startDir) {
|
|
|
8018
8217
|
try {
|
|
8019
8218
|
const pkgPath = resolve21(dir, "package.json");
|
|
8020
8219
|
const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
|
|
8021
|
-
if (pkg.name === "gzoo-cortex")
|
|
8220
|
+
if (pkg.name === "@gzoo/cortex" || pkg.name === "gzoo-cortex")
|
|
8022
8221
|
return dir;
|
|
8023
8222
|
} catch {
|
|
8024
8223
|
}
|
|
@@ -8180,7 +8379,7 @@ function getVersion() {
|
|
|
8180
8379
|
for (let i = 0; i < 6; i++) {
|
|
8181
8380
|
try {
|
|
8182
8381
|
const pkg = JSON.parse(readFileSync12(resolve23(dir, "package.json"), "utf-8"));
|
|
8183
|
-
if (pkg.name === "gzoo-cortex" && pkg.version)
|
|
8382
|
+
if ((pkg.name === "@gzoo/cortex" || pkg.name === "gzoo-cortex") && pkg.version)
|
|
8184
8383
|
return pkg.version;
|
|
8185
8384
|
} catch {
|
|
8186
8385
|
}
|