mindlink 2.1.0 → 2.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 +3 -3
- package/dist/cli.js +228 -13
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,9 +22,9 @@ Git gave every developer a shared version history. MindLink gives your AI team a
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
-
> ### ◉ Latest — v2.
|
|
26
|
-
>
|
|
27
|
-
> [→ Full release notes](https://github.com/404-not-found/mindlink/releases/tag/v2.
|
|
25
|
+
> ### ◉ Latest — v2.2.0
|
|
26
|
+
> **`mindlink learn` — teach your AI from any file or URL · Smart init · `mindlink recap` · `mindlink search`**
|
|
27
|
+
> [→ Full release notes](https://github.com/404-not-found/mindlink/releases/tag/v2.2.0)
|
|
28
28
|
|
|
29
29
|
---
|
|
30
30
|
|
package/dist/cli.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import "./chunk-2H7UOFLK.js";
|
|
3
3
|
|
|
4
4
|
// src/cli.ts
|
|
5
|
-
import { Command as
|
|
6
|
-
import
|
|
5
|
+
import { Command as Command23 } from "commander";
|
|
6
|
+
import chalk23 from "chalk";
|
|
7
7
|
|
|
8
8
|
// src/utils/version.ts
|
|
9
|
-
var VERSION = "2.
|
|
9
|
+
var VERSION = "2.2.0";
|
|
10
10
|
|
|
11
11
|
// src/commands/init.ts
|
|
12
12
|
import { Command } from "commander";
|
|
@@ -1583,7 +1583,7 @@ var REQUIRED_BRAIN_FILES = ["MEMORY.md", "SESSION.md", "SHARED.md", "LOG.md"];
|
|
|
1583
1583
|
async function latestVersion() {
|
|
1584
1584
|
try {
|
|
1585
1585
|
const { default: https } = await import("https");
|
|
1586
|
-
return new Promise((
|
|
1586
|
+
return new Promise((resolve20) => {
|
|
1587
1587
|
const req = https.get(
|
|
1588
1588
|
"https://registry.npmjs.org/mindlink/latest",
|
|
1589
1589
|
{ headers: { "User-Agent": "mindlink-cli" } },
|
|
@@ -1595,17 +1595,17 @@ async function latestVersion() {
|
|
|
1595
1595
|
res.on("end", () => {
|
|
1596
1596
|
try {
|
|
1597
1597
|
const parsed = JSON.parse(data);
|
|
1598
|
-
|
|
1598
|
+
resolve20(parsed.version ?? null);
|
|
1599
1599
|
} catch {
|
|
1600
|
-
|
|
1600
|
+
resolve20(null);
|
|
1601
1601
|
}
|
|
1602
1602
|
});
|
|
1603
1603
|
}
|
|
1604
1604
|
);
|
|
1605
|
-
req.on("error", () =>
|
|
1605
|
+
req.on("error", () => resolve20(null));
|
|
1606
1606
|
req.setTimeout(8e3, () => {
|
|
1607
1607
|
req.destroy();
|
|
1608
|
-
|
|
1608
|
+
resolve20(null);
|
|
1609
1609
|
});
|
|
1610
1610
|
});
|
|
1611
1611
|
} catch {
|
|
@@ -3810,8 +3810,222 @@ Examples:
|
|
|
3810
3810
|
}
|
|
3811
3811
|
});
|
|
3812
3812
|
|
|
3813
|
+
// src/commands/learn.ts
|
|
3814
|
+
import { Command as Command22 } from "commander";
|
|
3815
|
+
import chalk22 from "chalk";
|
|
3816
|
+
import { existsSync as existsSync22, readFileSync as readFileSync20, writeFileSync as writeFileSync11 } from "fs";
|
|
3817
|
+
import { join as join22, resolve as resolve19, extname as extname2 } from "path";
|
|
3818
|
+
function resolveSection(hint) {
|
|
3819
|
+
if (!hint) return "Important Context";
|
|
3820
|
+
const h = hint.toLowerCase();
|
|
3821
|
+
if (h.includes("core")) return "Core";
|
|
3822
|
+
if (h.includes("arch")) return "Architecture";
|
|
3823
|
+
if (h.includes("dec")) return "Decisions";
|
|
3824
|
+
if (h.includes("conv")) return "Conventions";
|
|
3825
|
+
return "Important Context";
|
|
3826
|
+
}
|
|
3827
|
+
async function fetchUrl(url) {
|
|
3828
|
+
const { default: https } = await (url.startsWith("https") ? import("https") : import("http"));
|
|
3829
|
+
return new Promise((resolve20, reject) => {
|
|
3830
|
+
const req = https.get(url, { headers: { "User-Agent": "mindlink-learn/1.0" } }, (res) => {
|
|
3831
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
3832
|
+
fetchUrl(res.headers.location).then(resolve20).catch(reject);
|
|
3833
|
+
return;
|
|
3834
|
+
}
|
|
3835
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
3836
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
3837
|
+
return;
|
|
3838
|
+
}
|
|
3839
|
+
let data = "";
|
|
3840
|
+
res.on("data", (chunk) => {
|
|
3841
|
+
data += chunk;
|
|
3842
|
+
});
|
|
3843
|
+
res.on("end", () => resolve20(data));
|
|
3844
|
+
});
|
|
3845
|
+
req.on("error", reject);
|
|
3846
|
+
req.setTimeout(15e3, () => {
|
|
3847
|
+
req.destroy();
|
|
3848
|
+
reject(new Error("Request timed out"));
|
|
3849
|
+
});
|
|
3850
|
+
});
|
|
3851
|
+
}
|
|
3852
|
+
function stripHtml(html) {
|
|
3853
|
+
return html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/\s{2,}/g, " ").replace(/\n{3,}/g, "\n\n").trim();
|
|
3854
|
+
}
|
|
3855
|
+
function readLocalFile(filePath) {
|
|
3856
|
+
const ext = extname2(filePath).toLowerCase();
|
|
3857
|
+
const raw = readFileSync20(filePath, "utf8");
|
|
3858
|
+
if (ext === ".json") {
|
|
3859
|
+
try {
|
|
3860
|
+
return JSON.stringify(JSON.parse(raw), null, 2).slice(0, 8e3);
|
|
3861
|
+
} catch {
|
|
3862
|
+
return raw;
|
|
3863
|
+
}
|
|
3864
|
+
}
|
|
3865
|
+
return raw;
|
|
3866
|
+
}
|
|
3867
|
+
function extractFacts(text3, maxLines = 60) {
|
|
3868
|
+
const SKIP_PATTERNS = [
|
|
3869
|
+
/^(cookie|privacy|terms|copyright|all rights reserved|subscribe|sign up|log in|home|menu|search|navigation)/i,
|
|
3870
|
+
/^\s*[\|<>\/\\]\s*$/,
|
|
3871
|
+
/^https?:\/\//
|
|
3872
|
+
];
|
|
3873
|
+
const lines = text3.split("\n").map((l) => l.trim()).filter(
|
|
3874
|
+
(l) => l.length > 10 && l.length < 300 && !SKIP_PATTERNS.some((p) => p.test(l))
|
|
3875
|
+
);
|
|
3876
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3877
|
+
const deduped = [];
|
|
3878
|
+
for (const line of lines) {
|
|
3879
|
+
const key = line.toLowerCase().replace(/\s+/g, " ");
|
|
3880
|
+
if (!seen.has(key)) {
|
|
3881
|
+
seen.add(key);
|
|
3882
|
+
deduped.push(line);
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
return deduped.slice(0, maxLines).join("\n");
|
|
3886
|
+
}
|
|
3887
|
+
function appendToMemory(memoryPath, section, facts, source) {
|
|
3888
|
+
let content = readFileSync20(memoryPath, "utf8");
|
|
3889
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
3890
|
+
const entry = `<!-- learned from: ${source} on ${date} -->
|
|
3891
|
+
${facts.trim()}`;
|
|
3892
|
+
const lines = content.split("\n");
|
|
3893
|
+
let headingIdx = -1;
|
|
3894
|
+
let nextSectionIdx = lines.length;
|
|
3895
|
+
let headingLevel = 0;
|
|
3896
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3897
|
+
const match = lines[i].match(/^(#{1,6})\s+(.+)/);
|
|
3898
|
+
if (match) {
|
|
3899
|
+
const level = match[1].length;
|
|
3900
|
+
const title = match[2].replace(/<!--.*?-->/g, "").trim();
|
|
3901
|
+
if (headingIdx < 0 && title.toLowerCase() === section.toLowerCase()) {
|
|
3902
|
+
headingIdx = i;
|
|
3903
|
+
headingLevel = level;
|
|
3904
|
+
} else if (headingIdx >= 0 && level <= headingLevel) {
|
|
3905
|
+
nextSectionIdx = i;
|
|
3906
|
+
break;
|
|
3907
|
+
}
|
|
3908
|
+
}
|
|
3909
|
+
}
|
|
3910
|
+
if (headingIdx < 0) {
|
|
3911
|
+
content = content.trimEnd() + `
|
|
3912
|
+
|
|
3913
|
+
## ${section}
|
|
3914
|
+
|
|
3915
|
+
${entry}
|
|
3916
|
+
`;
|
|
3917
|
+
} else {
|
|
3918
|
+
const insertAt = nextSectionIdx > 0 && lines[nextSectionIdx - 1].trim() === "---" ? nextSectionIdx - 1 : nextSectionIdx;
|
|
3919
|
+
lines.splice(insertAt, 0, "", entry, "");
|
|
3920
|
+
content = lines.join("\n");
|
|
3921
|
+
}
|
|
3922
|
+
writeFileSync11(memoryPath, content);
|
|
3923
|
+
}
|
|
3924
|
+
var learnCommand = new Command22("learn").description("Extract facts from a file or URL and save them into project memory").argument("<source>", "File path or URL to learn from").option("--section <name>", "Target memory section (core, architecture, decisions, conventions, context)", "context").option("--preview", "Show what would be learned without writing anything").addHelpText("after", `
|
|
3925
|
+
Examples:
|
|
3926
|
+
mindlink learn ./docs/architecture.md
|
|
3927
|
+
mindlink learn https://example.com/api-docs
|
|
3928
|
+
mindlink learn ./package.json --section architecture
|
|
3929
|
+
mindlink learn ./DECISIONS.md --section decisions
|
|
3930
|
+
mindlink learn https://stripe.com/docs/webhooks --preview
|
|
3931
|
+
`).action(async (source, opts) => {
|
|
3932
|
+
const projectPath = resolve19(process.cwd());
|
|
3933
|
+
const brainDir = join22(projectPath, BRAIN_DIR);
|
|
3934
|
+
if (!existsSync22(brainDir)) {
|
|
3935
|
+
console.log(` ${chalk22.red("\u2717")} No .brain/ found in this directory.`);
|
|
3936
|
+
console.log(` Run ${chalk22.cyan("mindlink init")} to get started.`);
|
|
3937
|
+
console.log("");
|
|
3938
|
+
process.exit(1);
|
|
3939
|
+
}
|
|
3940
|
+
const memoryPath = join22(brainDir, "MEMORY.md");
|
|
3941
|
+
if (!existsSync22(memoryPath)) {
|
|
3942
|
+
console.log(` ${chalk22.red("\u2717")} MEMORY.md not found in .brain/.`);
|
|
3943
|
+
console.log(` Run ${chalk22.cyan("mindlink init")} to set up memory.`);
|
|
3944
|
+
console.log("");
|
|
3945
|
+
process.exit(1);
|
|
3946
|
+
}
|
|
3947
|
+
const section = resolveSection(opts.section);
|
|
3948
|
+
const isUrl = source.startsWith("http://") || source.startsWith("https://");
|
|
3949
|
+
console.log("");
|
|
3950
|
+
let rawText = "";
|
|
3951
|
+
if (isUrl) {
|
|
3952
|
+
process.stdout.write(` Fetching ${chalk22.dim(source)}...
|
|
3953
|
+
`);
|
|
3954
|
+
try {
|
|
3955
|
+
const html = await fetchUrl(source);
|
|
3956
|
+
rawText = stripHtml(html);
|
|
3957
|
+
} catch (err) {
|
|
3958
|
+
console.log(` ${chalk22.red("\u2717")} Could not fetch URL: ${err instanceof Error ? err.message : String(err)}`);
|
|
3959
|
+
console.log(` Check the URL is reachable and try again.`);
|
|
3960
|
+
console.log("");
|
|
3961
|
+
process.exit(1);
|
|
3962
|
+
}
|
|
3963
|
+
} else {
|
|
3964
|
+
const absPath = resolve19(projectPath, source);
|
|
3965
|
+
if (!existsSync22(absPath)) {
|
|
3966
|
+
console.log(` ${chalk22.red("\u2717")} File not found: ${absPath}`);
|
|
3967
|
+
console.log("");
|
|
3968
|
+
process.exit(1);
|
|
3969
|
+
}
|
|
3970
|
+
try {
|
|
3971
|
+
rawText = readLocalFile(absPath);
|
|
3972
|
+
} catch (err) {
|
|
3973
|
+
console.log(` ${chalk22.red("\u2717")} Could not read file: ${err instanceof Error ? err.message : String(err)}`);
|
|
3974
|
+
console.log("");
|
|
3975
|
+
process.exit(1);
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
if (!rawText.trim()) {
|
|
3979
|
+
console.log(` ${chalk22.yellow("\u2192")} Source is empty \u2014 nothing to learn.`);
|
|
3980
|
+
console.log("");
|
|
3981
|
+
process.exit(0);
|
|
3982
|
+
}
|
|
3983
|
+
const facts = extractFacts(rawText);
|
|
3984
|
+
if (!facts.trim()) {
|
|
3985
|
+
console.log(` ${chalk22.yellow("\u2192")} Could not extract any meaningful content from this source.`);
|
|
3986
|
+
console.log("");
|
|
3987
|
+
process.exit(0);
|
|
3988
|
+
}
|
|
3989
|
+
if (opts.preview) {
|
|
3990
|
+
console.log(` ${chalk22.bold("Preview")} \u2014 would add to ${chalk22.cyan("## " + section)} in MEMORY.md:`);
|
|
3991
|
+
console.log("");
|
|
3992
|
+
const previewLines = facts.split("\n").slice(0, 20);
|
|
3993
|
+
for (const line of previewLines) {
|
|
3994
|
+
console.log(` ${chalk22.dim("\xB7")} ${line}`);
|
|
3995
|
+
}
|
|
3996
|
+
if (facts.split("\n").length > 20) {
|
|
3997
|
+
console.log(` ${chalk22.dim(`... and ${facts.split("\n").length - 20} more lines`)}`);
|
|
3998
|
+
}
|
|
3999
|
+
console.log("");
|
|
4000
|
+
console.log(` Run without ${chalk22.cyan("--preview")} to save.`);
|
|
4001
|
+
console.log("");
|
|
4002
|
+
process.exit(0);
|
|
4003
|
+
}
|
|
4004
|
+
try {
|
|
4005
|
+
appendToMemory(memoryPath, section, facts, source);
|
|
4006
|
+
} catch (err) {
|
|
4007
|
+
console.log(` ${chalk22.red("\u2717")} Failed to write to MEMORY.md: ${err instanceof Error ? err.message : String(err)}`);
|
|
4008
|
+
console.log("");
|
|
4009
|
+
process.exit(1);
|
|
4010
|
+
}
|
|
4011
|
+
const lineCount = facts.split("\n").filter((l) => l.trim()).length;
|
|
4012
|
+
console.log(` ${chalk22.green("\u2713")} Learned ${lineCount} lines from ${chalk22.dim(source)}`);
|
|
4013
|
+
console.log(` Added to ${chalk22.cyan("## " + section)} in MEMORY.md`);
|
|
4014
|
+
console.log("");
|
|
4015
|
+
console.log(` ${chalk22.dim("Your AI will see this context starting next session.")}`);
|
|
4016
|
+
console.log(` ${chalk22.dim("Run mindlink verify to check memory health.")}`);
|
|
4017
|
+
console.log("");
|
|
4018
|
+
const existing = extractSection(readFileSync20(memoryPath, "utf8"), section);
|
|
4019
|
+
const existingLines = existing.split("\n").filter((l) => l.trim() && !l.startsWith("<!--")).length;
|
|
4020
|
+
if (existingLines > 80) {
|
|
4021
|
+
console.log(` ${chalk22.yellow("\u2192")} ## ${section} is getting large (${existingLines} lines).`);
|
|
4022
|
+
console.log(` Run ${chalk22.cyan("mindlink prune")} to retire stale entries.`);
|
|
4023
|
+
console.log("");
|
|
4024
|
+
}
|
|
4025
|
+
});
|
|
4026
|
+
|
|
3813
4027
|
// src/cli.ts
|
|
3814
|
-
var program = new
|
|
4028
|
+
var program = new Command23();
|
|
3815
4029
|
program.name("mindlink").description("Give your AI a brain.").version(VERSION, "-v, --version");
|
|
3816
4030
|
program.addCommand(initCommand);
|
|
3817
4031
|
program.addCommand(statusCommand);
|
|
@@ -3834,9 +4048,10 @@ program.addCommand(pruneCommand);
|
|
|
3834
4048
|
program.addCommand(mcpCommand);
|
|
3835
4049
|
program.addCommand(recapCommand);
|
|
3836
4050
|
program.addCommand(searchCommand);
|
|
4051
|
+
program.addCommand(learnCommand);
|
|
3837
4052
|
program.on("command:*", (operands) => {
|
|
3838
4053
|
const unknown = operands[0];
|
|
3839
|
-
const known = ["init", "status", "log", "clear", "reset", "config", "sync", "update", "summary", "uninstall", "export", "import", "doctor", "version", "diff", "verify", "profile", "prune", "mcp", "recap", "search"];
|
|
4054
|
+
const known = ["init", "status", "log", "clear", "reset", "config", "sync", "update", "summary", "uninstall", "export", "import", "doctor", "version", "diff", "verify", "profile", "prune", "mcp", "recap", "search", "learn"];
|
|
3840
4055
|
function levenshtein(a, b) {
|
|
3841
4056
|
const m = a.length, n = b.length;
|
|
3842
4057
|
const dp = Array.from(
|
|
@@ -3852,11 +4067,11 @@ program.on("command:*", (operands) => {
|
|
|
3852
4067
|
}
|
|
3853
4068
|
const closest = known.map((cmd) => ({ cmd, dist: levenshtein(unknown, cmd) })).sort((a, b) => a.dist - b.dist)[0];
|
|
3854
4069
|
console.log("");
|
|
3855
|
-
console.log(` ${
|
|
4070
|
+
console.log(` ${chalk23.red("\u2717")} Unknown command: ${chalk23.bold(unknown)}`);
|
|
3856
4071
|
if (closest && closest.dist <= 3) {
|
|
3857
|
-
console.log(` Did you mean ${
|
|
4072
|
+
console.log(` Did you mean ${chalk23.cyan("mindlink " + closest.cmd)}?`);
|
|
3858
4073
|
}
|
|
3859
|
-
console.log(` Run ${
|
|
4074
|
+
console.log(` Run ${chalk23.cyan("mindlink --help")} to see all commands.`);
|
|
3860
4075
|
console.log("");
|
|
3861
4076
|
process.exit(1);
|
|
3862
4077
|
});
|