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
- return execFileSync("git", args, {
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) {
@@ -11,7 +11,7 @@ import {
11
11
  listRemoteBranches,
12
12
  migrateToBuckets,
13
13
  readFileFromBranch
14
- } from "./chunk-ICK2JU5B.js";
14
+ } from "./chunk-ZKUJ5M2W.js";
15
15
  import "./chunk-DCTG6IK4.js";
16
16
  export {
17
17
  appendAndCommit,
package/dist/index.js CHANGED
@@ -28,7 +28,7 @@ import {
28
28
  migrateToBuckets,
29
29
  readFileFromBranch,
30
30
  saveConfig
31
- } from "./chunk-ICK2JU5B.js";
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(/<\/data/gi, "&lt;/data");
344
+ const escaped = content.replace(/<\/?data/gi, (match) => `&lt;${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
- entry.content,
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} already existed)` : ""));
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: m.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-BRGF6DFD.js");
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.2.5",
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/MicroMediaSites/think-cli.git"
20
+ "url": "git+https://github.com/OpenThinkAi/think-cli.git"
21
21
  },
22
22
  "keywords": [
23
23
  "cli",