mindlink 1.0.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/dist/cli.js ADDED
@@ -0,0 +1,1811 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { Command as Command14 } from "commander";
5
+ import chalk15 from "chalk";
6
+
7
+ // src/commands/init.ts
8
+ import { Command } from "commander";
9
+ import {
10
+ intro,
11
+ multiselect,
12
+ select,
13
+ spinner,
14
+ note,
15
+ cancel,
16
+ isCancel
17
+ } from "@clack/prompts";
18
+ import chalk2 from "chalk";
19
+ import {
20
+ existsSync,
21
+ mkdirSync,
22
+ readFileSync,
23
+ writeFileSync,
24
+ appendFileSync
25
+ } from "fs";
26
+ import { join as join2, resolve, dirname as dirname2, basename } from "path";
27
+
28
+ // src/utils/paths.ts
29
+ import { fileURLToPath } from "url";
30
+ import { dirname, join } from "path";
31
+ var __filename = fileURLToPath(import.meta.url);
32
+ var __dirname = dirname(__filename);
33
+ var TEMPLATES_DIR = join(__dirname, "templates");
34
+ var BRAIN_TEMPLATES_DIR = join(TEMPLATES_DIR, "brain");
35
+ var AGENT_TEMPLATES_DIR = join(TEMPLATES_DIR, "agents");
36
+ var HOOKS_TEMPLATES_DIR = join(TEMPLATES_DIR, "hooks");
37
+ var BRAIN_DIR = ".brain";
38
+
39
+ // src/utils/banner.ts
40
+ import chalk from "chalk";
41
+ function printBanner() {
42
+ console.log("");
43
+ console.log(chalk.dim(" \u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B"));
44
+ console.log(` ${chalk.cyan("\u25C9")} ${chalk.bold("M I N D L I N K")}`);
45
+ console.log(chalk.dim(" \u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B\u224B"));
46
+ console.log("");
47
+ }
48
+
49
+ // src/utils/agents.ts
50
+ var AGENTS = [
51
+ { value: "claude", label: "Claude Code", hint: "CLAUDE.md", templateFile: "CLAUDE.md", destFile: "CLAUDE.md", selected: true },
52
+ { value: "cursor", label: "Cursor", hint: "CURSOR.md", templateFile: "CURSOR.md", destFile: "CURSOR.md", selected: true },
53
+ { value: "codex", label: "Codex / OpenAI", hint: "AGENTS.md", templateFile: "AGENTS.md", destFile: "AGENTS.md", selected: true },
54
+ { value: "gemini", label: "Gemini CLI", hint: "GEMINI.md", templateFile: "GEMINI.md", destFile: "GEMINI.md", selected: true },
55
+ { value: "copilot", label: "GitHub Copilot", hint: ".github/copilot-instructions.md", templateFile: "copilot-instructions.md", destFile: ".github/copilot-instructions.md", selected: true },
56
+ { value: "windsurf", label: "Windsurf", hint: ".windsurfrules", templateFile: ".windsurfrules", destFile: ".windsurfrules", selected: true },
57
+ { value: "cline", label: "Cline", hint: ".clinerules", templateFile: ".clinerules", destFile: ".clinerules", selected: false },
58
+ { value: "aider", label: "Aider", hint: "CONVENTIONS.md", templateFile: "CONVENTIONS.md", destFile: "CONVENTIONS.md", selected: false }
59
+ ];
60
+
61
+ // src/commands/init.ts
62
+ function detectProjectInfo(projectPath) {
63
+ const date = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" });
64
+ let name = basename(projectPath);
65
+ let description = "";
66
+ let stack = "";
67
+ const pkgPath = join2(projectPath, "package.json");
68
+ if (existsSync(pkgPath)) {
69
+ try {
70
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
71
+ if (pkg.name) name = pkg.name;
72
+ if (pkg.description) description = pkg.description;
73
+ stack = "Node.js";
74
+ } catch {
75
+ }
76
+ }
77
+ if (!stack) {
78
+ if (existsSync(join2(projectPath, "Cargo.toml"))) stack = "Rust";
79
+ else if (existsSync(join2(projectPath, "go.mod"))) stack = "Go";
80
+ else if (existsSync(join2(projectPath, "pyproject.toml")) || existsSync(join2(projectPath, "requirements.txt"))) stack = "Python";
81
+ else if (existsSync(join2(projectPath, "pom.xml"))) stack = "Java (Maven)";
82
+ else if (existsSync(join2(projectPath, "build.gradle")) || existsSync(join2(projectPath, "build.gradle.kts"))) stack = "Kotlin/Java (Gradle)";
83
+ else if (existsSync(join2(projectPath, "composer.json"))) stack = "PHP";
84
+ else if (existsSync(join2(projectPath, "Gemfile"))) stack = "Ruby";
85
+ }
86
+ return { name, description, stack, date };
87
+ }
88
+ function buildMemoryMd(templateContent, info2) {
89
+ let content = templateContent;
90
+ const whatLine = info2.description ? `**${info2.name}** \u2014 ${info2.description}` : `**${info2.name}**`;
91
+ content = content.replace(
92
+ /### What this project is\n<!--[^]*?-->/,
93
+ `### What this project is
94
+ ${whatLine}
95
+ <!-- 2\u20133 lines: what it does, who it's for, what problem it solves -->`
96
+ );
97
+ if (info2.stack) {
98
+ content = content.replace(
99
+ /### Stack\n<!--[^]*?-->/,
100
+ `### Stack
101
+ ${info2.stack}
102
+ <!-- Add layers: Frontend, Backend, Infra, etc. -->`
103
+ );
104
+ }
105
+ content = content.replace(
106
+ /### Current focus\n<!--[^]*?-->/,
107
+ `### Current focus
108
+ <!-- Initialized ${info2.date} \u2014 ask your AI to fill this in after your first session -->`
109
+ );
110
+ return content;
111
+ }
112
+ var BRAIN_FILES = [
113
+ { templateFile: "MEMORY.md", label: ".brain/MEMORY.md", desc: "permanent project facts" },
114
+ { templateFile: "SESSION.md", label: ".brain/SESSION.md", desc: "current session state" },
115
+ { templateFile: "SHARED.md", label: ".brain/SHARED.md", desc: "shared across sessions" },
116
+ { templateFile: "LOG.md", label: ".brain/LOG.md", desc: "full session history" }
117
+ ];
118
+ var DEFAULT_MAX_LOG_ENTRIES = 50;
119
+ var initCommand = new Command("init").description("Set up memory for the current project").option("-y, --yes", "Skip all prompts, use defaults").addHelpText("after", `
120
+ Examples:
121
+ mindlink init
122
+ mindlink init --yes
123
+ `).action(async (opts) => {
124
+ const projectPath = resolve(process.cwd());
125
+ const brainDir = join2(projectPath, BRAIN_DIR);
126
+ printBanner();
127
+ if (existsSync(brainDir)) {
128
+ if (opts.yes) {
129
+ console.log(` ${chalk2.red("\u2717")} Already initialized at this path.`);
130
+ console.log(` Run ${chalk2.cyan("mindlink config")} to change settings.`);
131
+ console.log("");
132
+ process.exit(1);
133
+ }
134
+ const action = await select({
135
+ message: ".brain/ already exists at this path. What would you like to do?",
136
+ options: [
137
+ { value: "config", label: "Change settings", hint: "mindlink config" },
138
+ { value: "status", label: "View current status", hint: "mindlink status" },
139
+ { value: "exit", label: "Nothing \u2014 exit", hint: "" }
140
+ ]
141
+ });
142
+ if (isCancel(action) || action === "exit") {
143
+ process.exit(0);
144
+ }
145
+ if (action === "status") {
146
+ const { execSync: execSync2 } = await import("child_process");
147
+ try {
148
+ execSync2("mindlink status", { stdio: "inherit" });
149
+ } catch {
150
+ }
151
+ }
152
+ if (action === "config") {
153
+ console.log(` Run ${chalk2.cyan("mindlink config")} to change settings.`);
154
+ }
155
+ console.log("");
156
+ process.exit(0);
157
+ }
158
+ intro(chalk2.bold("Initializing memory for this project:"));
159
+ console.log(` ${chalk2.dim(projectPath)}`);
160
+ console.log(` ${chalk2.dim("This creates a .brain/ folder scoped to this project only.")}`);
161
+ console.log(` ${chalk2.dim("Run mindlink init once per project \u2014 never needs to be run again.")}`);
162
+ console.log("");
163
+ let selectedAgents;
164
+ if (opts.yes) {
165
+ selectedAgents = AGENTS.filter((a) => a.selected).map((a) => a.value);
166
+ } else {
167
+ const agentChoices = AGENTS.map((a) => ({
168
+ value: a.value,
169
+ label: `${a.label.padEnd(18)} ${chalk2.dim(a.hint)}`,
170
+ hint: a.selected ? "recommended" : void 0
171
+ }));
172
+ const agentResult = await multiselect({
173
+ message: "Which AI agents do you use?",
174
+ options: agentChoices,
175
+ initialValues: AGENTS.filter((a) => a.selected).map((a) => a.value),
176
+ required: false
177
+ });
178
+ if (isCancel(agentResult)) {
179
+ cancel("Cancelled.");
180
+ process.exit(0);
181
+ }
182
+ selectedAgents = agentResult;
183
+ console.log(` ${chalk2.dim("\u21A9 Add or remove agents anytime: mindlink config \u2192 Agent instruction files")}`);
184
+ console.log("");
185
+ }
186
+ let gitTracking;
187
+ if (opts.yes) {
188
+ gitTracking = true;
189
+ } else {
190
+ console.log(` ${chalk2.dim(".brain/ is your AI's memory \u2014 MEMORY.md, SESSION.md, SHARED.md, LOG.md.")}`);
191
+ console.log(` ${chalk2.dim("Plain Markdown files. Commit them to share with your team, or keep them local.")}`);
192
+ console.log("");
193
+ const gitResult = await select({
194
+ message: "Should .brain/ be committed to git?",
195
+ options: [
196
+ { value: "enable", label: "Enable", hint: "team shares the same AI memory" },
197
+ { value: "disable", label: "Disable", hint: "add to .gitignore \u2014 personal memory only" }
198
+ ]
199
+ });
200
+ if (isCancel(gitResult)) {
201
+ cancel("Cancelled.");
202
+ process.exit(0);
203
+ }
204
+ gitTracking = gitResult === "enable";
205
+ console.log(` ${chalk2.dim("\u21A9 Change anytime: mindlink config \u2192 Git tracking")}`);
206
+ console.log("");
207
+ }
208
+ let autoSync;
209
+ if (opts.yes) {
210
+ autoSync = true;
211
+ } else {
212
+ const syncResult = await select({
213
+ message: "Auto-sync between sessions?",
214
+ options: [
215
+ { value: "enable", label: "Enable", hint: "watch for changes, sync automatically (recommended)" },
216
+ { value: "disable", label: "Disable", hint: "run mindlink sync manually when needed" }
217
+ ]
218
+ });
219
+ if (isCancel(syncResult)) {
220
+ cancel("Cancelled.");
221
+ process.exit(0);
222
+ }
223
+ autoSync = syncResult === "enable";
224
+ console.log(` ${chalk2.dim("\u21A9 Change anytime: mindlink config \u2192 Auto-sync")}`);
225
+ console.log("");
226
+ }
227
+ const s = spinner();
228
+ s.start("Creating memory files...");
229
+ const created = [];
230
+ const errors = [];
231
+ try {
232
+ mkdirSync(brainDir, { recursive: true });
233
+ const projectInfo = detectProjectInfo(projectPath);
234
+ for (const file of BRAIN_FILES) {
235
+ const dest = join2(brainDir, file.templateFile);
236
+ const templateContent = readFileSync(join2(BRAIN_TEMPLATES_DIR, file.templateFile), "utf8");
237
+ const content = file.templateFile === "MEMORY.md" ? buildMemoryMd(templateContent, projectInfo) : templateContent;
238
+ writeFileSync(dest, content);
239
+ created.push(`${file.label.padEnd(32)} ${chalk2.dim(file.desc)}`);
240
+ }
241
+ for (const agentValue of selectedAgents) {
242
+ const agent = AGENTS.find((a) => a.value === agentValue);
243
+ if (!agent) continue;
244
+ const destPath = join2(projectPath, agent.destFile);
245
+ mkdirSync(dirname2(destPath), { recursive: true });
246
+ writeFileSync(destPath, readFileSync(join2(AGENT_TEMPLATES_DIR, agent.templateFile), "utf8"));
247
+ created.push(`${agent.destFile.padEnd(32)} ${chalk2.dim(agent.label)}`);
248
+ }
249
+ if (selectedAgents.includes("claude")) {
250
+ const hookDest = join2(projectPath, ".claude", "settings.json");
251
+ if (!existsSync(hookDest)) {
252
+ mkdirSync(dirname2(hookDest), { recursive: true });
253
+ writeFileSync(hookDest, readFileSync(join2(HOOKS_TEMPLATES_DIR, "claude-settings.json"), "utf8"));
254
+ created.push(`.claude/settings.json${" ".repeat(14)} ${chalk2.dim("Claude Code compact hook")}`);
255
+ }
256
+ }
257
+ if (!gitTracking) {
258
+ const gitignorePath = join2(projectPath, ".gitignore");
259
+ const entry = "\n# MindLink memory (personal \u2014 not shared with team)\n.brain/\n";
260
+ if (existsSync(gitignorePath)) {
261
+ const current = readFileSync(gitignorePath, "utf8");
262
+ if (!current.includes(".brain/")) appendFileSync(gitignorePath, entry);
263
+ } else {
264
+ writeFileSync(gitignorePath, entry.trim() + "\n");
265
+ }
266
+ created.push(`.gitignore${" ".repeat(23)} ${chalk2.dim(".brain/ excluded")}`);
267
+ }
268
+ const config = {
269
+ gitTracking,
270
+ autoSync,
271
+ agents: selectedAgents,
272
+ maxLogEntries: DEFAULT_MAX_LOG_ENTRIES
273
+ };
274
+ writeFileSync(join2(brainDir, "config.json"), JSON.stringify(config, null, 2));
275
+ } catch (err) {
276
+ errors.push(err instanceof Error ? err.message : String(err));
277
+ }
278
+ s.stop("Done.");
279
+ console.log("");
280
+ for (const line of created) {
281
+ console.log(` ${chalk2.green("\u2713")} ${line}`);
282
+ }
283
+ if (errors.length > 0) {
284
+ console.log("");
285
+ for (const err of errors) console.log(` ${chalk2.red("\u2717")} ${err}`);
286
+ }
287
+ console.log("");
288
+ note(
289
+ `Your AI finally has a brain.
290
+
291
+ Every new session wakes up knowing the project, past decisions,
292
+ current task, and what other sessions have shared. No more
293
+ re-explaining from scratch. No more goldfish moments.
294
+
295
+ Like any good brain, it remembers what matters and quietly
296
+ lets go of the old stuff \u2014 that's what MEMORY.md is for:
297
+ promote anything important there and it stays forever.
298
+
299
+ Start a new AI session \u2014 it'll hit the ground running.
300
+
301
+ Run ${chalk2.cyan("mindlink help")} to see all commands.`,
302
+ "\u25C9 MindLink active"
303
+ );
304
+ console.log("");
305
+ });
306
+
307
+ // src/commands/status.ts
308
+ import { Command as Command2 } from "commander";
309
+ import chalk3 from "chalk";
310
+ import { existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "fs";
311
+ import { join as join3, resolve as resolve2 } from "path";
312
+
313
+ // src/utils/parser.ts
314
+ function extractSection(markdown, heading) {
315
+ const lines = markdown.split("\n");
316
+ let inSection = false;
317
+ let headingLevel = 0;
318
+ const result = [];
319
+ for (const line of lines) {
320
+ const match = line.match(/^(#{1,6})\s+(.+)/);
321
+ if (match) {
322
+ const level = match[1].length;
323
+ const title = match[2].trim();
324
+ if (title.toLowerCase() === heading.toLowerCase()) {
325
+ inSection = true;
326
+ headingLevel = level;
327
+ continue;
328
+ }
329
+ if (inSection && level <= headingLevel) {
330
+ break;
331
+ }
332
+ }
333
+ if (inSection) {
334
+ result.push(line);
335
+ }
336
+ }
337
+ return result.join("\n").trim();
338
+ }
339
+ function extractBullets(text3) {
340
+ return text3.split("\n").filter((l) => /^[-*]\s+/.test(l.trim())).map((l) => l.replace(/^[-*]\s+/, "").trim()).filter((l) => l.length > 0 && !l.startsWith("<!--"));
341
+ }
342
+ function countLogEntries(markdown) {
343
+ return (markdown.match(/^##\s+/gm) ?? []).length;
344
+ }
345
+ function lastLogDate(markdown) {
346
+ const matches = markdown.match(/^##\s+(.+)/m);
347
+ return matches ? matches[1].trim() : null;
348
+ }
349
+ function countDecisions(markdown) {
350
+ const section = extractSection(markdown, "Key Decisions");
351
+ return section.split("\n").filter((l) => l.startsWith("|") && !l.includes("---") && !l.toLowerCase().includes("decision")).filter((l) => {
352
+ const cols = l.split("|").map((c) => c.trim()).filter(Boolean);
353
+ return cols.some((c) => c.length > 0);
354
+ }).length;
355
+ }
356
+ function parseLogEntries(markdown) {
357
+ const blocks = markdown.split(/(?=^## )/m).filter((b) => b.trimStart().startsWith("## "));
358
+ return blocks.map((block) => {
359
+ const newline = block.indexOf("\n");
360
+ const heading = newline === -1 ? block.slice(3).trim() : block.slice(3, newline).trim();
361
+ const body = newline === -1 ? "" : block.slice(newline + 1).trim();
362
+ return { heading, body };
363
+ });
364
+ }
365
+ function relativeTime(date) {
366
+ const diff = Date.now() - date.getTime();
367
+ const minutes = Math.floor(diff / 6e4);
368
+ const hours = Math.floor(diff / 36e5);
369
+ const days = Math.floor(diff / 864e5);
370
+ if (minutes < 2) return "just now";
371
+ if (minutes < 60) return `${minutes} minutes ago`;
372
+ if (hours < 24) return `${hours} hour${hours > 1 ? "s" : ""} ago`;
373
+ return `${days} day${days > 1 ? "s" : ""} ago`;
374
+ }
375
+
376
+ // src/commands/status.ts
377
+ var statusCommand = new Command2("status").description("Show last session summary and what's next").option("--json", "Output as JSON").addHelpText("after", `
378
+ Examples:
379
+ mindlink status
380
+ mindlink status --json
381
+ `).action((opts) => {
382
+ const projectPath = resolve2(process.cwd());
383
+ const brainDir = join3(projectPath, BRAIN_DIR);
384
+ if (!existsSync2(brainDir)) {
385
+ console.log(` ${chalk3.red("\u2717")} No .brain/ found in this directory.`);
386
+ console.log(` Run ${chalk3.cyan("mindlink init")} to get started.`);
387
+ console.log("");
388
+ process.exit(1);
389
+ }
390
+ const sessionPath = join3(brainDir, "SESSION.md");
391
+ const logPath = join3(brainDir, "LOG.md");
392
+ const memoryPath = join3(brainDir, "MEMORY.md");
393
+ const sessionMd = existsSync2(sessionPath) ? readFileSync2(sessionPath, "utf8") : "";
394
+ const logMd = existsSync2(logPath) ? readFileSync2(logPath, "utf8") : "";
395
+ const memoryMd = existsSync2(memoryPath) ? readFileSync2(memoryPath, "utf8") : "";
396
+ const rawTask = extractSection(sessionMd, "Current Task");
397
+ const currentTask = rawTask.startsWith("<!--") ? "" : rawTask;
398
+ const inProgress = extractBullets(extractSection(sessionMd, "In Progress"));
399
+ const decisions = extractBullets(extractSection(sessionMd, "Decisions Made This Session"));
400
+ const blockers = extractBullets(extractSection(sessionMd, "Blockers"));
401
+ const upNext = extractBullets(extractSection(sessionMd, "Up Next"));
402
+ const sessionCount = countLogEntries(logMd);
403
+ const lastSession = lastLogDate(logMd);
404
+ const decisionCount = countDecisions(memoryMd);
405
+ const lastUpdated = existsSync2(sessionPath) ? relativeTime(statSync(sessionPath).mtime) : "never";
406
+ const isEmpty = !currentTask && inProgress.length === 0 && decisions.length === 0 && blockers.length === 0 && upNext.length === 0 && sessionCount === 0;
407
+ if (opts.json) {
408
+ console.log(JSON.stringify({
409
+ currentTask,
410
+ inProgress,
411
+ decisions,
412
+ blockers,
413
+ upNext,
414
+ stats: { sessionsLogged: sessionCount, decisionsMade: decisionCount, lastUpdated }
415
+ }, null, 2));
416
+ return;
417
+ }
418
+ console.log("");
419
+ if (isEmpty) {
420
+ console.log(` ${chalk3.dim("No sessions logged yet.")}`);
421
+ console.log(` Start an AI session \u2014 it will read ${chalk3.cyan(".brain/")} automatically.`);
422
+ console.log("");
423
+ return;
424
+ }
425
+ if (lastSession) {
426
+ console.log(` ${chalk3.bold("Last session")} ${chalk3.dim("\u2014")} ${lastSession}`);
427
+ console.log("");
428
+ }
429
+ if (currentTask && !currentTask.startsWith("<!--")) {
430
+ console.log(` ${chalk3.bold("Current task")}`);
431
+ console.log(` ${chalk3.cyan("\u25CE")} ${currentTask}`);
432
+ console.log("");
433
+ }
434
+ if (inProgress.length > 0) {
435
+ console.log(` ${chalk3.bold("In progress")}`);
436
+ for (const item of inProgress) {
437
+ console.log(` ${chalk3.yellow("\u25CF")} ${item}`);
438
+ }
439
+ console.log("");
440
+ }
441
+ if (decisions.length > 0) {
442
+ console.log(` ${chalk3.bold("Decided this session")}`);
443
+ for (const item of decisions) {
444
+ console.log(` ${chalk3.green("\u2713")} ${item}`);
445
+ }
446
+ console.log("");
447
+ }
448
+ if (blockers.length > 0) {
449
+ console.log(` ${chalk3.bold("Blockers")}`);
450
+ for (const item of blockers) {
451
+ console.log(` ${chalk3.red("\u2717")} ${item}`);
452
+ }
453
+ console.log("");
454
+ }
455
+ if (upNext.length > 0) {
456
+ console.log(` ${chalk3.bold("Up next")}`);
457
+ for (const item of upNext) {
458
+ console.log(` ${chalk3.dim("\u2192")} ${item}`);
459
+ }
460
+ console.log("");
461
+ }
462
+ console.log(` ${chalk3.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}`);
463
+ console.log(` ${chalk3.dim("Sessions logged")} ${String(sessionCount).padStart(4)}`);
464
+ console.log(` ${chalk3.dim("Decisions made")} ${String(decisionCount).padStart(4)}`);
465
+ console.log(` ${chalk3.dim("Last updated")} ${lastUpdated}`);
466
+ console.log("");
467
+ console.log(` Run ${chalk3.cyan("mindlink log")} to see full history.`);
468
+ console.log("");
469
+ });
470
+
471
+ // src/commands/log.ts
472
+ import { Command as Command3 } from "commander";
473
+ import chalk4 from "chalk";
474
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync } from "fs";
475
+ import { join as join4, resolve as resolve3 } from "path";
476
+ var logCommand = new Command3("log").description("Print session history").option("--all", "Show full history").option("--limit <n>", "Show last N sessions", "10").option("--since <date>", "Show sessions from a date (matched against heading text)").option("--json", "Output as JSON").addHelpText("after", `
477
+ Examples:
478
+ mindlink log
479
+ mindlink log --all
480
+ mindlink log --limit 20
481
+ mindlink log --since "Apr 1"
482
+ mindlink log --json
483
+ `).action((opts) => {
484
+ const projectPath = resolve3(process.cwd());
485
+ const brainDir = join4(projectPath, BRAIN_DIR);
486
+ if (!existsSync3(brainDir)) {
487
+ console.log(` ${chalk4.red("\u2717")} No .brain/ found in this directory.`);
488
+ console.log(` Run ${chalk4.cyan("mindlink init")} to get started.`);
489
+ console.log("");
490
+ process.exit(1);
491
+ }
492
+ const logPath = join4(brainDir, "LOG.md");
493
+ const logMd = existsSync3(logPath) ? readFileSync3(logPath, "utf8") : "";
494
+ let entries = parseLogEntries(logMd);
495
+ if (entries.length === 0) {
496
+ console.log("");
497
+ console.log(` ${chalk4.dim("No sessions logged yet.")}`);
498
+ console.log(` ${chalk4.dim("Your AI will append an entry here at the end of each session.")}`);
499
+ console.log("");
500
+ return;
501
+ }
502
+ if (opts.since) {
503
+ const since = opts.since.toLowerCase();
504
+ entries = entries.filter((e) => e.heading.toLowerCase().includes(since));
505
+ }
506
+ const limit = opts.all ? entries.length : parseInt(opts.limit, 10);
507
+ const total = entries.length;
508
+ const shown = entries.slice(0, limit);
509
+ if (opts.json) {
510
+ console.log(JSON.stringify(shown, null, 2));
511
+ return;
512
+ }
513
+ console.log("");
514
+ if (!opts.all) {
515
+ const rest = total - shown.length;
516
+ const hint = rest > 0 ? ` ${chalk4.dim(`${rest} more \u2014 run mindlink log --all to see everything`)}` : "";
517
+ console.log(` ${chalk4.dim(`Showing last ${shown.length} of ${total} session${total !== 1 ? "s" : ""}`)}${hint ? "" : ""}`);
518
+ if (rest > 0) console.log(` ${chalk4.dim(`${rest} more \u2014 run mindlink log --all to see everything`)}`);
519
+ console.log("");
520
+ }
521
+ for (const entry of shown) {
522
+ console.log(` ${chalk4.bold("\u2500\u2500")} ${chalk4.cyan(entry.heading)} ${"\u2500".repeat(Math.max(0, 40 - entry.heading.length))}`);
523
+ if (entry.body) {
524
+ const lines = entry.body.split("\n");
525
+ for (const line of lines) {
526
+ console.log(` ${chalk4.dim(line)}`);
527
+ }
528
+ }
529
+ console.log("");
530
+ }
531
+ const archiveFiles = readdirSync(brainDir).filter((f) => /^LOG-\d{4}-\d{2}-\d{2}\.md$/.test(f)).sort();
532
+ if (archiveFiles.length > 0) {
533
+ console.log(` ${chalk4.dim("\u2500".repeat(44))}`);
534
+ console.log(` ${chalk4.dim("Like all human brains, MindLink forgets old sessions")}`);
535
+ console.log(` ${chalk4.dim("that haven't come up in a while \u2014 that's by design.")}`);
536
+ console.log(` ${chalk4.dim(`Older entries archived to: ${archiveFiles.join(", ")}`)}`);
537
+ console.log(` ${chalk4.dim("Anything that truly matters belongs in MEMORY.md.")}`);
538
+ console.log("");
539
+ }
540
+ });
541
+
542
+ // src/commands/clear.ts
543
+ import { Command as Command4 } from "commander";
544
+ import chalk5 from "chalk";
545
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
546
+ import { join as join5, resolve as resolve4 } from "path";
547
+ var clearCommand = new Command4("clear").description("Reset SESSION.md for a fresh session start").option("-y, --yes", "Skip confirmation prompt").addHelpText("after", `
548
+ What it does:
549
+ Resets SESSION.md to a blank template \u2014 wipes current task, in-progress items,
550
+ blockers, and up-next. MEMORY.md, LOG.md, and SHARED.md are untouched.
551
+
552
+ Not what you need?
553
+ mindlink reset \u2014 wipe ALL memory files back to blank (scorched earth)
554
+ mindlink uninstall \u2014 remove MindLink from this project entirely
555
+
556
+ Examples:
557
+ mindlink clear
558
+ mindlink clear --yes
559
+ `).action(async (_opts) => {
560
+ const projectPath = resolve4(process.cwd());
561
+ const brainDir = join5(projectPath, BRAIN_DIR);
562
+ if (!existsSync4(brainDir)) {
563
+ console.log("");
564
+ console.log(` ${chalk5.red("\u2717")} No .brain/ found in this directory.`);
565
+ console.log(` Run ${chalk5.cyan("mindlink init")} to get started.`);
566
+ console.log("");
567
+ process.exit(1);
568
+ }
569
+ console.log("");
570
+ try {
571
+ const templatePath = join5(BRAIN_TEMPLATES_DIR, "SESSION.md");
572
+ const destPath = join5(brainDir, "SESSION.md");
573
+ writeFileSync2(destPath, readFileSync4(templatePath, "utf8"));
574
+ } catch (err) {
575
+ console.log(` ${chalk5.red("\u2717")} ${err instanceof Error ? err.message : String(err)}`);
576
+ console.log("");
577
+ process.exit(1);
578
+ }
579
+ console.log(` ${chalk5.green("\u2713")} SESSION.md cleared. Ready for a clean session.`);
580
+ console.log(` ${chalk5.dim("MEMORY.md, LOG.md, and SHARED.md are untouched.")}`);
581
+ console.log("");
582
+ });
583
+
584
+ // src/commands/reset.ts
585
+ import { Command as Command5 } from "commander";
586
+ import { confirm, isCancel as isCancel2, cancel as cancel2 } from "@clack/prompts";
587
+ import chalk6 from "chalk";
588
+ import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
589
+ import { join as join6, resolve as resolve5 } from "path";
590
+ var BRAIN_FILES2 = ["MEMORY.md", "SESSION.md", "SHARED.md", "LOG.md"];
591
+ var resetCommand = new Command5("reset").description("Wipe all .brain/ memory files and start completely fresh").option("-y, --yes", "Skip confirmation prompt").addHelpText("after", `
592
+ What it does:
593
+ Resets MEMORY.md, SESSION.md, SHARED.md, and LOG.md to blank templates.
594
+ Your settings (config.json) and agent instruction files are untouched.
595
+
596
+ Not what you need?
597
+ mindlink clear \u2014 reset SESSION.md only (lighter option)
598
+ mindlink uninstall \u2014 remove MindLink from this project entirely
599
+
600
+ Examples:
601
+ mindlink reset
602
+ mindlink reset --yes
603
+ `).action(async (opts) => {
604
+ const projectPath = resolve5(process.cwd());
605
+ const brainDir = join6(projectPath, BRAIN_DIR);
606
+ if (!existsSync5(brainDir)) {
607
+ console.log(` ${chalk6.red("\u2717")} No .brain/ found in this directory.`);
608
+ console.log(` Run ${chalk6.cyan("mindlink init")} to get started.`);
609
+ console.log("");
610
+ process.exit(1);
611
+ }
612
+ console.log("");
613
+ if (!opts.yes) {
614
+ console.log(` ${chalk6.yellow("!")} This will wipe ALL .brain/ memory files and start fresh.`);
615
+ console.log(` ${chalk6.dim("MEMORY.md, SESSION.md, SHARED.md, and LOG.md \u2192 reset to blank templates.")}`);
616
+ console.log(` ${chalk6.dim("Settings and agent instruction files are untouched.")}`);
617
+ console.log("");
618
+ console.log(` ${chalk6.dim("Lighter option: mindlink clear \u2014 resets SESSION.md only")}`);
619
+ console.log(` ${chalk6.dim("Remove entirely: mindlink uninstall \u2014 removes MindLink from this project")}`);
620
+ console.log("");
621
+ const confirmed = await confirm({
622
+ message: "Reset everything? This cannot be undone (unless .brain/ is tracked by git)."
623
+ });
624
+ if (isCancel2(confirmed) || !confirmed) {
625
+ cancel2("Cancelled.");
626
+ process.exit(0);
627
+ }
628
+ console.log("");
629
+ }
630
+ const errors = [];
631
+ for (const file of BRAIN_FILES2) {
632
+ try {
633
+ const templatePath = join6(BRAIN_TEMPLATES_DIR, file);
634
+ const destPath = join6(brainDir, file);
635
+ if (existsSync5(templatePath)) {
636
+ writeFileSync3(destPath, readFileSync5(templatePath, "utf8"));
637
+ }
638
+ } catch (err) {
639
+ errors.push(`${file}: ${err instanceof Error ? err.message : String(err)}`);
640
+ }
641
+ }
642
+ if (errors.length > 0) {
643
+ for (const err of errors) console.log(` ${chalk6.red("\u2717")} ${err}`);
644
+ console.log("");
645
+ process.exit(1);
646
+ }
647
+ console.log(` ${chalk6.green("\u2713")} .brain/ reset. All memory files are blank.`);
648
+ console.log(` ${chalk6.dim("Your AI will wake up with no memory of past sessions.")}`);
649
+ console.log("");
650
+ });
651
+
652
+ // src/commands/config.ts
653
+ import { Command as Command6 } from "commander";
654
+ import { select as select2, multiselect as multiselect2, text, isCancel as isCancel3 } from "@clack/prompts";
655
+ import chalk7 from "chalk";
656
+ import {
657
+ existsSync as existsSync6,
658
+ readFileSync as readFileSync6,
659
+ writeFileSync as writeFileSync4,
660
+ appendFileSync as appendFileSync2,
661
+ mkdirSync as mkdirSync2,
662
+ unlinkSync
663
+ } from "fs";
664
+ import { join as join7, resolve as resolve6, dirname as dirname3 } from "path";
665
+ function readConfig(brainDir) {
666
+ return JSON.parse(readFileSync6(join7(brainDir, "config.json"), "utf8"));
667
+ }
668
+ function saveConfig(brainDir, config) {
669
+ writeFileSync4(join7(brainDir, "config.json"), JSON.stringify(config, null, 2));
670
+ }
671
+ function enableGitTracking(projectPath) {
672
+ const gitignorePath = join7(projectPath, ".gitignore");
673
+ if (!existsSync6(gitignorePath)) return;
674
+ const content = readFileSync6(gitignorePath, "utf8");
675
+ const cleaned = content.replace(/\n# MindLink memory[^\n]*\n\.brain\/\n?/g, "").replace(/\n?\.brain\/\n?/g, "\n");
676
+ writeFileSync4(gitignorePath, cleaned.trimEnd() + "\n");
677
+ }
678
+ function disableGitTracking(projectPath) {
679
+ const gitignorePath = join7(projectPath, ".gitignore");
680
+ const entry = "\n# MindLink memory (personal \u2014 not shared with team)\n.brain/\n";
681
+ if (existsSync6(gitignorePath)) {
682
+ const content = readFileSync6(gitignorePath, "utf8");
683
+ if (!content.includes(".brain/")) appendFileSync2(gitignorePath, entry);
684
+ } else {
685
+ writeFileSync4(gitignorePath, entry.trim() + "\n");
686
+ }
687
+ }
688
+ function addAgentFile(projectPath, agentValue) {
689
+ const agent = AGENTS.find((a) => a.value === agentValue);
690
+ if (!agent) return null;
691
+ const destPath = join7(projectPath, agent.destFile);
692
+ if (existsSync6(destPath)) return null;
693
+ mkdirSync2(dirname3(destPath), { recursive: true });
694
+ writeFileSync4(destPath, readFileSync6(join7(AGENT_TEMPLATES_DIR, agent.templateFile), "utf8"));
695
+ return agent.destFile;
696
+ }
697
+ function removeAgentFile(projectPath, agentValue) {
698
+ const agent = AGENTS.find((a) => a.value === agentValue);
699
+ if (!agent) return null;
700
+ const destPath = join7(projectPath, agent.destFile);
701
+ if (!existsSync6(destPath)) return null;
702
+ unlinkSync(destPath);
703
+ return agent.destFile;
704
+ }
705
+ function addClaudeHook(projectPath) {
706
+ const hookDest = join7(projectPath, ".claude", "settings.json");
707
+ if (existsSync6(hookDest)) return false;
708
+ mkdirSync2(dirname3(hookDest), { recursive: true });
709
+ writeFileSync4(hookDest, readFileSync6(join7(HOOKS_TEMPLATES_DIR, "claude-settings.json"), "utf8"));
710
+ return true;
711
+ }
712
+ var configCommand = new Command6("config").description("Change settings for the current project").addHelpText("after", `
713
+ Examples:
714
+ mindlink config
715
+ `).action(async () => {
716
+ const projectPath = resolve6(process.cwd());
717
+ const brainDir = join7(projectPath, BRAIN_DIR);
718
+ if (!existsSync6(brainDir)) {
719
+ console.log(` ${chalk7.red("\u2717")} No .brain/ found in this directory.`);
720
+ console.log(` Run ${chalk7.cyan("mindlink init")} to get started.`);
721
+ console.log("");
722
+ process.exit(1);
723
+ }
724
+ if (!process.stdin.isTTY) {
725
+ const cfg = readConfig(brainDir);
726
+ const agentLabels = cfg.agents.map((v) => AGENTS.find((a) => a.value === v)?.hint ?? v).join(", ");
727
+ console.log(JSON.stringify({ ...cfg, agentFiles: agentLabels }, null, 2));
728
+ return;
729
+ }
730
+ let config = readConfig(brainDir);
731
+ while (true) {
732
+ console.log("");
733
+ console.log(` ${chalk7.bold("Current settings")} ${chalk7.dim("\xB7")} ${chalk7.dim(projectPath)}`);
734
+ console.log("");
735
+ console.log(` Git tracking : ${config.gitTracking ? chalk7.green("enabled") : chalk7.dim("disabled")} ${chalk7.dim(config.gitTracking ? "(team shares memory)" : "(.brain/ excluded from git)")}`);
736
+ console.log(` Auto-sync : ${config.autoSync ? chalk7.green("enabled") : chalk7.dim("disabled")} ${chalk7.dim(config.autoSync ? "(watch mode)" : "(run mindlink sync manually)")}`);
737
+ console.log(` Max log entries: ${chalk7.dim(String(config.maxLogEntries))} ${chalk7.dim("(archive rotation threshold)")}`);
738
+ const agentLabels = config.agents.map((v) => AGENTS.find((a) => a.value === v)?.hint ?? v).join(", ");
739
+ console.log(` Agent files : ${chalk7.dim(agentLabels || "none")}`);
740
+ console.log("");
741
+ const action = await select2({
742
+ message: "What would you like to change?",
743
+ options: [
744
+ { value: "git", label: "Git tracking" },
745
+ { value: "sync", label: "Auto-sync" },
746
+ { value: "memory", label: "Memory settings", hint: "log rotation threshold" },
747
+ { value: "agents", label: "Agent instruction files" },
748
+ { value: "exit", label: "Exit" }
749
+ ]
750
+ });
751
+ if (isCancel3(action) || action === "exit") break;
752
+ if (action === "git") {
753
+ const choice = await select2({
754
+ message: "Git tracking \u2014 should .brain/ be committed to git?",
755
+ options: [
756
+ { value: "enable", label: "Enable", hint: "commit memory (share with your team)" },
757
+ { value: "disable", label: "Disable", hint: "add to .gitignore (keep memory personal)" },
758
+ { value: "back", label: "\u21A9 Back" }
759
+ ],
760
+ initialValue: config.gitTracking ? "enable" : "disable"
761
+ });
762
+ if (isCancel3(choice) || choice === "back") continue;
763
+ const newValue = choice === "enable";
764
+ if (newValue === config.gitTracking) {
765
+ console.log(` ${chalk7.dim("No change.")}`);
766
+ continue;
767
+ }
768
+ if (newValue) {
769
+ enableGitTracking(projectPath);
770
+ console.log(` ${chalk7.green("\u2713")} Git tracking enabled. .brain/ will be committed.`);
771
+ } else {
772
+ disableGitTracking(projectPath);
773
+ console.log(` ${chalk7.green("\u2713")} .gitignore updated. .brain/ will no longer be tracked.`);
774
+ }
775
+ config.gitTracking = newValue;
776
+ saveConfig(brainDir, config);
777
+ console.log(` ${chalk7.dim("\u21A9 Change anytime: mindlink config \u2192 Git tracking")}`);
778
+ }
779
+ if (action === "sync") {
780
+ const choice = await select2({
781
+ message: "Auto-sync mode",
782
+ options: [
783
+ { value: "enable", label: "Enable", hint: "watch for changes, sync automatically" },
784
+ { value: "disable", label: "Disable", hint: "run mindlink sync manually" },
785
+ { value: "back", label: "\u21A9 Back" }
786
+ ],
787
+ initialValue: config.autoSync ? "enable" : "disable"
788
+ });
789
+ if (isCancel3(choice) || choice === "back") continue;
790
+ const newValue = choice === "enable";
791
+ if (newValue === config.autoSync) {
792
+ console.log(` ${chalk7.dim("No change.")}`);
793
+ continue;
794
+ }
795
+ config.autoSync = newValue;
796
+ saveConfig(brainDir, config);
797
+ console.log(` ${chalk7.green("\u2713")} Auto-sync ${newValue ? "enabled" : "disabled"}.`);
798
+ console.log(` ${chalk7.dim("\u21A9 Change anytime: mindlink config \u2192 Auto-sync")}`);
799
+ }
800
+ if (action === "memory") {
801
+ console.log("");
802
+ console.log(` ${chalk7.dim("How many session entries to keep in LOG.md before archiving older ones.")}`);
803
+ console.log(` ${chalk7.dim("Archived entries move to LOG-YYYY-MM-DD.md \u2014 never deleted.")}`);
804
+ console.log(` ${chalk7.dim("Lower = lighter context for your AI. Higher = more history visible.")}`);
805
+ console.log("");
806
+ const input = await text({
807
+ message: `Max log entries before archiving (current: ${config.maxLogEntries})`,
808
+ placeholder: String(config.maxLogEntries),
809
+ validate(value) {
810
+ const n = parseInt(value, 10);
811
+ if (isNaN(n) || n < 1) return "Enter a number greater than 0";
812
+ }
813
+ });
814
+ if (isCancel3(input)) continue;
815
+ const newValue = parseInt(input, 10);
816
+ if (newValue === config.maxLogEntries) {
817
+ console.log(` ${chalk7.dim("No change.")}`);
818
+ continue;
819
+ }
820
+ config.maxLogEntries = newValue;
821
+ saveConfig(brainDir, config);
822
+ console.log(` ${chalk7.green("\u2713")} Max log entries set to ${newValue}.`);
823
+ console.log(` ${chalk7.dim("\u21A9 Change anytime: mindlink config \u2192 Memory settings")}`);
824
+ }
825
+ if (action === "agents") {
826
+ const agentChoices = AGENTS.map((a) => ({
827
+ value: a.value,
828
+ label: `${a.label.padEnd(18)} ${chalk7.dim(a.hint)}`,
829
+ hint: a.selected ? "recommended" : void 0
830
+ }));
831
+ const result = await multiselect2({
832
+ message: "Which AI agents do you use?",
833
+ options: agentChoices,
834
+ initialValues: config.agents,
835
+ required: false
836
+ });
837
+ if (isCancel3(result)) continue;
838
+ const newAgents = result;
839
+ const added = newAgents.filter((v) => !config.agents.includes(v));
840
+ const removed = config.agents.filter((v) => !newAgents.includes(v));
841
+ const addedFiles = [];
842
+ const removedFiles = [];
843
+ for (const v of added) {
844
+ const f = addAgentFile(projectPath, v);
845
+ if (f) addedFiles.push(f);
846
+ if (v === "claude") {
847
+ if (addClaudeHook(projectPath)) addedFiles.push(".claude/settings.json");
848
+ }
849
+ }
850
+ for (const v of removed) {
851
+ const f = removeAgentFile(projectPath, v);
852
+ if (f) removedFiles.push(f);
853
+ }
854
+ config.agents = newAgents;
855
+ saveConfig(brainDir, config);
856
+ if (addedFiles.length === 0 && removedFiles.length === 0) {
857
+ console.log(` ${chalk7.dim("No change.")}`);
858
+ } else {
859
+ for (const f of addedFiles) console.log(` ${chalk7.green("\u2713")} ${f} added.`);
860
+ for (const f of removedFiles) console.log(` ${chalk7.dim("\u2717")} ${f} removed.`);
861
+ }
862
+ console.log(` ${chalk7.dim("\u21A9 Change anytime: mindlink config \u2192 Agent instruction files")}`);
863
+ }
864
+ }
865
+ console.log("");
866
+ });
867
+
868
+ // src/commands/sync.ts
869
+ import { Command as Command7 } from "commander";
870
+ import chalk8 from "chalk";
871
+ import { existsSync as existsSync7, readFileSync as readFileSync7, statSync as statSync2 } from "fs";
872
+ import { join as join8, resolve as resolve7, basename as basename2 } from "path";
873
+ function timestamp() {
874
+ const d = /* @__PURE__ */ new Date();
875
+ const month = d.toLocaleString("default", { month: "short" });
876
+ const day = d.getDate();
877
+ const time = d.toTimeString().slice(0, 5);
878
+ return `${month} ${day} ${time}`;
879
+ }
880
+ function describeFile(filePath) {
881
+ if (!existsSync7(filePath)) return chalk8.dim("(missing)");
882
+ const stat = statSync2(filePath);
883
+ const kb = (stat.size / 1024).toFixed(1);
884
+ const content = readFileSync7(filePath, "utf8");
885
+ const entries = (content.match(/^## /gm) ?? []).length;
886
+ const name = basename2(filePath);
887
+ if (name === "LOG.md") return entries > 0 ? `${entries} session${entries !== 1 ? "s" : ""}` : chalk8.dim("empty");
888
+ if (name === "SHARED.md") {
889
+ const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#") && !l.startsWith("<!--") && !l.startsWith(">") && l !== "---").length;
890
+ return lines > 0 ? `${lines} line${lines !== 1 ? "s" : ""}` : chalk8.dim("empty");
891
+ }
892
+ if (name === "SESSION.md") {
893
+ const hasTask = content.includes("## Current Task") && !content.includes("<!-- ");
894
+ return hasTask ? chalk8.green("active") : chalk8.dim("idle");
895
+ }
896
+ return `${kb} KB`;
897
+ }
898
+ var syncCommand = new Command7("sync").description("Sync shared context between sessions").option("--once", "Sync once and exit (default: watch mode)").addHelpText("after", `
899
+ Examples:
900
+ mindlink sync
901
+ mindlink sync --once
902
+ `).action(async (opts) => {
903
+ const projectPath = resolve7(process.cwd());
904
+ const brainDir = join8(projectPath, BRAIN_DIR);
905
+ if (!existsSync7(brainDir)) {
906
+ console.log(` ${chalk8.red("\u2717")} No .brain/ found in this directory.`);
907
+ console.log(` Run ${chalk8.cyan("mindlink init")} to get started.`);
908
+ console.log("");
909
+ process.exit(1);
910
+ }
911
+ const sharedPath = join8(brainDir, "SHARED.md");
912
+ const sessionPath = join8(brainDir, "SESSION.md");
913
+ const logPath = join8(brainDir, "LOG.md");
914
+ const memoryPath = join8(brainDir, "MEMORY.md");
915
+ if (opts.once) {
916
+ console.log("");
917
+ console.log(` ${chalk8.dim("Checking shared context...")}`);
918
+ console.log("");
919
+ const files = [
920
+ { label: "SHARED.md ", path: sharedPath },
921
+ { label: "SESSION.md ", path: sessionPath },
922
+ { label: "LOG.md ", path: logPath },
923
+ { label: "MEMORY.md ", path: memoryPath }
924
+ ];
925
+ for (const { label, path } of files) {
926
+ const desc = describeFile(path);
927
+ const mtime = existsSync7(path) ? statSync2(path).mtime : null;
928
+ const age = mtime ? (() => {
929
+ const diff = Date.now() - mtime.getTime();
930
+ const min = Math.floor(diff / 6e4);
931
+ const hr = Math.floor(diff / 36e5);
932
+ if (min < 2) return "just now";
933
+ if (min < 60) return `${min}m ago`;
934
+ return `${hr}h ago`;
935
+ })() : "";
936
+ console.log(` ${chalk8.dim(label)} ${desc} ${chalk8.dim(age)}`);
937
+ }
938
+ console.log("");
939
+ console.log(` ${chalk8.green("\u2713")} All sessions share the same .brain/ folder.`);
940
+ console.log(` ${chalk8.dim("Any session that writes to SHARED.md is immediately visible to all others.")}`);
941
+ console.log("");
942
+ return;
943
+ }
944
+ const { watch } = await import("chokidar");
945
+ console.log("");
946
+ console.log(` ${chalk8.dim("Watching for changes...")} ${chalk8.dim("(Ctrl+C to stop)")}`);
947
+ console.log("");
948
+ const watcher = watch(brainDir, {
949
+ ignoreInitial: true,
950
+ ignored: /(^|[/\\])\../,
951
+ persistent: true,
952
+ awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }
953
+ });
954
+ watcher.on("change", (filePath) => {
955
+ const name = basename2(filePath);
956
+ const stat = statSync2(filePath);
957
+ const kb = (stat.size / 1024).toFixed(1);
958
+ console.log(` ${chalk8.dim("[" + timestamp() + "]")} ${chalk8.cyan(name)} updated ${chalk8.dim(kb + " KB")} ${chalk8.green("\u2192 synced \u2713")}`);
959
+ });
960
+ watcher.on("add", (filePath) => {
961
+ const name = basename2(filePath);
962
+ console.log(` ${chalk8.dim("[" + timestamp() + "]")} ${chalk8.cyan(name)} created ${chalk8.green("\u2192 synced \u2713")}`);
963
+ });
964
+ watcher.on("error", (err) => {
965
+ console.log(` ${chalk8.red("\u2717")} Watch error: ${err instanceof Error ? err.message : String(err)}`);
966
+ });
967
+ process.on("SIGINT", () => {
968
+ watcher.close();
969
+ console.log("");
970
+ console.log(` ${chalk8.dim("Stopped.")}`);
971
+ console.log("");
972
+ process.exit(0);
973
+ });
974
+ });
975
+
976
+ // src/commands/update.ts
977
+ import { Command as Command8 } from "commander";
978
+ import { select as select3, isCancel as isCancel4, cancel as cancel4, spinner as spinner2 } from "@clack/prompts";
979
+ import chalk9 from "chalk";
980
+ import { execSync } from "child_process";
981
+ import { createRequire } from "module";
982
+ import { fileURLToPath as fileURLToPath2 } from "url";
983
+ import { dirname as dirname4, join as join9 } from "path";
984
+ var __filename2 = fileURLToPath2(import.meta.url);
985
+ var __dirname2 = dirname4(__filename2);
986
+ var require2 = createRequire(import.meta.url);
987
+ function currentVersion() {
988
+ try {
989
+ const pkg = require2(join9(__dirname2, "..", "..", "package.json"));
990
+ return pkg.version;
991
+ } catch {
992
+ return "0.0.0";
993
+ }
994
+ }
995
+ async function latestVersion() {
996
+ try {
997
+ const { default: https } = await import("https");
998
+ return new Promise((resolve13) => {
999
+ const req = https.get(
1000
+ "https://registry.npmjs.org/mindlink/latest",
1001
+ { headers: { "User-Agent": "mindlink-cli" } },
1002
+ (res) => {
1003
+ let data = "";
1004
+ res.on("data", (chunk) => {
1005
+ data += chunk;
1006
+ });
1007
+ res.on("end", () => {
1008
+ try {
1009
+ const parsed = JSON.parse(data);
1010
+ resolve13(parsed.version ?? null);
1011
+ } catch {
1012
+ resolve13(null);
1013
+ }
1014
+ });
1015
+ }
1016
+ );
1017
+ req.on("error", () => resolve13(null));
1018
+ req.setTimeout(8e3, () => {
1019
+ req.destroy();
1020
+ resolve13(null);
1021
+ });
1022
+ });
1023
+ } catch {
1024
+ return null;
1025
+ }
1026
+ }
1027
+ function semverGt(a, b) {
1028
+ const pa = a.split(".").map(Number);
1029
+ const pb = b.split(".").map(Number);
1030
+ for (let i = 0; i < 3; i++) {
1031
+ if ((pa[i] ?? 0) > (pb[i] ?? 0)) return true;
1032
+ if ((pa[i] ?? 0) < (pb[i] ?? 0)) return false;
1033
+ }
1034
+ return false;
1035
+ }
1036
+ var updateCommand = new Command8("update").description("Update mindlink to the latest version").addHelpText("after", `
1037
+ Examples:
1038
+ mindlink update
1039
+ `).action(async () => {
1040
+ const current = currentVersion();
1041
+ if (!process.stdin.isTTY) {
1042
+ const latest2 = await latestVersion();
1043
+ if (!latest2) {
1044
+ console.log(JSON.stringify({ current, latest: null, upToDate: null }));
1045
+ process.exit(1);
1046
+ }
1047
+ const upToDate = !semverGt(latest2, current);
1048
+ console.log(JSON.stringify({ current, latest: latest2, upToDate }));
1049
+ if (!upToDate) process.exit(2);
1050
+ return;
1051
+ }
1052
+ const s = spinner2();
1053
+ s.start("Checking for updates...");
1054
+ const latest = await latestVersion();
1055
+ if (!latest) {
1056
+ s.stop("Could not reach npm registry.");
1057
+ console.log("");
1058
+ console.log(` ${chalk9.red("\u2717")} Could not check for updates. Check your internet connection.`);
1059
+ console.log(` ${chalk9.dim("Latest releases: github.com/404-not-found/mindlink/releases")}`);
1060
+ console.log("");
1061
+ process.exit(1);
1062
+ }
1063
+ s.stop("Done.");
1064
+ console.log("");
1065
+ console.log(` Current version : ${chalk9.dim(current)}`);
1066
+ console.log(` Latest version : ${semverGt(latest, current) ? chalk9.green(latest) : chalk9.dim(latest)}`);
1067
+ console.log("");
1068
+ if (!semverGt(latest, current)) {
1069
+ console.log(` ${chalk9.green("\u2713")} You're on the latest version (${current}).`);
1070
+ console.log("");
1071
+ return;
1072
+ }
1073
+ const action = await select3({
1074
+ message: `Update to ${latest}?`,
1075
+ options: [
1076
+ { value: "update", label: `Update to ${latest}` },
1077
+ { value: "skip", label: "Skip this version" },
1078
+ { value: "cancel", label: "Cancel" }
1079
+ ]
1080
+ });
1081
+ if (isCancel4(action) || action === "cancel" || action === "skip") {
1082
+ if (action === "skip") {
1083
+ console.log(` ${chalk9.dim("Skipped. Run mindlink update again to install later.")}`);
1084
+ } else {
1085
+ cancel4("Cancelled.");
1086
+ }
1087
+ console.log("");
1088
+ return;
1089
+ }
1090
+ const s2 = spinner2();
1091
+ s2.start(`Installing mindlink@${latest}...`);
1092
+ try {
1093
+ execSync(`npm install -g mindlink@${latest}`, { stdio: "pipe" });
1094
+ s2.stop("Done.");
1095
+ console.log("");
1096
+ console.log(` ${chalk9.green("\u2713")} Updated to ${latest}.`);
1097
+ console.log(` ${chalk9.dim("See what's new: github.com/404-not-found/mindlink/releases")}`);
1098
+ } catch (err) {
1099
+ s2.stop("Failed.");
1100
+ console.log("");
1101
+ console.log(` ${chalk9.red("\u2717")} Update failed.`);
1102
+ console.log(` ${chalk9.dim("Try: npm install -g mindlink@" + latest)}`);
1103
+ if (err instanceof Error && err.message.includes("EACCES")) {
1104
+ console.log(` ${chalk9.dim("Permission error \u2014 try: sudo npm install -g mindlink@" + latest)}`);
1105
+ }
1106
+ process.exit(1);
1107
+ }
1108
+ console.log("");
1109
+ });
1110
+
1111
+ // src/commands/summary.ts
1112
+ import { Command as Command9 } from "commander";
1113
+ import chalk10 from "chalk";
1114
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
1115
+ import { join as join10, resolve as resolve8 } from "path";
1116
+ var summaryCommand = new Command9("summary").description("Print a full briefing of what your AI knows \u2014 great for sharing or reviewing context").option("--json", "Output as JSON").addHelpText("after", `
1117
+ Examples:
1118
+ mindlink summary
1119
+ mindlink summary --json
1120
+
1121
+ Tip: your AI agent can run this itself \u2014 ask it to run "mindlink summary"
1122
+ to get a full briefing on the current project state.
1123
+ `).action((opts) => {
1124
+ const projectPath = resolve8(process.cwd());
1125
+ const brainDir = join10(projectPath, BRAIN_DIR);
1126
+ if (!existsSync8(brainDir)) {
1127
+ console.log(` ${chalk10.red("\u2717")} No .brain/ found in this directory.`);
1128
+ console.log(` Run ${chalk10.cyan("mindlink init")} to get started.`);
1129
+ console.log("");
1130
+ process.exit(1);
1131
+ }
1132
+ const memoryPath = join10(brainDir, "MEMORY.md");
1133
+ const sessionPath = join10(brainDir, "SESSION.md");
1134
+ const logPath = join10(brainDir, "LOG.md");
1135
+ const sharedPath = join10(brainDir, "SHARED.md");
1136
+ const memoryMd = existsSync8(memoryPath) ? readFileSync8(memoryPath, "utf8") : "";
1137
+ const sessionMd = existsSync8(sessionPath) ? readFileSync8(sessionPath, "utf8") : "";
1138
+ const logMd = existsSync8(logPath) ? readFileSync8(logPath, "utf8") : "";
1139
+ const sharedMd = existsSync8(sharedPath) ? readFileSync8(sharedPath, "utf8") : "";
1140
+ const projectOverview = extractSection(memoryMd, "Project Overview") || extractSection(memoryMd, "Project Identity") || extractSection(memoryMd, "What Is This Project");
1141
+ const techStack = extractSection(memoryMd, "Tech Stack") || extractSection(memoryMd, "Stack");
1142
+ const decisions = extractBullets(
1143
+ extractSection(memoryMd, "Key Decisions") || extractSection(memoryMd, "Decisions")
1144
+ );
1145
+ const rawTask = extractSection(sessionMd, "Current Task");
1146
+ const task = rawTask.startsWith("<!--") ? "" : rawTask;
1147
+ const inProg = extractBullets(extractSection(sessionMd, "In Progress"));
1148
+ const upNext = extractBullets(extractSection(sessionMd, "Up Next"));
1149
+ const blockers = extractBullets(extractSection(sessionMd, "Blockers"));
1150
+ const sessionCount = countLogEntries(logMd);
1151
+ const lastDate = lastLogDate(logMd);
1152
+ const recentLogs = parseLogEntries(logMd).slice(0, 3);
1153
+ const sharedLines = sharedMd.split("\n").filter((l) => l.trim() && !l.startsWith("#") && !l.startsWith("<!--") && !l.startsWith(">") && l !== "---").slice(0, 10);
1154
+ if (opts.json) {
1155
+ console.log(JSON.stringify({
1156
+ project: { overview: projectOverview, techStack, decisions },
1157
+ session: { currentTask: task, inProgress: inProg, upNext, blockers },
1158
+ log: { totalSessions: sessionCount, lastSession: lastDate, recent: recentLogs },
1159
+ shared: sharedLines
1160
+ }, null, 2));
1161
+ return;
1162
+ }
1163
+ console.log("");
1164
+ console.log(` ${chalk10.bold("\u25C9 MindLink Memory Summary")} ${chalk10.dim("\xB7")} ${chalk10.dim(projectPath)}`);
1165
+ console.log("");
1166
+ if (projectOverview) {
1167
+ console.log(` ${chalk10.bold("Project")}`);
1168
+ for (const line of projectOverview.split("\n").filter(Boolean)) {
1169
+ console.log(` ${chalk10.dim(line)}`);
1170
+ }
1171
+ console.log("");
1172
+ }
1173
+ if (techStack) {
1174
+ console.log(` ${chalk10.bold("Tech stack")}`);
1175
+ for (const line of techStack.split("\n").filter(Boolean)) {
1176
+ console.log(` ${chalk10.dim(line)}`);
1177
+ }
1178
+ console.log("");
1179
+ }
1180
+ if (decisions.length > 0) {
1181
+ console.log(` ${chalk10.bold("Key decisions")}`);
1182
+ for (const d of decisions.slice(0, 5)) {
1183
+ console.log(` ${chalk10.dim("\xB7")} ${d}`);
1184
+ }
1185
+ if (decisions.length > 5) {
1186
+ console.log(` ${chalk10.dim(` \u2026and ${decisions.length - 5} more in MEMORY.md`)}`);
1187
+ }
1188
+ console.log("");
1189
+ }
1190
+ if (task || inProg.length > 0 || upNext.length > 0) {
1191
+ console.log(` ${chalk10.bold("Current session")}`);
1192
+ if (task) console.log(` ${chalk10.cyan("\u25CE")} ${task}`);
1193
+ for (const item of inProg) console.log(` ${chalk10.yellow("\u25CF")} ${item}`);
1194
+ for (const item of blockers) console.log(` ${chalk10.red("\u2717")} ${item}`);
1195
+ for (const item of upNext) console.log(` ${chalk10.dim("\u2192")} ${item}`);
1196
+ console.log("");
1197
+ }
1198
+ if (sharedLines.length > 0) {
1199
+ console.log(` ${chalk10.bold("Shared context")} ${chalk10.dim("(from other sessions)")}`);
1200
+ for (const line of sharedLines) {
1201
+ console.log(` ${chalk10.dim(line)}`);
1202
+ }
1203
+ console.log("");
1204
+ }
1205
+ if (recentLogs.length > 0) {
1206
+ console.log(` ${chalk10.bold("Recent sessions")} ${chalk10.dim(`(${sessionCount} total)`)}`);
1207
+ for (const entry of recentLogs) {
1208
+ console.log(` ${chalk10.dim("\u2500\u2500")} ${chalk10.cyan(entry.heading)}`);
1209
+ if (entry.body) {
1210
+ const preview = entry.body.split("\n").filter(Boolean)[0] ?? "";
1211
+ if (preview) console.log(` ${chalk10.dim(preview.slice(0, 72))}`);
1212
+ }
1213
+ }
1214
+ console.log("");
1215
+ }
1216
+ if (!projectOverview && !task && sessionCount === 0 && sharedLines.length === 0) {
1217
+ console.log(` ${chalk10.dim("Memory files are blank \u2014 your AI hasn't written anything yet.")}`);
1218
+ console.log(` ${chalk10.dim("Start a session and let it run \u2014 it will fill these in automatically.")}`);
1219
+ console.log("");
1220
+ }
1221
+ console.log(` ${chalk10.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}`);
1222
+ console.log(` ${chalk10.dim("Powered by MindLink \u2014 github.com/404-not-found/mindlink")}`);
1223
+ console.log("");
1224
+ });
1225
+
1226
+ // src/commands/uninstall.ts
1227
+ import { Command as Command10 } from "commander";
1228
+ import { select as select4, isCancel as isCancel5, cancel as cancel5 } from "@clack/prompts";
1229
+ import chalk11 from "chalk";
1230
+ import { existsSync as existsSync9, readFileSync as readFileSync9, rmSync, unlinkSync as unlinkSync2 } from "fs";
1231
+ import { join as join11, resolve as resolve9 } from "path";
1232
+ var uninstallCommand = new Command10("uninstall").description("Remove MindLink from the current project").option("-y, --yes", "Skip confirmation and remove all project files").addHelpText("after", `
1233
+ What gets removed:
1234
+ - .brain/ folder (all memory files)
1235
+ - Agent instruction files (CLAUDE.md, CURSOR.md, etc.)
1236
+ - .claude/settings.json hook (if it was created by MindLink)
1237
+
1238
+ What stays:
1239
+ - The mindlink CLI itself (run: npm uninstall -g mindlink)
1240
+ - Any files MindLink did not create
1241
+
1242
+ Not what you need?
1243
+ mindlink clear \u2014 reset SESSION.md only (fresh session, keep everything else)
1244
+ mindlink reset \u2014 wipe memory back to blank (keep MindLink installed)
1245
+
1246
+ Examples:
1247
+ mindlink uninstall
1248
+ mindlink uninstall --yes
1249
+ `).action(async (opts) => {
1250
+ const projectPath = resolve9(process.cwd());
1251
+ const brainDir = join11(projectPath, BRAIN_DIR);
1252
+ if (!existsSync9(brainDir)) {
1253
+ console.log(` ${chalk11.red("\u2717")} No .brain/ found in this directory.`);
1254
+ console.log(` Nothing to uninstall here.`);
1255
+ console.log("");
1256
+ process.exit(1);
1257
+ }
1258
+ let agents = [];
1259
+ try {
1260
+ const cfg = JSON.parse(readFileSync9(join11(brainDir, "config.json"), "utf8"));
1261
+ agents = cfg.agents ?? [];
1262
+ } catch {
1263
+ agents = AGENTS.map((a) => a.value);
1264
+ }
1265
+ console.log("");
1266
+ if (!opts.yes) {
1267
+ console.log(` ${chalk11.yellow("!")} This will remove MindLink from this project:`);
1268
+ console.log(` ${chalk11.dim("\xB7 .brain/ (all memory files)")}`);
1269
+ for (const v of agents) {
1270
+ const agent = AGENTS.find((a) => a.value === v);
1271
+ if (agent) console.log(` ${chalk11.dim("\xB7 " + agent.destFile)}`);
1272
+ }
1273
+ if (agents.includes("claude")) {
1274
+ console.log(` ${chalk11.dim("\xB7 .claude/settings.json (hook)")}`);
1275
+ }
1276
+ console.log("");
1277
+ console.log(` ${chalk11.dim("The mindlink CLI itself is NOT removed.")}`);
1278
+ console.log(` ${chalk11.dim("To remove the CLI: npm uninstall -g mindlink")}`);
1279
+ console.log("");
1280
+ console.log(` ${chalk11.dim("Lighter options:")}`);
1281
+ console.log(` ${chalk11.dim(" mindlink clear \u2014 fresh session only (keeps all memory)")}`);
1282
+ console.log(` ${chalk11.dim(" mindlink reset \u2014 wipe memory (keeps MindLink installed)")}`);
1283
+ console.log("");
1284
+ const action = await select4({
1285
+ message: "Remove MindLink from this project?",
1286
+ options: [
1287
+ { value: "remove", label: "Yes, remove everything listed above" },
1288
+ { value: "cancel", label: "Cancel" }
1289
+ ]
1290
+ });
1291
+ if (isCancel5(action) || action === "cancel") {
1292
+ cancel5("Cancelled.");
1293
+ console.log("");
1294
+ return;
1295
+ }
1296
+ console.log("");
1297
+ }
1298
+ const removed = [];
1299
+ const errors = [];
1300
+ try {
1301
+ rmSync(brainDir, { recursive: true, force: true });
1302
+ removed.push(".brain/");
1303
+ } catch (err) {
1304
+ errors.push(`.brain/: ${err instanceof Error ? err.message : String(err)}`);
1305
+ }
1306
+ for (const v of agents) {
1307
+ const agent = AGENTS.find((a) => a.value === v);
1308
+ if (!agent) continue;
1309
+ const destPath = join11(projectPath, agent.destFile);
1310
+ if (existsSync9(destPath)) {
1311
+ try {
1312
+ unlinkSync2(destPath);
1313
+ removed.push(agent.destFile);
1314
+ } catch (err) {
1315
+ errors.push(`${agent.destFile}: ${err instanceof Error ? err.message : String(err)}`);
1316
+ }
1317
+ }
1318
+ }
1319
+ if (agents.includes("claude")) {
1320
+ const hookPath = join11(projectPath, ".claude", "settings.json");
1321
+ if (existsSync9(hookPath)) {
1322
+ try {
1323
+ unlinkSync2(hookPath);
1324
+ removed.push(".claude/settings.json");
1325
+ } catch {
1326
+ }
1327
+ }
1328
+ }
1329
+ for (const f of removed) console.log(` ${chalk11.green("\u2713")} ${f} removed.`);
1330
+ for (const e of errors) console.log(` ${chalk11.red("\u2717")} ${e}`);
1331
+ console.log("");
1332
+ console.log(` ${chalk11.dim("MindLink removed from this project.")}`);
1333
+ console.log(` ${chalk11.dim("To remove the CLI itself: npm uninstall -g mindlink")}`);
1334
+ console.log("");
1335
+ });
1336
+
1337
+ // src/commands/export.ts
1338
+ import { Command as Command11 } from "commander";
1339
+ import { text as text2, isCancel as isCancel6, cancel as cancel6 } from "@clack/prompts";
1340
+ import chalk12 from "chalk";
1341
+ import { existsSync as existsSync10, readdirSync as readdirSync2 } from "fs";
1342
+ import { join as join12, resolve as resolve10, basename as basename3 } from "path";
1343
+ import AdmZip from "adm-zip";
1344
+ var exportCommand = new Command11("export").description("Export .brain/ memory to a shareable zip file").option("--output <path>", "Directory or full path to save the zip file").option("--include-agents", "Also include agent instruction files (CLAUDE.md, CURSOR.md, etc.)").addHelpText("after", `
1345
+ What gets exported:
1346
+ - .brain/MEMORY.md \u2014 permanent project facts
1347
+ - .brain/SESSION.md \u2014 current session state
1348
+ - .brain/SHARED.md \u2014 shared context across sessions
1349
+ - .brain/LOG.md \u2014 full session history
1350
+ - .brain/config.json \u2014 settings (excluded by default; use --include-agents for agent files)
1351
+
1352
+ Use cases:
1353
+ Onboard a new teammate \u2014 send them the zip; they run: mindlink import brain.zip
1354
+ Back up before reset \u2014 export first, then mindlink reset
1355
+ Share project context \u2014 hand off to a consultant without giving repo access
1356
+
1357
+ Examples:
1358
+ mindlink export
1359
+ mindlink export --output ~/Desktop
1360
+ mindlink export --output ~/Desktop/my-app-brain.zip
1361
+ mindlink export --include-agents
1362
+ `).action(async (opts) => {
1363
+ const projectPath = resolve10(process.cwd());
1364
+ const brainDir = join12(projectPath, BRAIN_DIR);
1365
+ if (!existsSync10(brainDir)) {
1366
+ console.log("");
1367
+ console.log(` ${chalk12.red("\u2717")} No .brain/ found in this directory.`);
1368
+ console.log(` Run ${chalk12.cyan("mindlink init")} to get started.`);
1369
+ console.log("");
1370
+ process.exit(1);
1371
+ }
1372
+ console.log("");
1373
+ const projectName = basename3(projectPath);
1374
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1375
+ const defaultFilename = `${projectName}-brain-${date}.zip`;
1376
+ let outputPath;
1377
+ if (opts.output) {
1378
+ const given = resolve10(opts.output);
1379
+ if (existsSync10(given) && !given.endsWith(".zip")) {
1380
+ outputPath = join12(given, defaultFilename);
1381
+ } else {
1382
+ outputPath = given;
1383
+ }
1384
+ } else if (process.stdin.isTTY) {
1385
+ const answer = await text2({
1386
+ message: "Where should the zip be saved?",
1387
+ placeholder: projectPath,
1388
+ initialValue: projectPath,
1389
+ hint: `default filename: ${defaultFilename}`
1390
+ });
1391
+ if (isCancel6(answer)) {
1392
+ cancel6("Cancelled.");
1393
+ process.exit(0);
1394
+ }
1395
+ const dest = resolve10(answer);
1396
+ if (!dest.endsWith(".zip")) {
1397
+ outputPath = join12(dest, defaultFilename);
1398
+ } else {
1399
+ outputPath = dest;
1400
+ }
1401
+ } else {
1402
+ outputPath = join12(projectPath, defaultFilename);
1403
+ }
1404
+ const zip = new AdmZip();
1405
+ const brainFiles = ["MEMORY.md", "SESSION.md", "SHARED.md", "LOG.md"];
1406
+ const included = [];
1407
+ const skipped = [];
1408
+ for (const file of brainFiles) {
1409
+ const filePath = join12(brainDir, file);
1410
+ if (existsSync10(filePath)) {
1411
+ zip.addLocalFile(filePath, ".brain");
1412
+ included.push(`.brain/${file}`);
1413
+ } else {
1414
+ skipped.push(`.brain/${file}`);
1415
+ }
1416
+ }
1417
+ const archiveFiles = readdirSync2(brainDir).filter((f) => /^LOG-\d{4}-\d{2}-\d{2}\.md$/.test(f));
1418
+ for (const file of archiveFiles) {
1419
+ zip.addLocalFile(join12(brainDir, file), ".brain");
1420
+ included.push(`.brain/${file}`);
1421
+ }
1422
+ if (opts.includeAgents) {
1423
+ for (const agent of AGENTS) {
1424
+ const agentPath = join12(projectPath, agent.destFile);
1425
+ if (existsSync10(agentPath)) {
1426
+ const dir = agent.destFile.includes("/") ? agent.destFile.split("/").slice(0, -1).join("/") : "";
1427
+ zip.addLocalFile(agentPath, dir);
1428
+ included.push(agent.destFile);
1429
+ }
1430
+ }
1431
+ }
1432
+ try {
1433
+ zip.writeZip(outputPath);
1434
+ } catch (err) {
1435
+ console.log(` ${chalk12.red("\u2717")} Could not write zip: ${err instanceof Error ? err.message : String(err)}`);
1436
+ console.log("");
1437
+ process.exit(1);
1438
+ }
1439
+ for (const f of included) console.log(` ${chalk12.green("\u2713")} ${f}`);
1440
+ if (skipped.length > 0) {
1441
+ for (const f of skipped) console.log(` ${chalk12.dim("\u2013")} ${f} ${chalk12.dim("(not found, skipped)")}`);
1442
+ }
1443
+ console.log("");
1444
+ console.log(` ${chalk12.green("\u2713")} Exported to: ${chalk12.bold(outputPath)}`);
1445
+ console.log("");
1446
+ console.log(` ${chalk12.dim("To import on another machine or project:")}`);
1447
+ console.log(` ${chalk12.cyan(`mindlink import ${basename3(outputPath)}`)}`);
1448
+ console.log("");
1449
+ });
1450
+
1451
+ // src/commands/import.ts
1452
+ import { Command as Command12 } from "commander";
1453
+ import { select as select5, isCancel as isCancel7, cancel as cancel7 } from "@clack/prompts";
1454
+ import chalk13 from "chalk";
1455
+ import { existsSync as existsSync11, mkdirSync as mkdirSync3 } from "fs";
1456
+ import { join as join13, resolve as resolve11, extname } from "path";
1457
+ import AdmZip2 from "adm-zip";
1458
+ var importCommand = new Command12("import").description("Import a MindLink memory zip into the current project").argument("<file>", "Path to the .zip file exported by mindlink export").option("-y, --yes", "Skip confirmation and overwrite existing memory").addHelpText("after", `
1459
+ What gets imported:
1460
+ All .brain/ files found in the zip (MEMORY.md, SESSION.md, SHARED.md, LOG.md,
1461
+ and any LOG archive files). Agent instruction files are also imported if the zip
1462
+ contains them.
1463
+
1464
+ If .brain/ already exists, you will be asked whether to:
1465
+ Merge \u2014 import only files that don't exist yet (keeps your current memory)
1466
+ Overwrite \u2014 replace everything (use when onboarding from a teammate's export)
1467
+ Cancel \u2014 do nothing
1468
+
1469
+ Use cases:
1470
+ Onboard on a new machine \u2014 copy the zip, run: mindlink import brain.zip
1471
+ Restore from backup \u2014 mindlink import my-app-brain-2026-04-10.zip
1472
+ Accept a colleague's brain \u2014 merge their context into your project
1473
+
1474
+ Examples:
1475
+ mindlink import my-app-brain-2026-04-10.zip
1476
+ mindlink import ~/Desktop/my-app-brain-2026-04-10.zip --yes
1477
+ `).action(async (file, opts) => {
1478
+ const projectPath = resolve11(process.cwd());
1479
+ const brainDir = join13(projectPath, BRAIN_DIR);
1480
+ const zipPath = resolve11(file);
1481
+ console.log("");
1482
+ if (!existsSync11(zipPath)) {
1483
+ console.log(` ${chalk13.red("\u2717")} File not found: ${zipPath}`);
1484
+ console.log("");
1485
+ process.exit(1);
1486
+ }
1487
+ if (extname(zipPath) !== ".zip") {
1488
+ console.log(` ${chalk13.red("\u2717")} Expected a .zip file. Got: ${zipPath}`);
1489
+ console.log("");
1490
+ process.exit(1);
1491
+ }
1492
+ let zip;
1493
+ try {
1494
+ zip = new AdmZip2(zipPath);
1495
+ } catch (err) {
1496
+ console.log(` ${chalk13.red("\u2717")} Could not read zip: ${err instanceof Error ? err.message : String(err)}`);
1497
+ console.log("");
1498
+ process.exit(1);
1499
+ }
1500
+ const entries = zip.getEntries();
1501
+ const brainEntries = entries.filter((e) => e.entryName.startsWith(".brain/") && !e.isDirectory);
1502
+ if (brainEntries.length === 0) {
1503
+ console.log(` ${chalk13.red("\u2717")} This zip doesn't contain any .brain/ files.`);
1504
+ console.log(` Make sure it was created with ${chalk13.cyan("mindlink export")}.`);
1505
+ console.log("");
1506
+ process.exit(1);
1507
+ }
1508
+ let mode = "overwrite";
1509
+ if (existsSync11(brainDir)) {
1510
+ if (opts.yes) {
1511
+ mode = "overwrite";
1512
+ } else {
1513
+ console.log(` ${chalk13.yellow("!")} .brain/ already exists in this project.`);
1514
+ console.log("");
1515
+ const action = await select5({
1516
+ message: "How should the import handle existing memory?",
1517
+ options: [
1518
+ { value: "merge", label: "Merge", hint: "add files that don't exist yet \u2014 keep your current memory" },
1519
+ { value: "overwrite", label: "Overwrite", hint: "replace everything with the imported version" },
1520
+ { value: "cancel", label: "Cancel", hint: "" }
1521
+ ]
1522
+ });
1523
+ if (isCancel7(action) || action === "cancel") {
1524
+ cancel7("Cancelled.");
1525
+ console.log("");
1526
+ return;
1527
+ }
1528
+ mode = action;
1529
+ console.log("");
1530
+ }
1531
+ }
1532
+ mkdirSync3(brainDir, { recursive: true });
1533
+ const written = [];
1534
+ const skipped = [];
1535
+ for (const entry of entries) {
1536
+ if (entry.isDirectory) continue;
1537
+ const destPath = join13(projectPath, entry.entryName);
1538
+ const destDir = join13(projectPath, entry.entryName.split("/").slice(0, -1).join("/"));
1539
+ if (mode === "merge" && existsSync11(destPath)) {
1540
+ skipped.push(entry.entryName);
1541
+ continue;
1542
+ }
1543
+ mkdirSync3(destDir, { recursive: true });
1544
+ zip.extractEntryTo(entry, destDir, false, true);
1545
+ written.push(entry.entryName);
1546
+ }
1547
+ for (const f of written) console.log(` ${chalk13.green("\u2713")} ${f}`);
1548
+ for (const f of skipped) console.log(` ${chalk13.dim("\u2013")} ${f} ${chalk13.dim("(already exists, kept)")}`);
1549
+ console.log("");
1550
+ if (written.length === 0) {
1551
+ console.log(` ${chalk13.dim("Nothing imported \u2014 all files already exist (merge mode).")}`);
1552
+ } else {
1553
+ console.log(` ${chalk13.green("\u2713")} Brain transplant complete. Your AI wakes up knowing everything.`);
1554
+ if (!existsSync11(join13(brainDir, "../CLAUDE.md")) && !existsSync11(join13(brainDir, "../CURSOR.md"))) {
1555
+ console.log(` ${chalk13.dim("No agent instruction files found \u2014 run mindlink init to wire them up.")}`);
1556
+ }
1557
+ }
1558
+ console.log("");
1559
+ });
1560
+
1561
+ // src/commands/doctor.ts
1562
+ import { Command as Command13 } from "commander";
1563
+ import chalk14 from "chalk";
1564
+ import { existsSync as existsSync12, readFileSync as readFileSync10, statSync as statSync3, readdirSync as readdirSync3 } from "fs";
1565
+ import { join as join14, resolve as resolve12 } from "path";
1566
+ var CORE_LINE_LIMIT = 50;
1567
+ var CORE_WARN_THRESHOLD = 40;
1568
+ function ok(label, detail) {
1569
+ return { status: "ok", label, detail };
1570
+ }
1571
+ function warn(label, detail) {
1572
+ return { status: "warn", label, detail };
1573
+ }
1574
+ function fail(label, detail) {
1575
+ return { status: "fail", label, detail };
1576
+ }
1577
+ function info(label, detail) {
1578
+ return { status: "info", label, detail };
1579
+ }
1580
+ function icon(status) {
1581
+ switch (status) {
1582
+ case "ok":
1583
+ return chalk14.green("\u2713");
1584
+ case "warn":
1585
+ return chalk14.yellow("!");
1586
+ case "fail":
1587
+ return chalk14.red("\u2717");
1588
+ case "info":
1589
+ return chalk14.dim("\xB7");
1590
+ }
1591
+ }
1592
+ var doctorCommand = new Command13("doctor").description("Check that your MindLink setup is healthy").addHelpText("after", `
1593
+ What gets checked:
1594
+ .brain/ \u2014 exists and contains all expected files
1595
+ MEMORY.md \u2014 Core has content; warns if Core is getting too long to reliably read
1596
+ SESSION.md \u2014 has content (agent is updating it)
1597
+ LOG.md \u2014 session count, how far back history goes, warns when oldest sessions near rotation
1598
+ Agent files \u2014 instruction files exist per your config.json
1599
+ Hook \u2014 .claude/settings.json exists (if Claude Code is configured)
1600
+
1601
+ Examples:
1602
+ mindlink doctor
1603
+ `).action(() => {
1604
+ const projectPath = resolve12(process.cwd());
1605
+ const brainDir = join14(projectPath, BRAIN_DIR);
1606
+ console.log("");
1607
+ console.log(` ${chalk14.bold("\u25C9 MindLink Doctor")}`);
1608
+ console.log(` ${chalk14.dim(projectPath)}`);
1609
+ console.log("");
1610
+ const checks = [];
1611
+ let failCount = 0;
1612
+ let warnCount = 0;
1613
+ if (!existsSync12(brainDir)) {
1614
+ checks.push(fail(".brain/ missing", `Run ${chalk14.cyan("mindlink init")} to get started.`));
1615
+ printChecks(checks);
1616
+ process.exit(1);
1617
+ }
1618
+ checks.push(ok(".brain/ found"));
1619
+ const configPath = join14(brainDir, "config.json");
1620
+ let config = {};
1621
+ if (!existsSync12(configPath)) {
1622
+ checks.push(warn("config.json missing", `Run ${chalk14.cyan("mindlink config")} to repair.`));
1623
+ } else {
1624
+ try {
1625
+ config = JSON.parse(readFileSync10(configPath, "utf8"));
1626
+ checks.push(ok("config.json valid"));
1627
+ } catch {
1628
+ checks.push(warn("config.json unreadable", "File may be corrupted \u2014 delete and re-run mindlink init."));
1629
+ }
1630
+ }
1631
+ const memoryPath = join14(brainDir, "MEMORY.md");
1632
+ if (!existsSync12(memoryPath)) {
1633
+ checks.push(fail("MEMORY.md missing", `Run ${chalk14.cyan("mindlink init")} to recreate it.`));
1634
+ } else {
1635
+ const memoryMd = readFileSync10(memoryPath, "utf8");
1636
+ const coreSection = extractSection(memoryMd, "Core");
1637
+ const coreLines = coreSection.split("\n").filter((l) => l.trim().length > 0 && !l.startsWith("<!--")).length;
1638
+ if (coreLines === 0) {
1639
+ checks.push(warn("MEMORY.md Core is empty", "Your AI has no permanent facts yet \u2014 ask it to fill in MEMORY.md after your next session."));
1640
+ } else if (coreLines >= CORE_LINE_LIMIT) {
1641
+ checks.push(warn(
1642
+ "MEMORY.md Core is getting too long",
1643
+ "Ask your AI to consolidate \u2014 Core is read every session start, so keep it tight."
1644
+ ));
1645
+ } else if (coreLines >= CORE_WARN_THRESHOLD) {
1646
+ checks.push(warn(
1647
+ "MEMORY.md Core is getting long",
1648
+ "Consider asking your AI to consolidate \u2014 Core is read on every session start."
1649
+ ));
1650
+ } else {
1651
+ checks.push(ok("MEMORY.md Core has content"));
1652
+ }
1653
+ }
1654
+ const sessionPath = join14(brainDir, "SESSION.md");
1655
+ if (!existsSync12(sessionPath)) {
1656
+ checks.push(fail("SESSION.md missing", `Run ${chalk14.cyan("mindlink init")} to recreate it.`));
1657
+ } else {
1658
+ const sessionMd = readFileSync10(sessionPath, "utf8");
1659
+ const hasContent = sessionMd.split("\n").some((l) => l.trim().length > 0 && !l.startsWith("<!--") && !l.startsWith("#"));
1660
+ const mtime = statSync3(sessionPath).mtime;
1661
+ const age = relativeTime(mtime);
1662
+ if (!hasContent) {
1663
+ checks.push(warn("SESSION.md has no content yet", "Start an AI session \u2014 it will fill this in automatically."));
1664
+ } else {
1665
+ checks.push(ok(`SESSION.md \u2014 updated ${age}`));
1666
+ }
1667
+ }
1668
+ const logPath = join14(brainDir, "LOG.md");
1669
+ if (!existsSync12(logPath)) {
1670
+ checks.push(fail("LOG.md missing", `Run ${chalk14.cyan("mindlink init")} to recreate it.`));
1671
+ } else {
1672
+ const logMd = readFileSync10(logPath, "utf8");
1673
+ const entries = parseLogEntries(logMd);
1674
+ const entryCount = entries.length;
1675
+ const maxEntries = config.maxLogEntries ?? 50;
1676
+ if (entryCount === 0) {
1677
+ checks.push(info("LOG.md \u2014 no sessions yet, start your first AI session"));
1678
+ } else {
1679
+ const newestHeading = entries[entries.length - 1].heading;
1680
+ const oldestHeading = entries[0].heading;
1681
+ const remaining = maxEntries - entryCount;
1682
+ if (remaining <= 3 && remaining > 0) {
1683
+ checks.push(warn(
1684
+ `LOG.md \u2014 ${entryCount}/${maxEntries} sessions, ${remaining} until oldest start archiving`,
1685
+ `Oldest session on record: ${oldestHeading} \u2014 it will be archived soon. Important decisions belong in MEMORY.md where they never rotate out.`
1686
+ ));
1687
+ } else if (remaining <= 0) {
1688
+ checks.push(warn(
1689
+ `LOG.md \u2014 at ${maxEntries}-session limit, oldest are being archived`,
1690
+ `Oldest session on record: ${oldestHeading}. Anything important should be in MEMORY.md.`
1691
+ ));
1692
+ } else {
1693
+ checks.push(ok(`LOG.md \u2014 ${entryCount} sessions logged (last: ${newestHeading}, going back to: ${oldestHeading})`));
1694
+ }
1695
+ }
1696
+ const archives = readdirSync3(brainDir).filter((f) => /^LOG-\d{4}-\d{2}\.md$/.test(f));
1697
+ if (archives.length > 0) {
1698
+ checks.push(info(`${archives.length} archive file${archives.length !== 1 ? "s" : ""} \u2014 old sessions are stored in LOG-*.md, not gone`));
1699
+ }
1700
+ }
1701
+ const configuredAgents = config.agents ?? [];
1702
+ if (configuredAgents.length === 0) {
1703
+ checks.push(warn("No agents configured", `Run ${chalk14.cyan("mindlink config")} \u2192 Agent instruction files.`));
1704
+ } else {
1705
+ for (const agentValue of configuredAgents) {
1706
+ const agent = AGENTS.find((a) => a.value === agentValue);
1707
+ if (!agent) continue;
1708
+ const destPath = join14(projectPath, agent.destFile);
1709
+ if (!existsSync12(destPath)) {
1710
+ checks.push(fail(`${agent.destFile} missing`, `Run ${chalk14.cyan("mindlink config")} \u2192 Agent instruction files to recreate.`));
1711
+ } else {
1712
+ checks.push(ok(`${agent.destFile} \u2014 ${agent.label}`));
1713
+ }
1714
+ }
1715
+ }
1716
+ if (configuredAgents.includes("claude")) {
1717
+ const hookPath = join14(projectPath, ".claude", "settings.json");
1718
+ if (!existsSync12(hookPath)) {
1719
+ checks.push(warn(
1720
+ ".claude/settings.json missing",
1721
+ `Claude Code won't auto-reload after context compaction. Run ${chalk14.cyan("mindlink config")} \u2192 Agent instruction files to restore.`
1722
+ ));
1723
+ } else {
1724
+ try {
1725
+ const settings = JSON.parse(readFileSync10(hookPath, "utf8"));
1726
+ const hasHook = settings?.hooks?.UserPromptSubmit != null;
1727
+ if (!hasHook) {
1728
+ checks.push(warn(".claude/settings.json exists but MindLink hook not found", "Hook may have been removed \u2014 check the file manually."));
1729
+ } else {
1730
+ checks.push(ok(".claude/settings.json \u2014 UserPromptSubmit hook active"));
1731
+ }
1732
+ } catch {
1733
+ checks.push(warn(".claude/settings.json is not valid JSON", "Fix or delete it to restore the hook."));
1734
+ }
1735
+ }
1736
+ }
1737
+ if (config.gitTracking === false) {
1738
+ checks.push(info(".brain/ is excluded from git (personal memory only)"));
1739
+ } else if (config.gitTracking === true) {
1740
+ checks.push(info(".brain/ is committed to git (shared team memory)"));
1741
+ }
1742
+ for (const c of checks) {
1743
+ if (c.status === "fail") failCount++;
1744
+ if (c.status === "warn") warnCount++;
1745
+ }
1746
+ printChecks(checks);
1747
+ if (failCount > 0) {
1748
+ console.log(` ${chalk14.red.bold(`${failCount} problem${failCount !== 1 ? "s" : ""} found`)} \u2014 fix the issues above and re-run ${chalk14.cyan("mindlink doctor")}.`);
1749
+ } else if (warnCount > 0) {
1750
+ console.log(` ${chalk14.yellow.bold(`${warnCount} warning${warnCount !== 1 ? "s" : ""}`)}, no critical issues. Your AI will still work.`);
1751
+ } else {
1752
+ console.log(` ${chalk14.green.bold("All good.")} Your AI has a healthy brain.`);
1753
+ }
1754
+ console.log("");
1755
+ if (failCount > 0) process.exit(1);
1756
+ });
1757
+ function printChecks(checks) {
1758
+ for (const c of checks) {
1759
+ const prefix = ` ${icon(c.status)} `;
1760
+ console.log(`${prefix}${c.label}`);
1761
+ if (c.detail) {
1762
+ console.log(` ${chalk14.dim(c.detail)}`);
1763
+ }
1764
+ }
1765
+ console.log("");
1766
+ }
1767
+
1768
+ // src/cli.ts
1769
+ var program = new Command14();
1770
+ program.name("mindlink").description("Give your AI a brain.").version("1.0.0", "-v, --version");
1771
+ program.addCommand(initCommand);
1772
+ program.addCommand(statusCommand);
1773
+ program.addCommand(logCommand);
1774
+ program.addCommand(clearCommand);
1775
+ program.addCommand(resetCommand);
1776
+ program.addCommand(configCommand);
1777
+ program.addCommand(syncCommand);
1778
+ program.addCommand(updateCommand);
1779
+ program.addCommand(summaryCommand);
1780
+ program.addCommand(uninstallCommand);
1781
+ program.addCommand(exportCommand);
1782
+ program.addCommand(importCommand);
1783
+ program.addCommand(doctorCommand);
1784
+ program.on("command:*", (operands) => {
1785
+ const unknown = operands[0];
1786
+ const known = ["init", "status", "log", "clear", "reset", "config", "sync", "update", "summary", "uninstall", "export", "import", "doctor"];
1787
+ function levenshtein(a, b) {
1788
+ const m = a.length, n = b.length;
1789
+ const dp = Array.from(
1790
+ { length: m + 1 },
1791
+ (_, i) => Array.from({ length: n + 1 }, (_2, j) => i === 0 ? j : j === 0 ? i : 0)
1792
+ );
1793
+ for (let i = 1; i <= m; i++) {
1794
+ for (let j = 1; j <= n; j++) {
1795
+ dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
1796
+ }
1797
+ }
1798
+ return dp[m][n];
1799
+ }
1800
+ const closest = known.map((cmd) => ({ cmd, dist: levenshtein(unknown, cmd) })).sort((a, b) => a.dist - b.dist)[0];
1801
+ console.log("");
1802
+ console.log(` ${chalk15.red("\u2717")} Unknown command: ${chalk15.bold(unknown)}`);
1803
+ if (closest && closest.dist <= 3) {
1804
+ console.log(` Did you mean ${chalk15.cyan("mindlink " + closest.cmd)}?`);
1805
+ }
1806
+ console.log(` Run ${chalk15.cyan("mindlink --help")} to see all commands.`);
1807
+ console.log("");
1808
+ process.exit(1);
1809
+ });
1810
+ program.parse();
1811
+ //# sourceMappingURL=cli.js.map