brainincorp 0.0.1 → 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.
package/README.md CHANGED
@@ -1,3 +1,17 @@
1
1
  # brainincorp
2
2
 
3
- reserved. coming soon. https://brainincorp.com
3
+ A brain that lives on your machine. Read and written by your LLM.
4
+
5
+ ```bash
6
+ npx brainincorp init
7
+ ```
8
+
9
+ Walks you through:
10
+
11
+ - installing `uv` if missing
12
+ - choosing where your brain lives (default `~/.braincorp/brain`)
13
+ - cloning the `brain-mcp` server to `~/.braincorp/brain-mcp`
14
+ - wiring it into Claude Code, Codex CLI, and Cursor (whichever you have)
15
+ - seeding the brain with a starter `docs/` tree
16
+
17
+ Open a fresh session in your client and ask it to list your brain.
@@ -0,0 +1,242 @@
1
+ #!/usr/bin/env node
2
+ import { intro, outro, text, select, multiselect, confirm, spinner, note, cancel, isCancel } from "@clack/prompts";
3
+ import { execSync, spawnSync } from "node:child_process";
4
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from "node:fs";
5
+ import { homedir } from "node:os";
6
+ import { join } from "node:path";
7
+
8
+ const cmd = process.argv[2] ?? "init";
9
+ if (cmd !== "init") {
10
+ console.error(`unknown command: ${cmd}. usage: brainincorp init`);
11
+ process.exit(1);
12
+ }
13
+
14
+ const HOME = homedir();
15
+ const BRAINCORP_DIR = join(HOME, ".braincorp");
16
+ const MCP_DIR = join(BRAINCORP_DIR, "brain-mcp");
17
+ const MCP_REPO = "https://github.com/brainincorp/brain-mcp.git";
18
+
19
+ function has(bin) {
20
+ return spawnSync("which", [bin], { stdio: "ignore" }).status === 0;
21
+ }
22
+ function run(cmdline, opts = {}) {
23
+ return execSync(cmdline, { stdio: "inherit", ...opts });
24
+ }
25
+ function runQuiet(cmdline, opts = {}) {
26
+ return execSync(cmdline, { stdio: ["ignore", "pipe", "pipe"], ...opts }).toString();
27
+ }
28
+ function bail(reason) {
29
+ cancel(reason);
30
+ process.exit(1);
31
+ }
32
+ function isEmpty(dir) {
33
+ try { return readdirSync(dir).length === 0; } catch { return true; }
34
+ }
35
+
36
+ intro("brainincorp — install a brain on this machine");
37
+
38
+ // ─── PREREQS ──────────────────────────────────────────────────────────────
39
+ if (!has("git")) bail("git is required. install it and re-run.");
40
+
41
+ if (!has("uv")) {
42
+ const ok = await confirm({
43
+ message: "uv (python runner the MCP needs) is not installed. install it now?",
44
+ initialValue: true,
45
+ });
46
+ if (isCancel(ok) || !ok) bail("uv is required: curl -LsSf https://astral.sh/uv/install.sh | sh");
47
+ const s = spinner();
48
+ s.start("installing uv");
49
+ run("curl -LsSf https://astral.sh/uv/install.sh | sh", { stdio: "ignore" });
50
+ s.stop("uv installed");
51
+ process.env.PATH = `${HOME}/.local/bin:${process.env.PATH}`;
52
+ if (!has("uv")) bail("uv installed but not on PATH. open a new shell and re-run.");
53
+ }
54
+
55
+ // ─── DECISIONS (collect all, execute later) ───────────────────────────────
56
+
57
+ // 1. new or existing brain?
58
+ const source = await select({
59
+ message: "your brain — new or existing GitHub repo?",
60
+ options: [
61
+ { value: "new", label: "new — create a fresh brain on GitHub" },
62
+ { value: "existing", label: "existing — clone one you already have" },
63
+ { value: "local", label: "local-only — no GitHub remote (writes will be disabled)" },
64
+ ],
65
+ initialValue: "new",
66
+ });
67
+ if (isCancel(source)) bail("cancelled.");
68
+
69
+ let remoteUrl = null;
70
+ let newRepoName = null;
71
+ let newRepoVisibility = null;
72
+
73
+ if (source === "existing") {
74
+ const urlRaw = await text({
75
+ message: "remote URL (ssh or https)",
76
+ placeholder: "git@github.com:you/your-brain.git",
77
+ validate: (v) => (v.trim() ? undefined : "required"),
78
+ });
79
+ if (isCancel(urlRaw)) bail("cancelled.");
80
+ remoteUrl = urlRaw.trim();
81
+ } else if (source === "new") {
82
+ if (!has("gh")) bail("`gh` CLI required to create a new repo. install it (brew install gh) or pick 'existing'.");
83
+ const ghUser = runQuiet(`gh api user --jq .login`).trim();
84
+ const nameRaw = await text({
85
+ message: "new repo name (owner/repo)",
86
+ initialValue: `${ghUser}/brain`,
87
+ validate: (v) => (/^[\w.-]+\/[\w.-]+$/.test(v.trim()) ? undefined : "use owner/repo format"),
88
+ });
89
+ if (isCancel(nameRaw)) bail("cancelled.");
90
+ newRepoName = nameRaw.trim();
91
+ const vis = await select({
92
+ message: "visibility",
93
+ options: [
94
+ { value: "--private", label: "private" },
95
+ { value: "--public", label: "public" },
96
+ ],
97
+ initialValue: "--private",
98
+ });
99
+ if (isCancel(vis)) bail("cancelled.");
100
+ newRepoVisibility = vis;
101
+ }
102
+
103
+ // 2. where on disk?
104
+ const defaultPath = source === "existing" && remoteUrl
105
+ ? join(HOME, ".braincorp", remoteUrl.split("/").pop().replace(/\.git$/, ""))
106
+ : join(HOME, ".braincorp", "brain");
107
+
108
+ const brainPathRaw = await text({
109
+ message: "where on disk should it live?",
110
+ initialValue: defaultPath,
111
+ validate: (v) => (v.startsWith("/") || v.startsWith("~") ? undefined : "must be an absolute path"),
112
+ });
113
+ if (isCancel(brainPathRaw)) bail("cancelled.");
114
+ const brainPath = brainPathRaw.replace(/^~/, HOME);
115
+
116
+ if (existsSync(brainPath) && !isEmpty(brainPath)) {
117
+ bail(`${brainPath} already exists and isn't empty. pick a different path or remove it.`);
118
+ }
119
+
120
+ // 3. which clients?
121
+ const detected = {
122
+ claude: has("claude"),
123
+ codex: has("codex"),
124
+ cursor: existsSync(join(HOME, ".cursor")) || has("cursor"),
125
+ };
126
+ const choices = [
127
+ { value: "claude", label: "Claude Code", hint: detected.claude ? "detected" : "not detected" },
128
+ { value: "codex", label: "Codex CLI", hint: detected.codex ? "detected" : "not detected" },
129
+ { value: "cursor", label: "Cursor", hint: detected.cursor ? "detected" : "not detected" },
130
+ ];
131
+ const initial = Object.entries(detected).filter(([, v]) => v).map(([k]) => k);
132
+ if (initial.length === 0) bail("no MCP clients found (claude / codex / cursor).");
133
+
134
+ const targets = await multiselect({
135
+ message: "wire the brain into which clients?",
136
+ options: choices,
137
+ initialValues: initial,
138
+ required: true,
139
+ });
140
+ if (isCancel(targets)) bail("cancelled.");
141
+
142
+ // ─── EXECUTE ──────────────────────────────────────────────────────────────
143
+
144
+ // install / update the MCP server
145
+ const s1 = spinner();
146
+ mkdirSync(BRAINCORP_DIR, { recursive: true });
147
+ if (existsSync(MCP_DIR)) {
148
+ s1.start("updating brain-mcp");
149
+ run(`git -C "${MCP_DIR}" pull --ff-only`, { stdio: "ignore" });
150
+ s1.stop("brain-mcp up to date");
151
+ } else {
152
+ s1.start("cloning brain-mcp");
153
+ run(`git clone --depth 1 ${MCP_REPO} "${MCP_DIR}"`, { stdio: "ignore" });
154
+ s1.stop("brain-mcp cloned");
155
+ }
156
+
157
+ const s2 = spinner();
158
+ s2.start("installing python deps (uv sync)");
159
+ run(`uv --directory "${MCP_DIR}" sync`, { stdio: "ignore" });
160
+ s2.stop("python deps ready");
161
+
162
+ // materialise the brain repo
163
+ const s3 = spinner();
164
+ if (source === "existing") {
165
+ s3.start(`cloning ${remoteUrl}`);
166
+ run(`git clone "${remoteUrl}" "${brainPath}"`, { stdio: "ignore" });
167
+ s3.stop(`brain cloned to ${brainPath}`);
168
+ } else if (source === "new") {
169
+ s3.start(`creating ${newRepoName} on GitHub`);
170
+ mkdirSync(join(brainPath, "docs"), { recursive: true });
171
+ writeFileSync(
172
+ join(brainPath, "docs", "start-here.md"),
173
+ `# start here\n\nthis is your brain. an LLM reads and writes it through the brainincorp MCP.\n\n## what lives where\n\n- \`docs/decisions/\` — choices you've made and why\n- \`docs/projects/\` — what you're working on\n- \`docs/knowledge/\` — durable facts, references, learnings\n- \`docs/contacts/\` — people and orgs\n\nadd files freely. the LLM will find them.\n`
174
+ );
175
+ writeFileSync(join(brainPath, ".gitignore"), ".DS_Store\n");
176
+ run(`git -C "${brainPath}" init -q -b main`, { stdio: "ignore" });
177
+ run(`git -C "${brainPath}" add -A`, { stdio: "ignore" });
178
+ run(`git -C "${brainPath}" commit -q -m "init: brain seeded by brainincorp"`, { stdio: "ignore" });
179
+ run(`gh repo create ${newRepoName} ${newRepoVisibility} --source="${brainPath}" --remote=origin --push`, { stdio: "ignore" });
180
+ s3.stop(`pushed to https://github.com/${newRepoName}`);
181
+ } else {
182
+ // local-only
183
+ s3.start("seeding local brain");
184
+ mkdirSync(join(brainPath, "docs"), { recursive: true });
185
+ writeFileSync(
186
+ join(brainPath, "docs", "start-here.md"),
187
+ `# start here\n\nthis is your brain. an LLM reads and writes it through the brainincorp MCP.\n`
188
+ );
189
+ writeFileSync(join(brainPath, ".gitignore"), ".DS_Store\n");
190
+ run(`git -C "${brainPath}" init -q -b main`, { stdio: "ignore" });
191
+ run(`git -C "${brainPath}" add -A`, { stdio: "ignore" });
192
+ run(`git -C "${brainPath}" commit -q -m "init: brain seeded by brainincorp"`, { stdio: "ignore" });
193
+ s3.stop(`seeded ${brainPath} (no remote)`);
194
+ }
195
+
196
+ // wire clients
197
+ const launcher = join(MCP_DIR, "brain-mcp.sh");
198
+ spawnSync("chmod", ["+x", launcher]);
199
+
200
+ const installers = {
201
+ claude: () => {
202
+ try { runQuiet(`claude mcp remove brain --scope user 2>/dev/null`); } catch {}
203
+ run(`claude mcp add brain --scope user -e BRAIN_REPO="${brainPath}" -- "${launcher}"`, { stdio: "ignore" });
204
+ },
205
+ codex: () => {
206
+ try { runQuiet(`codex mcp remove brain 2>/dev/null`); } catch {}
207
+ run(`codex mcp add brain --env BRAIN_REPO="${brainPath}" -- "${launcher}"`, { stdio: "ignore" });
208
+ },
209
+ cursor: () => {
210
+ const cfgPath = join(HOME, ".cursor", "mcp.json");
211
+ mkdirSync(join(HOME, ".cursor"), { recursive: true });
212
+ const cfg = existsSync(cfgPath) ? JSON.parse(readFileSync(cfgPath, "utf8")) : {};
213
+ cfg.mcpServers = cfg.mcpServers ?? {};
214
+ cfg.mcpServers.brain = { command: launcher, env: { BRAIN_REPO: brainPath } };
215
+ writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + "\n");
216
+ },
217
+ };
218
+ for (const t of targets) {
219
+ const s = spinner();
220
+ s.start(`wiring ${t}`);
221
+ try {
222
+ installers[t]();
223
+ s.stop(`${t} wired`);
224
+ } catch (err) {
225
+ s.stop(`${t} failed: ${err.message}`);
226
+ }
227
+ }
228
+
229
+ // Tell Claude (the most common client) to prefer brain_* over Bash for the brain.
230
+ if (targets.includes("claude")) {
231
+ const claudeMdPath = join(HOME, ".claude", "CLAUDE.md");
232
+ const marker = "<!-- brainincorp:auto -->";
233
+ const block = `\n${marker}\n## Brain\n\nA \`brain\` MCP server is installed (BRAIN_REPO=${brainPath}). When the user mentions "the brain", their notes, or asks to recall/save knowledge, prefer the brain tools — \`brain_search\`, \`brain_read\`, \`brain_list\`, \`brain_write\` — over Bash/Read/Write. The brain's vector index, retrieval log, and write validation only fire through these tools.\n<!-- /brainincorp:auto -->\n`;
234
+ mkdirSync(join(HOME, ".claude"), { recursive: true });
235
+ const cur = existsSync(claudeMdPath) ? readFileSync(claudeMdPath, "utf8") : "";
236
+ if (!cur.includes(marker)) {
237
+ writeFileSync(claudeMdPath, cur + block);
238
+ note("added brain hint to ~/.claude/CLAUDE.md");
239
+ }
240
+ }
241
+
242
+ outro(`done. open a fresh ${targets[0]} session and ask: "list my brain"`);
package/package.json CHANGED
@@ -1,9 +1,35 @@
1
1
  {
2
2
  "name": "brainincorp",
3
- "version": "0.0.1",
4
- "description": "reserved. your brain. lives in your repo.",
5
- "bin": { "brainincorp": "./cli.js" },
3
+ "version": "0.2.0",
4
+ "description": "Set up a brain on your machine — a policy-aware MCP server wired into Claude Code, Codex, and Cursor. Your brain lives in your git repo.",
5
+ "type": "module",
6
+ "bin": {
7
+ "brainincorp": "./bin/brainincorp.mjs"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "README.md"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18"
15
+ },
6
16
  "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/brainincorp/cli.git"
20
+ },
7
21
  "homepage": "https://brainincorp.com",
8
- "author": "brainincorp"
22
+ "bugs": "https://github.com/brainincorp/cli/issues",
23
+ "keywords": [
24
+ "mcp",
25
+ "brain",
26
+ "claude-code",
27
+ "codex",
28
+ "cursor",
29
+ "knowledge-base",
30
+ "agent"
31
+ ],
32
+ "dependencies": {
33
+ "@clack/prompts": "^0.10.0"
34
+ }
9
35
  }
package/cli.js DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- console.log("brainincorp — coming soon. https://brainincorp.com");