open-think 0.1.2 → 0.1.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.
Files changed (2) hide show
  1. package/dist/index.js +134 -40
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node --no-warnings=ExperimentalWarning
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command18 } from "commander";
4
+ import { Command as Command19 } from "commander";
5
5
 
6
6
  // src/commands/log.ts
7
7
  import { Command } from "commander";
@@ -324,6 +324,70 @@ function searchEngrams(cortexName, query3, limit = 20) {
324
324
  }
325
325
  }
326
326
 
327
+ // src/lib/update-check.ts
328
+ import fs4 from "fs";
329
+ import path4 from "path";
330
+ import { execFile } from "child_process";
331
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
332
+ var PACKAGE_NAME = "open-think";
333
+ function cachePath() {
334
+ return path4.join(getConfigDir(), "version-cache.json");
335
+ }
336
+ function readCache() {
337
+ try {
338
+ const raw = fs4.readFileSync(cachePath(), "utf-8");
339
+ return JSON.parse(raw);
340
+ } catch {
341
+ return null;
342
+ }
343
+ }
344
+ function writeCache(cache) {
345
+ const dir = getConfigDir();
346
+ fs4.mkdirSync(dir, { recursive: true });
347
+ fs4.writeFileSync(cachePath(), JSON.stringify(cache), "utf-8");
348
+ }
349
+ function getInstalledVersion() {
350
+ try {
351
+ const pkgPath = path4.join(import.meta.dirname, "..", "package.json");
352
+ const raw = fs4.readFileSync(pkgPath, "utf-8");
353
+ return JSON.parse(raw).version ?? null;
354
+ } catch {
355
+ return null;
356
+ }
357
+ }
358
+ function isNewer(latest, current) {
359
+ const l = latest.split(".").map(Number);
360
+ const c = current.split(".").map(Number);
361
+ for (let i = 0; i < 3; i++) {
362
+ if ((l[i] ?? 0) > (c[i] ?? 0)) return true;
363
+ if ((l[i] ?? 0) < (c[i] ?? 0)) return false;
364
+ }
365
+ return false;
366
+ }
367
+ function checkForUpdate() {
368
+ const installed = getInstalledVersion();
369
+ if (!installed) return null;
370
+ const cache = readCache();
371
+ const now = Date.now();
372
+ if (cache && now - cache.checkedAt < CHECK_INTERVAL_MS) {
373
+ if (isNewer(cache.latest, installed)) {
374
+ return `open-think ${cache.latest} available (you have ${installed}). Run: npm update -g open-think`;
375
+ }
376
+ return null;
377
+ }
378
+ execFile("npm", ["view", PACKAGE_NAME, "version"], { timeout: 5e3 }, (err, stdout) => {
379
+ if (err) return;
380
+ const latest = stdout.trim();
381
+ if (latest) {
382
+ writeCache({ latest, checkedAt: Date.now() });
383
+ }
384
+ });
385
+ if (cache && isNewer(cache.latest, installed)) {
386
+ return `open-think ${cache.latest} available (you have ${installed}). Run: npm update -g open-think`;
387
+ }
388
+ return null;
389
+ }
390
+
327
391
  // src/commands/log.ts
328
392
  var logCommand = new Command("log").description("Log a note or entry").argument("<message>", "The message to log").option("-s, --source <source>", "Source of the entry", "manual").option("-c, --category <category>", "Category: note, sync, meeting, decision, idea", "note").option("-t, --tags <tags>", "Comma-separated tags").option("--silent", "Suppress output").action((message, opts) => {
329
393
  const tags = opts.tags ? opts.tags.split(",").map((t) => t.trim()) : void 0;
@@ -391,6 +455,12 @@ var syncCommand = new Command("sync").description("Log a sync/work-log entry (sh
391
455
  }
392
456
  closeDb();
393
457
  }
458
+ if (!opts.silent) {
459
+ const updateMsg = checkForUpdate();
460
+ if (updateMsg) {
461
+ console.log(chalk.yellow(` \u2139 ${updateMsg}`));
462
+ }
463
+ }
394
464
  });
395
465
 
396
466
  // src/commands/list.ts
@@ -574,7 +644,7 @@ var deleteCommand = new Command4("delete").description("Soft-delete entries (tom
574
644
 
575
645
  // src/commands/export.ts
576
646
  import { Command as Command5 } from "commander";
577
- import fs4 from "fs";
647
+ import fs5 from "fs";
578
648
  import chalk5 from "chalk";
579
649
  var exportCommand = new Command5("export").description("Export entries as a sync bundle (file-based sync)").option("-o, --output <file>", "Write to file instead of stdout").option("--since <date>", "Export entries since date (ISO or YYYY-MM-DD)").option("-n, --limit <n>", "Max entries to export (default: all)").action((opts) => {
580
650
  const config = getConfig();
@@ -598,7 +668,7 @@ var exportCommand = new Command5("export").description("Export entries as a sync
598
668
  };
599
669
  const json = JSON.stringify(bundle, null, 2);
600
670
  if (opts.output) {
601
- fs4.writeFileSync(opts.output, json, "utf-8");
671
+ fs5.writeFileSync(opts.output, json, "utf-8");
602
672
  console.log(chalk5.green("\u2713") + ` Exported ${entries.length} entries to ${opts.output}`);
603
673
  if (opts.since) {
604
674
  console.log(chalk5.dim(` since: ${opts.since}`));
@@ -611,36 +681,36 @@ var exportCommand = new Command5("export").description("Export entries as a sync
611
681
 
612
682
  // src/commands/import.ts
613
683
  import { Command as Command6 } from "commander";
614
- import fs6 from "fs";
684
+ import fs7 from "fs";
615
685
  import chalk6 from "chalk";
616
686
 
617
687
  // src/lib/audit.ts
618
- import fs5 from "fs";
619
- import path4 from "path";
688
+ import fs6 from "fs";
689
+ import path5 from "path";
620
690
  function auditLogPath() {
621
- return path4.join(getDataDir(), "sync-audit.log");
691
+ return path5.join(getDataDir(), "sync-audit.log");
622
692
  }
623
693
  function logAudit(entry) {
624
694
  const line = JSON.stringify(entry) + "\n";
625
- fs5.appendFileSync(auditLogPath(), line, "utf-8");
695
+ fs6.appendFileSync(auditLogPath(), line, "utf-8");
626
696
  }
627
697
  function readAuditLog() {
628
698
  const logPath = auditLogPath();
629
- if (!fs5.existsSync(logPath)) return [];
630
- const lines = fs5.readFileSync(logPath, "utf-8").trim().split("\n").filter(Boolean);
699
+ if (!fs6.existsSync(logPath)) return [];
700
+ const lines = fs6.readFileSync(logPath, "utf-8").trim().split("\n").filter(Boolean);
631
701
  return lines.map((line) => JSON.parse(line));
632
702
  }
633
703
 
634
704
  // src/commands/import.ts
635
705
  var importCommand = new Command6("import").description("Import a sync bundle from another device").argument("<file>", "Path to the sync bundle JSON file").action((file) => {
636
- if (!fs6.existsSync(file)) {
706
+ if (!fs7.existsSync(file)) {
637
707
  console.error(chalk6.red(`File not found: ${file}`));
638
708
  closeDb();
639
709
  process.exit(1);
640
710
  }
641
711
  let bundle;
642
712
  try {
643
- const raw = fs6.readFileSync(file, "utf-8");
713
+ const raw = fs7.readFileSync(file, "utf-8");
644
714
  bundle = JSON.parse(raw);
645
715
  } catch {
646
716
  console.error(chalk6.red("Failed to parse sync bundle \u2014 is this a valid JSON file?"));
@@ -714,8 +784,8 @@ var importCommand = new Command6("import").description("Import a sync bundle fro
714
784
 
715
785
  // src/commands/init.ts
716
786
  import { Command as Command7 } from "commander";
717
- import fs7 from "fs";
718
- import path5 from "path";
787
+ import fs8 from "fs";
788
+ import path6 from "path";
719
789
  import readline from "readline";
720
790
  import chalk7 from "chalk";
721
791
  var CLAUDE_MD_SECTION = `# Work Logging
@@ -751,7 +821,7 @@ var initCommand = new Command7("init").description("Set up Claude Code integrati
751
821
  const defaultDir = home;
752
822
  let targetDir;
753
823
  if (opts.dir) {
754
- targetDir = path5.resolve(opts.dir);
824
+ targetDir = path6.resolve(opts.dir);
755
825
  } else if (opts.yes) {
756
826
  targetDir = defaultDir;
757
827
  } else {
@@ -760,25 +830,25 @@ var initCommand = new Command7("init").description("Set up Claude Code integrati
760
830
  defaultDir
761
831
  );
762
832
  targetDir = targetDir.replace(/^~/, home);
763
- targetDir = path5.resolve(targetDir);
833
+ targetDir = path6.resolve(targetDir);
764
834
  }
765
- if (!fs7.existsSync(targetDir)) {
835
+ if (!fs8.existsSync(targetDir)) {
766
836
  console.error(chalk7.red(`Directory does not exist: ${targetDir}`));
767
837
  process.exit(1);
768
838
  }
769
- const filePath = path5.join(targetDir, "CLAUDE.md");
770
- const exists = fs7.existsSync(filePath);
839
+ const filePath = path6.join(targetDir, "CLAUDE.md");
840
+ const exists = fs8.existsSync(filePath);
771
841
  if (exists) {
772
- const existing = fs7.readFileSync(filePath, "utf-8");
842
+ const existing = fs8.readFileSync(filePath, "utf-8");
773
843
  if (existing.includes("think sync")) {
774
844
  console.log(chalk7.dim("CLAUDE.md already contains think sync instructions. Nothing to do."));
775
845
  return;
776
846
  }
777
847
  const separator = existing.endsWith("\n") ? "\n" : "\n\n";
778
- fs7.writeFileSync(filePath, existing + separator + CLAUDE_MD_SECTION, "utf-8");
848
+ fs8.writeFileSync(filePath, existing + separator + CLAUDE_MD_SECTION, "utf-8");
779
849
  console.log(chalk7.green("\u2713") + ` Appended work logging instructions to ${filePath}`);
780
850
  } else {
781
- fs7.writeFileSync(filePath, CLAUDE_MD_SECTION, "utf-8");
851
+ fs8.writeFileSync(filePath, CLAUDE_MD_SECTION, "utf-8");
782
852
  console.log(chalk7.green("\u2713") + ` Created ${filePath} with work logging instructions`);
783
853
  }
784
854
  console.log(chalk7.dim(" Claude Code sessions under this directory will now auto-log with think sync."));
@@ -829,8 +899,8 @@ import readline2 from "readline";
829
899
 
830
900
  // src/lib/git.ts
831
901
  import { execFileSync } from "child_process";
832
- import fs8 from "fs";
833
- import path6 from "path";
902
+ import fs9 from "fs";
903
+ import path7 from "path";
834
904
  function runGit(args, cwd) {
835
905
  const repoPath = cwd ?? getRepoPath();
836
906
  return execFileSync("git", args, {
@@ -845,14 +915,14 @@ function ensureRepoCloned() {
845
915
  throw new Error("No cortex repo configured. Run: think cortex setup");
846
916
  }
847
917
  const repoPath = getRepoPath();
848
- if (fs8.existsSync(path6.join(repoPath, ".git"))) {
918
+ if (fs9.existsSync(path7.join(repoPath, ".git"))) {
849
919
  const remote = runGit(["remote", "get-url", "origin"], repoPath);
850
920
  if (remote !== config.cortex.repo) {
851
921
  throw new Error(`Repo at ${repoPath} points to ${remote}, expected ${config.cortex.repo}`);
852
922
  }
853
923
  return;
854
924
  }
855
- fs8.mkdirSync(repoPath, { recursive: true });
925
+ fs9.mkdirSync(repoPath, { recursive: true });
856
926
  execFileSync("git", ["clone", "--no-checkout", config.cortex.repo, repoPath], {
857
927
  encoding: "utf-8",
858
928
  stdio: ["pipe", "pipe", "pipe"]
@@ -873,7 +943,7 @@ function createOrphanBranch(branchName) {
873
943
  } catch {
874
944
  }
875
945
  const repoPath = getRepoPath();
876
- fs8.writeFileSync(path6.join(repoPath, "memories.jsonl"), "", "utf-8");
946
+ fs9.writeFileSync(path7.join(repoPath, "memories.jsonl"), "", "utf-8");
877
947
  runGit(["add", "memories.jsonl"]);
878
948
  runGit(["commit", "-m", `init: create cortex ${branchName}`]);
879
949
  runGit(["push", "--set-upstream", "origin", branchName]);
@@ -890,7 +960,7 @@ function readFileFromBranch(branchName, filePath) {
890
960
  }
891
961
  function appendAndCommit(branchName, newLines, commitMessage, maxRetries = 3) {
892
962
  const repoPath = getRepoPath();
893
- const memoriesPath = path6.join(repoPath, "memories.jsonl");
963
+ const memoriesPath = path7.join(repoPath, "memories.jsonl");
894
964
  try {
895
965
  runGit(["switch", branchName]);
896
966
  } catch {
@@ -909,7 +979,7 @@ function appendAndCommit(branchName, newLines, commitMessage, maxRetries = 3) {
909
979
  }
910
980
  }
911
981
  const content = newLines.join("\n") + "\n";
912
- fs8.appendFileSync(memoriesPath, content, "utf-8");
982
+ fs9.appendFileSync(memoriesPath, content, "utf-8");
913
983
  runGit(["add", "memories.jsonl"]);
914
984
  runGit(["commit", "-m", commitMessage]);
915
985
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
@@ -1039,7 +1109,7 @@ import readline3 from "readline";
1039
1109
  import chalk10 from "chalk";
1040
1110
 
1041
1111
  // src/lib/curator.ts
1042
- import fs9 from "fs";
1112
+ import fs10 from "fs";
1043
1113
  import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
1044
1114
  var BASE_CURATION_PROMPT = `You are a memory curator. You evaluate recent work events and decide which ones are significant enough to become shared team memory.
1045
1115
 
@@ -1090,8 +1160,8 @@ Rules:
1090
1160
  - Only add an entry if there is genuinely new information`;
1091
1161
  function readCuratorMd() {
1092
1162
  const mdPath = getCuratorMdPath();
1093
- if (fs9.existsSync(mdPath)) {
1094
- return fs9.readFileSync(mdPath, "utf-8").trim();
1163
+ if (fs10.existsSync(mdPath)) {
1164
+ return fs10.readFileSync(mdPath, "utf-8").trim();
1095
1165
  }
1096
1166
  return null;
1097
1167
  }
@@ -1339,7 +1409,7 @@ var monitorCommand = new Command11("monitor").description("Show what got promote
1339
1409
  // src/commands/recall.ts
1340
1410
  import { Command as Command12 } from "commander";
1341
1411
  import chalk12 from "chalk";
1342
- import fs10 from "fs";
1412
+ import fs11 from "fs";
1343
1413
  var recallCommand = new Command12("recall").argument("<query>", "What to recall").description("Search memories from the cortex branch + local engrams").option("--days <n>", "Days of memories to include", "14").action(async (query3, opts) => {
1344
1414
  const config = getConfig();
1345
1415
  const cortex = config.cortex?.active;
@@ -1356,7 +1426,7 @@ var recallCommand = new Command12("recall").argument("<query>", "What to recall"
1356
1426
  const recentMemories = allMemories.filter((m) => m.ts >= cutoff);
1357
1427
  const matchingEngrams = searchEngrams(cortex, query3);
1358
1428
  const ltPath = getLongtermPath(cortex);
1359
- const longterm = fs10.existsSync(ltPath) ? fs10.readFileSync(ltPath, "utf-8").trim() : null;
1429
+ const longterm = fs11.existsSync(ltPath) ? fs11.readFileSync(ltPath, "utf-8").trim() : null;
1360
1430
  if (recentMemories.length > 0) {
1361
1431
  console.log(chalk12.cyan(`Team memories (last ${days} days):`));
1362
1432
  for (const m of recentMemories) {
@@ -1425,7 +1495,7 @@ ${memories.length} memories`));
1425
1495
  // src/commands/curator-cmd.ts
1426
1496
  import { Command as Command14 } from "commander";
1427
1497
  import { spawnSync } from "child_process";
1428
- import fs11 from "fs";
1498
+ import fs12 from "fs";
1429
1499
  import chalk14 from "chalk";
1430
1500
  var CURATOR_TEMPLATE = `# Curator Guidance
1431
1501
 
@@ -1444,8 +1514,8 @@ var curatorCommand = new Command14("curator").description("Manage personal curat
1444
1514
  curatorCommand.addCommand(new Command14("edit").description("Edit your curator guidance in $EDITOR").action(() => {
1445
1515
  ensureThinkDirs();
1446
1516
  const mdPath = getCuratorMdPath();
1447
- if (!fs11.existsSync(mdPath)) {
1448
- fs11.writeFileSync(mdPath, CURATOR_TEMPLATE, "utf-8");
1517
+ if (!fs12.existsSync(mdPath)) {
1518
+ fs12.writeFileSync(mdPath, CURATOR_TEMPLATE, "utf-8");
1449
1519
  }
1450
1520
  const editor = process.env.EDITOR || "vi";
1451
1521
  const result = spawnSync(editor, [mdPath], { stdio: "inherit" });
@@ -1457,8 +1527,8 @@ curatorCommand.addCommand(new Command14("edit").description("Edit your curator g
1457
1527
  }));
1458
1528
  curatorCommand.addCommand(new Command14("show").description("Print your current curator guidance").action(() => {
1459
1529
  const mdPath = getCuratorMdPath();
1460
- if (fs11.existsSync(mdPath)) {
1461
- console.log(fs11.readFileSync(mdPath, "utf-8"));
1530
+ if (fs12.existsSync(mdPath)) {
1531
+ console.log(fs12.readFileSync(mdPath, "utf-8"));
1462
1532
  } else {
1463
1533
  console.log(chalk14.dim("No curator guidance configured. Run: think curator edit"));
1464
1534
  }
@@ -1553,8 +1623,31 @@ configCommand.addCommand(new Command17("set").argument("<key>", "Config key (e.g
1553
1623
  console.log(chalk17.green("\u2713") + ` ${key} = ${JSON.stringify(parsed)}`);
1554
1624
  }));
1555
1625
 
1626
+ // src/commands/update.ts
1627
+ import { Command as Command18 } from "commander";
1628
+ import { execFileSync as execFileSync2 } from "child_process";
1629
+ import chalk18 from "chalk";
1630
+ var updateCommand = new Command18("update").description("Update think to the latest version").action(() => {
1631
+ console.log(chalk18.cyan("Checking for updates..."));
1632
+ try {
1633
+ const result = execFileSync2("npm", ["install", "-g", "open-think@latest"], {
1634
+ encoding: "utf-8",
1635
+ stdio: ["pipe", "pipe", "pipe"]
1636
+ });
1637
+ const match = result.match(/open-think@(\S+)/);
1638
+ const version = match ? match[1] : "latest";
1639
+ console.log(chalk18.green("\u2713") + ` Updated to open-think@${version}`);
1640
+ } catch (err) {
1641
+ const message = err instanceof Error ? err.message : String(err);
1642
+ console.error(chalk18.red("Update failed. Try manually: npm install -g open-think@latest"));
1643
+ if (message.includes("EACCES")) {
1644
+ console.error(chalk18.dim(" You may need to run with sudo or fix npm permissions."));
1645
+ }
1646
+ }
1647
+ });
1648
+
1556
1649
  // src/index.ts
1557
- var program = new Command18();
1650
+ var program = new Command19();
1558
1651
  program.name("think").description("Local-first CLI tool for capturing notes, work logs, and ideas").version("0.1.0").option("-C, --cortex <name>", "Use a specific cortex for this command");
1559
1652
  program.addCommand(logCommand);
1560
1653
  program.addCommand(syncCommand);
@@ -1575,4 +1668,5 @@ program.addCommand(pullCommand);
1575
1668
  program.addCommand(pauseCommand);
1576
1669
  program.addCommand(resumeCommand);
1577
1670
  program.addCommand(configCommand);
1671
+ program.addCommand(updateCommand);
1578
1672
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-think",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "description": "Local-first CLI that gives AI agents persistent, curated memory",
6
6
  "bin": {