mindlink 1.1.4 → 1.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 +51 -10
- package/commands/diff.md +115 -0
- package/commands/index.md +1 -0
- package/commands/init.md +26 -1
- package/commands/update.md +23 -5
- package/dist/cli.js +446 -57
- package/dist/cli.js.map +1 -1
- package/dist/templates/agents/.clinerules +43 -9
- package/dist/templates/agents/.rules +75 -0
- package/dist/templates/agents/.windsurfrules +43 -9
- package/dist/templates/agents/AGENTS.md +46 -9
- package/dist/templates/agents/CLAUDE.md +75 -13
- package/dist/templates/agents/CONVENTIONS.md +43 -9
- package/dist/templates/agents/CURSOR.md +46 -9
- package/dist/templates/agents/GEMINI.md +46 -9
- package/dist/templates/agents/continue-rules.md +80 -0
- package/dist/templates/agents/copilot-instructions.md +41 -9
- package/dist/templates/agents/kiro-steering.md +79 -0
- package/dist/templates/agents/trae-rules.md +80 -0
- package/dist/templates/brain/MEMORY.md +12 -0
- package/dist/templates/hooks/claude-settings.json +13 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { Command as
|
|
5
|
-
import
|
|
4
|
+
import { Command as Command16 } from "commander";
|
|
5
|
+
import chalk17 from "chalk";
|
|
6
6
|
|
|
7
7
|
// src/utils/version.ts
|
|
8
|
-
var VERSION = "1.1.
|
|
8
|
+
var VERSION = "1.1.5";
|
|
9
9
|
|
|
10
10
|
// src/commands/init.ts
|
|
11
11
|
import { Command } from "commander";
|
|
@@ -24,8 +24,10 @@ import {
|
|
|
24
24
|
mkdirSync as mkdirSync2,
|
|
25
25
|
readFileSync as readFileSync2,
|
|
26
26
|
writeFileSync as writeFileSync2,
|
|
27
|
-
appendFileSync
|
|
27
|
+
appendFileSync,
|
|
28
|
+
readdirSync
|
|
28
29
|
} from "fs";
|
|
30
|
+
import { execSync } from "child_process";
|
|
29
31
|
import { join as join3, resolve, dirname as dirname2, basename } from "path";
|
|
30
32
|
|
|
31
33
|
// src/utils/paths.ts
|
|
@@ -58,7 +60,11 @@ var AGENTS = [
|
|
|
58
60
|
{ value: "copilot", label: "GitHub Copilot", hint: ".github/copilot-instructions.md", templateFile: "copilot-instructions.md", destFile: ".github/copilot-instructions.md", selected: true },
|
|
59
61
|
{ value: "windsurf", label: "Windsurf", hint: ".windsurfrules", templateFile: ".windsurfrules", destFile: ".windsurfrules", selected: true },
|
|
60
62
|
{ value: "cline", label: "Cline", hint: ".clinerules", templateFile: ".clinerules", destFile: ".clinerules", selected: false },
|
|
61
|
-
{ value: "aider", label: "Aider", hint: "CONVENTIONS.md", templateFile: "CONVENTIONS.md", destFile: "CONVENTIONS.md", selected: false }
|
|
63
|
+
{ value: "aider", label: "Aider", hint: "CONVENTIONS.md", templateFile: "CONVENTIONS.md", destFile: "CONVENTIONS.md", selected: false },
|
|
64
|
+
{ value: "zed", label: "Zed", hint: ".rules", templateFile: ".rules", destFile: ".rules", selected: false },
|
|
65
|
+
{ value: "kiro", label: "Kiro", hint: ".kiro/steering/mindlink.md", templateFile: "kiro-steering.md", destFile: ".kiro/steering/mindlink.md", selected: false },
|
|
66
|
+
{ value: "continue", label: "Continue.dev", hint: ".continue/rules/mindlink.md", templateFile: "continue-rules.md", destFile: ".continue/rules/mindlink.md", selected: false },
|
|
67
|
+
{ value: "trae", label: "Trae", hint: ".trae/rules/mindlink.md", templateFile: "trae-rules.md", destFile: ".trae/rules/mindlink.md", selected: false }
|
|
62
68
|
];
|
|
63
69
|
|
|
64
70
|
// src/utils/registry.ts
|
|
@@ -100,13 +106,21 @@ function detectProjectInfo(projectPath) {
|
|
|
100
106
|
let name = basename(projectPath);
|
|
101
107
|
let description = "";
|
|
102
108
|
let stack = "";
|
|
109
|
+
let recentActivity = "";
|
|
110
|
+
let topDirs = "";
|
|
103
111
|
const pkgPath = join3(projectPath, "package.json");
|
|
104
112
|
if (existsSync2(pkgPath)) {
|
|
105
113
|
try {
|
|
106
114
|
const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
107
115
|
if (pkg.name) name = pkg.name;
|
|
108
116
|
if (pkg.description) description = pkg.description;
|
|
109
|
-
|
|
117
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
118
|
+
const layers = ["Node.js"];
|
|
119
|
+
if (deps["typescript"] || deps["ts-node"] || deps["tsup"]) layers.push("TypeScript");
|
|
120
|
+
if (deps["react"] || deps["next"]) layers.push(deps["next"] ? "Next.js" : "React");
|
|
121
|
+
if (deps["express"] || deps["fastify"] || deps["koa"]) layers.push("Express/Fastify");
|
|
122
|
+
if (deps["vite"]) layers.push("Vite");
|
|
123
|
+
stack = layers.join(" + ");
|
|
110
124
|
} catch {
|
|
111
125
|
}
|
|
112
126
|
}
|
|
@@ -119,7 +133,48 @@ function detectProjectInfo(projectPath) {
|
|
|
119
133
|
else if (existsSync2(join3(projectPath, "composer.json"))) stack = "PHP";
|
|
120
134
|
else if (existsSync2(join3(projectPath, "Gemfile"))) stack = "Ruby";
|
|
121
135
|
}
|
|
122
|
-
|
|
136
|
+
if (!description) {
|
|
137
|
+
const readmePath = join3(projectPath, "README.md");
|
|
138
|
+
if (existsSync2(readmePath)) {
|
|
139
|
+
try {
|
|
140
|
+
const lines = readFileSync2(readmePath, "utf8").split("\n");
|
|
141
|
+
for (const line of lines.slice(0, 30)) {
|
|
142
|
+
const trimmed = line.trim();
|
|
143
|
+
if (trimmed && !trimmed.startsWith("#") && !trimmed.startsWith("!") && !trimmed.startsWith("<") && trimmed.length > 10) {
|
|
144
|
+
description = trimmed.replace(/\*\*/g, "").slice(0, 120);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const log = execSync("git log --oneline -5", { cwd: projectPath, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
154
|
+
if (log) {
|
|
155
|
+
recentActivity = log.split("\n").slice(0, 3).join(" \xB7 ");
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
const SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".brain", "binaries", ".cache", "coverage", ".next", "out"]);
|
|
160
|
+
try {
|
|
161
|
+
const entries = readdirSync(projectPath, { withFileTypes: true });
|
|
162
|
+
const dirs = entries.filter((e) => e.isDirectory() && !SKIP_DIRS.has(e.name) && !e.name.startsWith(".")).map((e) => e.name).slice(0, 6);
|
|
163
|
+
if (dirs.length > 0) topDirs = dirs.join(", ");
|
|
164
|
+
} catch {
|
|
165
|
+
}
|
|
166
|
+
return { name, description, stack, recentActivity, topDirs, date };
|
|
167
|
+
}
|
|
168
|
+
function memoryHasRealContent(memoryPath) {
|
|
169
|
+
try {
|
|
170
|
+
const lines = readFileSync2(memoryPath, "utf8").split("\n");
|
|
171
|
+
return lines.some((line) => {
|
|
172
|
+
const t = line.trim();
|
|
173
|
+
return t.length > 0 && !t.startsWith("#") && !t.startsWith("<!--") && !t.startsWith(">") && !t.startsWith("|") && t !== "---";
|
|
174
|
+
});
|
|
175
|
+
} catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
123
178
|
}
|
|
124
179
|
function buildMemoryMd(templateContent, info2) {
|
|
125
180
|
let content = templateContent;
|
|
@@ -138,10 +193,15 @@ ${info2.stack}
|
|
|
138
193
|
<!-- Add layers: Frontend, Backend, Infra, etc. -->`
|
|
139
194
|
);
|
|
140
195
|
}
|
|
196
|
+
const focusLines = [];
|
|
197
|
+
if (info2.topDirs) focusLines.push(`Directories: ${info2.topDirs}`);
|
|
198
|
+
if (info2.recentActivity) focusLines.push(`Recent commits: ${info2.recentActivity}`);
|
|
199
|
+
const focusBlock = focusLines.length > 0 ? focusLines.join("\n") + `
|
|
200
|
+
<!-- Initialized ${info2.date} \u2014 update to reflect the current active focus -->` : `<!-- Initialized ${info2.date} \u2014 ask your AI to fill this in after your first session -->`;
|
|
141
201
|
content = content.replace(
|
|
142
202
|
/### Current focus\n<!--[^]*?-->/,
|
|
143
203
|
`### Current focus
|
|
144
|
-
|
|
204
|
+
${focusBlock}`
|
|
145
205
|
);
|
|
146
206
|
return content;
|
|
147
207
|
}
|
|
@@ -161,41 +221,155 @@ Examples:
|
|
|
161
221
|
const brainDir = join3(projectPath, BRAIN_DIR);
|
|
162
222
|
printBanner();
|
|
163
223
|
if (existsSync2(brainDir)) {
|
|
164
|
-
|
|
224
|
+
const memoryPath = join3(brainDir, "MEMORY.md");
|
|
225
|
+
const hasMemory = existsSync2(memoryPath) && memoryHasRealContent(memoryPath);
|
|
226
|
+
if (!opts.yes && hasMemory) {
|
|
227
|
+
const hasAnyAgentFile = AGENTS.some((a) => existsSync2(join3(projectPath, a.destFile)));
|
|
228
|
+
if (!hasAnyAgentFile) {
|
|
229
|
+
console.log("");
|
|
230
|
+
console.log(` ${chalk2.cyan("\u25C9")} MindLink memory found in this project.`);
|
|
231
|
+
console.log(` ${chalk2.dim("MEMORY.md has content \u2014 this looks like a team project.")}`);
|
|
232
|
+
console.log("");
|
|
233
|
+
const action = await select({
|
|
234
|
+
message: "What would you like to do?",
|
|
235
|
+
options: [
|
|
236
|
+
{ value: "restore", label: "Set up agent files", hint: "recommended for new team members \u2014 writes CLAUDE.md, .cursorrules, etc." },
|
|
237
|
+
{ value: "reinit", label: "Full re-init", hint: "recreate everything, reconfigure settings" },
|
|
238
|
+
{ value: "exit", label: "Cancel", hint: "" }
|
|
239
|
+
]
|
|
240
|
+
});
|
|
241
|
+
if (isCancel(action) || action === "exit") {
|
|
242
|
+
process.exit(0);
|
|
243
|
+
}
|
|
244
|
+
if (action === "restore") {
|
|
245
|
+
const agentChoices = AGENTS.map((a) => ({
|
|
246
|
+
value: a.value,
|
|
247
|
+
label: `${a.label.padEnd(18)} ${chalk2.dim(a.hint)}`,
|
|
248
|
+
hint: a.selected ? "recommended" : void 0
|
|
249
|
+
}));
|
|
250
|
+
const agentResult = await multiselect({
|
|
251
|
+
message: "Which AI agents do you use?",
|
|
252
|
+
options: agentChoices,
|
|
253
|
+
initialValues: AGENTS.filter((a) => a.selected).map((a) => a.value),
|
|
254
|
+
required: false
|
|
255
|
+
});
|
|
256
|
+
if (isCancel(agentResult)) {
|
|
257
|
+
cancel("Cancelled.");
|
|
258
|
+
process.exit(0);
|
|
259
|
+
}
|
|
260
|
+
const toRestore = agentResult;
|
|
261
|
+
const restored = [];
|
|
262
|
+
for (const agentValue of toRestore) {
|
|
263
|
+
const agent = AGENTS.find((a) => a.value === agentValue);
|
|
264
|
+
if (!agent) continue;
|
|
265
|
+
const destPath = join3(projectPath, agent.destFile);
|
|
266
|
+
mkdirSync2(dirname2(destPath), { recursive: true });
|
|
267
|
+
writeFileSync2(destPath, readFileSync2(join3(AGENT_TEMPLATES_DIR, agent.templateFile), "utf8"));
|
|
268
|
+
restored.push(agent.destFile);
|
|
269
|
+
}
|
|
270
|
+
if (toRestore.includes("claude")) {
|
|
271
|
+
const hookDest = join3(projectPath, ".claude", "settings.json");
|
|
272
|
+
if (!existsSync2(hookDest)) {
|
|
273
|
+
mkdirSync2(dirname2(hookDest), { recursive: true });
|
|
274
|
+
writeFileSync2(hookDest, readFileSync2(join3(HOOKS_TEMPLATES_DIR, "claude-settings.json"), "utf8"));
|
|
275
|
+
restored.push(".claude/settings.json");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const configPath = join3(brainDir, "config.json");
|
|
279
|
+
if (!existsSync2(configPath)) {
|
|
280
|
+
const config = {
|
|
281
|
+
gitTracking: true,
|
|
282
|
+
autoSync: true,
|
|
283
|
+
agents: toRestore,
|
|
284
|
+
maxLogEntries: DEFAULT_MAX_LOG_ENTRIES
|
|
285
|
+
};
|
|
286
|
+
writeFileSync2(configPath, JSON.stringify(config, null, 2));
|
|
287
|
+
restored.push(".brain/config.json");
|
|
288
|
+
}
|
|
289
|
+
try {
|
|
290
|
+
registerProject(projectPath);
|
|
291
|
+
} catch {
|
|
292
|
+
}
|
|
293
|
+
console.log("");
|
|
294
|
+
for (const f of restored) console.log(` ${chalk2.green("\u2713")} ${f}`);
|
|
295
|
+
console.log("");
|
|
296
|
+
console.log(` ${chalk2.green("\u2713")} Agent files ready. Start a new session \u2014 your AI is already briefed.`);
|
|
297
|
+
console.log("");
|
|
298
|
+
process.exit(0);
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
if (opts.yes) {
|
|
302
|
+
console.log(` ${chalk2.red("\u2717")} Already initialized at this path.`);
|
|
303
|
+
console.log(` Run ${chalk2.cyan("mindlink config")} to change settings.`);
|
|
304
|
+
console.log("");
|
|
305
|
+
process.exit(1);
|
|
306
|
+
}
|
|
307
|
+
const action = await select({
|
|
308
|
+
message: ".brain/ already exists at this path. What would you like to do?",
|
|
309
|
+
options: [
|
|
310
|
+
{ value: "config", label: "Change settings", hint: "mindlink config" },
|
|
311
|
+
{ value: "status", label: "View current status", hint: "mindlink status" },
|
|
312
|
+
{ value: "exit", label: "Nothing \u2014 exit", hint: "" }
|
|
313
|
+
]
|
|
314
|
+
});
|
|
315
|
+
if (isCancel(action) || action === "exit") {
|
|
316
|
+
process.exit(0);
|
|
317
|
+
}
|
|
318
|
+
if (action === "status") {
|
|
319
|
+
try {
|
|
320
|
+
execSync("mindlink status", { cwd: projectPath, stdio: "inherit" });
|
|
321
|
+
} catch {
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (action === "config") {
|
|
325
|
+
console.log(` Run ${chalk2.cyan("mindlink config")} to change settings.`);
|
|
326
|
+
}
|
|
327
|
+
console.log("");
|
|
328
|
+
process.exit(0);
|
|
329
|
+
}
|
|
330
|
+
} else if (opts.yes) {
|
|
165
331
|
console.log(` ${chalk2.red("\u2717")} Already initialized at this path.`);
|
|
166
332
|
console.log(` Run ${chalk2.cyan("mindlink config")} to change settings.`);
|
|
167
333
|
console.log("");
|
|
168
334
|
process.exit(1);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (action === "status") {
|
|
182
|
-
const { execSync: execSync2 } = await import("child_process");
|
|
183
|
-
try {
|
|
184
|
-
execSync2("mindlink status", { stdio: "inherit" });
|
|
185
|
-
} catch {
|
|
335
|
+
} else if (!hasMemory) {
|
|
336
|
+
} else {
|
|
337
|
+
const action = await select({
|
|
338
|
+
message: ".brain/ already exists at this path. What would you like to do?",
|
|
339
|
+
options: [
|
|
340
|
+
{ value: "config", label: "Change settings", hint: "mindlink config" },
|
|
341
|
+
{ value: "status", label: "View current status", hint: "mindlink status" },
|
|
342
|
+
{ value: "exit", label: "Nothing \u2014 exit", hint: "" }
|
|
343
|
+
]
|
|
344
|
+
});
|
|
345
|
+
if (isCancel(action) || action === "exit") {
|
|
346
|
+
process.exit(0);
|
|
186
347
|
}
|
|
348
|
+
if (action === "status") {
|
|
349
|
+
try {
|
|
350
|
+
execSync("mindlink status", { cwd: projectPath, stdio: "inherit" });
|
|
351
|
+
} catch {
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (action === "config") {
|
|
355
|
+
console.log(` Run ${chalk2.cyan("mindlink config")} to change settings.`);
|
|
356
|
+
}
|
|
357
|
+
console.log("");
|
|
358
|
+
process.exit(0);
|
|
187
359
|
}
|
|
188
|
-
if (action === "config") {
|
|
189
|
-
console.log(` Run ${chalk2.cyan("mindlink config")} to change settings.`);
|
|
190
|
-
}
|
|
191
|
-
console.log("");
|
|
192
|
-
process.exit(0);
|
|
193
360
|
}
|
|
194
361
|
intro(chalk2.bold("Initializing memory for this project:"));
|
|
195
362
|
console.log(` ${chalk2.dim(projectPath)}`);
|
|
196
363
|
console.log(` ${chalk2.dim("This creates a .brain/ folder scoped to this project only.")}`);
|
|
197
364
|
console.log(` ${chalk2.dim("Run mindlink init once per project \u2014 never needs to be run again.")}`);
|
|
198
365
|
console.log("");
|
|
366
|
+
if (process.platform === "win32") {
|
|
367
|
+
console.log(` ${chalk2.yellow("\u26A0")} ${chalk2.bold("Windows detected")}`);
|
|
368
|
+
console.log(` Claude Code hooks use bash and won't run on Windows.`);
|
|
369
|
+
console.log(` Memory enforcement (Stop hook, session timestamps) will be disabled.`);
|
|
370
|
+
console.log(` All other features work normally.`);
|
|
371
|
+
console.log("");
|
|
372
|
+
}
|
|
199
373
|
let selectedAgents;
|
|
200
374
|
if (opts.yes) {
|
|
201
375
|
selectedAgents = AGENTS.filter((a) => a.selected).map((a) => a.value);
|
|
@@ -511,7 +685,7 @@ Examples:
|
|
|
511
685
|
// src/commands/log.ts
|
|
512
686
|
import { Command as Command3 } from "commander";
|
|
513
687
|
import chalk4 from "chalk";
|
|
514
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync } from "fs";
|
|
688
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync2 } from "fs";
|
|
515
689
|
import { join as join5, resolve as resolve3 } from "path";
|
|
516
690
|
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", `
|
|
517
691
|
Examples:
|
|
@@ -568,7 +742,7 @@ Examples:
|
|
|
568
742
|
}
|
|
569
743
|
console.log("");
|
|
570
744
|
}
|
|
571
|
-
const archiveFiles =
|
|
745
|
+
const archiveFiles = readdirSync2(brainDir).filter((f) => /^LOG-\d{4}-\d{2}-\d{2}\.md$/.test(f)).sort();
|
|
572
746
|
if (archiveFiles.length > 0) {
|
|
573
747
|
console.log(` ${chalk4.dim("\u2500".repeat(44))}`);
|
|
574
748
|
console.log(` ${chalk4.dim("Like all human brains, MindLink forgets old sessions")}`);
|
|
@@ -582,7 +756,7 @@ Examples:
|
|
|
582
756
|
// src/commands/clear.ts
|
|
583
757
|
import { Command as Command4 } from "commander";
|
|
584
758
|
import chalk5 from "chalk";
|
|
585
|
-
import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
759
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3, unlinkSync } from "fs";
|
|
586
760
|
import { join as join6, resolve as resolve4 } from "path";
|
|
587
761
|
var clearCommand = new Command4("clear").description("Reset SESSION.md for a fresh session start").option("-y, --yes", "Skip confirmation prompt").addHelpText("after", `
|
|
588
762
|
What it does:
|
|
@@ -611,6 +785,8 @@ Examples:
|
|
|
611
785
|
const templatePath = join6(BRAIN_TEMPLATES_DIR, "SESSION.md");
|
|
612
786
|
const destPath = join6(brainDir, "SESSION.md");
|
|
613
787
|
writeFileSync3(destPath, readFileSync5(templatePath, "utf8"));
|
|
788
|
+
const tsDest = join6(brainDir, ".session_ts");
|
|
789
|
+
if (existsSync5(tsDest)) unlinkSync(tsDest);
|
|
614
790
|
} catch (err) {
|
|
615
791
|
console.log(` ${chalk5.red("\u2717")} ${err instanceof Error ? err.message : String(err)}`);
|
|
616
792
|
console.log("");
|
|
@@ -699,7 +875,7 @@ import {
|
|
|
699
875
|
writeFileSync as writeFileSync5,
|
|
700
876
|
appendFileSync as appendFileSync2,
|
|
701
877
|
mkdirSync as mkdirSync3,
|
|
702
|
-
unlinkSync
|
|
878
|
+
unlinkSync as unlinkSync2
|
|
703
879
|
} from "fs";
|
|
704
880
|
import { join as join8, resolve as resolve6, dirname as dirname3 } from "path";
|
|
705
881
|
function readConfig(brainDir) {
|
|
@@ -739,7 +915,7 @@ function removeAgentFile(projectPath, agentValue) {
|
|
|
739
915
|
if (!agent) return null;
|
|
740
916
|
const destPath = join8(projectPath, agent.destFile);
|
|
741
917
|
if (!existsSync7(destPath)) return null;
|
|
742
|
-
|
|
918
|
+
unlinkSync2(destPath);
|
|
743
919
|
return agent.destFile;
|
|
744
920
|
}
|
|
745
921
|
function addClaudeHook(projectPath) {
|
|
@@ -1017,13 +1193,14 @@ Examples:
|
|
|
1017
1193
|
import { Command as Command8 } from "commander";
|
|
1018
1194
|
import { select as select3, isCancel as isCancel4, cancel as cancel4, spinner as spinner2 } from "@clack/prompts";
|
|
1019
1195
|
import chalk9 from "chalk";
|
|
1020
|
-
import { execSync } from "child_process";
|
|
1196
|
+
import { execSync as execSync2 } from "child_process";
|
|
1021
1197
|
import { join as join10, dirname as dirname4 } from "path";
|
|
1022
1198
|
import { existsSync as existsSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
1199
|
+
var REQUIRED_BRAIN_FILES = ["MEMORY.md", "SESSION.md", "SHARED.md", "LOG.md"];
|
|
1023
1200
|
async function latestVersion() {
|
|
1024
1201
|
try {
|
|
1025
1202
|
const { default: https } = await import("https");
|
|
1026
|
-
return new Promise((
|
|
1203
|
+
return new Promise((resolve14) => {
|
|
1027
1204
|
const req = https.get(
|
|
1028
1205
|
"https://registry.npmjs.org/mindlink/latest",
|
|
1029
1206
|
{ headers: { "User-Agent": "mindlink-cli" } },
|
|
@@ -1035,17 +1212,17 @@ async function latestVersion() {
|
|
|
1035
1212
|
res.on("end", () => {
|
|
1036
1213
|
try {
|
|
1037
1214
|
const parsed = JSON.parse(data);
|
|
1038
|
-
|
|
1215
|
+
resolve14(parsed.version ?? null);
|
|
1039
1216
|
} catch {
|
|
1040
|
-
|
|
1217
|
+
resolve14(null);
|
|
1041
1218
|
}
|
|
1042
1219
|
});
|
|
1043
1220
|
}
|
|
1044
1221
|
);
|
|
1045
|
-
req.on("error", () =>
|
|
1222
|
+
req.on("error", () => resolve14(null));
|
|
1046
1223
|
req.setTimeout(8e3, () => {
|
|
1047
1224
|
req.destroy();
|
|
1048
|
-
|
|
1225
|
+
resolve14(null);
|
|
1049
1226
|
});
|
|
1050
1227
|
});
|
|
1051
1228
|
} catch {
|
|
@@ -1117,7 +1294,7 @@ Examples:
|
|
|
1117
1294
|
const s2 = spinner2();
|
|
1118
1295
|
s2.start(`Installing mindlink@${latest}...`);
|
|
1119
1296
|
try {
|
|
1120
|
-
|
|
1297
|
+
execSync2(`npm install -g mindlink@${latest}`, { stdio: "pipe" });
|
|
1121
1298
|
s2.stop("Done.");
|
|
1122
1299
|
console.log("");
|
|
1123
1300
|
console.log(` ${chalk9.green("\u2713")} Updated to ${latest}.`);
|
|
@@ -1151,6 +1328,19 @@ Examples:
|
|
|
1151
1328
|
}
|
|
1152
1329
|
const agentValues = config.agents ?? AGENTS.filter((a) => a.selected).map((a) => a.value);
|
|
1153
1330
|
const refreshed = [];
|
|
1331
|
+
for (const brainFile of REQUIRED_BRAIN_FILES) {
|
|
1332
|
+
const dest = join10(projectPath, BRAIN_DIR, brainFile);
|
|
1333
|
+
if (!existsSync9(dest)) {
|
|
1334
|
+
try {
|
|
1335
|
+
const template = join10(BRAIN_TEMPLATES_DIR, brainFile);
|
|
1336
|
+
if (existsSync9(template)) {
|
|
1337
|
+
writeFileSync6(dest, readFileSync9(template, "utf8"));
|
|
1338
|
+
refreshed.push(`.brain/${brainFile}`);
|
|
1339
|
+
}
|
|
1340
|
+
} catch {
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1154
1344
|
for (const agentValue of agentValues) {
|
|
1155
1345
|
const agent = AGENTS.find((a) => a.value === agentValue);
|
|
1156
1346
|
if (!agent) continue;
|
|
@@ -1171,6 +1361,46 @@ Examples:
|
|
|
1171
1361
|
} catch {
|
|
1172
1362
|
}
|
|
1173
1363
|
}
|
|
1364
|
+
const memoryPath = join10(projectPath, BRAIN_DIR, "MEMORY.md");
|
|
1365
|
+
if (existsSync9(memoryPath)) {
|
|
1366
|
+
try {
|
|
1367
|
+
let content = readFileSync9(memoryPath, "utf8");
|
|
1368
|
+
let memoryChanged = false;
|
|
1369
|
+
const migrations = [
|
|
1370
|
+
{
|
|
1371
|
+
// v1.1.5: Add ## User Profile section
|
|
1372
|
+
marker: "## User Profile",
|
|
1373
|
+
block: `## User Profile <!-- READ EVERY SESSION \u2014 personal facts about the user -->
|
|
1374
|
+
|
|
1375
|
+
<!-- Job, company, level, years of experience, immigration status -->
|
|
1376
|
+
<!-- Age, health, physical details -->
|
|
1377
|
+
<!-- Family, relationships, major life events -->
|
|
1378
|
+
<!-- Long-term goals: career, financial, personal -->
|
|
1379
|
+
<!-- Strong opinions, values, preferences -->
|
|
1380
|
+
<!-- Update in place \u2014 do not append; consolidate when it grows -->
|
|
1381
|
+
|
|
1382
|
+
|
|
1383
|
+
---
|
|
1384
|
+
|
|
1385
|
+
`,
|
|
1386
|
+
before: "## Important Context"
|
|
1387
|
+
}
|
|
1388
|
+
// Future migrations go here — same pattern:
|
|
1389
|
+
// { marker: '## New Section', block: '## New Section\n\n<!-- ... -->\n\n\n---\n\n', before: '## Some Existing Section' },
|
|
1390
|
+
];
|
|
1391
|
+
for (const m of migrations) {
|
|
1392
|
+
if (!content.includes(m.marker) && content.includes(m.before)) {
|
|
1393
|
+
content = content.replace(m.before, m.block + m.before);
|
|
1394
|
+
memoryChanged = true;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
if (memoryChanged) {
|
|
1398
|
+
writeFileSync6(memoryPath, content);
|
|
1399
|
+
refreshed.push(".brain/MEMORY.md");
|
|
1400
|
+
}
|
|
1401
|
+
} catch {
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1174
1404
|
console.log(` ${chalk9.bold(projectPath)}`);
|
|
1175
1405
|
for (const f of refreshed) {
|
|
1176
1406
|
console.log(` ${chalk9.green("\u2713")} ${f}`);
|
|
@@ -1301,7 +1531,7 @@ to get a full briefing on the current project state.
|
|
|
1301
1531
|
import { Command as Command10 } from "commander";
|
|
1302
1532
|
import { select as select4, isCancel as isCancel5, cancel as cancel5 } from "@clack/prompts";
|
|
1303
1533
|
import chalk11 from "chalk";
|
|
1304
|
-
import { existsSync as existsSync11, readFileSync as readFileSync11, rmSync, unlinkSync as
|
|
1534
|
+
import { existsSync as existsSync11, readFileSync as readFileSync11, rmSync, unlinkSync as unlinkSync3 } from "fs";
|
|
1305
1535
|
import { join as join12, resolve as resolve9 } from "path";
|
|
1306
1536
|
var uninstallCommand = new Command10("uninstall").description("Remove MindLink from the current project").option("-y, --yes", "Skip confirmation and remove all project files").addHelpText("after", `
|
|
1307
1537
|
What gets removed:
|
|
@@ -1383,7 +1613,7 @@ Examples:
|
|
|
1383
1613
|
const destPath = join12(projectPath, agent.destFile);
|
|
1384
1614
|
if (existsSync11(destPath)) {
|
|
1385
1615
|
try {
|
|
1386
|
-
|
|
1616
|
+
unlinkSync3(destPath);
|
|
1387
1617
|
removed.push(agent.destFile);
|
|
1388
1618
|
} catch (err) {
|
|
1389
1619
|
errors.push(`${agent.destFile}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -1394,7 +1624,7 @@ Examples:
|
|
|
1394
1624
|
const hookPath = join12(projectPath, ".claude", "settings.json");
|
|
1395
1625
|
if (existsSync11(hookPath)) {
|
|
1396
1626
|
try {
|
|
1397
|
-
|
|
1627
|
+
unlinkSync3(hookPath);
|
|
1398
1628
|
removed.push(".claude/settings.json");
|
|
1399
1629
|
} catch {
|
|
1400
1630
|
}
|
|
@@ -1412,7 +1642,7 @@ Examples:
|
|
|
1412
1642
|
import { Command as Command11 } from "commander";
|
|
1413
1643
|
import { text as text2, isCancel as isCancel6, cancel as cancel6 } from "@clack/prompts";
|
|
1414
1644
|
import chalk12 from "chalk";
|
|
1415
|
-
import { existsSync as existsSync12, readdirSync as
|
|
1645
|
+
import { existsSync as existsSync12, readdirSync as readdirSync3 } from "fs";
|
|
1416
1646
|
import { join as join13, resolve as resolve10, basename as basename3 } from "path";
|
|
1417
1647
|
import AdmZip from "adm-zip";
|
|
1418
1648
|
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", `
|
|
@@ -1459,8 +1689,7 @@ Examples:
|
|
|
1459
1689
|
const answer = await text2({
|
|
1460
1690
|
message: "Where should the zip be saved?",
|
|
1461
1691
|
placeholder: projectPath,
|
|
1462
|
-
initialValue: projectPath
|
|
1463
|
-
hint: `default filename: ${defaultFilename}`
|
|
1692
|
+
initialValue: projectPath
|
|
1464
1693
|
});
|
|
1465
1694
|
if (isCancel6(answer)) {
|
|
1466
1695
|
cancel6("Cancelled.");
|
|
@@ -1488,7 +1717,7 @@ Examples:
|
|
|
1488
1717
|
skipped.push(`.brain/${file}`);
|
|
1489
1718
|
}
|
|
1490
1719
|
}
|
|
1491
|
-
const archiveFiles =
|
|
1720
|
+
const archiveFiles = readdirSync3(brainDir).filter((f) => /^LOG-\d{4}-\d{2}-\d{2}\.md$/.test(f));
|
|
1492
1721
|
for (const file of archiveFiles) {
|
|
1493
1722
|
zip.addLocalFile(join13(brainDir, file), ".brain");
|
|
1494
1723
|
included.push(`.brain/${file}`);
|
|
@@ -1635,7 +1864,7 @@ Examples:
|
|
|
1635
1864
|
// src/commands/doctor.ts
|
|
1636
1865
|
import { Command as Command13 } from "commander";
|
|
1637
1866
|
import chalk14 from "chalk";
|
|
1638
|
-
import { existsSync as existsSync14, readFileSync as readFileSync12, statSync as statSync3, readdirSync as
|
|
1867
|
+
import { existsSync as existsSync14, readFileSync as readFileSync12, statSync as statSync3, readdirSync as readdirSync4 } from "fs";
|
|
1639
1868
|
import { join as join15, resolve as resolve12 } from "path";
|
|
1640
1869
|
var CORE_LINE_LIMIT = 50;
|
|
1641
1870
|
var CORE_WARN_THRESHOLD = 40;
|
|
@@ -1767,7 +1996,7 @@ Examples:
|
|
|
1767
1996
|
checks.push(ok(`LOG.md \u2014 ${entryCount} sessions logged (last: ${newestHeading}, going back to: ${oldestHeading})`));
|
|
1768
1997
|
}
|
|
1769
1998
|
}
|
|
1770
|
-
const archives =
|
|
1999
|
+
const archives = readdirSync4(brainDir).filter((f) => /^LOG-\d{4}-\d{2}\.md$/.test(f));
|
|
1771
2000
|
if (archives.length > 0) {
|
|
1772
2001
|
checks.push(info(`${archives.length} archive file${archives.length !== 1 ? "s" : ""} \u2014 old sessions are stored in LOG-*.md, not gone`));
|
|
1773
2002
|
}
|
|
@@ -1850,8 +2079,167 @@ var versionCommand = new Command14("version").description("Show the current Mind
|
|
|
1850
2079
|
console.log("");
|
|
1851
2080
|
});
|
|
1852
2081
|
|
|
2082
|
+
// src/commands/diff.ts
|
|
2083
|
+
import { Command as Command15 } from "commander";
|
|
2084
|
+
import chalk16 from "chalk";
|
|
2085
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13, statSync as statSync4 } from "fs";
|
|
2086
|
+
import { join as join16, resolve as resolve13 } from "path";
|
|
2087
|
+
import { execSync as execSync3 } from "child_process";
|
|
2088
|
+
var BRAIN_FILES3 = ["MEMORY.md", "SESSION.md", "LOG.md", "SHARED.md"];
|
|
2089
|
+
function relativeTime2(ms) {
|
|
2090
|
+
const secs = Math.floor(ms / 1e3);
|
|
2091
|
+
if (secs < 60) return `${secs}s ago`;
|
|
2092
|
+
const mins = Math.floor(secs / 60);
|
|
2093
|
+
if (mins < 60) return `${mins}m ago`;
|
|
2094
|
+
const hours = Math.floor(mins / 60);
|
|
2095
|
+
if (hours < 24) return `${hours}h ago`;
|
|
2096
|
+
return `${Math.floor(hours / 24)}d ago`;
|
|
2097
|
+
}
|
|
2098
|
+
function getGitDiff(filePath, since) {
|
|
2099
|
+
try {
|
|
2100
|
+
const dir = resolve13(filePath, "..");
|
|
2101
|
+
execSync3("git rev-parse --is-inside-work-tree", { cwd: dir, stdio: "pipe" });
|
|
2102
|
+
const ref = since || "HEAD~1";
|
|
2103
|
+
const diff = execSync3(`git diff ${ref} -- "${filePath}"`, {
|
|
2104
|
+
cwd: dir,
|
|
2105
|
+
encoding: "utf8",
|
|
2106
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2107
|
+
});
|
|
2108
|
+
return diff || null;
|
|
2109
|
+
} catch {
|
|
2110
|
+
return null;
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
function parseDiffSummary(diff) {
|
|
2114
|
+
const added = [];
|
|
2115
|
+
const removed = [];
|
|
2116
|
+
for (const line of diff.split("\n")) {
|
|
2117
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
2118
|
+
const t = line.slice(1).trim();
|
|
2119
|
+
if (t && !t.startsWith("<!--") && !t.startsWith("#") && t !== "---") added.push(t);
|
|
2120
|
+
} else if (line.startsWith("-") && !line.startsWith("---")) {
|
|
2121
|
+
const t = line.slice(1).trim();
|
|
2122
|
+
if (t && !t.startsWith("<!--") && !t.startsWith("#") && t !== "---") removed.push(t);
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
return { added, removed };
|
|
2126
|
+
}
|
|
2127
|
+
var diffCommand = new Command15("diff").description("Show what changed in .brain/ since last session").option("--since <ref>", "Git ref or date to diff against (default: last mindlink clear or HEAD~1)").option("--json", "Output as JSON").addHelpText("after", `
|
|
2128
|
+
What it does:
|
|
2129
|
+
Shows what changed in each .brain/ file since your last session.
|
|
2130
|
+
Uses git diff when .brain/ is tracked; falls back to file modification times.
|
|
2131
|
+
|
|
2132
|
+
Examples:
|
|
2133
|
+
mindlink diff
|
|
2134
|
+
mindlink diff --since HEAD~3
|
|
2135
|
+
mindlink diff --since "2026-04-10"
|
|
2136
|
+
`).action((opts) => {
|
|
2137
|
+
const projectPath = resolve13(process.cwd());
|
|
2138
|
+
const brainDir = join16(projectPath, BRAIN_DIR);
|
|
2139
|
+
if (!existsSync15(brainDir)) {
|
|
2140
|
+
console.log("");
|
|
2141
|
+
console.log(` ${chalk16.red("\u2717")} No .brain/ found in this directory.`);
|
|
2142
|
+
console.log(` Run ${chalk16.cyan("mindlink init")} to get started.`);
|
|
2143
|
+
console.log("");
|
|
2144
|
+
process.exit(1);
|
|
2145
|
+
}
|
|
2146
|
+
const results = {};
|
|
2147
|
+
const sessionTsPath = join16(brainDir, ".session_ts");
|
|
2148
|
+
const sessionTs = existsSync15(sessionTsPath) ? parseInt(readFileSync13(sessionTsPath, "utf8").trim(), 10) * 1e3 : null;
|
|
2149
|
+
const now = Date.now();
|
|
2150
|
+
const isGitTracked = (() => {
|
|
2151
|
+
try {
|
|
2152
|
+
execSync3("git rev-parse --is-inside-work-tree", { cwd: projectPath, stdio: "pipe" });
|
|
2153
|
+
const tracked = execSync3("git ls-files --error-unmatch .brain/MEMORY.md", {
|
|
2154
|
+
cwd: projectPath,
|
|
2155
|
+
stdio: "pipe"
|
|
2156
|
+
});
|
|
2157
|
+
return true;
|
|
2158
|
+
} catch {
|
|
2159
|
+
return false;
|
|
2160
|
+
}
|
|
2161
|
+
})();
|
|
2162
|
+
for (const file of BRAIN_FILES3) {
|
|
2163
|
+
const filePath = join16(brainDir, file);
|
|
2164
|
+
if (!existsSync15(filePath)) {
|
|
2165
|
+
results[file] = { exists: false };
|
|
2166
|
+
continue;
|
|
2167
|
+
}
|
|
2168
|
+
const stat = statSync4(filePath);
|
|
2169
|
+
const mtime = stat.mtimeMs;
|
|
2170
|
+
const content = readFileSync13(filePath, "utf8");
|
|
2171
|
+
const sizeLines = content.split("\n").length;
|
|
2172
|
+
let diff = null;
|
|
2173
|
+
let added = [];
|
|
2174
|
+
let removed = [];
|
|
2175
|
+
if (isGitTracked) {
|
|
2176
|
+
diff = getGitDiff(filePath, opts.since);
|
|
2177
|
+
if (diff) {
|
|
2178
|
+
const summary = parseDiffSummary(diff);
|
|
2179
|
+
added = summary.added.slice(0, 5);
|
|
2180
|
+
removed = summary.removed.slice(0, 5);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
results[file] = { exists: true, mtime, diff, added, removed, sizeLines };
|
|
2184
|
+
}
|
|
2185
|
+
if (opts.json) {
|
|
2186
|
+
console.log(JSON.stringify(results, null, 2));
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
console.log("");
|
|
2190
|
+
console.log(` ${chalk16.bold(".brain/ changes")}`);
|
|
2191
|
+
if (sessionTs) {
|
|
2192
|
+
console.log(` ${chalk16.dim(`Session started ${relativeTime2(now - sessionTs)}`)}`);
|
|
2193
|
+
}
|
|
2194
|
+
if (isGitTracked) {
|
|
2195
|
+
const sinceRef = opts.since || "HEAD~1";
|
|
2196
|
+
console.log(` ${chalk16.dim(`Git diff against: ${sinceRef}`)}`);
|
|
2197
|
+
} else {
|
|
2198
|
+
console.log(` ${chalk16.dim(".brain/ is not git-tracked \u2014 showing modification times only")}`);
|
|
2199
|
+
}
|
|
2200
|
+
console.log("");
|
|
2201
|
+
for (const file of BRAIN_FILES3) {
|
|
2202
|
+
const r = results[file];
|
|
2203
|
+
if (!r.exists) {
|
|
2204
|
+
console.log(` ${chalk16.dim("\u2014")} ${file} ${chalk16.dim("(not found)")}`);
|
|
2205
|
+
continue;
|
|
2206
|
+
}
|
|
2207
|
+
const age = r.mtime ? relativeTime2(now - r.mtime) : "";
|
|
2208
|
+
const changedThisSession = sessionTs && r.mtime ? r.mtime > sessionTs : false;
|
|
2209
|
+
const indicator = changedThisSession ? chalk16.green("\u25CF") : chalk16.dim("\u25CB");
|
|
2210
|
+
const label = changedThisSession ? chalk16.green(file) : chalk16.dim(file);
|
|
2211
|
+
console.log(` ${indicator} ${label} ${chalk16.dim(`${r.sizeLines} lines \xB7 modified ${age}`)}`);
|
|
2212
|
+
if (isGitTracked && r.diff) {
|
|
2213
|
+
for (const line of (r.added ?? []).slice(0, 3)) {
|
|
2214
|
+
console.log(` ${chalk16.green("+")} ${line.length > 80 ? line.slice(0, 80) + "\u2026" : line}`);
|
|
2215
|
+
}
|
|
2216
|
+
for (const line of (r.removed ?? []).slice(0, 3)) {
|
|
2217
|
+
console.log(` ${chalk16.red("-")} ${line.length > 80 ? line.slice(0, 80) + "\u2026" : line}`);
|
|
2218
|
+
}
|
|
2219
|
+
} else if (!isGitTracked && changedThisSession) {
|
|
2220
|
+
console.log(` ${chalk16.dim("(content diff unavailable \u2014 commit .brain/ to git for line-level diff)")}`);
|
|
2221
|
+
} else if (isGitTracked && !r.diff) {
|
|
2222
|
+
console.log(` ${chalk16.dim("no changes since last commit")}`);
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
console.log("");
|
|
2226
|
+
const changedCount = BRAIN_FILES3.filter((f) => {
|
|
2227
|
+
const r = results[f];
|
|
2228
|
+
return r.exists && sessionTs && r.mtime && r.mtime > sessionTs;
|
|
2229
|
+
}).length;
|
|
2230
|
+
if (changedCount === 0) {
|
|
2231
|
+
console.log(` ${chalk16.dim("No .brain/ files were modified this session.")}`);
|
|
2232
|
+
if (sessionTs) {
|
|
2233
|
+
console.log(` ${chalk16.dim("If the session just started, this is expected.")}`);
|
|
2234
|
+
}
|
|
2235
|
+
} else {
|
|
2236
|
+
console.log(` ${chalk16.green("\u2713")} ${changedCount} file${changedCount !== 1 ? "s" : ""} updated this session.`);
|
|
2237
|
+
}
|
|
2238
|
+
console.log("");
|
|
2239
|
+
});
|
|
2240
|
+
|
|
1853
2241
|
// src/cli.ts
|
|
1854
|
-
var program = new
|
|
2242
|
+
var program = new Command16();
|
|
1855
2243
|
program.name("mindlink").description("Give your AI a brain.").version(VERSION, "-v, --version");
|
|
1856
2244
|
program.addCommand(initCommand);
|
|
1857
2245
|
program.addCommand(statusCommand);
|
|
@@ -1867,9 +2255,10 @@ program.addCommand(exportCommand);
|
|
|
1867
2255
|
program.addCommand(importCommand);
|
|
1868
2256
|
program.addCommand(doctorCommand);
|
|
1869
2257
|
program.addCommand(versionCommand);
|
|
2258
|
+
program.addCommand(diffCommand);
|
|
1870
2259
|
program.on("command:*", (operands) => {
|
|
1871
2260
|
const unknown = operands[0];
|
|
1872
|
-
const known = ["init", "status", "log", "clear", "reset", "config", "sync", "update", "summary", "uninstall", "export", "import", "doctor", "version"];
|
|
2261
|
+
const known = ["init", "status", "log", "clear", "reset", "config", "sync", "update", "summary", "uninstall", "export", "import", "doctor", "version", "diff"];
|
|
1873
2262
|
function levenshtein(a, b) {
|
|
1874
2263
|
const m = a.length, n = b.length;
|
|
1875
2264
|
const dp = Array.from(
|
|
@@ -1885,11 +2274,11 @@ program.on("command:*", (operands) => {
|
|
|
1885
2274
|
}
|
|
1886
2275
|
const closest = known.map((cmd) => ({ cmd, dist: levenshtein(unknown, cmd) })).sort((a, b) => a.dist - b.dist)[0];
|
|
1887
2276
|
console.log("");
|
|
1888
|
-
console.log(` ${
|
|
2277
|
+
console.log(` ${chalk17.red("\u2717")} Unknown command: ${chalk17.bold(unknown)}`);
|
|
1889
2278
|
if (closest && closest.dist <= 3) {
|
|
1890
|
-
console.log(` Did you mean ${
|
|
2279
|
+
console.log(` Did you mean ${chalk17.cyan("mindlink " + closest.cmd)}?`);
|
|
1891
2280
|
}
|
|
1892
|
-
console.log(` Run ${
|
|
2281
|
+
console.log(` Run ${chalk17.cyan("mindlink --help")} to see all commands.`);
|
|
1893
2282
|
console.log("");
|
|
1894
2283
|
process.exit(1);
|
|
1895
2284
|
});
|