open-think 0.1.14 → 0.2.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.
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env node --no-warnings=ExperimentalWarning
2
+
3
+ // src/lib/git.ts
4
+ import { execFileSync } from "child_process";
5
+ import fs3 from "fs";
6
+ import path3 from "path";
7
+
8
+ // src/lib/paths.ts
9
+ import path from "path";
10
+ import fs from "fs";
11
+ function getHome() {
12
+ const home = process.env.HOME;
13
+ if (!home) {
14
+ throw new Error("HOME environment variable is not set");
15
+ }
16
+ return home;
17
+ }
18
+ function sanitizeName(name) {
19
+ if (!name || /[\/\\\.]{2}/.test(name) || /[^a-zA-Z0-9_-]/.test(name)) {
20
+ throw new Error(`Invalid cortex name: "${name}". Use only alphanumeric characters, hyphens, and underscores.`);
21
+ }
22
+ return name;
23
+ }
24
+ function getThinkHome() {
25
+ const thinkHome = process.env.THINK_HOME;
26
+ if (thinkHome === void 0 || thinkHome === "") return null;
27
+ return thinkHome;
28
+ }
29
+ function getThinkDir() {
30
+ return getThinkHome() ?? path.join(getHome(), ".think");
31
+ }
32
+ function getThinkConfigDir() {
33
+ const thinkHome = getThinkHome();
34
+ if (thinkHome) return path.join(thinkHome, "config");
35
+ const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(getHome(), ".config");
36
+ return path.join(xdgConfig, "think");
37
+ }
38
+ function getThinkDataDir() {
39
+ const thinkHome = getThinkHome();
40
+ if (thinkHome) return path.join(thinkHome, "data");
41
+ const xdgData = process.env.XDG_DATA_HOME || path.join(getHome(), ".local", "share");
42
+ return path.join(xdgData, "think");
43
+ }
44
+ function getEngramsDir() {
45
+ return path.join(getThinkDir(), "engrams");
46
+ }
47
+ function getEngramDbPath(cortexName) {
48
+ return path.join(getEngramsDir(), `${sanitizeName(cortexName)}.db`);
49
+ }
50
+ function getRepoPath() {
51
+ return path.join(getThinkDir(), "repo");
52
+ }
53
+ function getLongtermDir() {
54
+ return path.join(getThinkDir(), "longterm");
55
+ }
56
+ function getLongtermPath(cortexName) {
57
+ return path.join(getLongtermDir(), `${sanitizeName(cortexName)}.md`);
58
+ }
59
+ function getCuratorMdPath() {
60
+ return path.join(getThinkDir(), "curator.md");
61
+ }
62
+ function ensureThinkDirs() {
63
+ fs.mkdirSync(getEngramsDir(), { recursive: true });
64
+ fs.mkdirSync(getLongtermDir(), { recursive: true });
65
+ }
66
+
67
+ // src/lib/config.ts
68
+ import path2 from "path";
69
+ import fs2 from "fs";
70
+ import { v4 as uuidv4 } from "uuid";
71
+ function getConfigDir() {
72
+ return getThinkConfigDir();
73
+ }
74
+ function configPath() {
75
+ return path2.join(getConfigDir(), "config.json");
76
+ }
77
+ function saveConfig(config) {
78
+ const dir = getConfigDir();
79
+ fs2.mkdirSync(dir, { recursive: true });
80
+ fs2.writeFileSync(configPath(), JSON.stringify(config, null, 2) + "\n", "utf-8");
81
+ }
82
+ function getConfig() {
83
+ const fp = configPath();
84
+ if (fs2.existsSync(fp)) {
85
+ const raw = fs2.readFileSync(fp, "utf-8");
86
+ return JSON.parse(raw);
87
+ }
88
+ const config = {
89
+ peerId: uuidv4(),
90
+ syncPort: 47821
91
+ };
92
+ saveConfig(config);
93
+ return config;
94
+ }
95
+
96
+ // src/lib/git.ts
97
+ function runGit(args, cwd) {
98
+ const repoPath = cwd ?? getRepoPath();
99
+ return execFileSync("git", args, {
100
+ cwd: repoPath,
101
+ encoding: "utf-8",
102
+ stdio: ["pipe", "pipe", "pipe"]
103
+ }).trim();
104
+ }
105
+ function ensureRepoCloned() {
106
+ const config = getConfig();
107
+ if (!config.cortex?.repo) {
108
+ throw new Error("No cortex repo configured. Run: think cortex setup");
109
+ }
110
+ const repoPath = getRepoPath();
111
+ if (fs3.existsSync(path3.join(repoPath, ".git"))) {
112
+ const remote = runGit(["remote", "get-url", "origin"], repoPath);
113
+ if (remote !== config.cortex.repo) {
114
+ throw new Error(`Repo at ${repoPath} points to ${remote}, expected ${config.cortex.repo}`);
115
+ }
116
+ return;
117
+ }
118
+ fs3.mkdirSync(repoPath, { recursive: true });
119
+ execFileSync("git", ["clone", "--no-checkout", config.cortex.repo, repoPath], {
120
+ encoding: "utf-8",
121
+ stdio: ["pipe", "pipe", "pipe"]
122
+ });
123
+ }
124
+ function branchExists(branchName) {
125
+ try {
126
+ runGit(["ls-remote", "--exit-code", "--heads", "origin", branchName]);
127
+ return true;
128
+ } catch {
129
+ return false;
130
+ }
131
+ }
132
+ function createOrphanBranch(branchName) {
133
+ runGit(["checkout", "--orphan", branchName]);
134
+ try {
135
+ runGit(["rm", "-rf", "."]);
136
+ } catch {
137
+ }
138
+ const repoPath = getRepoPath();
139
+ fs3.writeFileSync(path3.join(repoPath, "memories.jsonl"), "", "utf-8");
140
+ runGit(["add", "memories.jsonl"]);
141
+ runGit(["commit", "-m", `init: create cortex ${branchName}`]);
142
+ runGit(["push", "--set-upstream", "origin", branchName]);
143
+ }
144
+ function fetchBranch(branchName) {
145
+ runGit(["fetch", "origin", branchName]);
146
+ }
147
+ function readFileFromBranch(branchName, filePath) {
148
+ try {
149
+ return runGit(["show", `origin/${branchName}:${filePath}`]);
150
+ } catch {
151
+ return null;
152
+ }
153
+ }
154
+ function appendAndCommit(branchName, newLines, commitMessage, maxRetries = 3) {
155
+ const repoPath = getRepoPath();
156
+ const memoriesPath = path3.join(repoPath, "memories.jsonl");
157
+ try {
158
+ runGit(["switch", branchName]);
159
+ } catch {
160
+ runGit(["switch", "-c", branchName, `origin/${branchName}`]);
161
+ }
162
+ try {
163
+ runGit(["pull", "--rebase", "origin", branchName]);
164
+ } catch (err) {
165
+ const message = err instanceof Error ? err.message : String(err);
166
+ if (message.includes("CONFLICT") || message.includes("could not apply")) {
167
+ try {
168
+ runGit(["rebase", "--abort"]);
169
+ } catch {
170
+ }
171
+ throw new Error(`Rebase conflict on ${branchName}. This should not happen with append-only files \u2014 check for manual edits to memories.jsonl.`);
172
+ }
173
+ }
174
+ const content = newLines.join("\n") + "\n";
175
+ fs3.appendFileSync(memoriesPath, content, "utf-8");
176
+ runGit(["add", "memories.jsonl"]);
177
+ runGit(["commit", "-m", commitMessage]);
178
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
179
+ try {
180
+ runGit(["push", "origin", branchName]);
181
+ return;
182
+ } catch {
183
+ if (attempt === maxRetries) {
184
+ throw new Error(`Push failed after ${maxRetries} attempts. Run 'think curate' again.`);
185
+ }
186
+ runGit(["pull", "--rebase", "origin", branchName]);
187
+ }
188
+ }
189
+ }
190
+ function getFileLog(branchName, filePath) {
191
+ return runGit(["log", "--oneline", `origin/${branchName}`, "--", filePath]);
192
+ }
193
+ function listRemoteBranches() {
194
+ const output = runGit(["ls-remote", "--heads", "origin"]);
195
+ return output.trim().split("\n").filter(Boolean).map((line) => line.split(" ")[1]?.replace("refs/heads/", "")).filter(Boolean);
196
+ }
197
+
198
+ export {
199
+ getThinkDataDir,
200
+ getEngramsDir,
201
+ getEngramDbPath,
202
+ getLongtermPath,
203
+ getCuratorMdPath,
204
+ ensureThinkDirs,
205
+ getConfigDir,
206
+ saveConfig,
207
+ getConfig,
208
+ ensureRepoCloned,
209
+ branchExists,
210
+ createOrphanBranch,
211
+ fetchBranch,
212
+ readFileFromBranch,
213
+ appendAndCommit,
214
+ getFileLog,
215
+ listRemoteBranches
216
+ };
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node --no-warnings=ExperimentalWarning
2
+ import {
3
+ appendAndCommit,
4
+ branchExists,
5
+ createOrphanBranch,
6
+ ensureRepoCloned,
7
+ fetchBranch,
8
+ getFileLog,
9
+ listRemoteBranches,
10
+ readFileFromBranch
11
+ } from "./chunk-K2FT7ZHJ.js";
12
+ export {
13
+ appendAndCommit,
14
+ branchExists,
15
+ createOrphanBranch,
16
+ ensureRepoCloned,
17
+ fetchBranch,
18
+ getFileLog,
19
+ listRemoteBranches,
20
+ readFileFromBranch
21
+ };