open-think 0.2.5 → 0.3.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.
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 OpenThinkAi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -21,8 +21,8 @@ function configPath() {
|
|
|
21
21
|
}
|
|
22
22
|
function saveConfig(config) {
|
|
23
23
|
const dir = getConfigDir();
|
|
24
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
25
|
-
fs.writeFileSync(configPath(), JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
24
|
+
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
25
|
+
fs.writeFileSync(configPath(), JSON.stringify(config, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
26
26
|
}
|
|
27
27
|
function getConfig() {
|
|
28
28
|
const fp = configPath();
|
|
@@ -39,12 +39,34 @@ function getConfig() {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// src/lib/git.ts
|
|
42
|
+
function safeGitEnv() {
|
|
43
|
+
const env = { ...process.env };
|
|
44
|
+
delete env.GIT_SSH_COMMAND;
|
|
45
|
+
delete env.GIT_PROXY_COMMAND;
|
|
46
|
+
delete env.GIT_ASKPASS;
|
|
47
|
+
delete env.GIT_CONFIG_GLOBAL;
|
|
48
|
+
delete env.GIT_CONFIG_SYSTEM;
|
|
49
|
+
delete env.GIT_WORK_TREE;
|
|
50
|
+
delete env.GIT_DIR;
|
|
51
|
+
delete env.GIT_EXEC_PATH;
|
|
52
|
+
env.GIT_CONFIG_NOSYSTEM = "1";
|
|
53
|
+
env.GIT_TEMPLATE_DIR = "";
|
|
54
|
+
return env;
|
|
55
|
+
}
|
|
42
56
|
function runGit(args, cwd) {
|
|
43
57
|
const repoPath = cwd ?? getRepoPath();
|
|
44
|
-
|
|
58
|
+
const safeArgs = [
|
|
59
|
+
"-c",
|
|
60
|
+
"core.hooksPath=/dev/null",
|
|
61
|
+
"-c",
|
|
62
|
+
"core.fsmonitor=",
|
|
63
|
+
...args
|
|
64
|
+
];
|
|
65
|
+
return execFileSync("git", safeArgs, {
|
|
45
66
|
cwd: repoPath,
|
|
46
67
|
encoding: "utf-8",
|
|
47
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
68
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
69
|
+
env: safeGitEnv()
|
|
48
70
|
}).trim();
|
|
49
71
|
}
|
|
50
72
|
function ensureRepoCloned() {
|
|
@@ -61,9 +83,10 @@ function ensureRepoCloned() {
|
|
|
61
83
|
return;
|
|
62
84
|
}
|
|
63
85
|
fs2.mkdirSync(repoPath, { recursive: true });
|
|
64
|
-
execFileSync("git", ["clone", "--no-checkout", config.cortex.repo, repoPath], {
|
|
86
|
+
execFileSync("git", ["-c", "core.hooksPath=/dev/null", "-c", "core.fsmonitor=", "clone", "--no-checkout", config.cortex.repo, repoPath], {
|
|
65
87
|
encoding: "utf-8",
|
|
66
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
88
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
89
|
+
env: safeGitEnv()
|
|
67
90
|
});
|
|
68
91
|
}
|
|
69
92
|
function branchExists(branchName) {
|
package/dist/index.js
CHANGED
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
migrateToBuckets,
|
|
29
29
|
readFileFromBranch,
|
|
30
30
|
saveConfig
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-ZKUJ5M2W.js";
|
|
32
32
|
import {
|
|
33
33
|
ensureThinkDirs,
|
|
34
34
|
getCuratorMdPath,
|
|
@@ -185,11 +185,11 @@ function insertEngram(cortexName, params) {
|
|
|
185
185
|
).run(id, params.content, created_at, expires_at, episodeKey, context, decisions);
|
|
186
186
|
return { id, content: params.content, created_at, expires_at, evaluated_at: null, promoted: null, deleted_at: null, episode_key: episodeKey, context, decisions };
|
|
187
187
|
}
|
|
188
|
-
function getPendingEngrams(cortexName) {
|
|
188
|
+
function getPendingEngrams(cortexName, limit = 200) {
|
|
189
189
|
const db2 = getCortexDb(cortexName);
|
|
190
190
|
return db2.prepare(
|
|
191
|
-
`SELECT * FROM engrams WHERE evaluated_at IS NULL AND deleted_at IS NULL AND episode_key IS NULL AND expires_at > ? ORDER BY created_at ASC
|
|
192
|
-
).all((/* @__PURE__ */ new Date()).toISOString());
|
|
191
|
+
`SELECT * FROM engrams WHERE evaluated_at IS NULL AND deleted_at IS NULL AND episode_key IS NULL AND expires_at > ? ORDER BY created_at ASC LIMIT ?`
|
|
192
|
+
).all((/* @__PURE__ */ new Date()).toISOString(), limit);
|
|
193
193
|
}
|
|
194
194
|
function getPendingEpisodeEngrams(cortexName, episodeKey) {
|
|
195
195
|
const db2 = getCortexDb(cortexName);
|
|
@@ -341,7 +341,7 @@ function validateEngramContent(content) {
|
|
|
341
341
|
return { content, warnings };
|
|
342
342
|
}
|
|
343
343
|
function wrapData(label, content) {
|
|
344
|
-
const escaped = content.replace(
|
|
344
|
+
const escaped = content.replace(/<\/?data/gi, (match) => `<${match.slice(1)}`);
|
|
345
345
|
return `<data source="${label}">
|
|
346
346
|
${escaped}
|
|
347
347
|
</data>`;
|
|
@@ -786,12 +786,20 @@ function readAuditLog() {
|
|
|
786
786
|
}
|
|
787
787
|
|
|
788
788
|
// src/commands/import.ts
|
|
789
|
+
var MAX_IMPORT_FILE_SIZE = 50 * 1024 * 1024;
|
|
790
|
+
var MAX_IMPORT_ENTRIES = 5e4;
|
|
789
791
|
var importCommand = new Command6("import").description("Import a sync bundle from another device").argument("<file>", "Path to the sync bundle JSON file").action((file) => {
|
|
790
792
|
if (!fs5.existsSync(file)) {
|
|
791
793
|
console.error(chalk6.red(`File not found: ${file}`));
|
|
792
794
|
closeDb();
|
|
793
795
|
process.exit(1);
|
|
794
796
|
}
|
|
797
|
+
const stat = fs5.statSync(file);
|
|
798
|
+
if (stat.size > MAX_IMPORT_FILE_SIZE) {
|
|
799
|
+
console.error(chalk6.red(`File too large (${Math.round(stat.size / 1024 / 1024)}MB). Maximum import size is ${MAX_IMPORT_FILE_SIZE / 1024 / 1024}MB.`));
|
|
800
|
+
closeDb();
|
|
801
|
+
process.exit(1);
|
|
802
|
+
}
|
|
795
803
|
let bundle;
|
|
796
804
|
try {
|
|
797
805
|
const raw = fs5.readFileSync(file, "utf-8");
|
|
@@ -801,7 +809,7 @@ var importCommand = new Command6("import").description("Import a sync bundle fro
|
|
|
801
809
|
closeDb();
|
|
802
810
|
process.exit(1);
|
|
803
811
|
}
|
|
804
|
-
if (bundle.format !== "think-sync-bundle" || !bundle.entries) {
|
|
812
|
+
if (bundle.format !== "think-sync-bundle" || !Array.isArray(bundle.entries)) {
|
|
805
813
|
console.error(chalk6.red("Not a valid think sync bundle."));
|
|
806
814
|
closeDb();
|
|
807
815
|
process.exit(1);
|
|
@@ -811,6 +819,11 @@ var importCommand = new Command6("import").description("Import a sync bundle fro
|
|
|
811
819
|
closeDb();
|
|
812
820
|
return;
|
|
813
821
|
}
|
|
822
|
+
if (bundle.entries.length > MAX_IMPORT_ENTRIES) {
|
|
823
|
+
console.error(chalk6.red(`Bundle contains ${bundle.entries.length} entries. Maximum is ${MAX_IMPORT_ENTRIES}.`));
|
|
824
|
+
closeDb();
|
|
825
|
+
process.exit(1);
|
|
826
|
+
}
|
|
814
827
|
const db2 = getDb();
|
|
815
828
|
const insert = db2.prepare(
|
|
816
829
|
`INSERT OR IGNORE INTO entries (id, timestamp, source, category, content, tags, deleted_at)
|
|
@@ -818,16 +831,23 @@ var importCommand = new Command6("import").description("Import a sync bundle fro
|
|
|
818
831
|
);
|
|
819
832
|
let imported = 0;
|
|
820
833
|
let skipped = 0;
|
|
834
|
+
let warnings = 0;
|
|
821
835
|
try {
|
|
822
836
|
db2.exec("BEGIN");
|
|
823
837
|
for (const entry of bundle.entries) {
|
|
838
|
+
if (typeof entry.id !== "string" || typeof entry.content !== "string") {
|
|
839
|
+
skipped++;
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
const validated = validateEngramContent(entry.content);
|
|
843
|
+
if (validated.warnings.length > 0) warnings++;
|
|
824
844
|
const result = insert.run(
|
|
825
845
|
entry.id,
|
|
826
|
-
entry.timestamp,
|
|
827
|
-
entry.source,
|
|
828
|
-
entry.category,
|
|
829
|
-
|
|
830
|
-
entry.tags,
|
|
846
|
+
typeof entry.timestamp === "string" ? entry.timestamp : (/* @__PURE__ */ new Date()).toISOString(),
|
|
847
|
+
typeof entry.source === "string" ? entry.source : "import",
|
|
848
|
+
typeof entry.category === "string" ? entry.category : "",
|
|
849
|
+
validated.content,
|
|
850
|
+
typeof entry.tags === "string" ? entry.tags : "",
|
|
831
851
|
entry.deleted_at ?? null
|
|
832
852
|
);
|
|
833
853
|
if (result.changes > 0) {
|
|
@@ -853,10 +873,13 @@ var importCommand = new Command6("import").description("Import a sync bundle fro
|
|
|
853
873
|
count: bundle.entries.length
|
|
854
874
|
});
|
|
855
875
|
if (imported > 0) {
|
|
856
|
-
console.log(chalk6.green("\u2713") + ` Imported ${imported} entries` + (skipped > 0 ? ` (${skipped}
|
|
876
|
+
console.log(chalk6.green("\u2713") + ` Imported ${imported} entries` + (skipped > 0 ? ` (${skipped} skipped)` : ""));
|
|
857
877
|
} else {
|
|
858
878
|
console.log(chalk6.green("\u2713") + ` All ${skipped} entries already present \u2014 nothing new.`);
|
|
859
879
|
}
|
|
880
|
+
if (warnings > 0) {
|
|
881
|
+
console.log(chalk6.yellow("\u26A0") + ` ${warnings} entries contained suspicious content patterns`);
|
|
882
|
+
}
|
|
860
883
|
if (bundle.peerId) {
|
|
861
884
|
console.log(chalk6.dim(` from peer: ${bundle.peerId.slice(0, 8)}`));
|
|
862
885
|
}
|
|
@@ -1388,11 +1411,15 @@ var GitSyncAdapter = class {
|
|
|
1388
1411
|
tombstoneMemory(cortex, id);
|
|
1389
1412
|
continue;
|
|
1390
1413
|
}
|
|
1414
|
+
const { content: sanitizedContent, warnings } = validateEngramContent(m.content);
|
|
1415
|
+
if (warnings.length > 0) {
|
|
1416
|
+
result.errors.push(`Pulled memory from ${m.author} flagged: ${warnings.join(", ")}`);
|
|
1417
|
+
}
|
|
1391
1418
|
const wasInserted = insertMemoryIfNotExists(cortex, {
|
|
1392
1419
|
id,
|
|
1393
1420
|
ts: m.ts,
|
|
1394
1421
|
author: m.author,
|
|
1395
|
-
content:
|
|
1422
|
+
content: sanitizedContent,
|
|
1396
1423
|
source_ids: m.source_ids,
|
|
1397
1424
|
episode_key: m.episode_key
|
|
1398
1425
|
});
|
|
@@ -1517,7 +1544,7 @@ cortexCommand.addCommand(new Command9("setup").description("Configure a sync bac
|
|
|
1517
1544
|
const adapter = getSyncAdapter();
|
|
1518
1545
|
if (adapter) {
|
|
1519
1546
|
try {
|
|
1520
|
-
const { ensureRepoCloned: ensureRepoCloned2 } = await import("./git-
|
|
1547
|
+
const { ensureRepoCloned: ensureRepoCloned2 } = await import("./git-TG6OJFBT.js");
|
|
1521
1548
|
ensureRepoCloned2();
|
|
1522
1549
|
console.log(chalk9.green("\u2713") + " Repo cloned");
|
|
1523
1550
|
} catch (err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "open-think",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Local-first CLI that gives AI agents persistent, curated memory",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"homepage": "https://openthink.dev",
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
|
-
"url": "git+https://github.com/
|
|
20
|
+
"url": "git+https://github.com/OpenThinkAi/think-cli.git"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|
|
23
23
|
"cli",
|